@@ -46,6 +46,16 @@ class OutboxMessage {
46
46
final MessageDestination destination;
47
47
final String content;
48
48
49
+ /// Whether the OutboxMessage will be hidden to [MessageListView] or not.
50
+ ///
51
+ /// When set to false with [unhide] , this cannot be toggle back to true again.
52
+ bool get hidden => _hidden;
53
+ bool _hidden = true ;
54
+ void unhide () {
55
+ assert (_hidden);
56
+ _hidden = false ;
57
+ }
58
+
49
59
OutboxMessageLifecycle get state => _state;
50
60
OutboxMessageLifecycle _state;
51
61
set state (OutboxMessageLifecycle value) {
@@ -148,14 +158,57 @@ class MessageStoreImpl with MessageStore {
148
158
}
149
159
150
160
@override
151
- Future <void > sendMessage ({required MessageDestination destination, required String content}) {
152
- // TODO implement outbox; see design at
153
- // https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/.23M3881.20Sending.20outbox.20messages.20is.20fraught.20with.20issues/near/1405739
154
- return _apiSendMessage (connection,
155
- destination: destination,
156
- content: content,
157
- readBySender: true ,
158
- );
161
+ Future <void > sendMessage ({required MessageDestination destination, required String content}) async {
162
+ final outboxMessage = OutboxMessage ._(destination: destination, content: content);
163
+ assert (! outboxMessages.containsKey (outboxMessage.localMessageId));
164
+ outboxMessages[outboxMessage.localMessageId] = outboxMessage;
165
+ // The need:
166
+ // hide some outbox messages;
167
+ // comply with the state diagram;
168
+ // debounce until a timeout.
169
+
170
+ // The outbox message only become visible to views after
171
+ // [kLocalEchoDebounceDuration].
172
+ Future <void >.delayed (kLocalEchoDebounceDuration, () {
173
+ if (! outboxMessages.containsKey (outboxMessage.localMessageId)) {
174
+ // The outbox message was deleted, one such reason can be that the
175
+ // corresponding "message" event arrived quickly.
176
+ return ;
177
+ }
178
+ if (! outboxMessage.hidden) {
179
+ return ;
180
+ }
181
+ outboxMessage.unhide ();
182
+ for (final view in _messageListViews) {
183
+ view.handleOutboxMessage (outboxMessage);
184
+ }
185
+ });
186
+
187
+ try {
188
+ await _apiSendMessage (connection,
189
+ destination: destination,
190
+ content: content,
191
+ readBySender: true ,
192
+ queueId: 'TODO queue_id' ,
193
+ localId: outboxMessage.localMessageId);
194
+ } catch (e) {
195
+ outboxMessage.unhide ();
196
+ _updateOutboxMessage (outboxMessage,
197
+ newState: OutboxMessageLifecycle .failed);
198
+ // TODO handle 4xx errors
199
+ rethrow ;
200
+ }
201
+ _updateOutboxMessage (outboxMessage,
202
+ newState: OutboxMessageLifecycle .sent);
203
+ }
204
+
205
+ void _updateOutboxMessage (OutboxMessage outboxMessage, {
206
+ required OutboxMessageLifecycle newState,
207
+ }) {
208
+ outboxMessage.state = newState;
209
+ for (final view in _messageListViews) {
210
+ view.notifyListenersIfOutboxMessagePresent (outboxMessage.localMessageId);
211
+ }
159
212
}
160
213
161
214
@override
@@ -193,11 +246,24 @@ class MessageStoreImpl with MessageStore {
193
246
// See [fetchedMessages] for reasoning.
194
247
messages[event.message.id] = event.message;
195
248
249
+ final localMessageId = event.localMessageId;
250
+ if (localMessageId != null ) {
251
+ _removeOutboxMessage (localMessageId: localMessageId);
252
+ }
253
+
196
254
for (final view in _messageListViews) {
197
255
view.handleMessageEvent (event);
198
256
}
199
257
}
200
258
259
+ void _removeOutboxMessage ({required int localMessageId}) {
260
+ final removed = outboxMessages.remove (localMessageId);
261
+ if (removed == null ) return ;
262
+ for (final view in _messageListViews) {
263
+ view.handleRemoveOutboxMessage (localMessageId);
264
+ }
265
+ }
266
+
201
267
void handleUpdateMessageEvent (UpdateMessageEvent event) {
202
268
assert (event.messageIds.contains (event.messageId), "See https://github.com/zulip/zulip-flutter/pull/753#discussion_r1649463633" );
203
269
_handleUpdateMessageEventTimestamp (event);
0 commit comments