Skip to content

Commit 4793beb

Browse files
committed
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 45a5702 commit 4793beb

File tree

1 file changed

+76
-35
lines changed

1 file changed

+76
-35
lines changed

lib/widgets/actions.dart

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

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-
4899
while (true) {
49100
final result = await updateMessageFlagsForNarrow(connection,
50101
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
54-
// unconditionally false.
55-
includeAnchor: false,
102+
includeAnchor: includeAnchor,
56103
// There is an upper limit of 5000 messages per batch
57104
// (numBefore + numAfter <= 5000) enforced on the server.
58105
// See `update_message_flags_in_narrow` in zerver/views/message_flags.py .
@@ -61,11 +108,11 @@ Future<void> markNarrowAsRead(BuildContext context, Narrow narrow) async {
61108
numBefore: 0,
62109
numAfter: 1000,
63110
narrow: apiNarrow,
64-
op: UpdateMessageFlagsOp.add,
65-
flag: MessageFlag.read);
111+
op: op,
112+
flag: flag);
66113
if (!context.mounted) {
67114
scaffoldMessenger.clearSnackBars();
68-
return;
115+
return false;
69116
}
70117
responseCount++;
71118
updatedCount += result.updatedCount;
@@ -78,25 +125,24 @@ Future<void> markNarrowAsRead(BuildContext context, Narrow narrow) async {
78125
scaffoldMessenger
79126
..clearSnackBars()
80127
..showSnackBar(SnackBar(behavior: SnackBarBehavior.floating,
81-
content: Text(zulipLocalizations.markAsReadComplete(updatedCount))));
128+
content: Text(onCompletedMessage(updatedCount))));
82129
}
83-
break;
130+
return true;
84131
}
85132

86133
if (result.lastProcessedId == null) {
134+
final zulipLocalizations = ZulipLocalizations.of(context);
87135
// No messages were in the range of the request.
88136
// This should be impossible given that `foundNewest` was false
89137
// (and that our `numAfter` was positive.)
90138
showErrorDialog(context: context,
91-
title: zulipLocalizations.errorMarkAsReadFailedTitle,
139+
title: onFailedTitle,
92140
message: zulipLocalizations.errorInvalidResponse);
93-
return;
141+
return false;
94142
}
95143
anchor = NumericAnchor(result.lastProcessedId!);
96144

97145
// 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.
100146
// TODO: Ideally we'd have a progress widget here that showed up based
101147
// on actual time elapsed -- so it could appear before the first
102148
// batch returns, if that takes a while -- and that then stuck
@@ -109,19 +155,14 @@ Future<void> markNarrowAsRead(BuildContext context, Narrow narrow) async {
109155
// is better for now if we allow them to run their timer through
110156
// and clear the backlog later.
111157
scaffoldMessenger.showSnackBar(SnackBar(behavior: SnackBarBehavior.floating,
112-
content: Text(zulipLocalizations.markAsReadInProgress)));
113-
}
114-
if (!context.mounted) return;
115-
if (narrow is CombinedFeedNarrow && !useLegacy) {
116-
PerAccountStoreWidget.of(context).unreads.handleAllMessagesReadSuccess();
158+
content: Text(progressMessage)));
117159
}
118160
} catch (e) {
119-
if (!context.mounted) return;
120-
final zulipLocalizations = ZulipLocalizations.of(context);
161+
if (!context.mounted) return false;
121162
showErrorDialog(context: context,
122-
title: zulipLocalizations.errorMarkAsReadFailedTitle,
163+
title: onFailedTitle,
123164
message: e.toString()); // TODO(#741): extract user-facing message better
124-
return;
165+
return false;
125166
}
126167
}
127168

0 commit comments

Comments
 (0)