fix(llc): fix message order when sent quickly.
Signed-off-by: xsahil03x <[email protected]>
This commit is contained in:
@@ -7,6 +7,7 @@ import 'package:rxdart/rxdart.dart';
|
|||||||
import 'package:stream_chat/src/client/retry_queue.dart';
|
import 'package:stream_chat/src/client/retry_queue.dart';
|
||||||
import 'package:stream_chat/src/core/util/utils.dart';
|
import 'package:stream_chat/src/core/util/utils.dart';
|
||||||
import 'package:stream_chat/stream_chat.dart';
|
import 'package:stream_chat/stream_chat.dart';
|
||||||
|
import 'package:synchronized/synchronized.dart';
|
||||||
|
|
||||||
/// The maximum time the incoming [Event.typingStart] event is valid before a
|
/// The maximum time the incoming [Event.typingStart] event is valid before a
|
||||||
/// [Event.typingStop] event is emitted automatically.
|
/// [Event.typingStop] event is emitted automatically.
|
||||||
@@ -563,6 +564,8 @@ class Channel {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final _sendMessageLock = Lock();
|
||||||
|
|
||||||
/// Send a [message] to this channel.
|
/// Send a [message] to this channel.
|
||||||
///
|
///
|
||||||
/// If [skipPush] is true the message will not send a push notification.
|
/// If [skipPush] is true the message will not send a push notification.
|
||||||
@@ -586,7 +589,7 @@ class Channel {
|
|||||||
);
|
);
|
||||||
// ignore: parameter_assignments
|
// ignore: parameter_assignments
|
||||||
message = message.copyWith(
|
message = message.copyWith(
|
||||||
createdAt: message.createdAt,
|
localCreatedAt: DateTime.now(),
|
||||||
user: _client.state.currentUser,
|
user: _client.state.currentUser,
|
||||||
quotedMessage: quotedMessage,
|
quotedMessage: quotedMessage,
|
||||||
status: MessageSendingStatus.sending,
|
status: MessageSendingStatus.sending,
|
||||||
@@ -615,14 +618,21 @@ class Channel {
|
|||||||
message = await attachmentsUploadCompleter.future;
|
message = await attachmentsUploadCompleter.future;
|
||||||
}
|
}
|
||||||
|
|
||||||
final response = await _client.sendMessage(
|
// Wait for the previous sendMessage call to finish. Otherwise, the order
|
||||||
message,
|
// of messages will not be maintained.
|
||||||
id!,
|
final response = await _sendMessageLock.synchronized(
|
||||||
type,
|
() => _client.sendMessage(
|
||||||
skipPush: skipPush,
|
message,
|
||||||
skipEnrichUrl: skipEnrichUrl,
|
id!,
|
||||||
|
type,
|
||||||
|
skipPush: skipPush,
|
||||||
|
skipEnrichUrl: skipEnrichUrl,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
state!.updateMessage(response.message);
|
|
||||||
|
final sentMessage = response.message.syncWith(message);
|
||||||
|
|
||||||
|
state!.updateMessage(sentMessage);
|
||||||
if (cooldown > 0) cooldownStartedAt = DateTime.now();
|
if (cooldown > 0) cooldownStartedAt = DateTime.now();
|
||||||
return response;
|
return response;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -633,6 +643,8 @@ class Channel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final _updateMessageLock = Lock();
|
||||||
|
|
||||||
/// Updates the [message] in this channel.
|
/// Updates the [message] in this channel.
|
||||||
///
|
///
|
||||||
/// Waits for a [_messageAttachmentsUploadCompleter] to complete
|
/// Waits for a [_messageAttachmentsUploadCompleter] to complete
|
||||||
@@ -652,7 +664,7 @@ class Channel {
|
|||||||
// ignore: parameter_assignments
|
// ignore: parameter_assignments
|
||||||
message = message.copyWith(
|
message = message.copyWith(
|
||||||
status: MessageSendingStatus.updating,
|
status: MessageSendingStatus.updating,
|
||||||
updatedAt: message.updatedAt,
|
localUpdatedAt: DateTime.now(),
|
||||||
attachments: message.attachments.map(
|
attachments: message.attachments.map(
|
||||||
(it) {
|
(it) {
|
||||||
if (it.uploadState.isSuccess) return it;
|
if (it.uploadState.isSuccess) return it;
|
||||||
@@ -678,16 +690,20 @@ class Channel {
|
|||||||
message = await attachmentsUploadCompleter.future;
|
message = await attachmentsUploadCompleter.future;
|
||||||
}
|
}
|
||||||
|
|
||||||
final response = await _client.updateMessage(
|
// Wait for the previous update call to finish. Otherwise, the order of
|
||||||
message,
|
// messages will not be maintained.
|
||||||
skipEnrichUrl: skipEnrichUrl,
|
final response = await _updateMessageLock.synchronized(
|
||||||
|
() => _client.updateMessage(
|
||||||
|
message,
|
||||||
|
skipEnrichUrl: skipEnrichUrl,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
final m = response.message.copyWith(
|
final updatedMessage = response.message
|
||||||
ownReactions: message.ownReactions,
|
.syncWith(message)
|
||||||
);
|
.copyWith(ownReactions: message.ownReactions);
|
||||||
|
|
||||||
state?.updateMessage(m);
|
state?.updateMessage(updatedMessage);
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -714,16 +730,20 @@ class Channel {
|
|||||||
bool skipEnrichUrl = false,
|
bool skipEnrichUrl = false,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final response = await _client.partialUpdateMessage(
|
// Wait for the previous update call to finish. Otherwise, the order of
|
||||||
message.id,
|
// messages will not be maintained.
|
||||||
set: set,
|
final response = await _updateMessageLock.synchronized(
|
||||||
unset: unset,
|
() => _client.partialUpdateMessage(
|
||||||
skipEnrichUrl: skipEnrichUrl,
|
message.id,
|
||||||
|
set: set,
|
||||||
|
unset: unset,
|
||||||
|
skipEnrichUrl: skipEnrichUrl,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
final updatedMessage = response.message.copyWith(
|
final updatedMessage = response.message
|
||||||
ownReactions: message.ownReactions,
|
.syncWith(message)
|
||||||
);
|
.copyWith(ownReactions: message.ownReactions);
|
||||||
|
|
||||||
state?.updateMessage(updatedMessage);
|
state?.updateMessage(updatedMessage);
|
||||||
|
|
||||||
@@ -736,6 +756,8 @@ class Channel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final _deleteMessageLock = Lock();
|
||||||
|
|
||||||
/// Deletes the [message] from the channel.
|
/// Deletes the [message] from the channel.
|
||||||
Future<EmptyResponse> deleteMessage(Message message, {bool? hard}) async {
|
Future<EmptyResponse> deleteMessage(Message message, {bool? hard}) async {
|
||||||
final hardDelete = hard ?? false;
|
final hardDelete = hard ?? false;
|
||||||
@@ -746,7 +768,7 @@ class Channel {
|
|||||||
state!.deleteMessage(
|
state!.deleteMessage(
|
||||||
message.copyWith(
|
message.copyWith(
|
||||||
type: 'deleted',
|
type: 'deleted',
|
||||||
deletedAt: message.deletedAt ?? DateTime.now(),
|
localDeletedAt: DateTime.now(),
|
||||||
status: MessageSendingStatus.sent,
|
status: MessageSendingStatus.sent,
|
||||||
),
|
),
|
||||||
hardDelete: hardDelete,
|
hardDelete: hardDelete,
|
||||||
@@ -770,13 +792,18 @@ class Channel {
|
|||||||
|
|
||||||
state?.deleteMessage(message, hardDelete: hardDelete);
|
state?.deleteMessage(message, hardDelete: hardDelete);
|
||||||
|
|
||||||
final response = await _client.deleteMessage(message.id, hard: hard);
|
// Wait for the previous delete call to finish. Otherwise, the order of
|
||||||
|
// messages will not be maintained.
|
||||||
state?.deleteMessage(
|
final response = await _deleteMessageLock.synchronized(
|
||||||
message.copyWith(status: MessageSendingStatus.sent),
|
() => _client.deleteMessage(message.id, hard: hard),
|
||||||
hardDelete: hardDelete,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final deletedMessage = message.copyWith(
|
||||||
|
status: MessageSendingStatus.sent,
|
||||||
|
);
|
||||||
|
|
||||||
|
state?.deleteMessage(deletedMessage, hardDelete: hardDelete);
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e is StreamChatNetworkError && e.isRetriable) {
|
if (e is StreamChatNetworkError && e.isRetriable) {
|
||||||
@@ -1942,8 +1969,9 @@ class ChannelClientState {
|
|||||||
)
|
)
|
||||||
.listen((event) {
|
.listen((event) {
|
||||||
final message = event.message!;
|
final message = event.message!;
|
||||||
if (isUpToDate ||
|
final showInChannel =
|
||||||
(message.parentId != null && message.showInChannel != true)) {
|
message.parentId != null && message.showInChannel != true;
|
||||||
|
if (isUpToDate || showInChannel) {
|
||||||
updateMessage(message);
|
updateMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1960,10 +1988,10 @@ class ChannelClientState {
|
|||||||
var newMessages = [...messages];
|
var newMessages = [...messages];
|
||||||
final oldIndex = newMessages.indexWhere((m) => m.id == message.id);
|
final oldIndex = newMessages.indexWhere((m) => m.id == message.id);
|
||||||
if (oldIndex != -1) {
|
if (oldIndex != -1) {
|
||||||
var updatedMessage = message;
|
final oldMessage = newMessages[oldIndex];
|
||||||
|
var updatedMessage = message.syncWith(oldMessage);
|
||||||
// Add quoted message to the message if it is not present.
|
// Add quoted message to the message if it is not present.
|
||||||
if (message.quotedMessageId != null && message.quotedMessage == null) {
|
if (message.quotedMessageId != null && message.quotedMessage == null) {
|
||||||
final oldMessage = newMessages[oldIndex];
|
|
||||||
updatedMessage = updatedMessage.copyWith(
|
updatedMessage = updatedMessage.copyWith(
|
||||||
quotedMessage: oldMessage.quotedMessage,
|
quotedMessage: oldMessage.quotedMessage,
|
||||||
);
|
);
|
||||||
@@ -1980,7 +2008,7 @@ class ChannelClientState {
|
|||||||
return it.copyWith(
|
return it.copyWith(
|
||||||
quotedMessage: updatedMessage.copyWith(
|
quotedMessage: updatedMessage.copyWith(
|
||||||
type: 'deleted',
|
type: 'deleted',
|
||||||
deletedAt: updatedMessage.deletedAt ?? DateTime.now(),
|
deletedAt: DateTime.now(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}).toList();
|
}).toList();
|
||||||
@@ -2004,7 +2032,7 @@ class ChannelClientState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_channelState = _channelState.copyWith(
|
_channelState = _channelState.copyWith(
|
||||||
messages: newMessages..sort(_sortByCreatedAt),
|
messages: newMessages.sorted(_sortByCreatedAt),
|
||||||
pinnedMessages: newPinnedMessages,
|
pinnedMessages: newPinnedMessages,
|
||||||
channel: _channelState.channel?.copyWith(
|
channel: _channelState.channel?.copyWith(
|
||||||
lastMessageAt: message.createdAt,
|
lastMessageAt: message.createdAt,
|
||||||
@@ -2226,7 +2254,7 @@ class ChannelClientState {
|
|||||||
...newThreads[parentId]!.where(
|
...newThreads[parentId]!.where(
|
||||||
(newMessage) => !messages.any((m) => m.id == newMessage.id),
|
(newMessage) => !messages.any((m) => m.id == newMessage.id),
|
||||||
),
|
),
|
||||||
]..sort(_sortByCreatedAt);
|
].sorted(_sortByCreatedAt);
|
||||||
} else {
|
} else {
|
||||||
newThreads[parentId] = messages;
|
newThreads[parentId] = messages;
|
||||||
}
|
}
|
||||||
@@ -2245,15 +2273,10 @@ class ChannelClientState {
|
|||||||
|
|
||||||
/// Update channelState with updated information.
|
/// Update channelState with updated information.
|
||||||
void updateChannelState(ChannelState updatedState) {
|
void updateChannelState(ChannelState updatedState) {
|
||||||
final _existingStateMessages = _channelState.messages ?? [];
|
final _existingStateMessages = [...messages];
|
||||||
final _updatedStateMessages = updatedState.messages ?? [];
|
|
||||||
final newMessages = <Message>[
|
final newMessages = <Message>[
|
||||||
..._updatedStateMessages,
|
..._existingStateMessages.merge(updatedState.messages),
|
||||||
..._existingStateMessages
|
].sorted(_sortByCreatedAt);
|
||||||
.where((m) =>
|
|
||||||
!_updatedStateMessages.any((newMessage) => newMessage.id == m.id))
|
|
||||||
.toList(),
|
|
||||||
]..sort(_sortByCreatedAt);
|
|
||||||
|
|
||||||
final _existingStateWatchers = _channelState.watchers ?? [];
|
final _existingStateWatchers = _channelState.watchers ?? [];
|
||||||
final _updatedStateWatchers = updatedState.watchers ?? [];
|
final _updatedStateWatchers = updatedState.watchers ?? [];
|
||||||
@@ -2308,6 +2331,7 @@ class ChannelClientState {
|
|||||||
final Debounce _debouncedUpdatePersistenceChannelState;
|
final Debounce _debouncedUpdatePersistenceChannelState;
|
||||||
|
|
||||||
set _channelState(ChannelState v) {
|
set _channelState(ChannelState v) {
|
||||||
|
print('State: ${StackTrace.current}');
|
||||||
_channelStateController.add(v);
|
_channelStateController.add(v);
|
||||||
_debouncedUpdatePersistenceChannelState.call([v]);
|
_debouncedUpdatePersistenceChannelState.call([v]);
|
||||||
}
|
}
|
||||||
@@ -2471,3 +2495,21 @@ bool _pinIsValid(Message message) {
|
|||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
return message.pinExpires!.isAfter(now);
|
return message.pinExpires!.isAfter(now);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension on Iterable<Message> {
|
||||||
|
Iterable<Message> merge(Iterable<Message>? other) {
|
||||||
|
if (other == null) return this;
|
||||||
|
|
||||||
|
final messageMap = {for (final message in this) message.id: message};
|
||||||
|
|
||||||
|
for (final message in other) {
|
||||||
|
messageMap.update(
|
||||||
|
message.id,
|
||||||
|
message.syncWith,
|
||||||
|
ifAbsent: () => message,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return messageMap.values;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import 'package:stream_chat/src/event_type.dart';
|
|||||||
import 'package:stream_chat/src/ws/connection_status.dart';
|
import 'package:stream_chat/src/ws/connection_status.dart';
|
||||||
import 'package:stream_chat/src/ws/websocket.dart';
|
import 'package:stream_chat/src/ws/websocket.dart';
|
||||||
import 'package:stream_chat/version.dart';
|
import 'package:stream_chat/version.dart';
|
||||||
import 'package:synchronized/extension.dart';
|
import 'package:synchronized/synchronized.dart';
|
||||||
|
|
||||||
/// Handler function used for logging records. Function requires a single
|
/// Handler function used for logging records. Function requires a single
|
||||||
/// [LogRecord] as the only parameter.
|
/// [LogRecord] as the only parameter.
|
||||||
@@ -526,10 +526,13 @@ class StreamChatClient {
|
|||||||
event.type == eventType4);
|
event.type == eventType4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Lock to make sure only one sync process is running at a time.
|
||||||
|
final _syncLock = Lock();
|
||||||
|
|
||||||
/// Get the events missed while offline to sync the offline storage
|
/// Get the events missed while offline to sync the offline storage
|
||||||
/// Will automatically fetch [cids] and [lastSyncedAt] if [persistenceEnabled]
|
/// Will automatically fetch [cids] and [lastSyncedAt] if [persistenceEnabled]
|
||||||
Future<void> sync({List<String>? cids, DateTime? lastSyncAt}) {
|
Future<void> sync({List<String>? cids, DateTime? lastSyncAt}) {
|
||||||
return synchronized(() async {
|
return _syncLock.synchronized(() async {
|
||||||
final channels = cids ?? await chatPersistenceClient?.getChannelCids();
|
final channels = cids ?? await chatPersistenceClient?.getChannelCids();
|
||||||
if (channels == null || channels.isEmpty) {
|
if (channels == null || channels.isEmpty) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -64,8 +64,11 @@ class Message extends Equatable {
|
|||||||
this.showInChannel,
|
this.showInChannel,
|
||||||
this.command,
|
this.command,
|
||||||
DateTime? createdAt,
|
DateTime? createdAt,
|
||||||
|
this.localCreatedAt,
|
||||||
DateTime? updatedAt,
|
DateTime? updatedAt,
|
||||||
this.deletedAt,
|
this.localUpdatedAt,
|
||||||
|
DateTime? deletedAt,
|
||||||
|
this.localDeletedAt,
|
||||||
this.user,
|
this.user,
|
||||||
this.pinned = false,
|
this.pinned = false,
|
||||||
this.pinnedAt,
|
this.pinnedAt,
|
||||||
@@ -76,8 +79,9 @@ class Message extends Equatable {
|
|||||||
this.i18n,
|
this.i18n,
|
||||||
}) : id = id ?? const Uuid().v4(),
|
}) : id = id ?? const Uuid().v4(),
|
||||||
pinExpires = pinExpires?.toUtc(),
|
pinExpires = pinExpires?.toUtc(),
|
||||||
_createdAt = createdAt,
|
remoteCreatedAt = createdAt,
|
||||||
_updatedAt = updatedAt,
|
remoteUpdatedAt = updatedAt,
|
||||||
|
remoteDeletedAt = deletedAt,
|
||||||
_quotedMessageId = quotedMessageId;
|
_quotedMessageId = quotedMessageId;
|
||||||
|
|
||||||
/// Create a new instance from JSON.
|
/// Create a new instance from JSON.
|
||||||
@@ -161,21 +165,49 @@ class Message extends Equatable {
|
|||||||
@JsonKey(includeToJson: false)
|
@JsonKey(includeToJson: false)
|
||||||
final String? command;
|
final String? command;
|
||||||
|
|
||||||
final DateTime? _createdAt;
|
/// Indicates when the message was created.
|
||||||
|
///
|
||||||
/// Reserved field indicating when the message was deleted.
|
/// Returns the latest between [localCreatedAt] and [remoteCreatedAt].
|
||||||
|
/// If both are null, returns [DateTime.now].
|
||||||
@JsonKey(includeToJson: false)
|
@JsonKey(includeToJson: false)
|
||||||
final DateTime? deletedAt;
|
DateTime get createdAt => localCreatedAt ?? remoteCreatedAt ?? DateTime.now();
|
||||||
|
|
||||||
/// Reserved field indicating when the message was created.
|
/// Indicates when the message was created locally.
|
||||||
|
@JsonKey(includeToJson: false, includeFromJson: false)
|
||||||
|
final DateTime? localCreatedAt;
|
||||||
|
|
||||||
|
/// Indicates when the message was created on the server.
|
||||||
|
@JsonKey(includeToJson: false, includeFromJson: false)
|
||||||
|
final DateTime? remoteCreatedAt;
|
||||||
|
|
||||||
|
/// Indicates when the message was updated last time.
|
||||||
|
///
|
||||||
|
/// Returns the latest between [localUpdatedAt] and [remoteUpdatedAt].
|
||||||
|
/// If both are null, returns [createdAt].
|
||||||
@JsonKey(includeToJson: false)
|
@JsonKey(includeToJson: false)
|
||||||
DateTime get createdAt => _createdAt ?? DateTime.now();
|
DateTime get updatedAt => localUpdatedAt ?? remoteUpdatedAt ?? createdAt;
|
||||||
|
|
||||||
final DateTime? _updatedAt;
|
/// Indicates when the message was updated locally.
|
||||||
|
@JsonKey(includeToJson: false, includeFromJson: false)
|
||||||
|
final DateTime? localUpdatedAt;
|
||||||
|
|
||||||
/// Reserved field indicating when the message was updated last time.
|
/// Indicates when the message was updated on the server.
|
||||||
|
@JsonKey(includeToJson: false, includeFromJson: false)
|
||||||
|
final DateTime? remoteUpdatedAt;
|
||||||
|
|
||||||
|
/// Indicates when the message was deleted.
|
||||||
|
///
|
||||||
|
/// Returns the latest between [localDeletedAt] and [remoteDeletedAt].
|
||||||
@JsonKey(includeToJson: false)
|
@JsonKey(includeToJson: false)
|
||||||
DateTime get updatedAt => _updatedAt ?? DateTime.now();
|
DateTime? get deletedAt => localDeletedAt ?? remoteDeletedAt;
|
||||||
|
|
||||||
|
/// Indicates when the message was deleted locally.
|
||||||
|
@JsonKey(includeToJson: false, includeFromJson: false)
|
||||||
|
final DateTime? localDeletedAt;
|
||||||
|
|
||||||
|
/// Indicates when the message was deleted on the server.
|
||||||
|
@JsonKey(includeToJson: false, includeFromJson: false)
|
||||||
|
final DateTime? remoteDeletedAt;
|
||||||
|
|
||||||
/// User who sent the message.
|
/// User who sent the message.
|
||||||
@JsonKey(includeToJson: false)
|
@JsonKey(includeToJson: false)
|
||||||
@@ -273,8 +305,11 @@ class Message extends Equatable {
|
|||||||
bool? showInChannel,
|
bool? showInChannel,
|
||||||
String? command,
|
String? command,
|
||||||
DateTime? createdAt,
|
DateTime? createdAt,
|
||||||
|
DateTime? localCreatedAt,
|
||||||
DateTime? updatedAt,
|
DateTime? updatedAt,
|
||||||
|
DateTime? localUpdatedAt,
|
||||||
DateTime? deletedAt,
|
DateTime? deletedAt,
|
||||||
|
DateTime? localDeletedAt,
|
||||||
User? user,
|
User? user,
|
||||||
bool? pinned,
|
bool? pinned,
|
||||||
DateTime? pinnedAt,
|
DateTime? pinnedAt,
|
||||||
@@ -338,9 +373,12 @@ class Message extends Equatable {
|
|||||||
threadParticipants: threadParticipants ?? this.threadParticipants,
|
threadParticipants: threadParticipants ?? this.threadParticipants,
|
||||||
showInChannel: showInChannel ?? this.showInChannel,
|
showInChannel: showInChannel ?? this.showInChannel,
|
||||||
command: command ?? this.command,
|
command: command ?? this.command,
|
||||||
createdAt: createdAt ?? _createdAt,
|
createdAt: createdAt ?? remoteCreatedAt,
|
||||||
updatedAt: updatedAt ?? _updatedAt,
|
localCreatedAt: localCreatedAt ?? this.localCreatedAt,
|
||||||
deletedAt: deletedAt ?? this.deletedAt,
|
updatedAt: updatedAt ?? remoteUpdatedAt,
|
||||||
|
localUpdatedAt: localUpdatedAt ?? this.localUpdatedAt,
|
||||||
|
deletedAt: deletedAt ?? remoteDeletedAt,
|
||||||
|
localDeletedAt: localDeletedAt ?? this.localDeletedAt,
|
||||||
user: user ?? this.user,
|
user: user ?? this.user,
|
||||||
pinned: pinned ?? this.pinned,
|
pinned: pinned ?? this.pinned,
|
||||||
pinnedAt: pinnedAt ?? this.pinnedAt,
|
pinnedAt: pinnedAt ?? this.pinnedAt,
|
||||||
@@ -374,9 +412,12 @@ class Message extends Equatable {
|
|||||||
threadParticipants: other.threadParticipants,
|
threadParticipants: other.threadParticipants,
|
||||||
showInChannel: other.showInChannel,
|
showInChannel: other.showInChannel,
|
||||||
command: other.command,
|
command: other.command,
|
||||||
createdAt: other.createdAt,
|
createdAt: other.remoteCreatedAt,
|
||||||
updatedAt: other.updatedAt,
|
localCreatedAt: other.localCreatedAt,
|
||||||
deletedAt: other.deletedAt,
|
updatedAt: other.remoteUpdatedAt,
|
||||||
|
localUpdatedAt: other.localUpdatedAt,
|
||||||
|
deletedAt: other.remoteDeletedAt,
|
||||||
|
localDeletedAt: other.localDeletedAt,
|
||||||
user: other.user,
|
user: other.user,
|
||||||
pinned: other.pinned,
|
pinned: other.pinned,
|
||||||
pinnedAt: other.pinnedAt,
|
pinnedAt: other.pinnedAt,
|
||||||
@@ -387,6 +428,28 @@ class Message extends Equatable {
|
|||||||
i18n: other.i18n,
|
i18n: other.i18n,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// Returns a new [Message] that is [other] with local changes applied to it.
|
||||||
|
///
|
||||||
|
/// This ensures that the local sync changes are not lost when the message is
|
||||||
|
/// updated on the server.
|
||||||
|
///
|
||||||
|
/// For example, when a message is sent, it is immediately shown
|
||||||
|
/// optimistically in the UI. When the message is received from the server,
|
||||||
|
/// it will not contain the local changes. This method can be used to merge
|
||||||
|
/// the local changes back into the message.
|
||||||
|
///
|
||||||
|
/// This also helps in maintaining the order of the messages in the channel
|
||||||
|
/// when the messages are sorted by the [createdAt] field.
|
||||||
|
Message syncWith(Message? other) {
|
||||||
|
if (other == null) return this;
|
||||||
|
|
||||||
|
return copyWith(
|
||||||
|
localCreatedAt: other.localCreatedAt,
|
||||||
|
localUpdatedAt: other.localUpdatedAt,
|
||||||
|
localDeletedAt: other.localDeletedAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [
|
List<Object?> get props => [
|
||||||
id,
|
id,
|
||||||
@@ -407,8 +470,8 @@ class Message extends Equatable {
|
|||||||
shadowed,
|
shadowed,
|
||||||
silent,
|
silent,
|
||||||
command,
|
command,
|
||||||
_createdAt,
|
createdAt,
|
||||||
_updatedAt,
|
updatedAt,
|
||||||
deletedAt,
|
deletedAt,
|
||||||
user,
|
user,
|
||||||
pinned,
|
pinned,
|
||||||
|
|||||||
Reference in New Issue
Block a user