Skip to content

Commit 8451cb2

Browse files
committed
action_sheet: Add "Mark Topic As Read" button
fixes: #1225
1 parent 18b2811 commit 8451cb2

11 files changed

+128
-0
lines changed

assets/l10n/app_en.arb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,10 @@
120120
"@actionSheetOptionUnstarMessage": {
121121
"description": "Label for unstar button on action sheet."
122122
},
123+
"actionSheetOptionMarkTopicAsRead": "Mark Topic As Read",
124+
"@actionSheetOptionMarkTopicAsRead": {
125+
"description": "Option to mark a specific topic as read in the action sheet."
126+
},
123127
"errorWebAuthOperationalErrorTitle": "Something went wrong",
124128
"@errorWebAuthOperationalErrorTitle": {
125129
"description": "Error title when third-party authentication has an operational error (not necessarily caused by invalid credentials)."

lib/generated/l10n/zulip_localizations.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,12 @@ abstract class ZulipLocalizations {
285285
/// **'Unstar message'**
286286
String get actionSheetOptionUnstarMessage;
287287

288+
/// Option to mark a specific topic as read in the action sheet.
289+
///
290+
/// In en, this message translates to:
291+
/// **'Mark Topic As Read'**
292+
String get actionSheetOptionMarkTopicAsRead;
293+
288294
/// Error title when third-party authentication has an operational error (not necessarily caused by invalid credentials).
289295
///
290296
/// In en, this message translates to:

lib/generated/l10n/zulip_localizations_ar.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ class ZulipLocalizationsAr extends ZulipLocalizations {
100100
@override
101101
String get actionSheetOptionUnstarMessage => 'Unstar message';
102102

103+
@override
104+
String get actionSheetOptionMarkTopicAsRead => 'Mark Topic As Read';
105+
103106
@override
104107
String get errorWebAuthOperationalErrorTitle => 'Something went wrong';
105108

lib/generated/l10n/zulip_localizations_en.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ class ZulipLocalizationsEn extends ZulipLocalizations {
100100
@override
101101
String get actionSheetOptionUnstarMessage => 'Unstar message';
102102

103+
@override
104+
String get actionSheetOptionMarkTopicAsRead => 'Mark Topic As Read';
105+
103106
@override
104107
String get errorWebAuthOperationalErrorTitle => 'Something went wrong';
105108

lib/generated/l10n/zulip_localizations_ja.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ class ZulipLocalizationsJa extends ZulipLocalizations {
100100
@override
101101
String get actionSheetOptionUnstarMessage => 'Unstar message';
102102

103+
@override
104+
String get actionSheetOptionMarkTopicAsRead => 'Mark Topic As Read';
105+
103106
@override
104107
String get errorWebAuthOperationalErrorTitle => 'Something went wrong';
105108

lib/generated/l10n/zulip_localizations_nb.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ class ZulipLocalizationsNb extends ZulipLocalizations {
100100
@override
101101
String get actionSheetOptionUnstarMessage => 'Unstar message';
102102

103+
@override
104+
String get actionSheetOptionMarkTopicAsRead => 'Mark Topic As Read';
105+
103106
@override
104107
String get errorWebAuthOperationalErrorTitle => 'Something went wrong';
105108

lib/generated/l10n/zulip_localizations_pl.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ class ZulipLocalizationsPl extends ZulipLocalizations {
100100
@override
101101
String get actionSheetOptionUnstarMessage => 'Odbierz gwiazdkę';
102102

103+
@override
104+
String get actionSheetOptionMarkTopicAsRead => 'Mark Topic As Read';
105+
103106
@override
104107
String get errorWebAuthOperationalErrorTitle => 'Coś poszło nie tak';
105108

lib/generated/l10n/zulip_localizations_ru.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ class ZulipLocalizationsRu extends ZulipLocalizations {
100100
@override
101101
String get actionSheetOptionUnstarMessage => 'Снять отметку с сообщения';
102102

103+
@override
104+
String get actionSheetOptionMarkTopicAsRead => 'Mark Topic As Read';
105+
103106
@override
104107
String get errorWebAuthOperationalErrorTitle => 'Что-то пошло не так';
105108

lib/generated/l10n/zulip_localizations_sk.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ class ZulipLocalizationsSk extends ZulipLocalizations {
100100
@override
101101
String get actionSheetOptionUnstarMessage => 'Odhviezdičkovať správu';
102102

103+
@override
104+
String get actionSheetOptionMarkTopicAsRead => 'Mark Topic As Read';
105+
103106
@override
104107
String get errorWebAuthOperationalErrorTitle => 'Niečo sa pokazilo';
105108

lib/widgets/action_sheet.dart

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,14 @@ void showTopicActionSheet(BuildContext context, {
240240
pageContext: context);
241241
}));
242242

243+
final unreadCount = store.unreads.countInTopicNarrow(channelId, topic);
244+
if (unreadCount > 0) {
245+
optionButtons.add(MarkTopicAsReadButton(
246+
channelId: channelId,
247+
topic: topic,
248+
pageContext: context));
249+
}
250+
243251
if (optionButtons.isEmpty) {
244252
// TODO(a11y): This case makes a no-op gesture handler; as a consequence,
245253
// we're presenting some UI (to people who use screen-reader software) as
@@ -372,6 +380,30 @@ class UserTopicUpdateButton extends ActionSheetMenuItemButton {
372380
}
373381
}
374382

383+
class MarkTopicAsReadButton extends ActionSheetMenuItemButton {
384+
const MarkTopicAsReadButton({
385+
super.key,
386+
required this.channelId,
387+
required this.topic,
388+
required super.pageContext,
389+
});
390+
391+
final int channelId;
392+
final TopicName topic;
393+
394+
@override IconData get icon => ZulipIcons.message_checked;
395+
396+
@override
397+
String label(ZulipLocalizations zulipLocalizations) {
398+
return zulipLocalizations.actionSheetOptionMarkTopicAsRead;
399+
}
400+
401+
@override void onPressed() async {
402+
if (!pageContext.mounted) return;
403+
await markNarrowAsRead(pageContext, TopicNarrow(channelId, topic));
404+
}
405+
}
406+
375407
/// Show a sheet of actions you can take on a message in the message list.
376408
///
377409
/// Must have a [MessageListPage] ancestor.

test/widgets/action_sheet_test.dart

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,71 @@ void main() {
450450
}
451451
});
452452
});
453+
454+
group('MarkTopicAsReadButton', () {
455+
testWidgets('visible if topic has unread messages', (tester) async {
456+
await prepare();
457+
final message = eg.streamMessage(stream: someChannel, topic: someTopic);
458+
await store.handleEvent(MessageEvent(id: 0, message: message));
459+
await showFromAppBar(tester, messages: [message]);
460+
check(find.text('Mark Topic As Read')).findsOne();
461+
});
462+
463+
testWidgets('not visible if topic has no unread messages', (tester) async {
464+
await prepare();
465+
final message = eg.streamMessage(stream: someChannel, topic: someTopic);
466+
await showFromAppBar(tester, messages: [message]);
467+
check(find.text('Mark Topic As Read')).findsNothing();
468+
});
469+
470+
testWidgets('marks topic as read using legacy API when FL < 155', (tester) async {
471+
await prepare(zulipFeatureLevel: 154);
472+
final message = eg.streamMessage(stream: someChannel, topic: someTopic);
473+
await store.handleEvent(MessageEvent(id: 0, message: message));
474+
await showFromAppBar(tester, messages: [message]);
475+
476+
connection.prepare(json: {'result': 'success', 'msg': ''});
477+
await tester.tap(find.text('Mark Topic As Read'));
478+
await tester.pumpAndSettle();
479+
check(connection.lastRequest).isA<http.Request>()
480+
..method.equals('POST')
481+
..url.path.equals('/api/v1/mark_topic_as_read')
482+
..bodyFields.deepEquals({
483+
'stream_id': '${someChannel.streamId}',
484+
'topic_name': someTopic,
485+
});
486+
});
487+
488+
testWidgets('marks topic as read using new API when FL ≥ 155', (tester) async {
489+
await prepare(zulipFeatureLevel: 155);
490+
final message = eg.streamMessage(stream: someChannel, topic: someTopic);
491+
await store.handleEvent(MessageEvent(id: 0, message: message));
492+
await showFromAppBar(tester, messages: [message]);
493+
494+
connection.prepare(json: UpdateMessageFlagsForNarrowResult(
495+
processedCount: 1, updatedCount: 1,
496+
firstProcessedId: message.id, lastProcessedId: message.id,
497+
foundOldest: true, foundNewest: true).toJson());
498+
await tester.tap(find.text('Mark Topic As Read'));
499+
await tester.pumpAndSettle();
500+
501+
check(connection.lastRequest).isA<http.Request>()
502+
..method.equals('POST')
503+
..url.path.equals('/api/v1/messages/flags/narrow')
504+
..bodyFields.deepEquals({
505+
'anchor': 'oldest',
506+
'include_anchor': 'false',
507+
'num_before': '0',
508+
'num_after': '1000',
509+
'narrow': jsonEncode([
510+
...TopicNarrow(someChannel.streamId, TopicName(someTopic)).apiEncode(),
511+
{'operator': 'is', 'operand': 'unread'}
512+
]),
513+
'op': 'add',
514+
'flag': 'read',
515+
});
516+
});
517+
});
453518
});
454519

455520
group('message action sheet', () {

0 commit comments

Comments
 (0)