From b63d055b54b197642a72ec2b6d2d5d4d87a61e40 Mon Sep 17 00:00:00 2001 From: Rajesh Malviya Date: Wed, 22 Oct 2025 19:00:25 +0530 Subject: [PATCH 1/6] store: Make `realmName` and `realmIcon` getters non-nullable --- lib/model/store.dart | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/model/store.dart b/lib/model/store.dart index fda6e5b695..b98b520f99 100644 --- a/lib/model/store.dart +++ b/lib/model/store.dart @@ -423,9 +423,15 @@ abstract class PerAccountStoreBase { /// Always equal to `account.realmUrl` and `connection.realmUrl`. Uri get realmUrl => connection.realmUrl; - String? get realmName => account.realmName; - - Uri? get realmIcon => account.realmIcon; + // The `account` is populated with the `realmName` before + // PerAccountStore is created, so this should never be null. + // See `UpdateMachine.load`. + String get realmName => account.realmName!; + + // The `account` is populated with the `realmIcon` before + // PerAccountStore is created, so this should never be null. + // See `UpdateMachine.load`. + Uri get realmIcon => account.realmIcon!; /// Resolve [reference] as a URL relative to [realmUrl]. /// From 2f0c22ccdf1d091cc7d7336df251680142121674 Mon Sep 17 00:00:00 2001 From: Rajesh Malviya Date: Tue, 11 Nov 2025 00:17:25 +0530 Subject: [PATCH 2/6] content test: Cut out a redundant URL check --- test/widgets/content_test.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/test/widgets/content_test.dart b/test/widgets/content_test.dart index 92fe17796d..1a2cff04c9 100644 --- a/test/widgets/content_test.dart +++ b/test/widgets/content_test.dart @@ -400,7 +400,6 @@ void main() { // The image indeed has an invalid URL. final expectedImages = (example.expectedNodes[0] as ImagePreviewNodeList).imagePreviews; check(() => Uri.parse(expectedImages.single.srcUrl)).throws(); - check(tryResolveUrl(eg.realmUrl, expectedImages.single.srcUrl)).isNull(); // The MessageImagePreview has shown up, // but it doesn't attempt a RealmContentNetworkImage. check(tester.widgetList(find.byType(MessageImagePreview))).isNotEmpty(); From f4204a6ab0d95b109e9a3969d89685c609edf97f Mon Sep 17 00:00:00 2001 From: Rajesh Malviya Date: Tue, 28 Oct 2025 18:37:56 +0530 Subject: [PATCH 3/6] store [nfc]: Rename _tryResolveUrl to _tryResolveUrlStr --- lib/model/emoji.dart | 4 ++-- lib/model/store.dart | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/model/emoji.dart b/lib/model/emoji.dart index d5da2b34d9..78fbe2b5bd 100644 --- a/lib/model/emoji.dart +++ b/lib/model/emoji.dart @@ -234,12 +234,12 @@ class EmojiStoreImpl extends PerAccountStoreBase with EmojiStore { required String? stillUrl, required String emojiName, }) { - final resolvedUrl = this.tryResolveUrl(sourceUrl); + final resolvedUrl = tryResolveUrl(sourceUrl); if (resolvedUrl == null) return TextEmojiDisplay(emojiName: emojiName); Uri? resolvedStillUrl; if (stillUrl != null) { - resolvedStillUrl = this.tryResolveUrl(stillUrl); + resolvedStillUrl = tryResolveUrl(stillUrl); if (resolvedStillUrl == null) return TextEmojiDisplay(emojiName: emojiName); } diff --git a/lib/model/store.dart b/lib/model/store.dart index b98b520f99..d061bbd149 100644 --- a/lib/model/store.dart +++ b/lib/model/store.dart @@ -436,7 +436,7 @@ abstract class PerAccountStoreBase { /// Resolve [reference] as a URL relative to [realmUrl]. /// /// This returns null if [reference] fails to parse as a URL. - Uri? tryResolveUrl(String reference) => _tryResolveUrl(realmUrl, reference); + Uri? tryResolveUrl(String reference) => _tryResolveUrlStr(realmUrl, reference); /// Always equal to `connection.zulipFeatureLevel` /// and `account.zulipFeatureLevel`. @@ -465,10 +465,8 @@ abstract class PerAccountStoreBase { int get selfUserId => core.selfUserId; } -const _tryResolveUrl = tryResolveUrl; - /// Like [Uri.resolve], but on failure return null instead of throwing. -Uri? tryResolveUrl(Uri baseUrl, String reference) { +Uri? _tryResolveUrlStr(Uri baseUrl, String reference) { try { return baseUrl.resolve(reference); } on FormatException { From 19f6b224f3cee5e9d441b6a3e3c0944c547657d3 Mon Sep 17 00:00:00 2001 From: Rajesh Malviya Date: Tue, 28 Oct 2025 19:01:24 +0530 Subject: [PATCH 4/6] store: Add resolveRealmIconUrl This helper will be used soon to display organization logo in various places. --- lib/model/store.dart | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/model/store.dart b/lib/model/store.dart index d061bbd149..bc0a228b73 100644 --- a/lib/model/store.dart +++ b/lib/model/store.dart @@ -433,6 +433,11 @@ abstract class PerAccountStoreBase { // See `UpdateMachine.load`. Uri get realmIcon => account.realmIcon!; + /// Resolve [realmIcon] as a URL relative to [realmUrl]. + /// + /// This returns null if resolving fails. + Uri? resolveRealmIconUrl() => _tryResolveUrl(realmUrl, realmIcon); + /// Resolve [reference] as a URL relative to [realmUrl]. /// /// This returns null if [reference] fails to parse as a URL. @@ -474,6 +479,15 @@ Uri? _tryResolveUrlStr(Uri baseUrl, String reference) { } } +/// Like [Uri.resolve], but on failure return null instead of throwing. +Uri? _tryResolveUrl(Uri baseUrl, Uri reference) { + try { + return baseUrl.resolveUri(reference); + } on FormatException { + return null; + } +} + /// Store for the user's data for a given Zulip account. /// /// This should always have a consistent snapshot of the state on the server, From c2b79f860691f3b1c4f306007d3688b7566fd317 Mon Sep 17 00:00:00 2001 From: Rajesh Malviya Date: Fri, 14 Nov 2025 13:47:02 +0530 Subject: [PATCH 5/6] button: Give ZulipIconButton an optional `tooltip` param --- lib/widgets/button.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/widgets/button.dart b/lib/widgets/button.dart index 1cfb308365..e486cccab9 100644 --- a/lib/widgets/button.dart +++ b/lib/widgets/button.dart @@ -240,10 +240,12 @@ class ZulipIconButton extends StatelessWidget { super.key, required this.icon, required this.onPressed, + this.tooltip, }); final IconData icon; final VoidCallback onPressed; + final String? tooltip; @override Widget build(BuildContext context) { @@ -258,6 +260,7 @@ class ZulipIconButton extends StatelessWidget { iconSize: 24, icon: Icon(icon), onPressed: onPressed, + tooltip: tooltip, style: IconButton.styleFrom( tapTargetSize: MaterialTapTargetSize.shrinkWrap, fixedSize: Size.square(40), From 2617ebadfd909ca8b7d382956a2e97beecc10639 Mon Sep 17 00:00:00 2001 From: Rajesh Malviya Date: Wed, 22 Oct 2025 20:02:50 +0530 Subject: [PATCH 6/6] home: Show current organization's name and logo atop main menu Fixes: #1037 --- assets/l10n/app_ar.arb | 6 +- assets/l10n/app_de.arb | 6 +- assets/l10n/app_en.arb | 6 +- assets/l10n/app_fr.arb | 6 +- assets/l10n/app_it.arb | 6 +- assets/l10n/app_ja.arb | 6 +- assets/l10n/app_pl.arb | 6 +- assets/l10n/app_ru.arb | 6 +- assets/l10n/app_sk.arb | 6 +- assets/l10n/app_sl.arb | 6 +- assets/l10n/app_uk.arb | 6 +- assets/l10n/app_zh_Hans_CN.arb | 6 +- assets/l10n/app_zh_Hant_TW.arb | 6 +- lib/generated/l10n/zulip_localizations.dart | 4 +- .../l10n/zulip_localizations_ar.dart | 2 +- .../l10n/zulip_localizations_de.dart | 2 +- .../l10n/zulip_localizations_el.dart | 2 +- .../l10n/zulip_localizations_en.dart | 2 +- .../l10n/zulip_localizations_es.dart | 2 +- .../l10n/zulip_localizations_fr.dart | 2 +- .../l10n/zulip_localizations_he.dart | 2 +- .../l10n/zulip_localizations_hu.dart | 2 +- .../l10n/zulip_localizations_it.dart | 2 +- .../l10n/zulip_localizations_ja.dart | 2 +- .../l10n/zulip_localizations_nb.dart | 2 +- .../l10n/zulip_localizations_pl.dart | 2 +- .../l10n/zulip_localizations_ru.dart | 2 +- .../l10n/zulip_localizations_sk.dart | 2 +- .../l10n/zulip_localizations_sl.dart | 2 +- .../l10n/zulip_localizations_uk.dart | 2 +- .../l10n/zulip_localizations_zh.dart | 6 +- lib/widgets/home.dart | 137 +++++++++++++----- test/widgets/home_test.dart | 25 ++++ 33 files changed, 184 insertions(+), 98 deletions(-) diff --git a/assets/l10n/app_ar.arb b/assets/l10n/app_ar.arb index b9c2bd611b..d1ff99a8bb 100644 --- a/assets/l10n/app_ar.arb +++ b/assets/l10n/app_ar.arb @@ -17,8 +17,8 @@ "@settingsPageTitle": { "description": "Title for the settings page." }, - "@switchAccountButton": { - "description": "Label for main-menu button leading to the choose-account page." + "@switchAccountButtonTooltip": { + "description": "Tooltip message for main-menu button leading to the choose-account page." }, "@upgradeWelcomeDialogDismiss": { "description": "Label for button dismissing dialog shown on first upgrade from the legacy Zulip app." @@ -41,7 +41,7 @@ "aboutPageTitle": "عن زوليب", "chooseAccountPageTitle": "اختر حساب", "settingsPageTitle": "الإعدادات", - "switchAccountButton": "تبديل الحساب", + "switchAccountButtonTooltip": "تبديل الحساب", "upgradeWelcomeDialogDismiss": "هيا بنا", "upgradeWelcomeDialogTitle": "أهلا بك في تطبيق زوليب الجديد !", "wildcardMentionAll": "الجميع", diff --git a/assets/l10n/app_de.arb b/assets/l10n/app_de.arb index b69742ffcc..da489ff68e 100644 --- a/assets/l10n/app_de.arb +++ b/assets/l10n/app_de.arb @@ -1015,8 +1015,8 @@ "@successTopicLinkCopied": { "description": "Message when link of a topic was copied to the user's system clipboard." }, - "@switchAccountButton": { - "description": "Label for main-menu button leading to the choose-account page." + "@switchAccountButtonTooltip": { + "description": "Tooltip message for main-menu button leading to the choose-account page." }, "@themeSettingDark": { "description": "Label for dark theme setting." @@ -1485,7 +1485,7 @@ "successMessageLinkCopied": "Nachrichtenlink kopiert", "successMessageTextCopied": "Nachrichtentext kopiert", "successTopicLinkCopied": "Link zum Thema kopiert", - "switchAccountButton": "Konto wechseln", + "switchAccountButtonTooltip": "Konto wechseln", "themeSettingDark": "Dunkel", "themeSettingLight": "Hell", "themeSettingSystem": "System", diff --git a/assets/l10n/app_en.arb b/assets/l10n/app_en.arb index 45c7e6ca94..1218318e4b 100644 --- a/assets/l10n/app_en.arb +++ b/assets/l10n/app_en.arb @@ -39,9 +39,9 @@ "@settingsPageTitle": { "description": "Title for the settings page." }, - "switchAccountButton": "Switch account", - "@switchAccountButton": { - "description": "Label for main-menu button leading to the choose-account page." + "switchAccountButtonTooltip": "Switch account", + "@switchAccountButtonTooltip": { + "description": "Tooltip message for main-menu button leading to the choose-account page." }, "tryAnotherAccountMessage": "Your account at {url} is taking a while to load.", "@tryAnotherAccountMessage": { diff --git a/assets/l10n/app_fr.arb b/assets/l10n/app_fr.arb index 5a45f6081c..8a4fa23021 100644 --- a/assets/l10n/app_fr.arb +++ b/assets/l10n/app_fr.arb @@ -526,8 +526,8 @@ "@successTopicLinkCopied": { "description": "Message when link of a topic was copied to the user's system clipboard." }, - "@switchAccountButton": { - "description": "Label for main-menu button leading to the choose-account page." + "@switchAccountButtonTooltip": { + "description": "Tooltip message for main-menu button leading to the choose-account page." }, "@topicsButtonTooltip": { "description": "Tooltip for button to navigate to topic-list page." @@ -742,7 +742,7 @@ "successMessageLinkCopied": "Lien sur le message copié", "successMessageTextCopied": "Texte du message copié", "successTopicLinkCopied": "Lien sur le sujet copié", - "switchAccountButton": "Changer de compte", + "switchAccountButtonTooltip": "Changer de compte", "topicsButtonTooltip": "Sujets", "tryAnotherAccountButton": "Essayer un autre compte", "tryAnotherAccountMessage": "Votre compte à {url} prend du temps à se charger.", diff --git a/assets/l10n/app_it.arb b/assets/l10n/app_it.arb index 6ba8e3f2cd..8ab7320a13 100644 --- a/assets/l10n/app_it.arb +++ b/assets/l10n/app_it.arb @@ -841,8 +841,8 @@ "@successMessageTextCopied": { "description": "Message when content of a message was copied to the user's system clipboard." }, - "@switchAccountButton": { - "description": "Label for main-menu button leading to the choose-account page." + "@switchAccountButtonTooltip": { + "description": "Tooltip message for main-menu button leading to the choose-account page." }, "@themeSettingDark": { "description": "Label for dark theme setting." @@ -1173,7 +1173,7 @@ "successLinkCopied": "Collegamento copiato", "successMessageLinkCopied": "Collegamento messaggio copiato", "successMessageTextCopied": "Testo messaggio copiato", - "switchAccountButton": "Cambia account", + "switchAccountButtonTooltip": "Cambia account", "themeSettingDark": "Scuro", "themeSettingLight": "Chiaro", "themeSettingSystem": "Sistema", diff --git a/assets/l10n/app_ja.arb b/assets/l10n/app_ja.arb index 66c8389695..5a270b00e5 100644 --- a/assets/l10n/app_ja.arb +++ b/assets/l10n/app_ja.arb @@ -979,8 +979,8 @@ "@successTopicLinkCopied": { "description": "Message when link of a topic was copied to the user's system clipboard." }, - "@switchAccountButton": { - "description": "Label for main-menu button leading to the choose-account page." + "@switchAccountButtonTooltip": { + "description": "Tooltip message for main-menu button leading to the choose-account page." }, "@themeSettingDark": { "description": "Label for dark theme setting." @@ -1424,7 +1424,7 @@ "successMessageLinkCopied": "メッセージのリンクをコピーしました", "successMessageTextCopied": "メッセージ本文をコピーしました", "successTopicLinkCopied": "トピックのリンクをコピーしました", - "switchAccountButton": "アカウントを切り替える", + "switchAccountButtonTooltip": "アカウントを切り替える", "themeSettingDark": "ダークテーマ", "themeSettingLight": "ライトテーマ", "themeSettingSystem": "自動テーマ", diff --git a/assets/l10n/app_pl.arb b/assets/l10n/app_pl.arb index 41126f815e..1f3fba52c2 100644 --- a/assets/l10n/app_pl.arb +++ b/assets/l10n/app_pl.arb @@ -1045,8 +1045,8 @@ "@successTopicLinkCopied": { "description": "Message when link of a topic was copied to the user's system clipboard." }, - "@switchAccountButton": { - "description": "Label for main-menu button leading to the choose-account page." + "@switchAccountButtonTooltip": { + "description": "Tooltip message for main-menu button leading to the choose-account page." }, "@themeSettingDark": { "description": "Label for dark theme setting." @@ -1523,7 +1523,7 @@ "successMessageLinkCopied": "Skopiowano odnośnik wiadomości", "successMessageTextCopied": "Skopiowano tekst wiadomości", "successTopicLinkCopied": "Skopiowano odnośnik do wątku", - "switchAccountButton": "Przełącz konto", + "switchAccountButtonTooltip": "Przełącz konto", "themeSettingDark": "Ciemny", "themeSettingLight": "Jasny", "themeSettingSystem": "Systemowy", diff --git a/assets/l10n/app_ru.arb b/assets/l10n/app_ru.arb index 147680fda9..3a5e39b010 100644 --- a/assets/l10n/app_ru.arb +++ b/assets/l10n/app_ru.arb @@ -1045,8 +1045,8 @@ "@successTopicLinkCopied": { "description": "Message when link of a topic was copied to the user's system clipboard." }, - "@switchAccountButton": { - "description": "Label for main-menu button leading to the choose-account page." + "@switchAccountButtonTooltip": { + "description": "Tooltip message for main-menu button leading to the choose-account page." }, "@themeSettingDark": { "description": "Label for dark theme setting." @@ -1523,7 +1523,7 @@ "successMessageLinkCopied": "Ссылка на сообщение скопирована", "successMessageTextCopied": "Текст сообщения скопирован", "successTopicLinkCopied": "Ссылка на тему скопирована", - "switchAccountButton": "Сменить учетную запись", + "switchAccountButtonTooltip": "Сменить учетную запись", "themeSettingDark": "Темный", "themeSettingLight": "Светлый", "themeSettingSystem": "Системный", diff --git a/assets/l10n/app_sk.arb b/assets/l10n/app_sk.arb index 4d6279d7b1..d454a41421 100644 --- a/assets/l10n/app_sk.arb +++ b/assets/l10n/app_sk.arb @@ -43,9 +43,9 @@ "@actionSheetOptionUnfollowTopic": { "description": "Label for unfollowing a topic on action sheet." }, - "switchAccountButton": "Zmeniť účet", - "@switchAccountButton": { - "description": "Label for main-menu button leading to the choose-account page." + "switchAccountButtonTooltip": "Zmeniť účet", + "@switchAccountButtonTooltip": { + "description": "Tooltip message for main-menu button leading to the choose-account page." }, "permissionsNeededOpenSettings": "Otvoriť nastavenia", "@permissionsNeededOpenSettings": { diff --git a/assets/l10n/app_sl.arb b/assets/l10n/app_sl.arb index ed63911d71..bdf9190eac 100644 --- a/assets/l10n/app_sl.arb +++ b/assets/l10n/app_sl.arb @@ -1045,8 +1045,8 @@ "@successTopicLinkCopied": { "description": "Message when link of a topic was copied to the user's system clipboard." }, - "@switchAccountButton": { - "description": "Label for main-menu button leading to the choose-account page." + "@switchAccountButtonTooltip": { + "description": "Tooltip message for main-menu button leading to the choose-account page." }, "@themeSettingDark": { "description": "Label for dark theme setting." @@ -1523,7 +1523,7 @@ "successMessageLinkCopied": "Povezava do sporočila je bila kopirana", "successMessageTextCopied": "Besedilo sporočila je bilo kopirano", "successTopicLinkCopied": "Povezava do teme kopirana", - "switchAccountButton": "Preklopi račun", + "switchAccountButtonTooltip": "Preklopi račun", "themeSettingDark": "Temna", "themeSettingLight": "Svetla", "themeSettingSystem": "Sistemska", diff --git a/assets/l10n/app_uk.arb b/assets/l10n/app_uk.arb index 55ebb93abd..3be6b66351 100644 --- a/assets/l10n/app_uk.arb +++ b/assets/l10n/app_uk.arb @@ -1045,8 +1045,8 @@ "@successTopicLinkCopied": { "description": "Message when link of a topic was copied to the user's system clipboard." }, - "@switchAccountButton": { - "description": "Label for main-menu button leading to the choose-account page." + "@switchAccountButtonTooltip": { + "description": "Tooltip message for main-menu button leading to the choose-account page." }, "@themeSettingDark": { "description": "Label for dark theme setting." @@ -1523,7 +1523,7 @@ "successMessageLinkCopied": "Посилання на повідомлення скопійовано", "successMessageTextCopied": "Текст повідомлення скопійовано", "successTopicLinkCopied": "Посилання на тему скопійовано", - "switchAccountButton": "Змінити обліковий запис", + "switchAccountButtonTooltip": "Змінити обліковий запис", "themeSettingDark": "Темна", "themeSettingLight": "Світла", "themeSettingSystem": "Системна", diff --git a/assets/l10n/app_zh_Hans_CN.arb b/assets/l10n/app_zh_Hans_CN.arb index d843effdca..d3aed361f8 100644 --- a/assets/l10n/app_zh_Hans_CN.arb +++ b/assets/l10n/app_zh_Hans_CN.arb @@ -1004,8 +1004,8 @@ "@successTopicLinkCopied": { "description": "Message when link of a topic was copied to the user's system clipboard." }, - "@switchAccountButton": { - "description": "Label for main-menu button leading to the choose-account page." + "@switchAccountButtonTooltip": { + "description": "Tooltip message for main-menu button leading to the choose-account page." }, "@themeSettingDark": { "description": "Label for dark theme setting." @@ -1468,7 +1468,7 @@ "successMessageLinkCopied": "已复制消息链接", "successMessageTextCopied": "已复制消息文本", "successTopicLinkCopied": "话题链接已复制", - "switchAccountButton": "切换账号", + "switchAccountButtonTooltip": "切换账号", "themeSettingDark": "暗色模式", "themeSettingLight": "浅色模式", "themeSettingSystem": "跟随系统", diff --git a/assets/l10n/app_zh_Hant_TW.arb b/assets/l10n/app_zh_Hant_TW.arb index 6ae7f7ac92..8639296119 100644 --- a/assets/l10n/app_zh_Hant_TW.arb +++ b/assets/l10n/app_zh_Hant_TW.arb @@ -1016,8 +1016,8 @@ "@successTopicLinkCopied": { "description": "Message when link of a topic was copied to the user's system clipboard." }, - "@switchAccountButton": { - "description": "Label for main-menu button leading to the choose-account page." + "@switchAccountButtonTooltip": { + "description": "Tooltip message for main-menu button leading to the choose-account page." }, "@themeSettingDark": { "description": "Label for dark theme setting." @@ -1487,7 +1487,7 @@ "successMessageLinkCopied": "已複製訊息連結", "successMessageTextCopied": "已複製訊息文字", "successTopicLinkCopied": "議題連結已複製", - "switchAccountButton": "切換帳號", + "switchAccountButtonTooltip": "切換帳號", "themeSettingDark": "深色主題", "themeSettingLight": "淺色主題", "themeSettingSystem": "系統主題", diff --git a/lib/generated/l10n/zulip_localizations.dart b/lib/generated/l10n/zulip_localizations.dart index ce46ae6e7d..21b9bb890a 100644 --- a/lib/generated/l10n/zulip_localizations.dart +++ b/lib/generated/l10n/zulip_localizations.dart @@ -199,11 +199,11 @@ abstract class ZulipLocalizations { /// **'Settings'** String get settingsPageTitle; - /// Label for main-menu button leading to the choose-account page. + /// Tooltip message for main-menu button leading to the choose-account page. /// /// In en, this message translates to: /// **'Switch account'** - String get switchAccountButton; + String get switchAccountButtonTooltip; /// Message that appears on the loading screen after waiting for some time. /// diff --git a/lib/generated/l10n/zulip_localizations_ar.dart b/lib/generated/l10n/zulip_localizations_ar.dart index d4c35968bf..85955f29d4 100644 --- a/lib/generated/l10n/zulip_localizations_ar.dart +++ b/lib/generated/l10n/zulip_localizations_ar.dart @@ -41,7 +41,7 @@ class ZulipLocalizationsAr extends ZulipLocalizations { String get settingsPageTitle => 'الإعدادات'; @override - String get switchAccountButton => 'تبديل الحساب'; + String get switchAccountButtonTooltip => 'تبديل الحساب'; @override String tryAnotherAccountMessage(Object url) { diff --git a/lib/generated/l10n/zulip_localizations_de.dart b/lib/generated/l10n/zulip_localizations_de.dart index 0a43ac50b5..c1fd837d82 100644 --- a/lib/generated/l10n/zulip_localizations_de.dart +++ b/lib/generated/l10n/zulip_localizations_de.dart @@ -41,7 +41,7 @@ class ZulipLocalizationsDe extends ZulipLocalizations { String get settingsPageTitle => 'Einstellungen'; @override - String get switchAccountButton => 'Konto wechseln'; + String get switchAccountButtonTooltip => 'Konto wechseln'; @override String tryAnotherAccountMessage(Object url) { diff --git a/lib/generated/l10n/zulip_localizations_el.dart b/lib/generated/l10n/zulip_localizations_el.dart index c7725a64e2..d03b08a7db 100644 --- a/lib/generated/l10n/zulip_localizations_el.dart +++ b/lib/generated/l10n/zulip_localizations_el.dart @@ -41,7 +41,7 @@ class ZulipLocalizationsEl extends ZulipLocalizations { String get settingsPageTitle => 'Settings'; @override - String get switchAccountButton => 'Switch account'; + String get switchAccountButtonTooltip => 'Switch account'; @override String tryAnotherAccountMessage(Object url) { diff --git a/lib/generated/l10n/zulip_localizations_en.dart b/lib/generated/l10n/zulip_localizations_en.dart index 334df16239..aacbbc55a3 100644 --- a/lib/generated/l10n/zulip_localizations_en.dart +++ b/lib/generated/l10n/zulip_localizations_en.dart @@ -41,7 +41,7 @@ class ZulipLocalizationsEn extends ZulipLocalizations { String get settingsPageTitle => 'Settings'; @override - String get switchAccountButton => 'Switch account'; + String get switchAccountButtonTooltip => 'Switch account'; @override String tryAnotherAccountMessage(Object url) { diff --git a/lib/generated/l10n/zulip_localizations_es.dart b/lib/generated/l10n/zulip_localizations_es.dart index f45d3db383..934f643fd1 100644 --- a/lib/generated/l10n/zulip_localizations_es.dart +++ b/lib/generated/l10n/zulip_localizations_es.dart @@ -41,7 +41,7 @@ class ZulipLocalizationsEs extends ZulipLocalizations { String get settingsPageTitle => 'Settings'; @override - String get switchAccountButton => 'Switch account'; + String get switchAccountButtonTooltip => 'Switch account'; @override String tryAnotherAccountMessage(Object url) { diff --git a/lib/generated/l10n/zulip_localizations_fr.dart b/lib/generated/l10n/zulip_localizations_fr.dart index 0d64c3b679..e2014f71b5 100644 --- a/lib/generated/l10n/zulip_localizations_fr.dart +++ b/lib/generated/l10n/zulip_localizations_fr.dart @@ -42,7 +42,7 @@ class ZulipLocalizationsFr extends ZulipLocalizations { String get settingsPageTitle => 'Paramètres'; @override - String get switchAccountButton => 'Changer de compte'; + String get switchAccountButtonTooltip => 'Changer de compte'; @override String tryAnotherAccountMessage(Object url) { diff --git a/lib/generated/l10n/zulip_localizations_he.dart b/lib/generated/l10n/zulip_localizations_he.dart index 5e1609ccba..aeef6170bc 100644 --- a/lib/generated/l10n/zulip_localizations_he.dart +++ b/lib/generated/l10n/zulip_localizations_he.dart @@ -41,7 +41,7 @@ class ZulipLocalizationsHe extends ZulipLocalizations { String get settingsPageTitle => 'Settings'; @override - String get switchAccountButton => 'Switch account'; + String get switchAccountButtonTooltip => 'Switch account'; @override String tryAnotherAccountMessage(Object url) { diff --git a/lib/generated/l10n/zulip_localizations_hu.dart b/lib/generated/l10n/zulip_localizations_hu.dart index aacb5d9d8d..5f10bc0409 100644 --- a/lib/generated/l10n/zulip_localizations_hu.dart +++ b/lib/generated/l10n/zulip_localizations_hu.dart @@ -41,7 +41,7 @@ class ZulipLocalizationsHu extends ZulipLocalizations { String get settingsPageTitle => 'Settings'; @override - String get switchAccountButton => 'Switch account'; + String get switchAccountButtonTooltip => 'Switch account'; @override String tryAnotherAccountMessage(Object url) { diff --git a/lib/generated/l10n/zulip_localizations_it.dart b/lib/generated/l10n/zulip_localizations_it.dart index 4fc1ba6e42..a63552d229 100644 --- a/lib/generated/l10n/zulip_localizations_it.dart +++ b/lib/generated/l10n/zulip_localizations_it.dart @@ -41,7 +41,7 @@ class ZulipLocalizationsIt extends ZulipLocalizations { String get settingsPageTitle => 'Impostazioni'; @override - String get switchAccountButton => 'Cambia account'; + String get switchAccountButtonTooltip => 'Cambia account'; @override String tryAnotherAccountMessage(Object url) { diff --git a/lib/generated/l10n/zulip_localizations_ja.dart b/lib/generated/l10n/zulip_localizations_ja.dart index e779208dcf..ef8a5a382a 100644 --- a/lib/generated/l10n/zulip_localizations_ja.dart +++ b/lib/generated/l10n/zulip_localizations_ja.dart @@ -40,7 +40,7 @@ class ZulipLocalizationsJa extends ZulipLocalizations { String get settingsPageTitle => '設定'; @override - String get switchAccountButton => 'アカウントを切り替える'; + String get switchAccountButtonTooltip => 'アカウントを切り替える'; @override String tryAnotherAccountMessage(Object url) { diff --git a/lib/generated/l10n/zulip_localizations_nb.dart b/lib/generated/l10n/zulip_localizations_nb.dart index 3d9b7990e7..b10e0d390f 100644 --- a/lib/generated/l10n/zulip_localizations_nb.dart +++ b/lib/generated/l10n/zulip_localizations_nb.dart @@ -41,7 +41,7 @@ class ZulipLocalizationsNb extends ZulipLocalizations { String get settingsPageTitle => 'Settings'; @override - String get switchAccountButton => 'Switch account'; + String get switchAccountButtonTooltip => 'Switch account'; @override String tryAnotherAccountMessage(Object url) { diff --git a/lib/generated/l10n/zulip_localizations_pl.dart b/lib/generated/l10n/zulip_localizations_pl.dart index ea8d3b4fd8..57fe54d845 100644 --- a/lib/generated/l10n/zulip_localizations_pl.dart +++ b/lib/generated/l10n/zulip_localizations_pl.dart @@ -41,7 +41,7 @@ class ZulipLocalizationsPl extends ZulipLocalizations { String get settingsPageTitle => 'Ustawienia'; @override - String get switchAccountButton => 'Przełącz konto'; + String get switchAccountButtonTooltip => 'Przełącz konto'; @override String tryAnotherAccountMessage(Object url) { diff --git a/lib/generated/l10n/zulip_localizations_ru.dart b/lib/generated/l10n/zulip_localizations_ru.dart index 17f82a0d26..f021181f7e 100644 --- a/lib/generated/l10n/zulip_localizations_ru.dart +++ b/lib/generated/l10n/zulip_localizations_ru.dart @@ -41,7 +41,7 @@ class ZulipLocalizationsRu extends ZulipLocalizations { String get settingsPageTitle => 'Настройки'; @override - String get switchAccountButton => 'Сменить учетную запись'; + String get switchAccountButtonTooltip => 'Сменить учетную запись'; @override String tryAnotherAccountMessage(Object url) { diff --git a/lib/generated/l10n/zulip_localizations_sk.dart b/lib/generated/l10n/zulip_localizations_sk.dart index 092070d0f3..ca85e02dc8 100644 --- a/lib/generated/l10n/zulip_localizations_sk.dart +++ b/lib/generated/l10n/zulip_localizations_sk.dart @@ -41,7 +41,7 @@ class ZulipLocalizationsSk extends ZulipLocalizations { String get settingsPageTitle => 'Settings'; @override - String get switchAccountButton => 'Zmeniť účet'; + String get switchAccountButtonTooltip => 'Zmeniť účet'; @override String tryAnotherAccountMessage(Object url) { diff --git a/lib/generated/l10n/zulip_localizations_sl.dart b/lib/generated/l10n/zulip_localizations_sl.dart index f18f440748..274c3ad7f6 100644 --- a/lib/generated/l10n/zulip_localizations_sl.dart +++ b/lib/generated/l10n/zulip_localizations_sl.dart @@ -40,7 +40,7 @@ class ZulipLocalizationsSl extends ZulipLocalizations { String get settingsPageTitle => 'Nastavitve'; @override - String get switchAccountButton => 'Preklopi račun'; + String get switchAccountButtonTooltip => 'Preklopi račun'; @override String tryAnotherAccountMessage(Object url) { diff --git a/lib/generated/l10n/zulip_localizations_uk.dart b/lib/generated/l10n/zulip_localizations_uk.dart index 3810960b63..c68e3710e8 100644 --- a/lib/generated/l10n/zulip_localizations_uk.dart +++ b/lib/generated/l10n/zulip_localizations_uk.dart @@ -41,7 +41,7 @@ class ZulipLocalizationsUk extends ZulipLocalizations { String get settingsPageTitle => 'Налаштування'; @override - String get switchAccountButton => 'Змінити обліковий запис'; + String get switchAccountButtonTooltip => 'Змінити обліковий запис'; @override String tryAnotherAccountMessage(Object url) { diff --git a/lib/generated/l10n/zulip_localizations_zh.dart b/lib/generated/l10n/zulip_localizations_zh.dart index 5db806ac0e..666924ef1b 100644 --- a/lib/generated/l10n/zulip_localizations_zh.dart +++ b/lib/generated/l10n/zulip_localizations_zh.dart @@ -41,7 +41,7 @@ class ZulipLocalizationsZh extends ZulipLocalizations { String get settingsPageTitle => 'Settings'; @override - String get switchAccountButton => 'Switch account'; + String get switchAccountButtonTooltip => 'Switch account'; @override String tryAnotherAccountMessage(Object url) { @@ -1214,7 +1214,7 @@ class ZulipLocalizationsZhHansCn extends ZulipLocalizationsZh { String get settingsPageTitle => '设置'; @override - String get switchAccountButton => '切换账号'; + String get switchAccountButtonTooltip => '切换账号'; @override String tryAnotherAccountMessage(Object url) { @@ -2302,7 +2302,7 @@ class ZulipLocalizationsZhHantTw extends ZulipLocalizationsZh { String get settingsPageTitle => '設定'; @override - String get switchAccountButton => '切換帳號'; + String get switchAccountButtonTooltip => '切換帳號'; @override String tryAnotherAccountMessage(Object url) { diff --git a/lib/widgets/home.dart b/lib/widgets/home.dart index 62c09c0857..f20b1706bc 100644 --- a/lib/widgets/home.dart +++ b/lib/widgets/home.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import '../generated/l10n/zulip_localizations.dart'; +import '../log.dart'; import '../model/narrow.dart'; import 'about_zulip.dart'; import 'action_sheet.dart'; @@ -11,6 +12,7 @@ import 'app_bar.dart'; import 'button.dart'; import 'color.dart'; import 'icons.dart'; +import 'image.dart'; import 'inbox.dart'; import 'inset_shadow.dart'; import 'message_list.dart'; @@ -279,7 +281,6 @@ void _showMainMenu(BuildContext context, { _DirectMessagesButton(tabNotifier: tabNotifier), // TODO(#1094): Users const _MyProfileButton(), - const _SwitchAccountButton(), // TODO(#198): Set my status // const SizedBox(height: 8), const _SettingsButton(), @@ -307,29 +308,106 @@ void _showMainMenu(BuildContext context, { builder: (BuildContext _) { return PerAccountStoreWidget( accountId: accountId, - child: SafeArea( - minimum: const EdgeInsets.only(bottom: 8), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisSize: MainAxisSize.min, - children: [ - Flexible(child: InsetShadowBox( - top: 8, bottom: 8, - color: designVariables.bgBotBar, - child: SingleChildScrollView( - padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 8), - child: Column(children: menuItems)))), - const Padding( - padding: EdgeInsets.symmetric(horizontal: 16), - child: AnimatedScaleOnTap( - scaleEnd: 0.95, - duration: Duration(milliseconds: 100), - child: BottomSheetDismissButton( - style: BottomSheetDismissButtonStyle.close))), - ]))); + child: _MainMenu(menuItems: menuItems)); }); } +/// The main-menu sheet. +/// +/// Figma link: +/// https://www.figma.com/design/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=143-10939&t=s7AS3nEgNgjyqHck-4 +class _MainMenu extends StatelessWidget { + const _MainMenu({ + required this.menuItems, + }); + + final List menuItems; + + @override + Widget build(BuildContext context) { + final designVariables = DesignVariables.of(context); + + return SafeArea( + minimum: const EdgeInsets.only(bottom: 8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, + children: [ + _MainMenuHeader(), + Flexible(child: InsetShadowBox( + top: 8, bottom: 8, + color: designVariables.bgBotBar, + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 8), + child: Column(children: menuItems)))), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 16), + child: AnimatedScaleOnTap( + scaleEnd: 0.95, + duration: Duration(milliseconds: 100), + child: BottomSheetDismissButton( + style: BottomSheetDismissButtonStyle.close))), + ])); + } +} + +class _MainMenuHeader extends StatelessWidget { + const _MainMenuHeader(); + + void _onPressed(BuildContext context) { + Navigator.pop(context); // Close the main menu. + Navigator.push(context, + MaterialWidgetRoute(page: const ChooseAccountPage())); + } + + @override + Widget build(BuildContext context) { + final zulipLocalizations = ZulipLocalizations.of(context); + final designVariables = DesignVariables.of(context); + final store = PerAccountStoreWidget.of(context); + + final realmIconUrl = store.resolveRealmIconUrl(); + + final placeholder = ColoredBox(color: designVariables.avatarPlaceholderBg); + final logo = realmIconUrl != null + ? RealmContentNetworkImage( + realmIconUrl, + frameBuilder: (_, child, frame, _) { + if (frame == null) return placeholder; + return child; + }, + errorBuilder: (_, e, st) { + assert(debugLog('$e\n$st')); // TODO(log) + return placeholder; + }) + : placeholder; + + return Padding( + padding: const EdgeInsets.only(top: 6, left: 12, right: 12), + child: Row(spacing: 12, children: [ + Flexible(child: Padding( + padding: const EdgeInsets.symmetric(vertical: 6), + child: Row(spacing: 8, children: [ + AvatarShape( + size: 28, + borderRadius: 4, + child: logo), + Flexible(child: Text(store.realmName, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: designVariables.title, + fontSize: 20, + height: 24 / 20, + ).merge(weightVariableTextStyle(context, wght: 600)))), + ]))), + ZulipIconButton( + icon: ZulipIcons.arrow_left_right, + tooltip: zulipLocalizations.switchAccountButtonTooltip, + onPressed: () => _onPressed(context)), + ])); + } +} + abstract class _MenuButton extends StatelessWidget { const _MenuButton(); @@ -575,23 +653,6 @@ class _MyProfileButton extends _MenuButton { } } -class _SwitchAccountButton extends _MenuButton { - const _SwitchAccountButton(); - - @override - IconData? get icon => ZulipIcons.arrow_left_right; - - @override - String label(ZulipLocalizations zulipLocalizations) { - return zulipLocalizations.switchAccountButton; - } - - @override - void onPressed(BuildContext context) { - Navigator.of(context).push(MaterialWidgetRoute(page: const ChooseAccountPage())); - } -} - class _SettingsButton extends _MenuButton { const _SettingsButton(); diff --git a/test/widgets/home_test.dart b/test/widgets/home_test.dart index 1ee0a0ae8e..395113d35a 100644 --- a/test/widgets/home_test.dart +++ b/test/widgets/home_test.dart @@ -23,6 +23,7 @@ import '../flutter_checks.dart'; import '../model/binding.dart'; import '../model/store_checks.dart'; import '../model/test_store.dart'; +import '../test_images.dart'; import '../test_navigation.dart'; import 'checks.dart'; import 'test_app.dart'; @@ -225,6 +226,7 @@ void main () { } testWidgets('navigation states reflect on navigation bar menu buttons', (tester) async { + prepareBoringImageHttpClient(); await prepare(tester); await tapOpenMenuAndAwait(tester); @@ -238,9 +240,11 @@ void main () { await tapOpenMenuAndAwait(tester); checkIconNotSelected(tester, inboxMenuIconFinder); checkIconSelected(tester, channelsMenuIconFinder); + debugNetworkImageHttpClientProvider = null; }); testWidgets('navigation bar menu buttons control navigation states', (tester) async { + prepareBoringImageHttpClient(); await prepare(tester); await tapOpenMenuAndAwait(tester); @@ -256,21 +260,27 @@ void main () { await tapOpenMenuAndAwait(tester); checkIconNotSelected(tester, inboxMenuIconFinder); checkIconSelected(tester, channelsMenuIconFinder); + debugNetworkImageHttpClientProvider = null; }); testWidgets('navigation bar menu buttons dismiss the menu', (tester) async { + prepareBoringImageHttpClient(); await prepare(tester); await tapOpenMenuAndAwait(tester); await tapButtonAndAwaitTransition(tester, channelsMenuIconFinder); + debugNetworkImageHttpClientProvider = null; }); testWidgets('close button dismisses the menu', (tester) async { + prepareBoringImageHttpClient(); await prepare(tester); await tapOpenMenuAndAwait(tester); await tapButtonAndAwaitTransition(tester, find.text('Close')); + debugNetworkImageHttpClientProvider = null; }); testWidgets('menu buttons dismiss the menu', (tester) async { + prepareBoringImageHttpClient(); addTearDown(testBinding.reset); topRoute = null; previousTopRoute = null; @@ -297,21 +307,36 @@ void main () { await tester.pump((topBeforePop as TransitionRoute).reverseTransitionDuration); check(find.byType(BottomSheet)).findsNothing(); + debugNetworkImageHttpClientProvider = null; }); testWidgets('_MyProfileButton', (tester) async { + prepareBoringImageHttpClient(); await prepare(tester); await tapOpenMenuAndAwait(tester); await tapButtonAndAwaitTransition(tester, find.text('My profile')); check(find.byType(ProfilePage)).findsOne(); check(find.text(eg.selfUser.fullName)).findsAny(); + debugNetworkImageHttpClientProvider = null; }); testWidgets('_AboutZulipButton', (tester) async { + prepareBoringImageHttpClient(); await prepare(tester); await tapOpenMenuAndAwait(tester); + await tester.ensureVisible(find.byIcon(ZulipIcons.info)); await tapButtonAndAwaitTransition(tester, find.byIcon(ZulipIcons.info)); check(find.byType(AboutZulipPage)).findsOne(); + debugNetworkImageHttpClientProvider = null; + }); + + testWidgets('_MainMenuHeader', (tester) async { + prepareBoringImageHttpClient(); + await prepare(tester); + await tapOpenMenuAndAwait(tester); + await tapButtonAndAwaitTransition(tester, find.byIcon(ZulipIcons.arrow_left_right)); + check(find.byType(ChooseAccountPage)).findsOne(); + debugNetworkImageHttpClientProvider = null; }); });