diff --git a/assets/l10n/app_en.arb b/assets/l10n/app_en.arb index d1a08ded77..cf816f50c4 100644 --- a/assets/l10n/app_en.arb +++ b/assets/l10n/app_en.arb @@ -545,6 +545,10 @@ "@starredMessagesPageTitle": { "description": "Title for the page of starred messages." }, + "channelFeedButtonTooltip": "Channel feed", + "@channelFeedButtonTooltip": { + "description": "Tooltip for button to navigate to a given channel's feed" + }, "notifGroupDmConversationLabel": "{senderFullName} to you and {numOthers, plural, =1{1 other} other{{numOthers} others}}", "@notifGroupDmConversationLabel": { "description": "Label for a group DM conversation notification.", diff --git a/lib/generated/l10n/zulip_localizations.dart b/lib/generated/l10n/zulip_localizations.dart index 74856a64e0..972b1e1ad2 100644 --- a/lib/generated/l10n/zulip_localizations.dart +++ b/lib/generated/l10n/zulip_localizations.dart @@ -823,6 +823,12 @@ abstract class ZulipLocalizations { /// **'Starred messages'** String get starredMessagesPageTitle; + /// Tooltip for button to navigate to a given channel's feed + /// + /// In en, this message translates to: + /// **'Channel feed'** + String get channelFeedButtonTooltip; + /// Label for a group DM conversation notification. /// /// 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 09e4ae82e9..95ff1d0aea 100644 --- a/lib/generated/l10n/zulip_localizations_ar.dart +++ b/lib/generated/l10n/zulip_localizations_ar.dart @@ -431,6 +431,9 @@ class ZulipLocalizationsAr extends ZulipLocalizations { @override String get starredMessagesPageTitle => 'Starred messages'; + @override + String get channelFeedButtonTooltip => 'Channel feed'; + @override String notifGroupDmConversationLabel(String senderFullName, int numOthers) { String _temp0 = intl.Intl.pluralLogic( diff --git a/lib/generated/l10n/zulip_localizations_en.dart b/lib/generated/l10n/zulip_localizations_en.dart index da5fad3552..d440ed2b10 100644 --- a/lib/generated/l10n/zulip_localizations_en.dart +++ b/lib/generated/l10n/zulip_localizations_en.dart @@ -431,6 +431,9 @@ class ZulipLocalizationsEn extends ZulipLocalizations { @override String get starredMessagesPageTitle => 'Starred messages'; + @override + String get channelFeedButtonTooltip => 'Channel feed'; + @override String notifGroupDmConversationLabel(String senderFullName, int numOthers) { String _temp0 = intl.Intl.pluralLogic( diff --git a/lib/generated/l10n/zulip_localizations_ja.dart b/lib/generated/l10n/zulip_localizations_ja.dart index 693f0a36f9..42128ba024 100644 --- a/lib/generated/l10n/zulip_localizations_ja.dart +++ b/lib/generated/l10n/zulip_localizations_ja.dart @@ -431,6 +431,9 @@ class ZulipLocalizationsJa extends ZulipLocalizations { @override String get starredMessagesPageTitle => 'Starred messages'; + @override + String get channelFeedButtonTooltip => 'Channel feed'; + @override String notifGroupDmConversationLabel(String senderFullName, int numOthers) { String _temp0 = intl.Intl.pluralLogic( diff --git a/lib/widgets/message_list.dart b/lib/widgets/message_list.dart index cbfc296e0b..b8fcf3adbe 100644 --- a/lib/widgets/message_list.dart +++ b/lib/widgets/message_list.dart @@ -233,6 +233,7 @@ class _MessageListPageState extends State implements MessageLis Widget build(BuildContext context) { final store = PerAccountStoreWidget.of(context); final messageListTheme = MessageListTheme.of(context); + final zulipLocalizations = ZulipLocalizations.of(context); final Color? appBarBackgroundColor; bool removeAppBarBottomBorder = false; @@ -259,9 +260,20 @@ class _MessageListPageState extends State implements MessageLis removeAppBarBottomBorder = true; } + List? actions; + if (narrow case TopicNarrow(:final streamId)) { + (actions ??= []).add(IconButton( + icon: const Icon(ZulipIcons.message_feed), + tooltip: zulipLocalizations.channelFeedButtonTooltip, + onPressed: () => Navigator.push(context, + MessageListPage.buildRoute(context: context, + narrow: ChannelNarrow(streamId))))); + } + return Scaffold( appBar: ZulipAppBar( title: MessageListAppBarTitle(narrow: narrow), + actions: actions, backgroundColor: appBarBackgroundColor, shape: removeAppBarBottomBorder ? const Border() diff --git a/test/widgets/message_list_test.dart b/test/widgets/message_list_test.dart index 37ce921f43..1bd7f798cf 100644 --- a/test/widgets/message_list_test.dart +++ b/test/widgets/message_list_test.dart @@ -21,6 +21,7 @@ import 'package:zulip/widgets/color.dart'; import 'package:zulip/widgets/content.dart'; import 'package:zulip/widgets/icons.dart'; import 'package:zulip/widgets/message_list.dart'; +import 'package:zulip/widgets/page.dart'; import 'package:zulip/widgets/store.dart'; import 'package:zulip/widgets/channel_colors.dart'; @@ -32,8 +33,11 @@ import '../model/test_store.dart'; import '../flutter_checks.dart'; import '../stdlib_checks.dart'; import '../test_images.dart'; +import '../test_navigation.dart'; import 'content_checks.dart'; import 'dialog_checks.dart'; +import 'message_list_checks.dart'; +import 'page_checks.dart'; import 'test_app.dart'; void main() { @@ -51,6 +55,7 @@ void main() { List? users, List? subscriptions, UnreadMessagesSnapshot? unreadMsgs, + List navObservers = const [], }) async { TypingNotifier.debugEnable = false; addTearDown(TypingNotifier.debugReset); @@ -72,6 +77,7 @@ void main() { eg.newestGetMessagesResult(foundOldest: foundOldest, messages: messages).toJson()); await tester.pumpWidget(TestZulipApp(accountId: eg.selfAccount.id, + navigatorObservers: navObservers, child: MessageListPage(initNarrow: narrow))); // global store, per-account store, and message list get loaded @@ -126,6 +132,28 @@ void main() { }); }); + group('app bar', () { + testWidgets('has channel-feed action for topic narrows', (tester) async { + final pushedRoutes = >[]; + final navObserver = TestNavigatorObserver() + ..onPushed = (route, prevRoute) => pushedRoutes.add(route); + final channel = eg.stream(); + await setupMessageListPage(tester, narrow: TopicNarrow(channel.streamId, 'hi'), + navObservers: [navObserver], + streams: [channel], messageCount: 1); + + // Clear out initial route. + assert(pushedRoutes.length == 1); + pushedRoutes.clear(); + + // Tap button; it works. + await tester.tap(find.byIcon(ZulipIcons.message_feed)); + check(pushedRoutes).single.isA() + .page.isA().initNarrow + .equals(ChannelNarrow(channel.streamId)); + }); + }); + group('presents message content appropriately', () { testWidgets('content not asked to consume insets (including bottom), even without compose box', (tester) async { // Regression test for: https://github.com/zulip/zulip-flutter/issues/736