Skip to content

Commit 5ed1886

Browse files
Khader-1gnprice
authored andcommitted
actions [nfc]: Factor out updateMessageFlagsStartingFromAnchor
Most of the logic in `markNarrowAsRead` is not specific to marking narrow as read, rather it is actually updating message flags for the narrow starting from the oldest anchor. As we are going to use this logic for several other actions starting with the next commit, it is better to have that generic logic factored out and reused.
1 parent 28f5c2c commit 5ed1886

File tree

1 file changed

+81
-39
lines changed

1 file changed

+81
-39
lines changed

lib/widgets/actions.dart

Lines changed: 81 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -17,42 +17,90 @@ import 'dialog.dart';
1717
import 'store.dart';
1818

1919
Future<void> markNarrowAsRead(BuildContext context, Narrow narrow) async {
20-
try {
21-
final store = PerAccountStoreWidget.of(context);
22-
final connection = store.connection;
23-
final useLegacy = connection.zulipFeatureLevel! < 155; // TODO(server-6)
24-
if (useLegacy) {
20+
final store = PerAccountStoreWidget.of(context);
21+
final connection = store.connection;
22+
final zulipLocalizations = ZulipLocalizations.of(context);
23+
final useLegacy = connection.zulipFeatureLevel! < 155; // TODO(server-6)
24+
if (useLegacy) {
25+
try {
2526
await _legacyMarkNarrowAsRead(context, narrow);
2627
return;
28+
} catch (e) {
29+
if (!context.mounted) return;
30+
await showErrorDialog(context: context,
31+
title: zulipLocalizations.errorMarkAsReadFailedTitle,
32+
message: e.toString()); // TODO(#741): extract user-facing message better
33+
return;
2734
}
35+
}
2836

29-
// Compare web's `mark_all_as_read` in web/src/unread_ops.js
30-
// and zulip-mobile's `markAsUnreadFromMessage` in src/action-sheets/index.js .
31-
final zulipLocalizations = ZulipLocalizations.of(context);
32-
final scaffoldMessenger = ScaffoldMessenger.of(context);
37+
final didPass = await updateMessageFlagsStartingFromAnchor(
38+
context: context,
39+
// Include `is:unread` in the narrow. That has a database index, so
40+
// this can be an important optimization in narrows with a lot of history.
41+
// The server applies the same optimization within the (deprecated)
42+
// specialized endpoints for marking messages as read; see
43+
// `do_mark_stream_messages_as_read` in `zulip:zerver/actions/message_flags.py`.
44+
apiNarrow: narrow.apiEncode()..add(ApiNarrowIsUnread()),
3345
// Use [AnchorCode.oldest], because [AnchorCode.firstUnread]
3446
// will be the oldest non-muted unread message, which would
3547
// result in muted unreads older than the first unread not
3648
// being processed.
37-
Anchor anchor = AnchorCode.oldest;
49+
startingAnchor: AnchorCode.oldest,
50+
// [AnchorCode.oldest] is an anchor ID lower than any valid
51+
// message ID.
52+
includeAnchor: false,
53+
op: UpdateMessageFlagsOp.add,
54+
flag: MessageFlag.read,
55+
onCompletedMessage: zulipLocalizations.markAsReadComplete,
56+
progressMessage: zulipLocalizations.markAsReadInProgress,
57+
onFailedTitle: zulipLocalizations.errorMarkAsReadFailedTitle);
58+
59+
if (!didPass || !context.mounted) return;
60+
if (narrow is CombinedFeedNarrow) {
61+
PerAccountStoreWidget.of(context).unreads.handleAllMessagesReadSuccess();
62+
}
63+
}
64+
65+
/// Updates message flags by applying given operation `op` using given `flag`
66+
/// the update happens on given `apiNarrow` starting from given `startingAnchor`
67+
///
68+
/// This also handles interactions with the user as it shows a `Snackbar` with
69+
/// `progressMessage` while performing the update, shows an error dialog when
70+
/// update fails with the given title using `onFailedTitle` and shows
71+
/// a `Snackbar` with computed message using given `onCompletedMessage`.
72+
///
73+
/// Returns true in case the process is completed with no exceptions
74+
/// otherwise shows an error dialog and returns false.
75+
Future<bool> updateMessageFlagsStartingFromAnchor({
76+
required BuildContext context,
77+
required List<ApiNarrowElement> apiNarrow,
78+
required Anchor startingAnchor,
79+
required bool includeAnchor,
80+
required UpdateMessageFlagsOp op,
81+
required MessageFlag flag,
82+
required String Function(int) onCompletedMessage,
83+
required String progressMessage,
84+
required String onFailedTitle,
85+
}) async {
86+
try {
87+
final store = PerAccountStoreWidget.of(context);
88+
final connection = store.connection;
89+
final scaffoldMessenger = ScaffoldMessenger.of(context);
90+
91+
// Compare web's `mark_all_as_read` in web/src/unread_ops.js
92+
// and zulip-mobile's `markAsUnreadFromMessage` in src/action-sheets/index.js .
93+
Anchor anchor = startingAnchor;
3894
int responseCount = 0;
3995
int updatedCount = 0;
4096

41-
// Include `is:unread` in the narrow. That has a database index, so
42-
// this can be an important optimization in narrows with a lot of history.
43-
// The server applies the same optimization within the (deprecated)
44-
// specialized endpoints for marking messages as read; see
45-
// `do_mark_stream_messages_as_read` in `zulip:zerver/actions/message_flags.py`.
46-
final apiNarrow = narrow.apiEncode()..add(ApiNarrowIsUnread());
47-
4897
while (true) {
4998
final result = await updateMessageFlagsForNarrow(connection,
5099
anchor: anchor,
51-
// [AnchorCode.oldest] is an anchor ID lower than any valid
52-
// message ID; and follow-up requests will have already
53-
// processed the anchor ID, so we just want this to be
100+
// Follow-up requests will have already processed the
101+
// anchor ID, so after the first iteration, this will be
54102
// unconditionally false.
55-
includeAnchor: false,
103+
includeAnchor: responseCount == 0 ? includeAnchor : false,
56104
// There is an upper limit of 5000 messages per batch
57105
// (numBefore + numAfter <= 5000) enforced on the server.
58106
// See `update_message_flags_in_narrow` in zerver/views/message_flags.py .
@@ -61,11 +109,11 @@ Future<void> markNarrowAsRead(BuildContext context, Narrow narrow) async {
61109
numBefore: 0,
62110
numAfter: 1000,
63111
narrow: apiNarrow,
64-
op: UpdateMessageFlagsOp.add,
65-
flag: MessageFlag.read);
112+
op: op,
113+
flag: flag);
66114
if (!context.mounted) {
67115
scaffoldMessenger.clearSnackBars();
68-
return;
116+
return false;
69117
}
70118
responseCount++;
71119
updatedCount += result.updatedCount;
@@ -78,25 +126,24 @@ Future<void> markNarrowAsRead(BuildContext context, Narrow narrow) async {
78126
scaffoldMessenger
79127
..clearSnackBars()
80128
..showSnackBar(SnackBar(behavior: SnackBarBehavior.floating,
81-
content: Text(zulipLocalizations.markAsReadComplete(updatedCount))));
129+
content: Text(onCompletedMessage(updatedCount))));
82130
}
83-
break;
131+
return true;
84132
}
85133

86134
if (result.lastProcessedId == null) {
135+
final zulipLocalizations = ZulipLocalizations.of(context);
87136
// No messages were in the range of the request.
88137
// This should be impossible given that `foundNewest` was false
89138
// (and that our `numAfter` was positive.)
90139
showErrorDialog(context: context,
91-
title: zulipLocalizations.errorMarkAsReadFailedTitle,
140+
title: onFailedTitle,
92141
message: zulipLocalizations.errorInvalidResponse);
93-
return;
142+
return false;
94143
}
95144
anchor = NumericAnchor(result.lastProcessedId!);
96145

97146
// The task is taking a while, so tell the user we're working on it.
98-
// No need to say how many messages, as the [MarkAsUnread] widget
99-
// should follow along.
100147
// TODO: Ideally we'd have a progress widget here that showed up based
101148
// on actual time elapsed -- so it could appear before the first
102149
// batch returns, if that takes a while -- and that then stuck
@@ -109,19 +156,14 @@ Future<void> markNarrowAsRead(BuildContext context, Narrow narrow) async {
109156
// is better for now if we allow them to run their timer through
110157
// and clear the backlog later.
111158
scaffoldMessenger.showSnackBar(SnackBar(behavior: SnackBarBehavior.floating,
112-
content: Text(zulipLocalizations.markAsReadInProgress)));
159+
content: Text(progressMessage)));
113160
}
114161
} catch (e) {
115-
if (!context.mounted) return;
116-
final zulipLocalizations = ZulipLocalizations.of(context);
162+
if (!context.mounted) return false;
117163
showErrorDialog(context: context,
118-
title: zulipLocalizations.errorMarkAsReadFailedTitle,
164+
title: onFailedTitle,
119165
message: e.toString()); // TODO(#741): extract user-facing message better
120-
return;
121-
}
122-
if (!context.mounted) return;
123-
if (narrow is CombinedFeedNarrow) {
124-
PerAccountStoreWidget.of(context).unreads.handleAllMessagesReadSuccess();
166+
return false;
125167
}
126168
}
127169

0 commit comments

Comments
 (0)