Skip to content

Commit 94e5ebe

Browse files
committed
action_sheet: Add "Mark Topic As Read" button
Fixes: #1225
1 parent b8d36af commit 94e5ebe

11 files changed

+104
-0
lines changed

assets/l10n/app_en.arb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,10 @@
136136
"@actionSheetOptionUnstarMessage": {
137137
"description": "Label for unstar button on action sheet."
138138
},
139+
"actionSheetOptionMarkTopicAsRead": "Mark topic as read",
140+
"@actionSheetOptionMarkTopicAsRead": {
141+
"description": "Option to mark a specific topic as read in the action sheet."
142+
},
139143
"errorWebAuthOperationalErrorTitle": "Something went wrong",
140144
"@errorWebAuthOperationalErrorTitle": {
141145
"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
@@ -309,6 +309,12 @@ abstract class ZulipLocalizations {
309309
/// **'Unstar message'**
310310
String get actionSheetOptionUnstarMessage;
311311

312+
/// Option to mark a specific topic as read in the action sheet.
313+
///
314+
/// In en, this message translates to:
315+
/// **'Mark topic as read'**
316+
String get actionSheetOptionMarkTopicAsRead;
317+
312318
/// Error title when third-party authentication has an operational error (not necessarily caused by invalid credentials).
313319
///
314320
/// 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
@@ -112,6 +112,9 @@ class ZulipLocalizationsAr extends ZulipLocalizations {
112112
@override
113113
String get actionSheetOptionUnstarMessage => 'Unstar message';
114114

115+
@override
116+
String get actionSheetOptionMarkTopicAsRead => 'Mark topic as read';
117+
115118
@override
116119
String get errorWebAuthOperationalErrorTitle => 'Something went wrong';
117120

lib/generated/l10n/zulip_localizations_en.dart

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

115+
@override
116+
String get actionSheetOptionMarkTopicAsRead => 'Mark topic as read';
117+
115118
@override
116119
String get errorWebAuthOperationalErrorTitle => 'Something went wrong';
117120

lib/generated/l10n/zulip_localizations_ja.dart

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

115+
@override
116+
String get actionSheetOptionMarkTopicAsRead => 'Mark topic as read';
117+
115118
@override
116119
String get errorWebAuthOperationalErrorTitle => 'Something went wrong';
117120

lib/generated/l10n/zulip_localizations_nb.dart

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

115+
@override
116+
String get actionSheetOptionMarkTopicAsRead => 'Mark topic as read';
117+
115118
@override
116119
String get errorWebAuthOperationalErrorTitle => 'Something went wrong';
117120

lib/generated/l10n/zulip_localizations_pl.dart

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

115+
@override
116+
String get actionSheetOptionMarkTopicAsRead => 'Mark topic as read';
117+
115118
@override
116119
String get errorWebAuthOperationalErrorTitle => 'Coś poszło nie tak';
117120

lib/generated/l10n/zulip_localizations_ru.dart

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

115+
@override
116+
String get actionSheetOptionMarkTopicAsRead => 'Mark topic as read';
117+
115118
@override
116119
String get errorWebAuthOperationalErrorTitle => 'Что-то пошло не так';
117120

lib/generated/l10n/zulip_localizations_sk.dart

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

115+
@override
116+
String get actionSheetOptionMarkTopicAsRead => 'Mark topic as read';
117+
115118
@override
116119
String get errorWebAuthOperationalErrorTitle => 'Niečo sa pokazilo';
117120

lib/widgets/action_sheet.dart

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,14 @@ void showTopicActionSheet(BuildContext context, {
255255
someMessageIdInTopic: someMessageIdInTopic));
256256
}
257257

258+
final unreadCount = store.unreads.countInTopicNarrow(channelId, topic);
259+
if (unreadCount > 0) {
260+
optionButtons.add(MarkTopicAsReadButton(
261+
channelId: channelId,
262+
topic: topic,
263+
pageContext: context));
264+
}
265+
258266
if (optionButtons.isEmpty) {
259267
// TODO(a11y): This case makes a no-op gesture handler; as a consequence,
260268
// we're presenting some UI (to people who use screen-reader software) as
@@ -461,6 +469,29 @@ class ResolveUnresolveButton extends ActionSheetMenuItemButton {
461469
}
462470
}
463471

472+
class MarkTopicAsReadButton extends ActionSheetMenuItemButton {
473+
const MarkTopicAsReadButton({
474+
super.key,
475+
required this.channelId,
476+
required this.topic,
477+
required super.pageContext,
478+
});
479+
480+
final int channelId;
481+
final TopicName topic;
482+
483+
@override IconData get icon => ZulipIcons.message_checked;
484+
485+
@override
486+
String label(ZulipLocalizations zulipLocalizations) {
487+
return zulipLocalizations.actionSheetOptionMarkTopicAsRead;
488+
}
489+
490+
@override void onPressed() async {
491+
await ZulipAction.markNarrowAsRead(pageContext, TopicNarrow(channelId, topic));
492+
}
493+
}
494+
464495
/// Show a sheet of actions you can take on a message in the message list.
465496
///
466497
/// Must have a [MessageListPage] ancestor.

test/widgets/action_sheet_test.dart

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import 'package:http/http.dart' as http;
1010
import 'package:zulip/api/model/events.dart';
1111
import 'package:zulip/api/model/initial_snapshot.dart';
1212
import 'package:zulip/api/model/model.dart';
13+
import 'package:zulip/api/model/narrow.dart';
1314
import 'package:zulip/api/route/channels.dart';
1415
import 'package:zulip/api/route/messages.dart';
1516
import 'package:zulip/model/binding.dart';
@@ -562,6 +563,47 @@ void main() {
562563
expectedTitle: 'Failed to mark topic as unresolved');
563564
});
564565
});
566+
567+
group('MarkTopicAsReadButton', () {
568+
testWidgets('visible if topic has unread messages', (tester) async {
569+
await prepare();
570+
final message = eg.streamMessage(stream: someChannel, topic: someTopic, flags: []);
571+
await store.addMessage(message);
572+
await showFromAppBar(tester, messages: [message]);
573+
check(find.text('Mark topic as read')).findsOne();
574+
});
575+
576+
testWidgets('not visible if topic has no unread messages', (tester) async {
577+
await prepare();
578+
final message = eg.streamMessage(stream: someChannel, topic: someTopic, flags: [MessageFlag.read]);
579+
await store.addMessage(message);
580+
await showFromAppBar(tester, messages: [message]);
581+
check(find.text('Mark topic as read')).findsNothing();
582+
});
583+
584+
testWidgets('marks topic as read when pressed', (tester) async {
585+
await prepare();
586+
final message = eg.streamMessage(stream: someChannel, topic: someTopic);
587+
await store.handleEvent(MessageEvent(id: 0, message: message));
588+
await showFromAppBar(tester, messages: [message]);
589+
590+
connection.prepare(json: UpdateMessageFlagsForNarrowResult(
591+
processedCount: 1, updatedCount: 1,
592+
firstProcessedId: message.id, lastProcessedId: message.id,
593+
foundOldest: true, foundNewest: true).toJson());
594+
await tester.tap(find.text('Mark topic as read'));
595+
await tester.pumpAndSettle();
596+
597+
check(connection.lastRequest).isA<http.Request>()
598+
..url.path.equals('/api/v1/messages/flags/narrow')
599+
..bodyFields['narrow'].equals(jsonEncode([
600+
...eg.topicNarrow(someChannel.streamId, someTopic).apiEncode(),
601+
ApiNarrowIs(IsOperand.unread),
602+
]))
603+
..bodyFields['op'].equals('add')
604+
..bodyFields['flag'].equals('read');
605+
});
606+
});
565607
});
566608

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

0 commit comments

Comments
 (0)