@@ -17,42 +17,90 @@ import 'dialog.dart';
17
17
import 'store.dart' ;
18
18
19
19
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 {
25
26
await _legacyMarkNarrowAsRead (context, narrow);
26
27
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 ;
27
34
}
35
+ }
28
36
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 ()),
33
45
// Use [AnchorCode.oldest], because [AnchorCode.firstUnread]
34
46
// will be the oldest non-muted unread message, which would
35
47
// result in muted unreads older than the first unread not
36
48
// 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;
38
94
int responseCount = 0 ;
39
95
int updatedCount = 0 ;
40
96
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
-
48
97
while (true ) {
49
98
final result = await updateMessageFlagsForNarrow (connection,
50
99
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
54
102
// unconditionally false.
55
- includeAnchor: false ,
103
+ includeAnchor: responseCount == 0 ? includeAnchor : false ,
56
104
// There is an upper limit of 5000 messages per batch
57
105
// (numBefore + numAfter <= 5000) enforced on the server.
58
106
// See `update_message_flags_in_narrow` in zerver/views/message_flags.py .
@@ -61,11 +109,11 @@ Future<void> markNarrowAsRead(BuildContext context, Narrow narrow) async {
61
109
numBefore: 0 ,
62
110
numAfter: 1000 ,
63
111
narrow: apiNarrow,
64
- op: UpdateMessageFlagsOp .add ,
65
- flag: MessageFlag .read );
112
+ op: op ,
113
+ flag: flag );
66
114
if (! context.mounted) {
67
115
scaffoldMessenger.clearSnackBars ();
68
- return ;
116
+ return false ;
69
117
}
70
118
responseCount++ ;
71
119
updatedCount += result.updatedCount;
@@ -78,25 +126,24 @@ Future<void> markNarrowAsRead(BuildContext context, Narrow narrow) async {
78
126
scaffoldMessenger
79
127
..clearSnackBars ()
80
128
..showSnackBar (SnackBar (behavior: SnackBarBehavior .floating,
81
- content: Text (zulipLocalizations. markAsReadComplete (updatedCount))));
129
+ content: Text (onCompletedMessage (updatedCount))));
82
130
}
83
- break ;
131
+ return true ;
84
132
}
85
133
86
134
if (result.lastProcessedId == null ) {
135
+ final zulipLocalizations = ZulipLocalizations .of (context);
87
136
// No messages were in the range of the request.
88
137
// This should be impossible given that `foundNewest` was false
89
138
// (and that our `numAfter` was positive.)
90
139
showErrorDialog (context: context,
91
- title: zulipLocalizations.errorMarkAsReadFailedTitle ,
140
+ title: onFailedTitle ,
92
141
message: zulipLocalizations.errorInvalidResponse);
93
- return ;
142
+ return false ;
94
143
}
95
144
anchor = NumericAnchor (result.lastProcessedId! );
96
145
97
146
// 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.
100
147
// TODO: Ideally we'd have a progress widget here that showed up based
101
148
// on actual time elapsed -- so it could appear before the first
102
149
// batch returns, if that takes a while -- and that then stuck
@@ -109,19 +156,14 @@ Future<void> markNarrowAsRead(BuildContext context, Narrow narrow) async {
109
156
// is better for now if we allow them to run their timer through
110
157
// and clear the backlog later.
111
158
scaffoldMessenger.showSnackBar (SnackBar (behavior: SnackBarBehavior .floating,
112
- content: Text (zulipLocalizations.markAsReadInProgress )));
159
+ content: Text (progressMessage )));
113
160
}
114
161
} catch (e) {
115
- if (! context.mounted) return ;
116
- final zulipLocalizations = ZulipLocalizations .of (context);
162
+ if (! context.mounted) return false ;
117
163
showErrorDialog (context: context,
118
- title: zulipLocalizations.errorMarkAsReadFailedTitle ,
164
+ title: onFailedTitle ,
119
165
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 ;
125
167
}
126
168
}
127
169
0 commit comments