diff --git a/assets/l10n/app_en.arb b/assets/l10n/app_en.arb index 38b49c2edd..80b37efa8b 100644 --- a/assets/l10n/app_en.arb +++ b/assets/l10n/app_en.arb @@ -932,6 +932,18 @@ "@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": "To preserve your reading state, this view does not mark messages as read.", + "@markAllAsReadConfirmationDialogMessage": { + "description": "Message that warns the user they may unintentionally mark messages as read" + }, + "markAllAsReadConfirmationDialogAction": "Mark as read", + "@markAllAsReadConfirmationDialogAction": { + "description": "Action message to confirm 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 fe79921c07..34640c15f7 100644 --- a/lib/generated/l10n/zulip_localizations.dart +++ b/lib/generated/l10n/zulip_localizations.dart @@ -1387,6 +1387,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 that warns the user they may unintentionally mark messages as read + /// + /// In en, this message translates to: + /// **'To preserve your reading state, this view does not mark messages as read.'** + String get markAllAsReadConfirmationDialogMessage; + + /// Action message to confirm marking all messages as read. + /// + /// In en, this message translates to: + /// **'Mark as read'** + String get markAllAsReadConfirmationDialogAction; + /// 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 5e69a181da..1200c4c8fd 100644 --- a/lib/generated/l10n/zulip_localizations_ar.dart +++ b/lib/generated/l10n/zulip_localizations_ar.dart @@ -774,6 +774,16 @@ class ZulipLocalizationsAr extends ZulipLocalizations { @override String get errorMarkAsUnreadFailedTitle => 'Mark as unread failed'; + @override + String get markAllAsReadConfirmationDialogTitle => 'Mark messages as read?'; + + @override + String get markAllAsReadConfirmationDialogMessage => + 'To preserve your reading state, this view does not mark messages as read.'; + + @override + String get markAllAsReadConfirmationDialogAction => '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 241e886e55..8fe9236974 100644 --- a/lib/generated/l10n/zulip_localizations_de.dart +++ b/lib/generated/l10n/zulip_localizations_de.dart @@ -793,6 +793,16 @@ class ZulipLocalizationsDe extends ZulipLocalizations { String get errorMarkAsUnreadFailedTitle => 'Als ungelesen markieren fehlgeschlagen'; + @override + String get markAllAsReadConfirmationDialogTitle => 'Mark messages as read?'; + + @override + String get markAllAsReadConfirmationDialogMessage => + 'To preserve your reading state, this view does not mark messages as read.'; + + @override + String get markAllAsReadConfirmationDialogAction => '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 ce72dc77bf..5a53732071 100644 --- a/lib/generated/l10n/zulip_localizations_el.dart +++ b/lib/generated/l10n/zulip_localizations_el.dart @@ -774,6 +774,16 @@ class ZulipLocalizationsEl extends ZulipLocalizations { @override String get errorMarkAsUnreadFailedTitle => 'Mark as unread failed'; + @override + String get markAllAsReadConfirmationDialogTitle => 'Mark messages as read?'; + + @override + String get markAllAsReadConfirmationDialogMessage => + 'To preserve your reading state, this view does not mark messages as read.'; + + @override + String get markAllAsReadConfirmationDialogAction => '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 6d10362555..46ebc9bdf8 100644 --- a/lib/generated/l10n/zulip_localizations_en.dart +++ b/lib/generated/l10n/zulip_localizations_en.dart @@ -774,6 +774,16 @@ class ZulipLocalizationsEn extends ZulipLocalizations { @override String get errorMarkAsUnreadFailedTitle => 'Mark as unread failed'; + @override + String get markAllAsReadConfirmationDialogTitle => 'Mark messages as read?'; + + @override + String get markAllAsReadConfirmationDialogMessage => + 'To preserve your reading state, this view does not mark messages as read.'; + + @override + String get markAllAsReadConfirmationDialogAction => '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 80098d557f..ef1ecbe462 100644 --- a/lib/generated/l10n/zulip_localizations_es.dart +++ b/lib/generated/l10n/zulip_localizations_es.dart @@ -774,6 +774,16 @@ class ZulipLocalizationsEs extends ZulipLocalizations { @override String get errorMarkAsUnreadFailedTitle => 'Mark as unread failed'; + @override + String get markAllAsReadConfirmationDialogTitle => 'Mark messages as read?'; + + @override + String get markAllAsReadConfirmationDialogMessage => + 'To preserve your reading state, this view does not mark messages as read.'; + + @override + String get markAllAsReadConfirmationDialogAction => '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 adb41177b2..25a802da52 100644 --- a/lib/generated/l10n/zulip_localizations_fr.dart +++ b/lib/generated/l10n/zulip_localizations_fr.dart @@ -790,6 +790,16 @@ class ZulipLocalizationsFr extends ZulipLocalizations { @override String get errorMarkAsUnreadFailedTitle => 'Mark as unread failed'; + @override + String get markAllAsReadConfirmationDialogTitle => 'Mark messages as read?'; + + @override + String get markAllAsReadConfirmationDialogMessage => + 'To preserve your reading state, this view does not mark messages as read.'; + + @override + String get markAllAsReadConfirmationDialogAction => '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 e59309be3e..1b53cbd8d6 100644 --- a/lib/generated/l10n/zulip_localizations_he.dart +++ b/lib/generated/l10n/zulip_localizations_he.dart @@ -774,6 +774,16 @@ class ZulipLocalizationsHe extends ZulipLocalizations { @override String get errorMarkAsUnreadFailedTitle => 'Mark as unread failed'; + @override + String get markAllAsReadConfirmationDialogTitle => 'Mark messages as read?'; + + @override + String get markAllAsReadConfirmationDialogMessage => + 'To preserve your reading state, this view does not mark messages as read.'; + + @override + String get markAllAsReadConfirmationDialogAction => '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 6a3c11465c..7baf15a8dd 100644 --- a/lib/generated/l10n/zulip_localizations_hu.dart +++ b/lib/generated/l10n/zulip_localizations_hu.dart @@ -774,6 +774,16 @@ class ZulipLocalizationsHu extends ZulipLocalizations { @override String get errorMarkAsUnreadFailedTitle => 'Mark as unread failed'; + @override + String get markAllAsReadConfirmationDialogTitle => 'Mark messages as read?'; + + @override + String get markAllAsReadConfirmationDialogMessage => + 'To preserve your reading state, this view does not mark messages as read.'; + + @override + String get markAllAsReadConfirmationDialogAction => '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 cd27a1daa7..d04554dc8b 100644 --- a/lib/generated/l10n/zulip_localizations_it.dart +++ b/lib/generated/l10n/zulip_localizations_it.dart @@ -785,6 +785,16 @@ class ZulipLocalizationsIt extends ZulipLocalizations { String get errorMarkAsUnreadFailedTitle => 'Contrassegno come non letti non riuscito'; + @override + String get markAllAsReadConfirmationDialogTitle => 'Mark messages as read?'; + + @override + String get markAllAsReadConfirmationDialogMessage => + 'To preserve your reading state, this view does not mark messages as read.'; + + @override + String get markAllAsReadConfirmationDialogAction => '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 d11aedf19f..b5b264bc1d 100644 --- a/lib/generated/l10n/zulip_localizations_ja.dart +++ b/lib/generated/l10n/zulip_localizations_ja.dart @@ -752,6 +752,16 @@ class ZulipLocalizationsJa extends ZulipLocalizations { @override String get errorMarkAsUnreadFailedTitle => '未読にできませんでした'; + @override + String get markAllAsReadConfirmationDialogTitle => 'Mark messages as read?'; + + @override + String get markAllAsReadConfirmationDialogMessage => + 'To preserve your reading state, this view does not mark messages as read.'; + + @override + String get markAllAsReadConfirmationDialogAction => '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 162ca9e677..1b287e1e5d 100644 --- a/lib/generated/l10n/zulip_localizations_nb.dart +++ b/lib/generated/l10n/zulip_localizations_nb.dart @@ -774,6 +774,16 @@ class ZulipLocalizationsNb extends ZulipLocalizations { @override String get errorMarkAsUnreadFailedTitle => 'Mark as unread failed'; + @override + String get markAllAsReadConfirmationDialogTitle => 'Mark messages as read?'; + + @override + String get markAllAsReadConfirmationDialogMessage => + 'To preserve your reading state, this view does not mark messages as read.'; + + @override + String get markAllAsReadConfirmationDialogAction => '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 40682171b2..8c45b615af 100644 --- a/lib/generated/l10n/zulip_localizations_pl.dart +++ b/lib/generated/l10n/zulip_localizations_pl.dart @@ -790,6 +790,16 @@ class ZulipLocalizationsPl extends ZulipLocalizations { String get errorMarkAsUnreadFailedTitle => 'Oznaczanie jako nieprzeczytane bez powodzenia'; + @override + String get markAllAsReadConfirmationDialogTitle => 'Mark messages as read?'; + + @override + String get markAllAsReadConfirmationDialogMessage => + 'To preserve your reading state, this view does not mark messages as read.'; + + @override + String get markAllAsReadConfirmationDialogAction => '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 04f8903b91..cd3c172702 100644 --- a/lib/generated/l10n/zulip_localizations_ru.dart +++ b/lib/generated/l10n/zulip_localizations_ru.dart @@ -795,6 +795,16 @@ class ZulipLocalizationsRu extends ZulipLocalizations { String get errorMarkAsUnreadFailedTitle => 'Не удалось снять отметку прочтения'; + @override + String get markAllAsReadConfirmationDialogTitle => 'Mark messages as read?'; + + @override + String get markAllAsReadConfirmationDialogMessage => + 'To preserve your reading state, this view does not mark messages as read.'; + + @override + String get markAllAsReadConfirmationDialogAction => '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 e2dc542fc5..00686f38b5 100644 --- a/lib/generated/l10n/zulip_localizations_sk.dart +++ b/lib/generated/l10n/zulip_localizations_sk.dart @@ -776,6 +776,16 @@ 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 get markAllAsReadConfirmationDialogMessage => + 'To preserve your reading state, this view does not mark messages as read.'; + + @override + String get markAllAsReadConfirmationDialogAction => '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 6f9d8343ca..6eb45640a6 100644 --- a/lib/generated/l10n/zulip_localizations_sl.dart +++ b/lib/generated/l10n/zulip_localizations_sl.dart @@ -804,6 +804,16 @@ class ZulipLocalizationsSl extends ZulipLocalizations { String get errorMarkAsUnreadFailedTitle => 'Označevanje kot neprebrano ni uspelo'; + @override + String get markAllAsReadConfirmationDialogTitle => 'Mark messages as read?'; + + @override + String get markAllAsReadConfirmationDialogMessage => + 'To preserve your reading state, this view does not mark messages as read.'; + + @override + String get markAllAsReadConfirmationDialogAction => '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 84df26a42e..3872f12348 100644 --- a/lib/generated/l10n/zulip_localizations_uk.dart +++ b/lib/generated/l10n/zulip_localizations_uk.dart @@ -791,6 +791,16 @@ class ZulipLocalizationsUk extends ZulipLocalizations { String get errorMarkAsUnreadFailedTitle => 'Не вдалося позначити як непрочитане'; + @override + String get markAllAsReadConfirmationDialogTitle => 'Mark messages as read?'; + + @override + String get markAllAsReadConfirmationDialogMessage => + 'To preserve your reading state, this view does not mark messages as read.'; + + @override + String get markAllAsReadConfirmationDialogAction => '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 b08c25004e..13ce0ffd23 100644 --- a/lib/generated/l10n/zulip_localizations_zh.dart +++ b/lib/generated/l10n/zulip_localizations_zh.dart @@ -774,6 +774,16 @@ class ZulipLocalizationsZh extends ZulipLocalizations { @override String get errorMarkAsUnreadFailedTitle => 'Mark as unread failed'; + @override + String get markAllAsReadConfirmationDialogTitle => 'Mark messages as read?'; + + @override + String get markAllAsReadConfirmationDialogMessage => + 'To preserve your reading state, this view does not mark messages as read.'; + + @override + String get markAllAsReadConfirmationDialogAction => 'Mark as read'; + @override String get today => 'Today'; diff --git a/lib/widgets/actions.dart b/lib/widgets/actions.dart index bd8274613f..a769ff7f13 100644 --- a/lib/widgets/actions.dart +++ b/lib/widgets/actions.dart @@ -32,6 +32,19 @@ abstract final class ZulipAction { static Future markNarrowAsRead(BuildContext context, Narrow narrow) async { final zulipLocalizations = ZulipLocalizations.of(context); + if (narrow is CombinedFeedNarrow || narrow is ChannelNarrow || + narrow is MentionsNarrow || narrow is KeywordSearchNarrow || + narrow is StarredMessagesNarrow) { + final didConfirm = showSuggestedActionDialog(context: context, + title: zulipLocalizations.markAllAsReadConfirmationDialogTitle, + message: zulipLocalizations.markAllAsReadConfirmationDialogMessage, + actionButtonText: zulipLocalizations.markAllAsReadConfirmationDialogAction, + ); + + if (await didConfirm.result != true) return; + if (!context.mounted) return; + } + final didPass = await updateMessageFlagsStartingFromAnchor( context: context, // Include `is:unread` in the narrow. That has a database index, so diff --git a/test/widgets/action_sheet_test.dart b/test/widgets/action_sheet_test.dart index c43c6daab7..8c65743a44 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 (confirmButton, _) = checkSuggestedActionDialog(tester, + expectedTitle: zulipLocalizations.markAllAsReadConfirmationDialogTitle, + expectedMessage: zulipLocalizations.markAllAsReadConfirmationDialogMessage, + expectedActionButtonText: zulipLocalizations.markAllAsReadConfirmationDialogAction, + ); + 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 (confirmButton, _) = checkSuggestedActionDialog(tester, + expectedTitle: zulipLocalizations.markAllAsReadConfirmationDialogTitle, + expectedMessage: zulipLocalizations.markAllAsReadConfirmationDialogMessage, + expectedActionButtonText: zulipLocalizations.markAllAsReadConfirmationDialogAction, + ); + 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..db1f7a4fd2 100644 --- a/test/widgets/actions_test.dart +++ b/test/widgets/actions_test.dart @@ -56,6 +56,26 @@ void main() { } group('markNarrowAsRead', () { + Future confirmToMarkNarrowAsRead({required WidgetTester tester, + required BuildContext context, required Narrow narrow + }) async { + final zulipLocalizations = GlobalLocalizations.zulipLocalizations; + + final future = tester.runAsync(() => ZulipAction.markNarrowAsRead(context, narrow)); + + await tester.pump(); + final (confirmButton, _) = checkSuggestedActionDialog(tester, + expectedTitle: zulipLocalizations.markAllAsReadConfirmationDialogTitle, + expectedMessage: zulipLocalizations.markAllAsReadConfirmationDialogMessage, + expectedActionButtonText: zulipLocalizations.markAllAsReadConfirmationDialogAction, + ); + + await tester.tap(find.byWidget(confirmButton)); + await tester.pump(); + + await future; + } + testWidgets('smoke test on modern server', (tester) async { final narrow = TopicNarrow.ofMessage(eg.streamMessage()); await prepare(tester); @@ -88,9 +108,7 @@ void main() { processedCount: 11, updatedCount: 3, firstProcessedId: null, lastProcessedId: null, foundOldest: true, foundNewest: true).toJson()); - final future = ZulipAction.markNarrowAsRead(context, narrow); - await tester.pump(Duration.zero); - await future; + await confirmToMarkNarrowAsRead(tester: tester, context: context, narrow: narrow); check(connection.lastRequest).isA() ..method.equals('POST') ..url.path.equals('/api/v1/messages/flags/narrow') @@ -114,9 +132,7 @@ void main() { processedCount: 11, updatedCount: 3, firstProcessedId: null, lastProcessedId: null, foundOldest: true, foundNewest: true).toJson()); - final future = ZulipAction.markNarrowAsRead(context, narrow); - await tester.pump(Duration.zero); - await future; + await confirmToMarkNarrowAsRead(tester: tester, context: context, narrow: narrow); check(store.unreads.oldUnreadsMissing).isFalse(); }); }); diff --git a/test/widgets/message_list_test.dart b/test/widgets/message_list_test.dart index 89b2a31bd8..905b16b14f 100644 --- a/test/widgets/message_list_test.dart +++ b/test/widgets/message_list_test.dart @@ -972,6 +972,16 @@ void main() { return finder.evaluate().isNotEmpty; } + (Widget, Widget) checkConfirmDialog(WidgetTester tester) { + final zulipLocalizations = GlobalLocalizations.zulipLocalizations; + return checkSuggestedActionDialog(tester, + expectedTitle: zulipLocalizations.markAllAsReadConfirmationDialogTitle, + expectedMessage: zulipLocalizations.markAllAsReadConfirmationDialogMessage, + expectDestructiveActionButton: true, + expectedActionButtonText: zulipLocalizations.markAllAsReadConfirmationDialogAction, + ); + } + testWidgets('from read to unread', (tester) async { final message = eg.streamMessage(flags: [MessageFlag.read]); await setupMessageListPage(tester, messages: [message]); @@ -1161,6 +1171,9 @@ void main() { firstProcessedId: null, lastProcessedId: null, foundOldest: true, foundNewest: true).toJson()); await tester.tap(find.byType(MarkAsReadWidget)); + await tester.pump(); + final (confirmButton, _) = checkConfirmDialog(tester); + await tester.tap(find.byWidget(confirmButton)); await tester.pumpAndSettle(); check(store.unreads.oldUnreadsMissing).isFalse(); }); @@ -1174,6 +1187,9 @@ void main() { connection.prepare(httpException: http.ClientException('Oops')); await tester.tap(find.byType(MarkAsReadWidget)); + await tester.pump(); + final (confirmButton, _) = checkConfirmDialog(tester); + await tester.tap(find.byWidget(confirmButton)); await tester.pumpAndSettle(); checkErrorDialog(tester, expectedTitle: zulipLocalizations.errorMarkAsReadFailedTitle,