diff --git a/assets/l10n/app_en.arb b/assets/l10n/app_en.arb index 2b3622387b..908eff43da 100644 --- a/assets/l10n/app_en.arb +++ b/assets/l10n/app_en.arb @@ -1001,6 +1001,24 @@ "@errorMarkAsUnreadFailedTitle": { "description": "Error title when mark as unread action failed." }, + "markAllAsReadConfirmationDialogTitle": "Mark messages as read?", + "@markAllAsReadConfirmationDialogTitle": { + "description": "Title of the confirmation dialog for marking all messages as read." + }, + "markAllAsReadConfirmationDialogMessage": "{count, plural, =1{{count} message will be marked as read.} other{{count} messages will be marked as read.}}", + "@markAllAsReadConfirmationDialogMessage": { + "description": "Message in the confirmation dialog for marking all messages as read.", + "placeholders": { + "count": { + "type": "int", + "description": "Number of messages that will be marked as read" + } + } + }, + "markAllAsReadConfirmationDialogConfirmButton": "Mark as read", + "@markAllAsReadConfirmationDialogConfirmButton": { + "description": "Label for the 'Mark as read' button on a confirmation dialog for marking all messages as read." + }, "today": "Today", "@today": { "description": "Term to use to reference the current day." diff --git a/lib/generated/l10n/zulip_localizations.dart b/lib/generated/l10n/zulip_localizations.dart index 3cf5ddc85d..26f1d73824 100644 --- a/lib/generated/l10n/zulip_localizations.dart +++ b/lib/generated/l10n/zulip_localizations.dart @@ -1477,6 +1477,24 @@ abstract class ZulipLocalizations { /// **'Mark as unread failed'** String get errorMarkAsUnreadFailedTitle; + /// Title of the confirmation dialog for marking all messages as read. + /// + /// In en, this message translates to: + /// **'Mark messages as read?'** + String get markAllAsReadConfirmationDialogTitle; + + /// Message in the confirmation dialog for marking all messages as read. + /// + /// In en, this message translates to: + /// **'{count, plural, =1{{count} message will be marked as read.} other{{count} messages will be marked as read.}}'** + String markAllAsReadConfirmationDialogMessage(int count); + + /// Label for the 'Mark as read' button on a confirmation dialog for marking all messages as read. + /// + /// In en, this message translates to: + /// **'Mark as read'** + String get markAllAsReadConfirmationDialogConfirmButton; + /// Term to use to reference the current day. /// /// 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 27189bbb11..7b30c738a3 100644 --- a/lib/generated/l10n/zulip_localizations_ar.dart +++ b/lib/generated/l10n/zulip_localizations_ar.dart @@ -836,6 +836,23 @@ class ZulipLocalizationsAr extends ZulipLocalizations { @override String get errorMarkAsUnreadFailedTitle => 'Mark as unread failed'; + @override + String get markAllAsReadConfirmationDialogTitle => 'Mark messages as read?'; + + @override + String markAllAsReadConfirmationDialogMessage(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count messages will be marked as read.', + one: '$count message will be marked as read.', + ); + return '$_temp0'; + } + + @override + String get markAllAsReadConfirmationDialogConfirmButton => 'Mark as read'; + @override String get today => 'Today'; diff --git a/lib/generated/l10n/zulip_localizations_de.dart b/lib/generated/l10n/zulip_localizations_de.dart index 4caa2a8082..2904d8e4c8 100644 --- a/lib/generated/l10n/zulip_localizations_de.dart +++ b/lib/generated/l10n/zulip_localizations_de.dart @@ -855,6 +855,23 @@ class ZulipLocalizationsDe extends ZulipLocalizations { String get errorMarkAsUnreadFailedTitle => 'Als ungelesen markieren fehlgeschlagen'; + @override + String get markAllAsReadConfirmationDialogTitle => 'Mark messages as read?'; + + @override + String markAllAsReadConfirmationDialogMessage(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count messages will be marked as read.', + one: '$count message will be marked as read.', + ); + return '$_temp0'; + } + + @override + String get markAllAsReadConfirmationDialogConfirmButton => 'Mark as read'; + @override String get today => 'Heute'; diff --git a/lib/generated/l10n/zulip_localizations_el.dart b/lib/generated/l10n/zulip_localizations_el.dart index 670a442e87..abc3c3a02d 100644 --- a/lib/generated/l10n/zulip_localizations_el.dart +++ b/lib/generated/l10n/zulip_localizations_el.dart @@ -836,6 +836,23 @@ class ZulipLocalizationsEl extends ZulipLocalizations { @override String get errorMarkAsUnreadFailedTitle => 'Mark as unread failed'; + @override + String get markAllAsReadConfirmationDialogTitle => 'Mark messages as read?'; + + @override + String markAllAsReadConfirmationDialogMessage(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count messages will be marked as read.', + one: '$count message will be marked as read.', + ); + return '$_temp0'; + } + + @override + String get markAllAsReadConfirmationDialogConfirmButton => 'Mark as read'; + @override String get today => 'Today'; diff --git a/lib/generated/l10n/zulip_localizations_en.dart b/lib/generated/l10n/zulip_localizations_en.dart index b2fbfcb72f..253fbed74f 100644 --- a/lib/generated/l10n/zulip_localizations_en.dart +++ b/lib/generated/l10n/zulip_localizations_en.dart @@ -836,6 +836,23 @@ class ZulipLocalizationsEn extends ZulipLocalizations { @override String get errorMarkAsUnreadFailedTitle => 'Mark as unread failed'; + @override + String get markAllAsReadConfirmationDialogTitle => 'Mark messages as read?'; + + @override + String markAllAsReadConfirmationDialogMessage(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count messages will be marked as read.', + one: '$count message will be marked as read.', + ); + return '$_temp0'; + } + + @override + String get markAllAsReadConfirmationDialogConfirmButton => 'Mark as read'; + @override String get today => 'Today'; diff --git a/lib/generated/l10n/zulip_localizations_es.dart b/lib/generated/l10n/zulip_localizations_es.dart index f3ee9284e9..8b92dee437 100644 --- a/lib/generated/l10n/zulip_localizations_es.dart +++ b/lib/generated/l10n/zulip_localizations_es.dart @@ -836,6 +836,23 @@ class ZulipLocalizationsEs extends ZulipLocalizations { @override String get errorMarkAsUnreadFailedTitle => 'Mark as unread failed'; + @override + String get markAllAsReadConfirmationDialogTitle => 'Mark messages as read?'; + + @override + String markAllAsReadConfirmationDialogMessage(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count messages will be marked as read.', + one: '$count message will be marked as read.', + ); + return '$_temp0'; + } + + @override + String get markAllAsReadConfirmationDialogConfirmButton => 'Mark as read'; + @override String get today => 'Today'; diff --git a/lib/generated/l10n/zulip_localizations_fr.dart b/lib/generated/l10n/zulip_localizations_fr.dart index 32cb94bf82..979e9bde59 100644 --- a/lib/generated/l10n/zulip_localizations_fr.dart +++ b/lib/generated/l10n/zulip_localizations_fr.dart @@ -852,6 +852,23 @@ class ZulipLocalizationsFr extends ZulipLocalizations { @override String get errorMarkAsUnreadFailedTitle => 'Mark as unread failed'; + @override + String get markAllAsReadConfirmationDialogTitle => 'Mark messages as read?'; + + @override + String markAllAsReadConfirmationDialogMessage(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count messages will be marked as read.', + one: '$count message will be marked as read.', + ); + return '$_temp0'; + } + + @override + String get markAllAsReadConfirmationDialogConfirmButton => 'Mark as read'; + @override String get today => 'Today'; diff --git a/lib/generated/l10n/zulip_localizations_he.dart b/lib/generated/l10n/zulip_localizations_he.dart index 37dc15e7e4..b376a01c75 100644 --- a/lib/generated/l10n/zulip_localizations_he.dart +++ b/lib/generated/l10n/zulip_localizations_he.dart @@ -836,6 +836,23 @@ class ZulipLocalizationsHe extends ZulipLocalizations { @override String get errorMarkAsUnreadFailedTitle => 'Mark as unread failed'; + @override + String get markAllAsReadConfirmationDialogTitle => 'Mark messages as read?'; + + @override + String markAllAsReadConfirmationDialogMessage(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count messages will be marked as read.', + one: '$count message will be marked as read.', + ); + return '$_temp0'; + } + + @override + String get markAllAsReadConfirmationDialogConfirmButton => 'Mark as read'; + @override String get today => 'Today'; diff --git a/lib/generated/l10n/zulip_localizations_hu.dart b/lib/generated/l10n/zulip_localizations_hu.dart index 44404f1955..85955b62f0 100644 --- a/lib/generated/l10n/zulip_localizations_hu.dart +++ b/lib/generated/l10n/zulip_localizations_hu.dart @@ -836,6 +836,23 @@ class ZulipLocalizationsHu extends ZulipLocalizations { @override String get errorMarkAsUnreadFailedTitle => 'Mark as unread failed'; + @override + String get markAllAsReadConfirmationDialogTitle => 'Mark messages as read?'; + + @override + String markAllAsReadConfirmationDialogMessage(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count messages will be marked as read.', + one: '$count message will be marked as read.', + ); + return '$_temp0'; + } + + @override + String get markAllAsReadConfirmationDialogConfirmButton => 'Mark as read'; + @override String get today => 'Today'; diff --git a/lib/generated/l10n/zulip_localizations_it.dart b/lib/generated/l10n/zulip_localizations_it.dart index 1533008103..3e6aa919ec 100644 --- a/lib/generated/l10n/zulip_localizations_it.dart +++ b/lib/generated/l10n/zulip_localizations_it.dart @@ -847,6 +847,23 @@ class ZulipLocalizationsIt extends ZulipLocalizations { String get errorMarkAsUnreadFailedTitle => 'Contrassegno come non letti non riuscito'; + @override + String get markAllAsReadConfirmationDialogTitle => 'Mark messages as read?'; + + @override + String markAllAsReadConfirmationDialogMessage(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count messages will be marked as read.', + one: '$count message will be marked as read.', + ); + return '$_temp0'; + } + + @override + String get markAllAsReadConfirmationDialogConfirmButton => 'Mark as read'; + @override String get today => 'Oggi'; diff --git a/lib/generated/l10n/zulip_localizations_ja.dart b/lib/generated/l10n/zulip_localizations_ja.dart index 6465223b8a..eb7a366304 100644 --- a/lib/generated/l10n/zulip_localizations_ja.dart +++ b/lib/generated/l10n/zulip_localizations_ja.dart @@ -814,6 +814,23 @@ class ZulipLocalizationsJa extends ZulipLocalizations { @override String get errorMarkAsUnreadFailedTitle => '未読にできませんでした'; + @override + String get markAllAsReadConfirmationDialogTitle => 'Mark messages as read?'; + + @override + String markAllAsReadConfirmationDialogMessage(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count messages will be marked as read.', + one: '$count message will be marked as read.', + ); + return '$_temp0'; + } + + @override + String get markAllAsReadConfirmationDialogConfirmButton => 'Mark as read'; + @override String get today => '今日'; diff --git a/lib/generated/l10n/zulip_localizations_nb.dart b/lib/generated/l10n/zulip_localizations_nb.dart index cfcc46ebb6..68fe7de0ba 100644 --- a/lib/generated/l10n/zulip_localizations_nb.dart +++ b/lib/generated/l10n/zulip_localizations_nb.dart @@ -836,6 +836,23 @@ class ZulipLocalizationsNb extends ZulipLocalizations { @override String get errorMarkAsUnreadFailedTitle => 'Mark as unread failed'; + @override + String get markAllAsReadConfirmationDialogTitle => 'Mark messages as read?'; + + @override + String markAllAsReadConfirmationDialogMessage(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count messages will be marked as read.', + one: '$count message will be marked as read.', + ); + return '$_temp0'; + } + + @override + String get markAllAsReadConfirmationDialogConfirmButton => 'Mark as read'; + @override String get today => 'Today'; diff --git a/lib/generated/l10n/zulip_localizations_pl.dart b/lib/generated/l10n/zulip_localizations_pl.dart index e12fe1aeb2..c2e689d355 100644 --- a/lib/generated/l10n/zulip_localizations_pl.dart +++ b/lib/generated/l10n/zulip_localizations_pl.dart @@ -852,6 +852,23 @@ class ZulipLocalizationsPl extends ZulipLocalizations { String get errorMarkAsUnreadFailedTitle => 'Oznaczanie jako nieprzeczytane bez powodzenia'; + @override + String get markAllAsReadConfirmationDialogTitle => 'Mark messages as read?'; + + @override + String markAllAsReadConfirmationDialogMessage(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count messages will be marked as read.', + one: '$count message will be marked as read.', + ); + return '$_temp0'; + } + + @override + String get markAllAsReadConfirmationDialogConfirmButton => 'Mark as read'; + @override String get today => 'Dzisiaj'; diff --git a/lib/generated/l10n/zulip_localizations_ru.dart b/lib/generated/l10n/zulip_localizations_ru.dart index f2a9f0561e..0ae93e1ea5 100644 --- a/lib/generated/l10n/zulip_localizations_ru.dart +++ b/lib/generated/l10n/zulip_localizations_ru.dart @@ -857,6 +857,23 @@ class ZulipLocalizationsRu extends ZulipLocalizations { String get errorMarkAsUnreadFailedTitle => 'Не удалось снять отметку прочтения'; + @override + String get markAllAsReadConfirmationDialogTitle => 'Mark messages as read?'; + + @override + String markAllAsReadConfirmationDialogMessage(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count messages will be marked as read.', + one: '$count message will be marked as read.', + ); + return '$_temp0'; + } + + @override + String get markAllAsReadConfirmationDialogConfirmButton => 'Mark as read'; + @override String get today => 'Сегодня'; diff --git a/lib/generated/l10n/zulip_localizations_sk.dart b/lib/generated/l10n/zulip_localizations_sk.dart index b28622eb3e..17d9bc2590 100644 --- a/lib/generated/l10n/zulip_localizations_sk.dart +++ b/lib/generated/l10n/zulip_localizations_sk.dart @@ -838,6 +838,23 @@ class ZulipLocalizationsSk extends ZulipLocalizations { String get errorMarkAsUnreadFailedTitle => 'Zlyhalo označenie správ za prečítané'; + @override + String get markAllAsReadConfirmationDialogTitle => 'Mark messages as read?'; + + @override + String markAllAsReadConfirmationDialogMessage(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count messages will be marked as read.', + one: '$count message will be marked as read.', + ); + return '$_temp0'; + } + + @override + String get markAllAsReadConfirmationDialogConfirmButton => 'Mark as read'; + @override String get today => 'Dnes'; diff --git a/lib/generated/l10n/zulip_localizations_sl.dart b/lib/generated/l10n/zulip_localizations_sl.dart index 10dc8727cd..a5665c6437 100644 --- a/lib/generated/l10n/zulip_localizations_sl.dart +++ b/lib/generated/l10n/zulip_localizations_sl.dart @@ -866,6 +866,23 @@ class ZulipLocalizationsSl extends ZulipLocalizations { String get errorMarkAsUnreadFailedTitle => 'Označevanje kot neprebrano ni uspelo'; + @override + String get markAllAsReadConfirmationDialogTitle => 'Mark messages as read?'; + + @override + String markAllAsReadConfirmationDialogMessage(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count messages will be marked as read.', + one: '$count message will be marked as read.', + ); + return '$_temp0'; + } + + @override + String get markAllAsReadConfirmationDialogConfirmButton => 'Mark as read'; + @override String get today => 'Danes'; diff --git a/lib/generated/l10n/zulip_localizations_uk.dart b/lib/generated/l10n/zulip_localizations_uk.dart index e2872b11a0..25ab8b1cbb 100644 --- a/lib/generated/l10n/zulip_localizations_uk.dart +++ b/lib/generated/l10n/zulip_localizations_uk.dart @@ -853,6 +853,23 @@ class ZulipLocalizationsUk extends ZulipLocalizations { String get errorMarkAsUnreadFailedTitle => 'Не вдалося позначити як непрочитане'; + @override + String get markAllAsReadConfirmationDialogTitle => 'Mark messages as read?'; + + @override + String markAllAsReadConfirmationDialogMessage(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count messages will be marked as read.', + one: '$count message will be marked as read.', + ); + return '$_temp0'; + } + + @override + String get markAllAsReadConfirmationDialogConfirmButton => 'Mark as read'; + @override String get today => 'Сьогодні'; diff --git a/lib/generated/l10n/zulip_localizations_zh.dart b/lib/generated/l10n/zulip_localizations_zh.dart index e05f76bee3..8ca6c48b32 100644 --- a/lib/generated/l10n/zulip_localizations_zh.dart +++ b/lib/generated/l10n/zulip_localizations_zh.dart @@ -836,6 +836,23 @@ class ZulipLocalizationsZh extends ZulipLocalizations { @override String get errorMarkAsUnreadFailedTitle => 'Mark as unread failed'; + @override + String get markAllAsReadConfirmationDialogTitle => 'Mark messages as read?'; + + @override + String markAllAsReadConfirmationDialogMessage(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count messages will be marked as read.', + one: '$count message will be marked as read.', + ); + return '$_temp0'; + } + + @override + String get markAllAsReadConfirmationDialogConfirmButton => 'Mark as read'; + @override String get today => 'Today'; diff --git a/lib/widgets/actions.dart b/lib/widgets/actions.dart index bd8274613f..340e69b571 100644 --- a/lib/widgets/actions.dart +++ b/lib/widgets/actions.dart @@ -29,8 +29,31 @@ abstract final class ZulipAction { /// /// This is mostly a wrapper around [updateMessageFlagsStartingFromAnchor]; /// for details on the UI feedback, see there. + /// + /// A confirmation dialog is shown if the narrow is a non-conversation narrow. static Future markNarrowAsRead(BuildContext context, Narrow narrow) async { final zulipLocalizations = ZulipLocalizations.of(context); + final store = PerAccountStoreWidget.of(context); + + // See link when deciding behavior for a new narrow: + // https://chat.zulip.org/#narrow/channel/48-mobile/topic/mark.20all.20messages.20as.20read/near/2261768 + final shouldShowConfirmationDialog = switch (narrow) { + CombinedFeedNarrow() + || MentionsNarrow() + || StarredMessagesNarrow() + || KeywordSearchNarrow() + || ChannelNarrow() => true, + DmNarrow() || TopicNarrow() => false, + }; + if (shouldShowConfirmationDialog) { + final unreadCount = store.unreads.countInNarrow(narrow); + final didConfirm = showSuggestedActionDialog(context: context, + title: zulipLocalizations.markAllAsReadConfirmationDialogTitle, + message: zulipLocalizations.markAllAsReadConfirmationDialogMessage(unreadCount), + actionButtonText: zulipLocalizations.markAllAsReadConfirmationDialogConfirmButton); + if (await didConfirm.result != true) return; + if (!context.mounted) return; + } final didPass = await updateMessageFlagsStartingFromAnchor( context: context, diff --git a/test/widgets/action_sheet_test.dart b/test/widgets/action_sheet_test.dart index c43c6daab7..06e0ab65ba 100644 --- a/test/widgets/action_sheet_test.dart +++ b/test/widgets/action_sheet_test.dart @@ -471,6 +471,8 @@ void main() { } testWidgets('happy path from inbox', (tester) async { + final zulipLocalizations = GlobalLocalizations.zulipLocalizations; + await prepare(); final message = eg.streamMessage(stream: someChannel, topic: someTopic); await store.addMessage(message); @@ -480,17 +482,37 @@ void main() { firstProcessedId: message.id, lastProcessedId: message.id, foundOldest: true, foundNewest: true).toJson()); await tester.tap(findButtonForLabel('Mark channel as read')); + await tester.pump(); + await tester.pump(); + final unreadCount = store.unreads.countInChannelNarrow(someChannel.streamId); + final (confirmButton, _) = checkSuggestedActionDialog(tester, + expectedTitle: zulipLocalizations.markAllAsReadConfirmationDialogTitle, + expectedMessage: zulipLocalizations.markAllAsReadConfirmationDialogMessage(unreadCount), + expectedActionButtonText: zulipLocalizations.markAllAsReadConfirmationDialogConfirmButton); + await tester.tap(find.byWidget(confirmButton)); await tester.pumpAndSettle(); + checkRequest(someChannel.streamId); checkNoDialog(tester); }); testWidgets('request fails', (tester) async { + final zulipLocalizations = GlobalLocalizations.zulipLocalizations; + await prepare(); await showFromInbox(tester); connection.prepare(httpException: http.ClientException('Oops')); await tester.tap(findButtonForLabel('Mark channel as read')); + await tester.pump(); + await tester.pump(); + final unreadCount = store.unreads.countInChannelNarrow(someChannel.streamId); + final (confirmButton, _) = checkSuggestedActionDialog(tester, + expectedTitle: zulipLocalizations.markAllAsReadConfirmationDialogTitle, + expectedMessage: zulipLocalizations.markAllAsReadConfirmationDialogMessage(unreadCount), + expectedActionButtonText: zulipLocalizations.markAllAsReadConfirmationDialogConfirmButton); + await tester.tap(find.byWidget(confirmButton)); await tester.pumpAndSettle(); + checkRequest(someChannel.streamId); checkErrorDialog(tester, expectedTitle: "Mark as read failed"); diff --git a/test/widgets/actions_test.dart b/test/widgets/actions_test.dart index 7c292aa62e..dc02263d02 100644 --- a/test/widgets/actions_test.dart +++ b/test/widgets/actions_test.dart @@ -56,6 +56,15 @@ void main() { } group('markNarrowAsRead', () { + (Widget, Widget) checkConfirmDialog(WidgetTester tester, int unreadCount) { + final zulipLocalizations = GlobalLocalizations.zulipLocalizations; + return checkSuggestedActionDialog(tester, + expectedTitle: zulipLocalizations.markAllAsReadConfirmationDialogTitle, + expectedMessage: zulipLocalizations.markAllAsReadConfirmationDialogMessage(unreadCount), + expectDestructiveActionButton: false, + expectedActionButtonText: zulipLocalizations.markAllAsReadConfirmationDialogConfirmButton); + } + testWidgets('smoke test on modern server', (tester) async { final narrow = TopicNarrow.ofMessage(eg.streamMessage()); await prepare(tester); @@ -88,8 +97,12 @@ void main() { processedCount: 11, updatedCount: 3, firstProcessedId: null, lastProcessedId: null, foundOldest: true, foundNewest: true).toJson()); + final unreadCount = store.unreads.countInCombinedFeedNarrow(); final future = ZulipAction.markNarrowAsRead(context, narrow); - await tester.pump(Duration.zero); + await tester.pump(); // confirmation dialog appears + final (confirmButton, _) = checkConfirmDialog(tester, unreadCount); + await tester.tap(find.byWidget(confirmButton)); + await tester.pump(Duration.zero); // wait through API request await future; check(connection.lastRequest).isA() ..method.equals('POST') @@ -114,8 +127,12 @@ void main() { processedCount: 11, updatedCount: 3, firstProcessedId: null, lastProcessedId: null, foundOldest: true, foundNewest: true).toJson()); + final unreadCount = store.unreads.countInCombinedFeedNarrow(); final future = ZulipAction.markNarrowAsRead(context, narrow); - await tester.pump(Duration.zero); + await tester.pump(); // confirmation dialog appears + final (confirmButton, _) = checkConfirmDialog(tester, unreadCount); + await tester.tap(find.byWidget(confirmButton)); + await tester.pump(Duration.zero); // wait through API request await future; check(store.unreads.oldUnreadsMissing).isFalse(); }); diff --git a/test/widgets/message_list_test.dart b/test/widgets/message_list_test.dart index 91a5e33ba1..5299c7c87b 100644 --- a/test/widgets/message_list_test.dart +++ b/test/widgets/message_list_test.dart @@ -1182,6 +1182,133 @@ void main() { return finder.evaluate().isNotEmpty; } + (Widget, Widget) checkConfirmDialog(WidgetTester tester, int unreadCount) { + final zulipLocalizations = GlobalLocalizations.zulipLocalizations; + return checkSuggestedActionDialog(tester, + expectedTitle: zulipLocalizations.markAllAsReadConfirmationDialogTitle, + expectedMessage: zulipLocalizations.markAllAsReadConfirmationDialogMessage(unreadCount), + expectDestructiveActionButton: false, + expectedActionButtonText: zulipLocalizations.markAllAsReadConfirmationDialogConfirmButton); + } + + group('confirmation dialog', () { + Future showsConfirmDialog(WidgetTester tester, { + required Narrow narrow, + required List messages, + required UnreadMessagesSnapshot unreadMsgs, + }) async { + await setupMessageListPage(tester, narrow: narrow, + messages: messages, unreadMsgs: unreadMsgs); + check(isMarkAsReadButtonVisible(tester)).isTrue(); + + await tester.tap(find.byType(MarkAsReadWidget)); + await tester.pump(); + + final unreadCount = store.unreads.countInNarrow(narrow); + checkConfirmDialog(tester, unreadCount); + } + + Future doesNotShowConfirmDialog(WidgetTester tester, { + required Narrow narrow, + required List messages, + required UnreadMessagesSnapshot unreadMsgs, + required UpdateMessageFlagsForNarrowResult updateResult, + }) async { + await setupMessageListPage(tester, + narrow: narrow, messages: messages, unreadMsgs: unreadMsgs); + check(isMarkAsReadButtonVisible(tester)).isTrue(); + + connection.prepare(json: updateResult.toJson()); + await tester.tap(find.byType(MarkAsReadWidget)); + await tester.pump(); + + checkNoDialog(tester); + await tester.pump(Duration.zero); + final apiNarrow = narrow.apiEncode()..add(ApiNarrowIs(IsOperand.unread)); + check(connection.lastRequest).isA() + ..method.equals('POST') + ..url.path.equals('/api/v1/messages/flags/narrow') + ..bodyFields.deepEquals({ + 'anchor': 'oldest', + 'include_anchor': 'false', + 'num_before': '0', + 'num_after': '1000', + 'narrow': jsonEncode(resolveApiNarrowForServer(apiNarrow, connection.zulipFeatureLevel!)), + 'op': 'add', + 'flag': 'read', + }); + } + + testWidgets('CombinedFeedNarrow: show dialog', (tester) async { + final message = eg.streamMessage(flags: []); + await showsConfirmDialog(tester, + narrow: CombinedFeedNarrow(), + messages: [message], + unreadMsgs: eg.unreadMsgs(channels: [ + UnreadChannelSnapshot( + streamId: message.streamId, + topic: message.topic, + unreadMessageIds: [message.id]), + ])); + }); + + testWidgets('MentionsNarrow: show dialog', (tester) async { + final message = eg.streamMessage(flags: [MessageFlag.mentioned]); + await showsConfirmDialog(tester, + narrow: MentionsNarrow(), + messages: [message], + unreadMsgs: eg.unreadMsgs( + mentions: [message.id], + channels: [ + UnreadChannelSnapshot( + streamId: message.streamId, + topic: message.topic, + unreadMessageIds: [message.id]), + ])); + }); + + testWidgets('ChannelNarrow: show dialog', (tester) async { + final message = eg.streamMessage(flags: []); + await showsConfirmDialog(tester, + narrow: ChannelNarrow(message.streamId), + messages: [message], + unreadMsgs: eg.unreadMsgs(channels: [ + UnreadChannelSnapshot( + streamId: message.streamId, + topic: message.topic, + unreadMessageIds: [message.id]), + ])); + }); + + testWidgets('DmNarrow: do not show dialog', (tester) async { + final message = eg.dmMessage(flags: [], from: eg.selfUser, to: [eg.otherUser]); + await doesNotShowConfirmDialog(tester, + narrow: DmNarrow.ofMessage(message, selfUserId: eg.selfUser.userId), + messages: [message], + unreadMsgs: eg.unreadMsgs(dms: [ + UnreadDmSnapshot(otherUserId: eg.otherUser.userId, + unreadMessageIds: [message.id])]), + updateResult: UpdateMessageFlagsForNarrowResult( + processedCount: 1, updatedCount: 1, + firstProcessedId: message.id, lastProcessedId: message.id, + foundOldest: true, foundNewest: true)); + }); + + testWidgets('TopicNarrow: do not show dialog', (tester) async { + final message = eg.streamMessage(flags: []); + await doesNotShowConfirmDialog(tester, + narrow: TopicNarrow.ofMessage(message), + messages: [message], + unreadMsgs: eg.unreadMsgs(channels: [ + UnreadChannelSnapshot(topic: message.topic, + streamId: message.streamId, unreadMessageIds: [message.id])]), + updateResult: UpdateMessageFlagsForNarrowResult( + processedCount: 1, updatedCount: 1, + firstProcessedId: message.id, lastProcessedId: message.id, + foundOldest: true, foundNewest: true)); + }); + }); + testWidgets('from read to unread', (tester) async { final message = eg.streamMessage(flags: [MessageFlag.read]); await setupMessageListPage(tester, messages: [message]); @@ -1371,6 +1498,10 @@ void main() { firstProcessedId: null, lastProcessedId: null, foundOldest: true, foundNewest: true).toJson()); await tester.tap(find.byType(MarkAsReadWidget)); + await tester.pump(); + final unreadCount = store.unreads.countInCombinedFeedNarrow(); + final (confirmButton, _) = checkConfirmDialog(tester, unreadCount); + await tester.tap(find.byWidget(confirmButton)); await tester.pumpAndSettle(); check(store.unreads.oldUnreadsMissing).isFalse(); }); @@ -1384,6 +1515,10 @@ void main() { connection.prepare(httpException: http.ClientException('Oops')); await tester.tap(find.byType(MarkAsReadWidget)); + await tester.pump(); + final unreadCount = store.unreads.countInCombinedFeedNarrow(); + final (confirmButton, _) = checkConfirmDialog(tester, unreadCount); + await tester.tap(find.byWidget(confirmButton)); await tester.pumpAndSettle(); checkErrorDialog(tester, expectedTitle: zulipLocalizations.errorMarkAsReadFailedTitle,