diff --git a/assets/l10n/app_en.arb b/assets/l10n/app_en.arb index 1f52ac32ba..25c3d7df74 100644 --- a/assets/l10n/app_en.arb +++ b/assets/l10n/app_en.arb @@ -1247,6 +1247,10 @@ "@wildcardMentionTopicDescription": { "description": "Description for \"@topic\" wildcard-mention autocomplete options when writing a channel message." }, + "navBarMenuLabel": "Menu", + "@navBarMenuLabel": { + "description": "Label for the Menu button on the bottom navigation bar." + }, "messageIsEditedLabel": "EDITED", "@messageIsEditedLabel": { "description": "Label for an edited message. (Use ALL CAPS for cased alphabets: Latin, Greek, Cyrillic, etc.)" diff --git a/lib/generated/l10n/zulip_localizations.dart b/lib/generated/l10n/zulip_localizations.dart index 31d472227d..ce8e5da937 100644 --- a/lib/generated/l10n/zulip_localizations.dart +++ b/lib/generated/l10n/zulip_localizations.dart @@ -1823,6 +1823,12 @@ abstract class ZulipLocalizations { /// **'Notify topic'** String get wildcardMentionTopicDescription; + /// Label for the Menu button on the bottom navigation bar. + /// + /// In en, this message translates to: + /// **'Menu'** + String get navBarMenuLabel; + /// Label for an edited message. (Use ALL CAPS for cased alphabets: Latin, Greek, Cyrillic, etc.) /// /// In en, this message translates to: diff --git a/lib/generated/l10n/zulip_localizations_ar.dart b/lib/generated/l10n/zulip_localizations_ar.dart index 1a292881ec..cd5b9663d8 100644 --- a/lib/generated/l10n/zulip_localizations_ar.dart +++ b/lib/generated/l10n/zulip_localizations_ar.dart @@ -1041,6 +1041,9 @@ class ZulipLocalizationsAr extends ZulipLocalizations { @override String get wildcardMentionTopicDescription => 'إخطار الموضوع'; + @override + String get navBarMenuLabel => 'Menu'; + @override String get messageIsEditedLabel => 'EDITED'; diff --git a/lib/generated/l10n/zulip_localizations_de.dart b/lib/generated/l10n/zulip_localizations_de.dart index 06e2ea1a29..0e44d5a5f7 100644 --- a/lib/generated/l10n/zulip_localizations_de.dart +++ b/lib/generated/l10n/zulip_localizations_de.dart @@ -1065,6 +1065,9 @@ class ZulipLocalizationsDe extends ZulipLocalizations { @override String get wildcardMentionTopicDescription => 'Thema benachrichtigen'; + @override + String get navBarMenuLabel => 'Menu'; + @override String get messageIsEditedLabel => 'BEARBEITET'; diff --git a/lib/generated/l10n/zulip_localizations_el.dart b/lib/generated/l10n/zulip_localizations_el.dart index ab2e6e686c..4fea45ea98 100644 --- a/lib/generated/l10n/zulip_localizations_el.dart +++ b/lib/generated/l10n/zulip_localizations_el.dart @@ -1041,6 +1041,9 @@ class ZulipLocalizationsEl extends ZulipLocalizations { @override String get wildcardMentionTopicDescription => 'Notify topic'; + @override + String get navBarMenuLabel => 'Menu'; + @override String get messageIsEditedLabel => 'EDITED'; diff --git a/lib/generated/l10n/zulip_localizations_en.dart b/lib/generated/l10n/zulip_localizations_en.dart index 80c1c2c475..61d9fbdd80 100644 --- a/lib/generated/l10n/zulip_localizations_en.dart +++ b/lib/generated/l10n/zulip_localizations_en.dart @@ -1041,6 +1041,9 @@ class ZulipLocalizationsEn extends ZulipLocalizations { @override String get wildcardMentionTopicDescription => 'Notify topic'; + @override + String get navBarMenuLabel => 'Menu'; + @override String get messageIsEditedLabel => 'EDITED'; diff --git a/lib/generated/l10n/zulip_localizations_es.dart b/lib/generated/l10n/zulip_localizations_es.dart index 7058e699fd..5c554322cf 100644 --- a/lib/generated/l10n/zulip_localizations_es.dart +++ b/lib/generated/l10n/zulip_localizations_es.dart @@ -1041,6 +1041,9 @@ class ZulipLocalizationsEs extends ZulipLocalizations { @override String get wildcardMentionTopicDescription => 'Notify topic'; + @override + String get navBarMenuLabel => 'Menu'; + @override String get messageIsEditedLabel => 'EDITED'; diff --git a/lib/generated/l10n/zulip_localizations_fr.dart b/lib/generated/l10n/zulip_localizations_fr.dart index 2e430ea046..f4d192f2e7 100644 --- a/lib/generated/l10n/zulip_localizations_fr.dart +++ b/lib/generated/l10n/zulip_localizations_fr.dart @@ -1057,6 +1057,9 @@ class ZulipLocalizationsFr extends ZulipLocalizations { @override String get wildcardMentionTopicDescription => 'Notify topic'; + @override + String get navBarMenuLabel => 'Menu'; + @override String get messageIsEditedLabel => 'EDITED'; diff --git a/lib/generated/l10n/zulip_localizations_he.dart b/lib/generated/l10n/zulip_localizations_he.dart index 16324eede7..8735f18101 100644 --- a/lib/generated/l10n/zulip_localizations_he.dart +++ b/lib/generated/l10n/zulip_localizations_he.dart @@ -1041,6 +1041,9 @@ class ZulipLocalizationsHe extends ZulipLocalizations { @override String get wildcardMentionTopicDescription => 'Notify topic'; + @override + String get navBarMenuLabel => 'Menu'; + @override String get messageIsEditedLabel => 'EDITED'; diff --git a/lib/generated/l10n/zulip_localizations_hu.dart b/lib/generated/l10n/zulip_localizations_hu.dart index 4e3da8f3ef..acb79326e0 100644 --- a/lib/generated/l10n/zulip_localizations_hu.dart +++ b/lib/generated/l10n/zulip_localizations_hu.dart @@ -1041,6 +1041,9 @@ class ZulipLocalizationsHu extends ZulipLocalizations { @override String get wildcardMentionTopicDescription => 'Notify topic'; + @override + String get navBarMenuLabel => 'Menu'; + @override String get messageIsEditedLabel => 'EDITED'; diff --git a/lib/generated/l10n/zulip_localizations_it.dart b/lib/generated/l10n/zulip_localizations_it.dart index 5d5dd0e278..bb7b216860 100644 --- a/lib/generated/l10n/zulip_localizations_it.dart +++ b/lib/generated/l10n/zulip_localizations_it.dart @@ -1060,6 +1060,9 @@ class ZulipLocalizationsIt extends ZulipLocalizations { @override String get wildcardMentionTopicDescription => 'Notifica argomento'; + @override + String get navBarMenuLabel => 'Menu'; + @override String get messageIsEditedLabel => 'MODIFICATO'; diff --git a/lib/generated/l10n/zulip_localizations_ja.dart b/lib/generated/l10n/zulip_localizations_ja.dart index eae3a0c3eb..6f5e7ab6d6 100644 --- a/lib/generated/l10n/zulip_localizations_ja.dart +++ b/lib/generated/l10n/zulip_localizations_ja.dart @@ -1020,6 +1020,9 @@ class ZulipLocalizationsJa extends ZulipLocalizations { @override String get wildcardMentionTopicDescription => 'トピック参加者に通知'; + @override + String get navBarMenuLabel => 'Menu'; + @override String get messageIsEditedLabel => '編集済み'; diff --git a/lib/generated/l10n/zulip_localizations_nb.dart b/lib/generated/l10n/zulip_localizations_nb.dart index cc2c5e1852..c991be1645 100644 --- a/lib/generated/l10n/zulip_localizations_nb.dart +++ b/lib/generated/l10n/zulip_localizations_nb.dart @@ -1041,6 +1041,9 @@ class ZulipLocalizationsNb extends ZulipLocalizations { @override String get wildcardMentionTopicDescription => 'Notify topic'; + @override + String get navBarMenuLabel => 'Menu'; + @override String get messageIsEditedLabel => 'EDITED'; diff --git a/lib/generated/l10n/zulip_localizations_pl.dart b/lib/generated/l10n/zulip_localizations_pl.dart index b038e14fed..ca80a9733f 100644 --- a/lib/generated/l10n/zulip_localizations_pl.dart +++ b/lib/generated/l10n/zulip_localizations_pl.dart @@ -1056,6 +1056,9 @@ class ZulipLocalizationsPl extends ZulipLocalizations { @override String get wildcardMentionTopicDescription => 'Powiadom w wątku'; + @override + String get navBarMenuLabel => 'Menu'; + @override String get messageIsEditedLabel => 'ZMIENIONO'; diff --git a/lib/generated/l10n/zulip_localizations_ru.dart b/lib/generated/l10n/zulip_localizations_ru.dart index e13c7cc1a6..ad92145c78 100644 --- a/lib/generated/l10n/zulip_localizations_ru.dart +++ b/lib/generated/l10n/zulip_localizations_ru.dart @@ -1068,6 +1068,9 @@ class ZulipLocalizationsRu extends ZulipLocalizations { @override String get wildcardMentionTopicDescription => 'Оповестить тему'; + @override + String get navBarMenuLabel => 'Menu'; + @override String get messageIsEditedLabel => 'ИЗМЕНЕНО'; diff --git a/lib/generated/l10n/zulip_localizations_sk.dart b/lib/generated/l10n/zulip_localizations_sk.dart index ca321e40a6..419ec72b79 100644 --- a/lib/generated/l10n/zulip_localizations_sk.dart +++ b/lib/generated/l10n/zulip_localizations_sk.dart @@ -1043,6 +1043,9 @@ class ZulipLocalizationsSk extends ZulipLocalizations { @override String get wildcardMentionTopicDescription => 'Notify topic'; + @override + String get navBarMenuLabel => 'Menu'; + @override String get messageIsEditedLabel => 'UPRAVENÉ'; diff --git a/lib/generated/l10n/zulip_localizations_sl.dart b/lib/generated/l10n/zulip_localizations_sl.dart index 4c34d94983..ee0bbcb8aa 100644 --- a/lib/generated/l10n/zulip_localizations_sl.dart +++ b/lib/generated/l10n/zulip_localizations_sl.dart @@ -1078,6 +1078,9 @@ class ZulipLocalizationsSl extends ZulipLocalizations { @override String get wildcardMentionTopicDescription => 'Obvesti udeležence teme'; + @override + String get navBarMenuLabel => 'Menu'; + @override String get messageIsEditedLabel => 'UREJENO'; diff --git a/lib/generated/l10n/zulip_localizations_uk.dart b/lib/generated/l10n/zulip_localizations_uk.dart index d9bc2ae98c..62dfc778c0 100644 --- a/lib/generated/l10n/zulip_localizations_uk.dart +++ b/lib/generated/l10n/zulip_localizations_uk.dart @@ -1057,6 +1057,9 @@ class ZulipLocalizationsUk extends ZulipLocalizations { @override String get wildcardMentionTopicDescription => 'Повідомити канал'; + @override + String get navBarMenuLabel => 'Menu'; + @override String get messageIsEditedLabel => 'РЕДАГОВАНО'; diff --git a/lib/generated/l10n/zulip_localizations_zh.dart b/lib/generated/l10n/zulip_localizations_zh.dart index 9a5015cf01..3997f30eda 100644 --- a/lib/generated/l10n/zulip_localizations_zh.dart +++ b/lib/generated/l10n/zulip_localizations_zh.dart @@ -1041,6 +1041,9 @@ class ZulipLocalizationsZh extends ZulipLocalizations { @override String get wildcardMentionTopicDescription => 'Notify topic'; + @override + String get navBarMenuLabel => 'Menu'; + @override String get messageIsEditedLabel => 'EDITED'; diff --git a/lib/widgets/home.dart b/lib/widgets/home.dart index 62c09c0857..9ea958a034 100644 --- a/lib/widgets/home.dart +++ b/lib/widgets/home.dart @@ -86,6 +86,8 @@ class _HomePageState extends State { @override Widget build(BuildContext context) { + final zulipLocalizations = ZulipLocalizations.of(context); + const pageBodies = [ (_HomePageTab.inbox, InboxPageBody()), (_HomePageTab.channels, SubscriptionListPageBody()), @@ -93,28 +95,34 @@ class _HomePageState extends State { (_HomePageTab.directMessages, RecentDmConversationsPageBody()), ]; - _NavigationBarButton button(_HomePageTab tab, IconData icon) { + _NavigationBarButton button(_HomePageTab tab, IconData icon, String label) { return _NavigationBarButton(icon: icon, selected: _tab.value == tab, onPressed: () { _tab.value = tab; - }); + }, + label: label); } // TODO(a11y): add tooltips for these buttons final navigationBarButtons = [ - button(_HomePageTab.inbox, ZulipIcons.inbox), + button(_HomePageTab.inbox, ZulipIcons.inbox, + zulipLocalizations.inboxPageTitle), _NavigationBarButton( icon: ZulipIcons.message_feed, selected: false, onPressed: () => Navigator.push(context, MessageListPage.buildRoute(context: context, - narrow: const CombinedFeedNarrow()))), - button(_HomePageTab.channels, ZulipIcons.hash_italic), + narrow: const CombinedFeedNarrow())), + label: zulipLocalizations.combinedFeedPageTitle), + button(_HomePageTab.channels, ZulipIcons.hash_italic, + zulipLocalizations.channelsPageTitle), // TODO(#1094): Users - button(_HomePageTab.directMessages, ZulipIcons.two_person), + button(_HomePageTab.directMessages, ZulipIcons.two_person, + zulipLocalizations.recentDmConversationsPageTitle), _NavigationBarButton( icon: ZulipIcons.menu, selected: false, - onPressed: () => _showMainMenu(context, tabNotifier: _tab)), + onPressed: () => _showMainMenu(context, tabNotifier: _tab), + label: zulipLocalizations.navBarMenuLabel), ]; final designVariables = DesignVariables.of(context); @@ -134,17 +142,15 @@ class _HomePageState extends State { border: Border(top: BorderSide(color: designVariables.borderBar)), color: designVariables.bgBotBar), child: SafeArea( - child: SizedBox(height: 48, - child: Center( - child: ConstrainedBox( - // TODO(design): determine a suitable max width for bottom nav bar - constraints: const BoxConstraints(maxWidth: 600), - child: Row( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - for (final navigationBarButton in navigationBarButtons) - Expanded(child: navigationBarButton), - ]))))))); + child: ConstrainedBox( + // TODO(design): determine a suitable max width for bottom nav bar + constraints: const BoxConstraints(maxWidth: 600, minHeight: 48), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + for (final navigationBarButton in navigationBarButtons) + Expanded(child: navigationBarButton), + ]))))); } } @@ -231,35 +237,48 @@ class _NavigationBarButton extends StatelessWidget { required this.icon, required this.selected, required this.onPressed, + required this.label, }); final IconData icon; final bool selected; final void Function() onPressed; + final String label; @override Widget build(BuildContext context) { final designVariables = DesignVariables.of(context); - final iconColor = WidgetStateColor.fromMap({ - WidgetState.pressed: designVariables.iconSelected, - ~WidgetState.pressed: selected ? designVariables.iconSelected - : designVariables.icon, - }); + final color = selected ? designVariables.iconSelected : designVariables.icon; return AnimatedScaleOnTap( scaleEnd: 0.875, duration: const Duration(milliseconds: 100), - child: IconButton( - icon: Icon(icon, size: 24), - onPressed: onPressed, - style: IconButton.styleFrom( + child: Material( + type: MaterialType.transparency, + child: InkWell( + borderRadius: BorderRadius.all(Radius.circular(4)), // TODO(#417): Disable splash effects for all buttons globally. splashFactory: NoSplash.splashFactory, highlightColor: designVariables.navigationButtonBg, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(4))), - ).copyWith(foregroundColor: iconColor))); + onTap: onPressed, + child: Padding( + // (Added 3px horizontal padding not present in Figma, to make the + // text wrap before getting too close to the button's edge, which is + // visible on tap-down.) + padding: const EdgeInsets.fromLTRB(3, 6, 3, 3), + child: Column( + spacing: 3, + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon, size: 24, color: color), + Flexible( + child: Text( + label, + style: TextStyle(fontSize: 12, color: color, height: 12 / 12), + textAlign: TextAlign.center, + textScaler: MediaQuery.textScalerOf(context).clamp(maxScaleFactor: 1.5))), + ]))))); } } diff --git a/test/widgets/home_test.dart b/test/widgets/home_test.dart index 1ee0a0ae8e..88a964a8ee 100644 --- a/test/widgets/home_test.dart +++ b/test/widgets/home_test.dart @@ -82,6 +82,20 @@ void main () { } group('bottom nav navigation', () { + final findBottomNavDecoratedBox = find.byWidgetPredicate((widget) { + if (widget is! DecoratedBox) return false; + + final decoration = widget.decoration; + if (decoration is! BoxDecoration) return false; + + final expectedBorderTop = BorderSide(color: Colors.black.withValues(alpha: 0.2)); + return decoration.border == Border(top: expectedBorderTop); + }); + + // Finds a widget within the bottom navbar's decorated box subtree. + Finder findInBottomNav(Finder finder) => + find.descendant(of: findBottomNavDecoratedBox, matching: finder); + testWidgets('preserve states when switching between views', (tester) async { await prepare(tester); await store.addUser(eg.otherUser); @@ -131,6 +145,26 @@ void main () { matching: find.text('Direct messages'))).findsOne(); }); + testWidgets("view switches when labels are tapped", (tester) async { + await prepare(tester); + + check(find.descendant( + of: find.byType(ZulipAppBar), + matching: find.text('Inbox'))).findsOne(); + + await tester.tap(findInBottomNav(find.text('Channels'))); + await tester.pump(); + check(find.descendant( + of: find.byType(ZulipAppBar), + matching: find.text('Channels'))).findsOne(); + + await tester.tap(findInBottomNav(find.text('Direct messages'))); + await tester.pump(); + check(find.descendant( + of: find.byType(ZulipAppBar), + matching: find.text('Direct messages'))).findsOne(); + }); + testWidgets('combined feed', (tester) async { await prepare(tester); pushedRoutes.clear(); diff --git a/test/widgets/recent_dm_conversations_test.dart b/test/widgets/recent_dm_conversations_test.dart index 32c1d3f28d..52b10f6b24 100644 --- a/test/widgets/recent_dm_conversations_test.dart +++ b/test/widgets/recent_dm_conversations_test.dart @@ -58,7 +58,7 @@ Future setupPage(WidgetTester tester, { // Switch to direct messages tab. await tester.tap(find.descendant( - of: find.byType(Center), + of: find.byType(DecoratedBox), matching: find.byIcon(ZulipIcons.two_person))); await tester.pump(); }