From f1aa5b62a7b674927561513e268342201092693a Mon Sep 17 00:00:00 2001 From: Rajesh Malviya Date: Wed, 22 Oct 2025 19:00:25 +0530 Subject: [PATCH 1/5] store: Make `realmName` and `realmIcon` getters non-nullable Additionally, add a bit of dartdoc for `realmName` and `realmIcon`. --- lib/model/store.dart | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/model/store.dart b/lib/model/store.dart index fda6e5b695..7e48ab2556 100644 --- a/lib/model/store.dart +++ b/lib/model/store.dart @@ -423,9 +423,11 @@ abstract class PerAccountStoreBase { /// Always equal to `account.realmUrl` and `connection.realmUrl`. Uri get realmUrl => connection.realmUrl; - String? get realmName => account.realmName; + /// Always equal to `account.realmName`. + String get realmName => account.realmName!; - Uri? get realmIcon => account.realmIcon; + /// Always equal to `account.realmIcon`. + Uri get realmIcon => account.realmIcon!; /// Resolve [reference] as a URL relative to [realmUrl]. /// @@ -507,6 +509,8 @@ class PerAccountStore extends PerAccountStoreBase with assert(account.zulipVersion == initialSnapshot.zulipVersion && account.zulipMergeBase == initialSnapshot.zulipMergeBase && account.zulipFeatureLevel == initialSnapshot.zulipFeatureLevel); + assert(account.realmName == initialSnapshot.realmName + && account.realmIcon == initialSnapshot.realmIconUrl); connection ??= globalStore.apiConnectionFromAccount(account); assert(connection.zulipFeatureLevel == account.zulipFeatureLevel); From de4b28a2ac602c8a4ac7c518578fb6223c8a282b Mon Sep 17 00:00:00 2001 From: Rajesh Malviya Date: Tue, 28 Oct 2025 19:01:24 +0530 Subject: [PATCH 2/5] store: Replace realmIcon getter with resolvedRealmIcon We almost always need the full resolved realm icon URL, so provide a getter that does that. --- lib/model/store.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/model/store.dart b/lib/model/store.dart index 7e48ab2556..3e7f1a5eba 100644 --- a/lib/model/store.dart +++ b/lib/model/store.dart @@ -426,8 +426,11 @@ abstract class PerAccountStoreBase { /// Always equal to `account.realmName`. String get realmName => account.realmName!; - /// Always equal to `account.realmIcon`. - Uri get realmIcon => account.realmIcon!; + /// The full, resolved URL for the Zulip realm icon. + /// + /// Returned URL is derived by resolving [account.realmIcon] (relative) URL + /// against the base [realmUrl]. + Uri get resolvedRealmIcon => realmUrl.resolveUri(account.realmIcon!); /// Resolve [reference] as a URL relative to [realmUrl]. /// From c478ac3d3b8d1b7e8b77d8c4531d2746878d411e Mon Sep 17 00:00:00 2001 From: Rajesh Malviya Date: Fri, 14 Nov 2025 13:47:02 +0530 Subject: [PATCH 3/5] 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 36e4d41535b7f56963ec7b39889b1b452298ddd4 Mon Sep 17 00:00:00 2001 From: Rajesh Malviya Date: Mon, 24 Nov 2025 23:47:56 +0530 Subject: [PATCH 4/5] home [nfc]: Factor out _MainMenu widget --- lib/widgets/home.dart | 105 +++++++++++++++++++++++++----------------- 1 file changed, 62 insertions(+), 43 deletions(-) diff --git a/lib/widgets/home.dart b/lib/widgets/home.dart index c74da27a86..2432b1f732 100644 --- a/lib/widgets/home.dart +++ b/lib/widgets/home.dart @@ -334,29 +334,6 @@ class _NavigationBarButton extends StatelessWidget { void _showMainMenu(BuildContext context, { required ValueNotifier<_HomePageTab> tabNotifier, }) { - final menuItems = [ - const _SearchButton(), - // const SizedBox(height: 8), - _InboxButton(tabNotifier: tabNotifier), - // TODO: Recent conversations - const _MentionsButton(), - const _StarredMessagesButton(), - const _CombinedFeedButton(), - // TODO: Drafts - _ChannelsButton(tabNotifier: tabNotifier), - _DirectMessagesButton(tabNotifier: tabNotifier), - // TODO(#1094): Users - const _MyProfileButton(), - const _SwitchAccountButton(), - // TODO(#198): Set my status - // const SizedBox(height: 8), - const _SettingsButton(), - // TODO(#661): Notifications - // const SizedBox(height: 8), - const _AboutZulipButton(), - // TODO(#1095): VersionInfo - ]; - final designVariables = DesignVariables.of(context); final accountId = PerAccountStoreWidget.accountIdOf(context); showModalBottomSheet( @@ -375,29 +352,71 @@ 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(tabNotifier: tabNotifier)); }); } +/// 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.tabNotifier, + }); + + final ValueNotifier<_HomePageTab> tabNotifier; + + @override + Widget build(BuildContext context) { + final designVariables = DesignVariables.of(context); + + final menuItems = [ + const _SearchButton(), + // const SizedBox(height: 8), + _InboxButton(tabNotifier: tabNotifier), + // TODO: Recent conversations + const _MentionsButton(), + const _StarredMessagesButton(), + const _CombinedFeedButton(), + // TODO: Drafts + _ChannelsButton(tabNotifier: tabNotifier), + _DirectMessagesButton(tabNotifier: tabNotifier), + // TODO(#1094): Users + const _MyProfileButton(), + const _SwitchAccountButton(), + // TODO(#198): Set my status + // const SizedBox(height: 8), + const _SettingsButton(), + // TODO(#661): Notifications + // const SizedBox(height: 8), + const _AboutZulipButton(), + // TODO(#1095): VersionInfo + ]; + + return 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))), + ])); + } +} + abstract class _MenuButton extends StatelessWidget { const _MenuButton(); From 603edcdbb4c6b32c1a1eed8540bb6a18fadf5517 Mon Sep 17 00:00:00 2001 From: Rajesh Malviya Date: Wed, 22 Oct 2025 20:02:50 +0530 Subject: [PATCH 5/5] 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 | 87 +++++++++++++++---- test/widgets/home_test.dart | 25 ++++++ 33 files changed, 154 insertions(+), 78 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 38b49c2edd..5f322254d2 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 fe79921c07..0487c838de 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 5e69a181da..ecc618f00e 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 241e886e55..68cf34623a 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 ce72dc77bf..2b1e6a98a0 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 6d10362555..8196d87dfd 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 80098d557f..51fcdd4079 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 adb41177b2..178064c3b2 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 e59309be3e..4f5eafd078 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 6a3c11465c..057051f526 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 cd27a1daa7..1f75c6ad2e 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 d11aedf19f..9a2f2e5053 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 162ca9e677..f18b7818b3 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 40682171b2..3e6bb6414b 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 04f8903b91..1ee21963f0 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 e2dc542fc5..2819b5f602 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 6f9d8343ca..e4737be1d8 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 84df26a42e..7323f621a9 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 b08c25004e..bb498a2b7a 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) { @@ -1217,7 +1217,7 @@ class ZulipLocalizationsZhHansCn extends ZulipLocalizationsZh { String get settingsPageTitle => '设置'; @override - String get switchAccountButton => '切换账号'; + String get switchAccountButtonTooltip => '切换账号'; @override String tryAnotherAccountMessage(Object url) { @@ -2305,7 +2305,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 2432b1f732..f4a7650f6f 100644 --- a/lib/widgets/home.dart +++ b/lib/widgets/home.dart @@ -12,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'; @@ -384,7 +385,6 @@ class _MainMenu extends StatelessWidget { _DirectMessagesButton(tabNotifier: tabNotifier), // TODO(#1094): Users const _MyProfileButton(), - const _SwitchAccountButton(), // TODO(#198): Set my status // const SizedBox(height: 8), const _SettingsButton(), @@ -400,6 +400,7 @@ class _MainMenu extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min, children: [ + _MainMenuHeader(), Flexible(child: InsetShadowBox( top: 8, bottom: 8, color: designVariables.bgBotBar, @@ -417,6 +418,73 @@ class _MainMenu extends StatelessWidget { } } +class _MainMenuHeader extends StatefulWidget { + const _MainMenuHeader(); + + @override + State<_MainMenuHeader> createState() => _MainMenuHeaderState(); +} + +class _MainMenuHeaderState extends State<_MainMenuHeader> { + bool _isPressed = false; + + void _setIsPressed(bool isPressed) { + setState(() { + _isPressed = isPressed; + }); + } + + void _handleSwitchAccount(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); + + return Tooltip( + message: zulipLocalizations.switchAccountButtonTooltip, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () => _handleSwitchAccount(context), + onTapDown: (_) => _setIsPressed(true), + onTapUp: (_) => _setIsPressed(false), + onTapCancel: () => _setIsPressed(false), + child: AnimatedOpacity( + opacity: _isPressed ? 0.5 : 1, + duration: const Duration(milliseconds: 100), + child: 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: RealmContentNetworkImage( + store.resolvedRealmIcon, + filterQuality: FilterQuality.medium, + fit: BoxFit.cover)), + Flexible(child: Text(store.realmName, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: designVariables.title, + fontSize: 20, + height: 24 / 20, + ).merge(weightVariableTextStyle(context, wght: 600)))), + ]))), + Icon(ZulipIcons.arrow_left_right, + color: designVariables.icon, + size: 24), + ]))))); + } +} + abstract class _MenuButton extends StatelessWidget { const _MenuButton(); @@ -662,23 +730,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 239449c990..84475108ea 100644 --- a/test/widgets/home_test.dart +++ b/test/widgets/home_test.dart @@ -25,6 +25,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'; @@ -256,6 +257,7 @@ void main () { } testWidgets('navigation states reflect on navigation bar menu buttons', (tester) async { + prepareBoringImageHttpClient(); await prepare(tester); await tapOpenMenuAndAwait(tester); @@ -269,9 +271,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); @@ -287,21 +291,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; @@ -328,21 +338,36 @@ void main () { await tester.pump((topBeforePop as TransitionRoute).reverseTransitionDuration); check(find.byType(BottomSheet)).findsNothing(); + 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; }); 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; }); });