1
1
import 'dart:async' ;
2
- import 'dart:collection' ;
3
2
import 'dart:convert' ;
4
3
4
+ import 'package:collection/collection.dart' ;
5
5
import 'package:crypto/crypto.dart' ;
6
6
import 'package:flutter/foundation.dart' ;
7
7
8
+ import '../api/exception.dart' ;
8
9
import '../api/model/events.dart' ;
9
10
import '../api/model/model.dart' ;
10
11
import '../api/route/messages.dart' ;
@@ -28,6 +29,8 @@ mixin MessageStore {
28
29
void registerMessageList (MessageListView view);
29
30
void unregisterMessageList (MessageListView view);
30
31
32
+ void markReadFromScroll (Iterable <int > messageIds);
33
+
31
34
Future <void > sendMessage ({
32
35
required MessageDestination destination,
33
36
required String content,
@@ -180,6 +183,67 @@ class MessageStoreImpl extends PerAccountStoreBase with MessageStore, _OutboxMes
180
183
_disposed = true ;
181
184
}
182
185
186
+ static const _markReadOnScrollBatchSize = 1000 ;
187
+ static const _markReadOnScrollDebounceDuration = Duration (milliseconds: 500 );
188
+ final _markReadOnScrollQueue = _MarkReadOnScrollQueue ();
189
+ bool _markReadOnScrollBusy = false ;
190
+
191
+ /// Returns true on success, false on failure.
192
+ Future <bool > _sendMarkReadOnScrollRequest (List <int > toSend) async {
193
+ assert (toSend.isNotEmpty);
194
+
195
+ // TODO(#1581) mark as read locally for latency compensation
196
+ // (in Unreads and on the message objects)
197
+ try {
198
+ await updateMessageFlags (connection,
199
+ messages: toSend,
200
+ op: UpdateMessageFlagsOp .add,
201
+ flag: MessageFlag .read);
202
+ } on ApiRequestException {
203
+ // TODO(#1581) un-mark as read locally?
204
+ return false ;
205
+ }
206
+ return true ;
207
+ }
208
+
209
+ @override
210
+ void markReadFromScroll (Iterable <int > messageIds) async {
211
+ assert (! _disposed);
212
+ _markReadOnScrollQueue.addAll (messageIds);
213
+ if (_markReadOnScrollBusy) return ;
214
+
215
+ _markReadOnScrollBusy = true ;
216
+ try {
217
+ do {
218
+ final toSend = < int > [];
219
+ int numFromQueue = 0 ;
220
+ for (final messageId in _markReadOnScrollQueue.iterable) {
221
+ if (toSend.length == _markReadOnScrollBatchSize) {
222
+ break ;
223
+ }
224
+ final message = messages[messageId];
225
+ if (message != null && ! message.flags.contains (MessageFlag .read)) {
226
+ toSend.add (message.id);
227
+ }
228
+ numFromQueue++ ;
229
+ }
230
+
231
+ if (toSend.isEmpty || await _sendMarkReadOnScrollRequest (toSend)) {
232
+ if (_disposed) return ;
233
+ _markReadOnScrollQueue.removeFirstN (numFromQueue);
234
+ }
235
+ if (_disposed) return ;
236
+
237
+ await Future <void >.delayed (_markReadOnScrollDebounceDuration);
238
+ if (_disposed) return ;
239
+ } while (_markReadOnScrollQueue.isNotEmpty);
240
+ } finally {
241
+ if (! _disposed) {
242
+ _markReadOnScrollBusy = false ;
243
+ }
244
+ }
245
+ }
246
+
183
247
@override
184
248
Future <void > sendMessage ({required MessageDestination destination, required String content}) {
185
249
assert (! _disposed);
@@ -517,6 +581,34 @@ class MessageStoreImpl extends PerAccountStoreBase with MessageStore, _OutboxMes
517
581
}
518
582
}
519
583
584
+ class _MarkReadOnScrollQueue {
585
+ _MarkReadOnScrollQueue ();
586
+
587
+ bool get isNotEmpty => _queue.isNotEmpty;
588
+
589
+ final _set = < int > {};
590
+ final _queue = QueueList <int >();
591
+
592
+ /// Add [messageIds] to the end of the queue,
593
+ /// if they aren't already in the queue.
594
+ void addAll (Iterable <int > messageIds) {
595
+ for (final messageId in messageIds) {
596
+ if (_set.add (messageId)) {
597
+ _queue.add (messageId);
598
+ }
599
+ }
600
+ }
601
+
602
+ Iterable <int > get iterable => _queue;
603
+
604
+ void removeFirstN (int n) {
605
+ for (int i = 0 ; i < n; i++ ) {
606
+ if (_queue.isEmpty) break ;
607
+ _set.remove (_queue.removeFirst ());
608
+ }
609
+ }
610
+ }
611
+
520
612
/// The duration an outbox message stays hidden to the user.
521
613
///
522
614
/// See [OutboxMessageState.waiting] .
0 commit comments