diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index 6be503a3a..da34e4ca6 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -1,3 +1,10 @@ +## 6.6.0 + +🔄 Changed + +- Deprecated `Message.status` in favor of `Message.state`. +- Deprecated `RetryPolicy.retryTimeout` in favor of `RetryPolicy.delayFactor`. + ## 6.5.0 🔄 Changed diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index 9d80fcb6d..838f267c1 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -582,7 +582,7 @@ class Channel { // Eg. Updating the message while the previous call is in progress. _messageAttachmentsUploadCompleter .remove(message.id) - ?.completeError('Message Cancelled'); + ?.completeError(const StreamChatError('Message cancelled')); final quotedMessage = state!.messages.firstWhereOrNull( (m) => m.id == message.quotedMessageId, @@ -592,7 +592,7 @@ class Channel { localCreatedAt: DateTime.now(), user: _client.state.currentUser, quotedMessage: quotedMessage, - status: MessageSendingStatus.sending, + state: MessageState.sending, attachments: message.attachments.map( (it) { if (it.uploadState.isSuccess) return it; @@ -630,15 +630,24 @@ class Channel { ), ); - final sentMessage = response.message.syncWith(message); + final sentMessage = response.message.syncWith(message).copyWith( + // Update the message state to sent. + state: MessageState.sent, + ); state!.updateMessage(sentMessage); if (cooldown > 0) cooldownStartedAt = DateTime.now(); return response; } catch (e) { if (e is StreamChatNetworkError && e.isRetriable) { - state!._retryQueue.add([message]); + state!._retryQueue.add([ + message.copyWith( + // Update the message state to failed. + state: MessageState.sendingFailed, + ), + ]); } + rethrow; } } @@ -653,17 +662,18 @@ class Channel { Message message, { bool skipEnrichUrl = false, }) async { + _checkInitialized(); final originalMessage = message; // Cancelling previous completer in case it's called again in the process // Eg. Updating the message while the previous call is in progress. _messageAttachmentsUploadCompleter .remove(message.id) - ?.completeError('Message Cancelled'); + ?.completeError(const StreamChatError('Message cancelled')); // ignore: parameter_assignments message = message.copyWith( - status: MessageSendingStatus.updating, + state: MessageState.updating, localUpdatedAt: DateTime.now(), attachments: message.attachments.map( (it) { @@ -699,19 +709,30 @@ class Channel { ), ); - final updatedMessage = response.message - .syncWith(message) - .copyWith(ownReactions: message.ownReactions); + final updateMessage = response.message.syncWith(message).copyWith( + // Update the message state to updated. + state: MessageState.updated, + ownReactions: message.ownReactions, + ); - state?.updateMessage(updatedMessage); + state?.updateMessage(updateMessage); return response; } catch (e) { if (e is StreamChatNetworkError) { if (e.isRetriable) { - state!._retryQueue.add([message]); + state!._retryQueue.add([ + message.copyWith( + // Update the message state to failed. + state: MessageState.updatingFailed, + ), + ]); } else { - state?.updateMessage(originalMessage); + // Reset the message to original state if the update fails and is not + // retriable. + state?.updateMessage(originalMessage.copyWith( + state: MessageState.updatingFailed, + )); } } rethrow; @@ -729,6 +750,23 @@ class Channel { List? unset, bool skipEnrichUrl = false, }) async { + _checkInitialized(); + final originalMessage = message; + + // Cancelling previous completer in case it's called again in the process + // Eg. Updating the message while the previous call is in progress. + _messageAttachmentsUploadCompleter + .remove(message.id) + ?.completeError(const StreamChatError('Message cancelled')); + + // ignore: parameter_assignments + message = message.copyWith( + state: MessageState.updating, + localUpdatedAt: DateTime.now(), + ); + + state?.updateMessage(message); + try { // Wait for the previous update call to finish. Otherwise, the order of // messages will not be maintained. @@ -741,17 +779,33 @@ class Channel { ), ); - final updatedMessage = response.message - .syncWith(message) - .copyWith(ownReactions: message.ownReactions); + final updatedMessage = response.message.syncWith(message).copyWith( + // Update the message state to updated. + state: MessageState.updated, + ownReactions: message.ownReactions, + ); state?.updateMessage(updatedMessage); return response; } catch (e) { - if (e is StreamChatNetworkError && e.isRetriable) { - state!._retryQueue.add([message]); + if (e is StreamChatNetworkError) { + if (e.isRetriable) { + state!._retryQueue.add([ + message.copyWith( + // Update the message state to failed. + state: MessageState.updatingFailed, + ), + ]); + } else { + // Reset the message to original state if the update fails and is not + // retriable. + state?.updateMessage(originalMessage.copyWith( + state: MessageState.updatingFailed, + )); + } } + rethrow; } } @@ -759,39 +813,43 @@ class Channel { final _deleteMessageLock = Lock(); /// Deletes the [message] from the channel. - Future deleteMessage(Message message, {bool? hard}) async { - final hardDelete = hard ?? false; + Future deleteMessage( + Message message, { + bool hard = false, + }) async { + _checkInitialized(); - // Directly deleting the local messages which are not yet sent to server - if (message.status == MessageSendingStatus.sending || - message.status == MessageSendingStatus.failed) { + // Directly deleting the local messages which are not yet sent to server. + if (message.remoteCreatedAt == null) { state!.deleteMessage( message.copyWith( type: 'deleted', localDeletedAt: DateTime.now(), - status: MessageSendingStatus.sent, + state: MessageState.deleted(hard: hard), ), - hardDelete: hardDelete, + hardDelete: hard, ); // Removing the attachments upload completer to stop the `sendMessage` // waiting for attachments to complete. _messageAttachmentsUploadCompleter .remove(message.id) - ?.completeError(Exception('Message deleted')); + ?.completeError(const StreamChatError('Message deleted')); + + // Returning empty response to mark the api call as success. return EmptyResponse(); } - try { - // ignore: parameter_assignments - message = message.copyWith( - type: 'deleted', - status: MessageSendingStatus.deleting, - deletedAt: message.deletedAt ?? DateTime.now(), - ); + // ignore: parameter_assignments + message = message.copyWith( + type: 'deleted', + deletedAt: DateTime.now(), + state: MessageState.deleting(hard: hard), + ); - state?.deleteMessage(message, hardDelete: hardDelete); + state?.deleteMessage(message, hardDelete: hard); + try { // Wait for the previous delete call to finish. Otherwise, the order of // messages will not be maintained. final response = await _deleteMessageLock.synchronized( @@ -799,20 +857,42 @@ class Channel { ); final deletedMessage = message.copyWith( - status: MessageSendingStatus.sent, + state: MessageState.deleted(hard: hard), ); - state?.deleteMessage(deletedMessage, hardDelete: hardDelete); + state?.deleteMessage(deletedMessage, hardDelete: hard); return response; } catch (e) { if (e is StreamChatNetworkError && e.isRetriable) { - state!._retryQueue.add([message]); + state!._retryQueue.add([ + message.copyWith( + // Update the message state to failed. + state: MessageState.deletingFailed(hard: hard), + ), + ]); } rethrow; } } + /// Retry the operation on the message based on the failed state. + /// + /// For example, if the message failed to send, it will retry sending the + /// message and vice-versa. + Future retryMessage(Message message) async { + assert(message.state.isFailed, 'Message state is not failed'); + + return message.state.maybeWhen( + failed: (state, _) => state.when( + sendingFailed: () => sendMessage(message), + updatingFailed: () => updateMessage(message), + deletingFailed: (hard) => deleteMessage(message, hard: hard), + ), + orElse: () => throw StateError('Message state is not failed'), + ); + } + /// Pins provided message Future pinMessage( Message message, { @@ -1895,15 +1975,7 @@ class ChannelClientState { /// Retry failed message. Future retryFailedMessages() async { final failedMessages = [...messages, ...threads.values.expand((v) => v)] - .where( - (message) => - message.status != MessageSendingStatus.sent && - message.createdAt.isBefore( - DateTime.now().subtract(const Duration(seconds: 5)), - ), - ) - .toList(); - + .where((it) => it.state.isFailed); _retryQueue.add(failedMessages); } diff --git a/packages/stream_chat/lib/src/client/client.dart b/packages/stream_chat/lib/src/client/client.dart index ec6443e97..9fc995312 100644 --- a/packages/stream_chat/lib/src/client/client.dart +++ b/packages/stream_chat/lib/src/client/client.dart @@ -109,8 +109,9 @@ class StreamChatClient { _retryPolicy = retryPolicy ?? RetryPolicy( - shouldRetry: (_, attempt, __) => attempt < 5, - retryTimeout: (_, attempt, __) => Duration(seconds: attempt), + shouldRetry: (_, __, error) { + return error is StreamChatNetworkError && error.isRetriable; + }, ); state = ClientState(this); @@ -1397,12 +1398,19 @@ class StreamChatClient { ); /// Deletes the given message - Future deleteMessage(String messageId, {bool? hard}) async { - final response = - await _chatApi.message.deleteMessage(messageId, hard: hard); - if (hard == true) { + Future deleteMessage( + String messageId, { + bool hard = false, + }) async { + final response = await _chatApi.message.deleteMessage( + messageId, + hard: hard, + ); + + if (hard) { await chatPersistenceClient?.deleteMessageById(messageId); } + return response; } diff --git a/packages/stream_chat/lib/src/client/retry_policy.dart b/packages/stream_chat/lib/src/client/retry_policy.dart index 5b7812abb..f086d44b4 100644 --- a/packages/stream_chat/lib/src/client/retry_policy.dart +++ b/packages/stream_chat/lib/src/client/retry_policy.dart @@ -1,27 +1,54 @@ +import 'dart:async'; + import 'package:stream_chat/src/client/client.dart'; import 'package:stream_chat/src/core/error/error.dart'; -/// The retry options -/// When sending/updating/deleting a message any temporary error will trigger the retry policy -/// The retry policy exposes 2 methods -/// - shouldRetry: returns a boolean if the request should be retried -/// - retryTimeout: How many milliseconds to wait till the next attempt +/// The retry policy associated to a client. +/// +/// This policy is used to determine if a request should be retried and when. /// -/// maxRetryAttempts is a hard limit on maximum retry attempts before giving up +/// also see: +/// - [RetryQueue] class RetryPolicy { /// Instantiate a new RetryPolicy RetryPolicy({ required this.shouldRetry, - required this.retryTimeout, + @Deprecated("Use 'delayFactor' instead.") this.retryTimeout, this.maxRetryAttempts = 6, + this.delayFactor = const Duration(milliseconds: 200), + this.randomizationFactor = 0.25, + this.maxDelay = const Duration(seconds: 30), }); - /// Hard limit on maximum retry attempts before giving up, defaults to 6 - /// Resets once connection recovers. + /// Delay factor to double after every attempt. + /// + /// Defaults to 200 ms, which results in the following delays: + /// + /// 1. 400 ms + /// 2. 800 ms + /// 3. 1600 ms + /// 4. 3200 ms + /// 5. 6400 ms + /// 6. 12800 ms + /// + /// Before application of [randomizationFactor]. + final Duration delayFactor; + + /// Percentage the delay should be randomized, given as fraction between + /// 0 and 1. + /// + /// If [randomizationFactor] is `0.25` (default) this indicates 25 % of the + /// delay should be increased or decreased by 25 %. + final double randomizationFactor; + + /// Maximum delay between retries, defaults to 30 seconds. + final Duration maxDelay; + + /// Maximum number of attempts before giving up, defaults to 6. final int maxRetryAttempts; - /// This function evaluates if we should retry the failure - final bool Function( + /// Function to determine if a retry should be attempted. + final FutureOr Function( StreamChatClient client, int attempt, StreamChatError? error, @@ -29,9 +56,10 @@ class RetryPolicy { /// In the case that we want to retry a failed request the retryTimeout /// method is called to determine the timeout + @Deprecated("Use 'delayFactor' instead.") final Duration Function( StreamChatClient client, int attempt, StreamChatError? error, - ) retryTimeout; + )? retryTimeout; } diff --git a/packages/stream_chat/lib/src/client/retry_queue.dart b/packages/stream_chat/lib/src/client/retry_queue.dart index 388dda575..43ad314d5 100644 --- a/packages/stream_chat/lib/src/client/retry_queue.dart +++ b/packages/stream_chat/lib/src/client/retry_queue.dart @@ -14,7 +14,7 @@ class RetryQueue { }) : client = channel.client { _retryPolicy = client.retryPolicy; _listenConnectionRecovered(); - _listenFailedEvents(); + _listenMessageEvents(); } /// The channel of this queue. @@ -31,153 +31,94 @@ class RetryQueue { final _compositeSubscription = CompositeSubscription(); final _messageQueue = HeapPriorityQueue(_byDate); - bool _isRetrying = false; void _listenConnectionRecovered() { - client.on(EventType.connectionRecovered).listen((event) { + client.on(EventType.connectionRecovered).distinct().listen((event) { if (event.online == true) { - _startRetrying(); + logger?.info('Connection recovered, retrying failed messages'); + channel.state?.retryFailedMessages(); } }).addTo(_compositeSubscription); } - void _listenFailedEvents() { + void _listenMessageEvents() { channel.on().where((event) => event.message != null).listen((event) { final message = event.message!; final containsMessage = _messageQueue.containsMessage(message); if (!containsMessage) return; - if (message.status == MessageSendingStatus.sent) { + + if (message.state.isCompleted) { logger?.info('Removing sent message from queue : ${message.id}'); - _messageQueue.removeMessage(message); - return; - } else { - if ([ - MessageSendingStatus.failed_update, - MessageSendingStatus.failed, - MessageSendingStatus.failed_delete, - ].contains(message.status)) { - logger?.info('Adding failed message from event : ${event.type}'); - add([message]); - } + return _messageQueue.removeMessage(message); } }).addTo(_compositeSubscription); } /// Add a list of messages. - void add(List messages) { - if (messages.isEmpty) return; - if (!_messageQueue.containsAllMessage(messages)) { - logger?.info('Adding ${messages.length} messages'); - final messageList = _messageQueue.toList(); - // we should not add message if already available in the queue - _messageQueue.addAll(messages.where( - (it) => !messageList.any((m) => m.id == it.id), - )); - } + void add(Iterable messages) { + assert( + messages.every((it) => it.state.isFailed), + 'Only failed messages can be added to the queue', + ); + + // Filter out messages that are already in the queue. + final messagesToAdd = messages.where((it) { + return !_messageQueue.containsMessage(it); + }); - _startRetrying(); + // If there are no messages to add, return. + if (messagesToAdd.isEmpty) return; + + logger?.info('Adding ${messagesToAdd.length} messages to the queue'); + _messageQueue.addAll(messagesToAdd); + + _processQueue(); } - Future _startRetrying() async { - if (_isRetrying) return; - _isRetrying = true; + bool _isProcessing = false; + + Future _processQueue() async { + if (_isProcessing) return; + _isProcessing = true; logger?.info('Started retrying failed messages'); while (_messageQueue.isNotEmpty) { logger?.info('${_messageQueue.length} messages remaining in the queue'); - final message = _messageQueue.first; - final succeeded = await _runAndRetry(message); - if (!succeeded) { - _messageQueue.toList().forEach(_sendFailedEvent); - break; - } - } - _isRetrying = false; - } - - Future _runAndRetry(Message message) async { - var attempt = 1; - - final maxAttempt = _retryPolicy.maxRetryAttempts; - // early return in case maxAttempt is less than 0 - if (attempt > maxAttempt) return false; - // ignore: literal_only_boolean_expressions - while (true) { + final message = _messageQueue.first; + final retryPolicy = _retryPolicy; try { - logger?.info('Message (${message.id}) retry attempt $attempt'); - await _retryMessage(message); - logger?.info('Message (${message.id}) sent successfully'); - _messageQueue.removeMessage(message); - return true; - } catch (e) { - if (e is! StreamChatNetworkError || !e.isRetriable) { - _messageQueue.removeMessage(message); - _sendFailedEvent(message); - return true; - } - // retry logic - final maxAttempt = _retryPolicy.maxRetryAttempts; - if (attempt < maxAttempt) { - final shouldRetry = _retryPolicy.shouldRetry(client, attempt, e); - if (shouldRetry) { - final timeout = _retryPolicy.retryTimeout(client, attempt, e); - // temporary failure, continue - logger?.info( - 'API call failed (attempt $attempt), ' - 'retrying in ${timeout.inSeconds} seconds. Error was $e', - ); - await Future.delayed(timeout); - attempt += 1; - } else { - logger?.info( - 'API call failed (attempt $attempt). ' - 'Giving up for now, will retry when connection recovers. ' - 'Error was $e', - ); - _sendFailedEvent(message); - break; - } - } else { - logger?.info( - 'API call failed (attempt $attempt). ' - 'Exceeds maxRetryAttempt : $maxAttempt ' - 'Giving up for now, will retry when connection recovers. ' - 'Error was $e', - ); - _sendFailedEvent(message); - break; - } + await backOff( + () => channel.retryMessage(message), + delayFactor: retryPolicy.delayFactor, + randomizationFactor: retryPolicy.randomizationFactor, + maxDelay: retryPolicy.maxDelay, + maxAttempts: retryPolicy.maxRetryAttempts, + retryIf: (error, attempt) { + if (error is! StreamChatError) return false; + return retryPolicy.shouldRetry(client, attempt, error); + }, + ); + } catch (error) { + logger?.severe('Error while retrying message ${message.id}', error); + // If we are unable to successfully retry the message, update the state + // with the failed state. + channel.state?.updateMessage(message); + } finally { + // remove the message from the queue after it's handled. + _messageQueue.removeFirst(); } } - return false; - } - - void _sendFailedEvent(Message message) { - final newStatus = message.status == MessageSendingStatus.sending - ? MessageSendingStatus.failed - : message.status == MessageSendingStatus.updating - ? MessageSendingStatus.failed_update - : MessageSendingStatus.failed_delete; - channel.state?.updateMessage(message.copyWith(status: newStatus)); - } - Future _retryMessage(Message message) async { - if (message.status == MessageSendingStatus.failed_update || - message.status == MessageSendingStatus.updating) { - await channel.updateMessage(message); - } else if (message.status == MessageSendingStatus.failed || - message.status == MessageSendingStatus.sending) { - await channel.sendMessage(message); - } else if (message.status == MessageSendingStatus.failed_delete || - message.status == MessageSendingStatus.deleting) { - await channel.deleteMessage(message); - } + _isProcessing = false; } /// Whether our [_messageQueue] has messages or not. bool get hasMessages => _messageQueue.isNotEmpty; + /// Returns true if the queue contains the given [message]. + bool contains(Message message) => _messageQueue.containsMessage(message); + /// Call this method to dispose this object. void dispose() { _messageQueue.clear(); @@ -188,33 +129,25 @@ class RetryQueue { final date1 = _getMessageDate(m1); final date2 = _getMessageDate(m2); - if (date1 == null || date2 == null) { - return 0; - } - + if (date1 == null && date2 == null) return 0; + if (date1 == null) return -1; + if (date2 == null) return 1; return date1.compareTo(date2); } - static DateTime? _getMessageDate(Message m1) { - switch (m1.status) { - case MessageSendingStatus.failed_delete: - case MessageSendingStatus.deleting: - return m1.deletedAt; - - case MessageSendingStatus.failed: - case MessageSendingStatus.sending: - return m1.createdAt; - - case MessageSendingStatus.failed_update: - case MessageSendingStatus.updating: - return m1.updatedAt; - default: - return null; - } + static DateTime? _getMessageDate(Message message) { + return message.state.maybeWhen( + failed: (state, _) => state.when( + sendingFailed: () => message.createdAt, + updatingFailed: () => message.updatedAt, + deletingFailed: (_) => message.deletedAt, + ), + orElse: () => null, + ); } } -extension _MessageHeapPriorityQueue on HeapPriorityQueue { +extension on HeapPriorityQueue { void removeMessage(Message message) { final list = toUnorderedList(); final index = list.indexWhere((it) => it.id == message.id); @@ -229,11 +162,4 @@ extension _MessageHeapPriorityQueue on HeapPriorityQueue { if (index == -1) return false; return true; } - - bool containsAllMessage(List messages) { - if (isEmpty) return false; - final list = toUnorderedList(); - final messageIds = messages.map((it) => it.id); - return list.every((it) => messageIds.contains(it.id)); - } } diff --git a/packages/stream_chat/lib/src/core/models/message.dart b/packages/stream_chat/lib/src/core/models/message.dart index 59491c5c4..aa872f117 100644 --- a/packages/stream_chat/lib/src/core/models/message.dart +++ b/packages/stream_chat/lib/src/core/models/message.dart @@ -1,6 +1,7 @@ import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:stream_chat/src/core/models/attachment.dart'; +import 'package:stream_chat/src/core/models/message_state.dart'; import 'package:stream_chat/src/core/models/reaction.dart'; import 'package:stream_chat/src/core/models/user.dart'; import 'package:stream_chat/src/core/util/serializer.dart'; @@ -37,7 +38,45 @@ enum MessageSendingStatus { failed_delete, /// Message correctly sent - sent, + sent; + + /// Returns a [MessageState] from a [MessageSendingStatus] + MessageState toMessageState() { + switch (this) { + case MessageSendingStatus.sending: + return MessageState.sending; + case MessageSendingStatus.updating: + return MessageState.updating; + case MessageSendingStatus.deleting: + return MessageState.softDeleting; + case MessageSendingStatus.failed: + return MessageState.sendingFailed; + case MessageSendingStatus.failed_update: + return MessageState.updatingFailed; + case MessageSendingStatus.failed_delete: + return MessageState.softDeletingFailed; + case MessageSendingStatus.sent: + return MessageState.sent; + } + } + + /// Returns a [MessageSendingStatus] from a [MessageState]. + static MessageSendingStatus fromMessageState(MessageState state) { + return state.when( + initial: () => MessageSendingStatus.sending, + outgoing: (it) => it.when( + sending: () => MessageSendingStatus.sending, + updating: () => MessageSendingStatus.updating, + deleting: (_) => MessageSendingStatus.deleting, + ), + completed: (_) => MessageSendingStatus.sent, + failed: (it, __) => it.when( + sendingFailed: () => MessageSendingStatus.failed, + updatingFailed: () => MessageSendingStatus.failed_update, + deletingFailed: (_) => MessageSendingStatus.failed_delete, + ), + ); + } } /// The class that contains the information about a message. @@ -75,21 +114,39 @@ class Message extends Equatable { DateTime? pinExpires, this.pinnedBy, this.extraData = const {}, - this.status = MessageSendingStatus.sending, + @Deprecated('Use `state` instead') MessageSendingStatus? status, + MessageState? state, this.i18n, }) : id = id ?? const Uuid().v4(), pinExpires = pinExpires?.toUtc(), remoteCreatedAt = createdAt, remoteUpdatedAt = updatedAt, remoteDeletedAt = deletedAt, - _quotedMessageId = quotedMessageId; + _quotedMessageId = quotedMessageId { + var messageState = state ?? const MessageState.initial(); + // Backward compatibility. TODO: Remove in the next major version + if (status != null) { + messageState = status.toMessageState(); + } + + this.state = messageState; + } /// Create a new instance from JSON. - factory Message.fromJson(Map json) => _$MessageFromJson( - Serializer.moveToExtraDataFromRoot(json, topLevelFields), - ).copyWith( - status: MessageSendingStatus.sent, - ); + factory Message.fromJson(Map json) { + final message = _$MessageFromJson( + Serializer.moveToExtraDataFromRoot(json, topLevelFields), + ); + + var state = MessageState.sent; + if (message.deletedAt != null) { + state = MessageState.softDeleted; + } else if (message.updatedAt.isAfter(message.createdAt)) { + state = MessageState.updated; + } + + return message.copyWith(state: state); + } /// The message ID. This is either created by Stream or set client side when /// the message is added. @@ -99,8 +156,16 @@ class Message extends Equatable { final String? text; /// The status of a sending message. + @Deprecated('Use `state` instead') @JsonKey(includeFromJson: false, includeToJson: false) - final MessageSendingStatus status; + MessageSendingStatus get status { + return MessageSendingStatus.fromMessageState(state); + } + + // TODO: Remove late modifier in the next major version. + /// The current state of the message. + @JsonKey(includeFromJson: false, includeToJson: false) + late final MessageState state; /// The message type. @JsonKey(includeToJson: false) @@ -316,7 +381,8 @@ class Message extends Equatable { Object? pinExpires = _nullConst, User? pinnedBy, Map? extraData, - MessageSendingStatus? status, + @Deprecated('Use `state` instead') MessageSendingStatus? status, + MessageState? state, Map? i18n, }) { assert(() { @@ -350,6 +416,8 @@ class Message extends Equatable { return true; }(), 'Validate type for quotedMessage'); + final messageState = state ?? status?.toMessageState(); + return Message( id: id ?? this.id, text: text ?? this.text, @@ -386,47 +454,49 @@ class Message extends Equatable { pinExpires == _nullConst ? this.pinExpires : pinExpires as DateTime?, pinnedBy: pinnedBy ?? this.pinnedBy, extraData: extraData ?? this.extraData, - status: status ?? this.status, + state: messageState ?? this.state, i18n: i18n ?? this.i18n, ); } /// Returns a new [Message] that is a combination of this message and the /// given [other] message. - Message merge(Message other) => copyWith( - id: other.id, - text: other.text, - type: other.type, - attachments: other.attachments, - mentionedUsers: other.mentionedUsers, - silent: other.silent, - shadowed: other.shadowed, - reactionCounts: other.reactionCounts, - reactionScores: other.reactionScores, - latestReactions: other.latestReactions, - ownReactions: other.ownReactions, - parentId: other.parentId, - quotedMessage: other.quotedMessage, - quotedMessageId: other.quotedMessageId, - replyCount: other.replyCount, - threadParticipants: other.threadParticipants, - showInChannel: other.showInChannel, - command: other.command, - createdAt: other.remoteCreatedAt, - localCreatedAt: other.localCreatedAt, - updatedAt: other.remoteUpdatedAt, - localUpdatedAt: other.localUpdatedAt, - deletedAt: other.remoteDeletedAt, - localDeletedAt: other.localDeletedAt, - user: other.user, - pinned: other.pinned, - pinnedAt: other.pinnedAt, - pinExpires: other.pinExpires, - pinnedBy: other.pinnedBy, - extraData: other.extraData, - status: other.status, - i18n: other.i18n, - ); + Message merge(Message other) { + return copyWith( + id: other.id, + text: other.text, + type: other.type, + attachments: other.attachments, + mentionedUsers: other.mentionedUsers, + silent: other.silent, + shadowed: other.shadowed, + reactionCounts: other.reactionCounts, + reactionScores: other.reactionScores, + latestReactions: other.latestReactions, + ownReactions: other.ownReactions, + parentId: other.parentId, + quotedMessage: other.quotedMessage, + quotedMessageId: other.quotedMessageId, + replyCount: other.replyCount, + threadParticipants: other.threadParticipants, + showInChannel: other.showInChannel, + command: other.command, + createdAt: other.remoteCreatedAt, + localCreatedAt: other.localCreatedAt, + updatedAt: other.remoteUpdatedAt, + localUpdatedAt: other.localUpdatedAt, + deletedAt: other.remoteDeletedAt, + localDeletedAt: other.localDeletedAt, + user: other.user, + pinned: other.pinned, + pinnedAt: other.pinnedAt, + pinExpires: other.pinExpires, + pinnedBy: other.pinnedBy, + extraData: other.extraData, + state: other.state, + i18n: other.i18n, + ); + } /// Returns a new [Message] that is [other] with local changes applied to it. /// @@ -482,7 +552,7 @@ class Message extends Equatable { pinExpires, pinnedBy, extraData, - status, + state, i18n, ]; } diff --git a/packages/stream_chat/lib/src/core/models/message_state.dart b/packages/stream_chat/lib/src/core/models/message_state.dart new file mode 100644 index 000000000..642924519 --- /dev/null +++ b/packages/stream_chat/lib/src/core/models/message_state.dart @@ -0,0 +1,299 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'message_state.freezed.dart'; + +part 'message_state.g.dart'; + +/// Helper extension for [MessageState]. +extension MessageStateX on MessageState { + /// Returns true if the message is in initial state. + bool get isInitial => this is MessageInitial; + + /// Returns true if the message is in outgoing state. + bool get isOutgoing => this is MessageOutgoing; + + /// Returns true if the message is in completed state. + bool get isCompleted => this is MessageCompleted; + + /// Returns true if the message is in failed state. + bool get isFailed => this is MessageFailed; + + /// Returns true if the message is in outgoing sending state. + bool get isSending { + final messageState = this; + return messageState is MessageOutgoing && messageState.state is Sending; + } + + /// Returns true if the message is in outgoing updating state. + bool get isUpdating { + final messageState = this; + return messageState is MessageOutgoing && messageState.state is Updating; + } + + /// Returns true if the message is in outgoing deleting state. + bool get isDeleting => isSoftDeleting || isHardDeleting; + + /// Returns true if the message is in outgoing soft deleting state. + bool get isSoftDeleting { + final messageState = this; + if (messageState is! MessageOutgoing) return false; + + final outgoingState = messageState.state; + if (outgoingState is! Deleting) return false; + + return !outgoingState.hard; + } + + /// Returns true if the message is in outgoing hard deleting state. + bool get isHardDeleting { + final messageState = this; + if (messageState is! MessageOutgoing) return false; + + final outgoingState = messageState.state; + if (outgoingState is! Deleting) return false; + + return outgoingState.hard; + } + + /// Returns true if the message is in completed sent state. + bool get isSent { + final messageState = this; + return messageState is MessageCompleted && messageState.state is Sent; + } + + /// Returns true if the message is in completed updated state. + bool get isUpdated { + final messageState = this; + return messageState is MessageCompleted && messageState.state is Updated; + } + + /// Returns true if the message is in completed deleted state. + bool get isDeleted => isSoftDeleted || isHardDeleted; + + /// Returns true if the message is in completed soft deleted state. + bool get isSoftDeleted { + final messageState = this; + if (messageState is! MessageCompleted) return false; + + final completedState = messageState.state; + if (completedState is! Deleted) return false; + + return !completedState.hard; + } + + /// Returns true if the message is in completed hard deleted state. + bool get isHardDeleted { + final messageState = this; + if (messageState is! MessageCompleted) return false; + + final completedState = messageState.state; + if (completedState is! Deleted) return false; + + return completedState.hard; + } + + /// Returns true if the message is in failed sending state. + bool get isSendingFailed { + final messageState = this; + if (messageState is! MessageFailed) return false; + return messageState.state is SendingFailed; + } + + /// Returns true if the message is in failed updating state. + bool get isUpdatingFailed { + final messageState = this; + if (messageState is! MessageFailed) return false; + return messageState.state is UpdatingFailed; + } + + /// Returns true if the message is in failed deleting state. + bool get isDeletingFailed => isSoftDeletingFailed || isHardDeletingFailed; + + /// Returns true if the message is in failed soft deleting state. + bool get isSoftDeletingFailed { + final messageState = this; + if (messageState is! MessageFailed) return false; + + final failedState = messageState.state; + if (failedState is! DeletingFailed) return false; + + return !failedState.hard; + } + + /// Returns true if the message is in failed hard deleting state. + bool get isHardDeletingFailed { + final messageState = this; + if (messageState is! MessageFailed) return false; + + final failedState = messageState.state; + if (failedState is! DeletingFailed) return false; + + return failedState.hard; + } +} + +/// Represents the various states a message can be in. +@freezed +class MessageState with _$MessageState { + /// Initial state when the message is created. + const factory MessageState.initial() = MessageInitial; + + /// Outgoing state when the message is being sent, updated, or deleted. + const factory MessageState.outgoing({ + required OutgoingState state, + }) = MessageOutgoing; + + /// Completed state when the message has been successfully sent, updated, or + /// deleted. + const factory MessageState.completed({ + required CompletedState state, + }) = MessageCompleted; + + /// Failed state when the message fails to be sent, updated, or deleted. + const factory MessageState.failed({ + required FailedState state, + Object? reason, + }) = MessageFailed; + + /// Creates a new instance from a json + factory MessageState.fromJson(Map json) => + _$MessageStateFromJson(json); + + /// Deleting state when the message is being deleted. + factory MessageState.deleting({required bool hard}) { + return MessageState.outgoing( + state: OutgoingState.deleting(hard: hard), + ); + } + + /// Deleting state when the message has been successfully deleted. + factory MessageState.deleted({required bool hard}) { + return MessageState.completed( + state: CompletedState.deleted(hard: hard), + ); + } + + /// Deleting failed state when the message fails to be deleted. + factory MessageState.deletingFailed({required bool hard}) { + return MessageState.failed( + state: FailedState.deletingFailed(hard: hard), + ); + } + + /// Sending state when the message is being sent. + static const sending = MessageState.outgoing( + state: OutgoingState.sending(), + ); + + /// Updating state when the message is being updated. + static const updating = MessageState.outgoing( + state: OutgoingState.updating(), + ); + + /// Deleting state when the message is being soft deleted. + static const softDeleting = MessageState.outgoing( + state: OutgoingState.deleting(), + ); + + /// Hard deleting state when the message is being hard deleted. + static const hardDeleting = MessageState.outgoing( + state: OutgoingState.deleting(hard: true), + ); + + /// Sent state when the message has been successfully sent. + static const sent = MessageState.completed( + state: CompletedState.sent(), + ); + + /// Updated state when the message has been successfully updated. + static const updated = MessageState.completed( + state: CompletedState.updated(), + ); + + /// Deleted state when the message has been successfully soft deleted. + static const softDeleted = MessageState.completed( + state: CompletedState.deleted(), + ); + + /// Hard deleted state when the message has been successfully hard deleted. + static const hardDeleted = MessageState.completed( + state: CompletedState.deleted(hard: true), + ); + + /// Sending failed state when the message fails to be sent. + static const sendingFailed = MessageState.failed( + state: FailedState.sendingFailed(), + ); + + /// Updating failed state when the message fails to be updated. + static const updatingFailed = MessageState.failed( + state: FailedState.updatingFailed(), + ); + + /// Deleting failed state when the message fails to be soft deleted. + static const softDeletingFailed = MessageState.failed( + state: FailedState.deletingFailed(), + ); + + /// Hard deleting failed state when the message fails to be hard deleted. + static const hardDeletingFailed = MessageState.failed( + state: FailedState.deletingFailed(hard: true), + ); +} + +/// Represents the state of an outgoing message. +@freezed +class OutgoingState with _$OutgoingState { + /// Sending state when the message is being sent. + const factory OutgoingState.sending() = Sending; + + /// Updating state when the message is being updated. + const factory OutgoingState.updating() = Updating; + + /// Deleting state when the message is being deleted. + const factory OutgoingState.deleting({ + @Default(false) bool hard, + }) = Deleting; + + /// Creates a new instance from a json + factory OutgoingState.fromJson(Map json) => + _$OutgoingStateFromJson(json); +} + +/// Represents the completed state of a message. +@freezed +class CompletedState with _$CompletedState { + /// Sent state when the message has been successfully sent. + const factory CompletedState.sent() = Sent; + + /// Updated state when the message has been successfully updated. + const factory CompletedState.updated() = Updated; + + /// Deleted state when the message has been successfully deleted. + const factory CompletedState.deleted({ + @Default(false) bool hard, + }) = Deleted; + + /// Creates a new instance from a json + factory CompletedState.fromJson(Map json) => + _$CompletedStateFromJson(json); +} + +/// Represents the failed state of a message. +@freezed +class FailedState with _$FailedState { + /// Sending failed state when the message fails to be sent. + const factory FailedState.sendingFailed() = SendingFailed; + + /// Updating failed state when the message fails to be updated. + const factory FailedState.updatingFailed() = UpdatingFailed; + + /// Deleting failed state when the message fails to be deleted. + const factory FailedState.deletingFailed({ + @Default(false) bool hard, + }) = DeletingFailed; + + /// Creates a new instance from a json + factory FailedState.fromJson(Map json) => + _$FailedStateFromJson(json); +} diff --git a/packages/stream_chat/lib/src/core/models/message_state.freezed.dart b/packages/stream_chat/lib/src/core/models/message_state.freezed.dart new file mode 100644 index 000000000..5096839d9 --- /dev/null +++ b/packages/stream_chat/lib/src/core/models/message_state.freezed.dart @@ -0,0 +1,2221 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'message_state.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +MessageState _$MessageStateFromJson(Map json) { + switch (json['runtimeType']) { + case 'initial': + return MessageInitial.fromJson(json); + case 'outgoing': + return MessageOutgoing.fromJson(json); + case 'completed': + return MessageCompleted.fromJson(json); + case 'failed': + return MessageFailed.fromJson(json); + + default: + throw CheckedFromJsonException(json, 'runtimeType', 'MessageState', + 'Invalid union type "${json['runtimeType']}"!'); + } +} + +/// @nodoc +mixin _$MessageState { + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function(OutgoingState state) outgoing, + required TResult Function(CompletedState state) completed, + required TResult Function(FailedState state, Object? reason) failed, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function(OutgoingState state)? outgoing, + TResult? Function(CompletedState state)? completed, + TResult? Function(FailedState state, Object? reason)? failed, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function(OutgoingState state)? outgoing, + TResult Function(CompletedState state)? completed, + TResult Function(FailedState state, Object? reason)? failed, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(MessageInitial value) initial, + required TResult Function(MessageOutgoing value) outgoing, + required TResult Function(MessageCompleted value) completed, + required TResult Function(MessageFailed value) failed, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(MessageInitial value)? initial, + TResult? Function(MessageOutgoing value)? outgoing, + TResult? Function(MessageCompleted value)? completed, + TResult? Function(MessageFailed value)? failed, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(MessageInitial value)? initial, + TResult Function(MessageOutgoing value)? outgoing, + TResult Function(MessageCompleted value)? completed, + TResult Function(MessageFailed value)? failed, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + Map toJson() => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $MessageStateCopyWith<$Res> { + factory $MessageStateCopyWith( + MessageState value, $Res Function(MessageState) then) = + _$MessageStateCopyWithImpl<$Res, MessageState>; +} + +/// @nodoc +class _$MessageStateCopyWithImpl<$Res, $Val extends MessageState> + implements $MessageStateCopyWith<$Res> { + _$MessageStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; +} + +/// @nodoc +abstract class _$$MessageInitialCopyWith<$Res> { + factory _$$MessageInitialCopyWith( + _$MessageInitial value, $Res Function(_$MessageInitial) then) = + __$$MessageInitialCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$MessageInitialCopyWithImpl<$Res> + extends _$MessageStateCopyWithImpl<$Res, _$MessageInitial> + implements _$$MessageInitialCopyWith<$Res> { + __$$MessageInitialCopyWithImpl( + _$MessageInitial _value, $Res Function(_$MessageInitial) _then) + : super(_value, _then); +} + +/// @nodoc +@JsonSerializable() +class _$MessageInitial implements MessageInitial { + const _$MessageInitial({final String? $type}) : $type = $type ?? 'initial'; + + factory _$MessageInitial.fromJson(Map json) => + _$$MessageInitialFromJson(json); + + @JsonKey(name: 'runtimeType') + final String $type; + + @override + String toString() { + return 'MessageState.initial()'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$MessageInitial); + } + + @JsonKey(ignore: true) + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function(OutgoingState state) outgoing, + required TResult Function(CompletedState state) completed, + required TResult Function(FailedState state, Object? reason) failed, + }) { + return initial(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function(OutgoingState state)? outgoing, + TResult? Function(CompletedState state)? completed, + TResult? Function(FailedState state, Object? reason)? failed, + }) { + return initial?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function(OutgoingState state)? outgoing, + TResult Function(CompletedState state)? completed, + TResult Function(FailedState state, Object? reason)? failed, + required TResult orElse(), + }) { + if (initial != null) { + return initial(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(MessageInitial value) initial, + required TResult Function(MessageOutgoing value) outgoing, + required TResult Function(MessageCompleted value) completed, + required TResult Function(MessageFailed value) failed, + }) { + return initial(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(MessageInitial value)? initial, + TResult? Function(MessageOutgoing value)? outgoing, + TResult? Function(MessageCompleted value)? completed, + TResult? Function(MessageFailed value)? failed, + }) { + return initial?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(MessageInitial value)? initial, + TResult Function(MessageOutgoing value)? outgoing, + TResult Function(MessageCompleted value)? completed, + TResult Function(MessageFailed value)? failed, + required TResult orElse(), + }) { + if (initial != null) { + return initial(this); + } + return orElse(); + } + + @override + Map toJson() { + return _$$MessageInitialToJson( + this, + ); + } +} + +abstract class MessageInitial implements MessageState { + const factory MessageInitial() = _$MessageInitial; + + factory MessageInitial.fromJson(Map json) = + _$MessageInitial.fromJson; +} + +/// @nodoc +abstract class _$$MessageOutgoingCopyWith<$Res> { + factory _$$MessageOutgoingCopyWith( + _$MessageOutgoing value, $Res Function(_$MessageOutgoing) then) = + __$$MessageOutgoingCopyWithImpl<$Res>; + @useResult + $Res call({OutgoingState state}); + + $OutgoingStateCopyWith<$Res> get state; +} + +/// @nodoc +class __$$MessageOutgoingCopyWithImpl<$Res> + extends _$MessageStateCopyWithImpl<$Res, _$MessageOutgoing> + implements _$$MessageOutgoingCopyWith<$Res> { + __$$MessageOutgoingCopyWithImpl( + _$MessageOutgoing _value, $Res Function(_$MessageOutgoing) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? state = null, + }) { + return _then(_$MessageOutgoing( + state: null == state + ? _value.state + : state // ignore: cast_nullable_to_non_nullable + as OutgoingState, + )); + } + + @override + @pragma('vm:prefer-inline') + $OutgoingStateCopyWith<$Res> get state { + return $OutgoingStateCopyWith<$Res>(_value.state, (value) { + return _then(_value.copyWith(state: value)); + }); + } +} + +/// @nodoc +@JsonSerializable() +class _$MessageOutgoing implements MessageOutgoing { + const _$MessageOutgoing({required this.state, final String? $type}) + : $type = $type ?? 'outgoing'; + + factory _$MessageOutgoing.fromJson(Map json) => + _$$MessageOutgoingFromJson(json); + + @override + final OutgoingState state; + + @JsonKey(name: 'runtimeType') + final String $type; + + @override + String toString() { + return 'MessageState.outgoing(state: $state)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$MessageOutgoing && + (identical(other.state, state) || other.state == state)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, state); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$MessageOutgoingCopyWith<_$MessageOutgoing> get copyWith => + __$$MessageOutgoingCopyWithImpl<_$MessageOutgoing>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function(OutgoingState state) outgoing, + required TResult Function(CompletedState state) completed, + required TResult Function(FailedState state, Object? reason) failed, + }) { + return outgoing(state); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function(OutgoingState state)? outgoing, + TResult? Function(CompletedState state)? completed, + TResult? Function(FailedState state, Object? reason)? failed, + }) { + return outgoing?.call(state); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function(OutgoingState state)? outgoing, + TResult Function(CompletedState state)? completed, + TResult Function(FailedState state, Object? reason)? failed, + required TResult orElse(), + }) { + if (outgoing != null) { + return outgoing(state); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(MessageInitial value) initial, + required TResult Function(MessageOutgoing value) outgoing, + required TResult Function(MessageCompleted value) completed, + required TResult Function(MessageFailed value) failed, + }) { + return outgoing(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(MessageInitial value)? initial, + TResult? Function(MessageOutgoing value)? outgoing, + TResult? Function(MessageCompleted value)? completed, + TResult? Function(MessageFailed value)? failed, + }) { + return outgoing?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(MessageInitial value)? initial, + TResult Function(MessageOutgoing value)? outgoing, + TResult Function(MessageCompleted value)? completed, + TResult Function(MessageFailed value)? failed, + required TResult orElse(), + }) { + if (outgoing != null) { + return outgoing(this); + } + return orElse(); + } + + @override + Map toJson() { + return _$$MessageOutgoingToJson( + this, + ); + } +} + +abstract class MessageOutgoing implements MessageState { + const factory MessageOutgoing({required final OutgoingState state}) = + _$MessageOutgoing; + + factory MessageOutgoing.fromJson(Map json) = + _$MessageOutgoing.fromJson; + + OutgoingState get state; + @JsonKey(ignore: true) + _$$MessageOutgoingCopyWith<_$MessageOutgoing> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$MessageCompletedCopyWith<$Res> { + factory _$$MessageCompletedCopyWith( + _$MessageCompleted value, $Res Function(_$MessageCompleted) then) = + __$$MessageCompletedCopyWithImpl<$Res>; + @useResult + $Res call({CompletedState state}); + + $CompletedStateCopyWith<$Res> get state; +} + +/// @nodoc +class __$$MessageCompletedCopyWithImpl<$Res> + extends _$MessageStateCopyWithImpl<$Res, _$MessageCompleted> + implements _$$MessageCompletedCopyWith<$Res> { + __$$MessageCompletedCopyWithImpl( + _$MessageCompleted _value, $Res Function(_$MessageCompleted) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? state = null, + }) { + return _then(_$MessageCompleted( + state: null == state + ? _value.state + : state // ignore: cast_nullable_to_non_nullable + as CompletedState, + )); + } + + @override + @pragma('vm:prefer-inline') + $CompletedStateCopyWith<$Res> get state { + return $CompletedStateCopyWith<$Res>(_value.state, (value) { + return _then(_value.copyWith(state: value)); + }); + } +} + +/// @nodoc +@JsonSerializable() +class _$MessageCompleted implements MessageCompleted { + const _$MessageCompleted({required this.state, final String? $type}) + : $type = $type ?? 'completed'; + + factory _$MessageCompleted.fromJson(Map json) => + _$$MessageCompletedFromJson(json); + + @override + final CompletedState state; + + @JsonKey(name: 'runtimeType') + final String $type; + + @override + String toString() { + return 'MessageState.completed(state: $state)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$MessageCompleted && + (identical(other.state, state) || other.state == state)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, state); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$MessageCompletedCopyWith<_$MessageCompleted> get copyWith => + __$$MessageCompletedCopyWithImpl<_$MessageCompleted>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function(OutgoingState state) outgoing, + required TResult Function(CompletedState state) completed, + required TResult Function(FailedState state, Object? reason) failed, + }) { + return completed(state); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function(OutgoingState state)? outgoing, + TResult? Function(CompletedState state)? completed, + TResult? Function(FailedState state, Object? reason)? failed, + }) { + return completed?.call(state); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function(OutgoingState state)? outgoing, + TResult Function(CompletedState state)? completed, + TResult Function(FailedState state, Object? reason)? failed, + required TResult orElse(), + }) { + if (completed != null) { + return completed(state); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(MessageInitial value) initial, + required TResult Function(MessageOutgoing value) outgoing, + required TResult Function(MessageCompleted value) completed, + required TResult Function(MessageFailed value) failed, + }) { + return completed(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(MessageInitial value)? initial, + TResult? Function(MessageOutgoing value)? outgoing, + TResult? Function(MessageCompleted value)? completed, + TResult? Function(MessageFailed value)? failed, + }) { + return completed?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(MessageInitial value)? initial, + TResult Function(MessageOutgoing value)? outgoing, + TResult Function(MessageCompleted value)? completed, + TResult Function(MessageFailed value)? failed, + required TResult orElse(), + }) { + if (completed != null) { + return completed(this); + } + return orElse(); + } + + @override + Map toJson() { + return _$$MessageCompletedToJson( + this, + ); + } +} + +abstract class MessageCompleted implements MessageState { + const factory MessageCompleted({required final CompletedState state}) = + _$MessageCompleted; + + factory MessageCompleted.fromJson(Map json) = + _$MessageCompleted.fromJson; + + CompletedState get state; + @JsonKey(ignore: true) + _$$MessageCompletedCopyWith<_$MessageCompleted> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$MessageFailedCopyWith<$Res> { + factory _$$MessageFailedCopyWith( + _$MessageFailed value, $Res Function(_$MessageFailed) then) = + __$$MessageFailedCopyWithImpl<$Res>; + @useResult + $Res call({FailedState state, Object? reason}); + + $FailedStateCopyWith<$Res> get state; +} + +/// @nodoc +class __$$MessageFailedCopyWithImpl<$Res> + extends _$MessageStateCopyWithImpl<$Res, _$MessageFailed> + implements _$$MessageFailedCopyWith<$Res> { + __$$MessageFailedCopyWithImpl( + _$MessageFailed _value, $Res Function(_$MessageFailed) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? state = null, + Object? reason = freezed, + }) { + return _then(_$MessageFailed( + state: null == state + ? _value.state + : state // ignore: cast_nullable_to_non_nullable + as FailedState, + reason: freezed == reason ? _value.reason : reason, + )); + } + + @override + @pragma('vm:prefer-inline') + $FailedStateCopyWith<$Res> get state { + return $FailedStateCopyWith<$Res>(_value.state, (value) { + return _then(_value.copyWith(state: value)); + }); + } +} + +/// @nodoc +@JsonSerializable() +class _$MessageFailed implements MessageFailed { + const _$MessageFailed({required this.state, this.reason, final String? $type}) + : $type = $type ?? 'failed'; + + factory _$MessageFailed.fromJson(Map json) => + _$$MessageFailedFromJson(json); + + @override + final FailedState state; + @override + final Object? reason; + + @JsonKey(name: 'runtimeType') + final String $type; + + @override + String toString() { + return 'MessageState.failed(state: $state, reason: $reason)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$MessageFailed && + (identical(other.state, state) || other.state == state) && + const DeepCollectionEquality().equals(other.reason, reason)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, state, const DeepCollectionEquality().hash(reason)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$MessageFailedCopyWith<_$MessageFailed> get copyWith => + __$$MessageFailedCopyWithImpl<_$MessageFailed>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function(OutgoingState state) outgoing, + required TResult Function(CompletedState state) completed, + required TResult Function(FailedState state, Object? reason) failed, + }) { + return failed(state, reason); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function(OutgoingState state)? outgoing, + TResult? Function(CompletedState state)? completed, + TResult? Function(FailedState state, Object? reason)? failed, + }) { + return failed?.call(state, reason); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function(OutgoingState state)? outgoing, + TResult Function(CompletedState state)? completed, + TResult Function(FailedState state, Object? reason)? failed, + required TResult orElse(), + }) { + if (failed != null) { + return failed(state, reason); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(MessageInitial value) initial, + required TResult Function(MessageOutgoing value) outgoing, + required TResult Function(MessageCompleted value) completed, + required TResult Function(MessageFailed value) failed, + }) { + return failed(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(MessageInitial value)? initial, + TResult? Function(MessageOutgoing value)? outgoing, + TResult? Function(MessageCompleted value)? completed, + TResult? Function(MessageFailed value)? failed, + }) { + return failed?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(MessageInitial value)? initial, + TResult Function(MessageOutgoing value)? outgoing, + TResult Function(MessageCompleted value)? completed, + TResult Function(MessageFailed value)? failed, + required TResult orElse(), + }) { + if (failed != null) { + return failed(this); + } + return orElse(); + } + + @override + Map toJson() { + return _$$MessageFailedToJson( + this, + ); + } +} + +abstract class MessageFailed implements MessageState { + const factory MessageFailed( + {required final FailedState state, + final Object? reason}) = _$MessageFailed; + + factory MessageFailed.fromJson(Map json) = + _$MessageFailed.fromJson; + + FailedState get state; + Object? get reason; + @JsonKey(ignore: true) + _$$MessageFailedCopyWith<_$MessageFailed> get copyWith => + throw _privateConstructorUsedError; +} + +OutgoingState _$OutgoingStateFromJson(Map json) { + switch (json['runtimeType']) { + case 'sending': + return Sending.fromJson(json); + case 'updating': + return Updating.fromJson(json); + case 'deleting': + return Deleting.fromJson(json); + + default: + throw CheckedFromJsonException(json, 'runtimeType', 'OutgoingState', + 'Invalid union type "${json['runtimeType']}"!'); + } +} + +/// @nodoc +mixin _$OutgoingState { + @optionalTypeArgs + TResult when({ + required TResult Function() sending, + required TResult Function() updating, + required TResult Function(bool hard) deleting, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? sending, + TResult? Function()? updating, + TResult? Function(bool hard)? deleting, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? sending, + TResult Function()? updating, + TResult Function(bool hard)? deleting, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(Sending value) sending, + required TResult Function(Updating value) updating, + required TResult Function(Deleting value) deleting, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(Sending value)? sending, + TResult? Function(Updating value)? updating, + TResult? Function(Deleting value)? deleting, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(Sending value)? sending, + TResult Function(Updating value)? updating, + TResult Function(Deleting value)? deleting, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + Map toJson() => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $OutgoingStateCopyWith<$Res> { + factory $OutgoingStateCopyWith( + OutgoingState value, $Res Function(OutgoingState) then) = + _$OutgoingStateCopyWithImpl<$Res, OutgoingState>; +} + +/// @nodoc +class _$OutgoingStateCopyWithImpl<$Res, $Val extends OutgoingState> + implements $OutgoingStateCopyWith<$Res> { + _$OutgoingStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; +} + +/// @nodoc +abstract class _$$SendingCopyWith<$Res> { + factory _$$SendingCopyWith(_$Sending value, $Res Function(_$Sending) then) = + __$$SendingCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$SendingCopyWithImpl<$Res> + extends _$OutgoingStateCopyWithImpl<$Res, _$Sending> + implements _$$SendingCopyWith<$Res> { + __$$SendingCopyWithImpl(_$Sending _value, $Res Function(_$Sending) _then) + : super(_value, _then); +} + +/// @nodoc +@JsonSerializable() +class _$Sending implements Sending { + const _$Sending({final String? $type}) : $type = $type ?? 'sending'; + + factory _$Sending.fromJson(Map json) => + _$$SendingFromJson(json); + + @JsonKey(name: 'runtimeType') + final String $type; + + @override + String toString() { + return 'OutgoingState.sending()'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$Sending); + } + + @JsonKey(ignore: true) + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() sending, + required TResult Function() updating, + required TResult Function(bool hard) deleting, + }) { + return sending(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? sending, + TResult? Function()? updating, + TResult? Function(bool hard)? deleting, + }) { + return sending?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? sending, + TResult Function()? updating, + TResult Function(bool hard)? deleting, + required TResult orElse(), + }) { + if (sending != null) { + return sending(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(Sending value) sending, + required TResult Function(Updating value) updating, + required TResult Function(Deleting value) deleting, + }) { + return sending(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(Sending value)? sending, + TResult? Function(Updating value)? updating, + TResult? Function(Deleting value)? deleting, + }) { + return sending?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(Sending value)? sending, + TResult Function(Updating value)? updating, + TResult Function(Deleting value)? deleting, + required TResult orElse(), + }) { + if (sending != null) { + return sending(this); + } + return orElse(); + } + + @override + Map toJson() { + return _$$SendingToJson( + this, + ); + } +} + +abstract class Sending implements OutgoingState { + const factory Sending() = _$Sending; + + factory Sending.fromJson(Map json) = _$Sending.fromJson; +} + +/// @nodoc +abstract class _$$UpdatingCopyWith<$Res> { + factory _$$UpdatingCopyWith( + _$Updating value, $Res Function(_$Updating) then) = + __$$UpdatingCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$UpdatingCopyWithImpl<$Res> + extends _$OutgoingStateCopyWithImpl<$Res, _$Updating> + implements _$$UpdatingCopyWith<$Res> { + __$$UpdatingCopyWithImpl(_$Updating _value, $Res Function(_$Updating) _then) + : super(_value, _then); +} + +/// @nodoc +@JsonSerializable() +class _$Updating implements Updating { + const _$Updating({final String? $type}) : $type = $type ?? 'updating'; + + factory _$Updating.fromJson(Map json) => + _$$UpdatingFromJson(json); + + @JsonKey(name: 'runtimeType') + final String $type; + + @override + String toString() { + return 'OutgoingState.updating()'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$Updating); + } + + @JsonKey(ignore: true) + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() sending, + required TResult Function() updating, + required TResult Function(bool hard) deleting, + }) { + return updating(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? sending, + TResult? Function()? updating, + TResult? Function(bool hard)? deleting, + }) { + return updating?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? sending, + TResult Function()? updating, + TResult Function(bool hard)? deleting, + required TResult orElse(), + }) { + if (updating != null) { + return updating(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(Sending value) sending, + required TResult Function(Updating value) updating, + required TResult Function(Deleting value) deleting, + }) { + return updating(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(Sending value)? sending, + TResult? Function(Updating value)? updating, + TResult? Function(Deleting value)? deleting, + }) { + return updating?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(Sending value)? sending, + TResult Function(Updating value)? updating, + TResult Function(Deleting value)? deleting, + required TResult orElse(), + }) { + if (updating != null) { + return updating(this); + } + return orElse(); + } + + @override + Map toJson() { + return _$$UpdatingToJson( + this, + ); + } +} + +abstract class Updating implements OutgoingState { + const factory Updating() = _$Updating; + + factory Updating.fromJson(Map json) = _$Updating.fromJson; +} + +/// @nodoc +abstract class _$$DeletingCopyWith<$Res> { + factory _$$DeletingCopyWith( + _$Deleting value, $Res Function(_$Deleting) then) = + __$$DeletingCopyWithImpl<$Res>; + @useResult + $Res call({bool hard}); +} + +/// @nodoc +class __$$DeletingCopyWithImpl<$Res> + extends _$OutgoingStateCopyWithImpl<$Res, _$Deleting> + implements _$$DeletingCopyWith<$Res> { + __$$DeletingCopyWithImpl(_$Deleting _value, $Res Function(_$Deleting) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? hard = null, + }) { + return _then(_$Deleting( + hard: null == hard + ? _value.hard + : hard // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$Deleting implements Deleting { + const _$Deleting({this.hard = false, final String? $type}) + : $type = $type ?? 'deleting'; + + factory _$Deleting.fromJson(Map json) => + _$$DeletingFromJson(json); + + @override + @JsonKey() + final bool hard; + + @JsonKey(name: 'runtimeType') + final String $type; + + @override + String toString() { + return 'OutgoingState.deleting(hard: $hard)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$Deleting && + (identical(other.hard, hard) || other.hard == hard)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, hard); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$DeletingCopyWith<_$Deleting> get copyWith => + __$$DeletingCopyWithImpl<_$Deleting>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() sending, + required TResult Function() updating, + required TResult Function(bool hard) deleting, + }) { + return deleting(hard); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? sending, + TResult? Function()? updating, + TResult? Function(bool hard)? deleting, + }) { + return deleting?.call(hard); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? sending, + TResult Function()? updating, + TResult Function(bool hard)? deleting, + required TResult orElse(), + }) { + if (deleting != null) { + return deleting(hard); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(Sending value) sending, + required TResult Function(Updating value) updating, + required TResult Function(Deleting value) deleting, + }) { + return deleting(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(Sending value)? sending, + TResult? Function(Updating value)? updating, + TResult? Function(Deleting value)? deleting, + }) { + return deleting?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(Sending value)? sending, + TResult Function(Updating value)? updating, + TResult Function(Deleting value)? deleting, + required TResult orElse(), + }) { + if (deleting != null) { + return deleting(this); + } + return orElse(); + } + + @override + Map toJson() { + return _$$DeletingToJson( + this, + ); + } +} + +abstract class Deleting implements OutgoingState { + const factory Deleting({final bool hard}) = _$Deleting; + + factory Deleting.fromJson(Map json) = _$Deleting.fromJson; + + bool get hard; + @JsonKey(ignore: true) + _$$DeletingCopyWith<_$Deleting> get copyWith => + throw _privateConstructorUsedError; +} + +CompletedState _$CompletedStateFromJson(Map json) { + switch (json['runtimeType']) { + case 'sent': + return Sent.fromJson(json); + case 'updated': + return Updated.fromJson(json); + case 'deleted': + return Deleted.fromJson(json); + + default: + throw CheckedFromJsonException(json, 'runtimeType', 'CompletedState', + 'Invalid union type "${json['runtimeType']}"!'); + } +} + +/// @nodoc +mixin _$CompletedState { + @optionalTypeArgs + TResult when({ + required TResult Function() sent, + required TResult Function() updated, + required TResult Function(bool hard) deleted, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? sent, + TResult? Function()? updated, + TResult? Function(bool hard)? deleted, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? sent, + TResult Function()? updated, + TResult Function(bool hard)? deleted, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(Sent value) sent, + required TResult Function(Updated value) updated, + required TResult Function(Deleted value) deleted, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(Sent value)? sent, + TResult? Function(Updated value)? updated, + TResult? Function(Deleted value)? deleted, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(Sent value)? sent, + TResult Function(Updated value)? updated, + TResult Function(Deleted value)? deleted, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + Map toJson() => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CompletedStateCopyWith<$Res> { + factory $CompletedStateCopyWith( + CompletedState value, $Res Function(CompletedState) then) = + _$CompletedStateCopyWithImpl<$Res, CompletedState>; +} + +/// @nodoc +class _$CompletedStateCopyWithImpl<$Res, $Val extends CompletedState> + implements $CompletedStateCopyWith<$Res> { + _$CompletedStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; +} + +/// @nodoc +abstract class _$$SentCopyWith<$Res> { + factory _$$SentCopyWith(_$Sent value, $Res Function(_$Sent) then) = + __$$SentCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$SentCopyWithImpl<$Res> + extends _$CompletedStateCopyWithImpl<$Res, _$Sent> + implements _$$SentCopyWith<$Res> { + __$$SentCopyWithImpl(_$Sent _value, $Res Function(_$Sent) _then) + : super(_value, _then); +} + +/// @nodoc +@JsonSerializable() +class _$Sent implements Sent { + const _$Sent({final String? $type}) : $type = $type ?? 'sent'; + + factory _$Sent.fromJson(Map json) => _$$SentFromJson(json); + + @JsonKey(name: 'runtimeType') + final String $type; + + @override + String toString() { + return 'CompletedState.sent()'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$Sent); + } + + @JsonKey(ignore: true) + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() sent, + required TResult Function() updated, + required TResult Function(bool hard) deleted, + }) { + return sent(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? sent, + TResult? Function()? updated, + TResult? Function(bool hard)? deleted, + }) { + return sent?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? sent, + TResult Function()? updated, + TResult Function(bool hard)? deleted, + required TResult orElse(), + }) { + if (sent != null) { + return sent(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(Sent value) sent, + required TResult Function(Updated value) updated, + required TResult Function(Deleted value) deleted, + }) { + return sent(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(Sent value)? sent, + TResult? Function(Updated value)? updated, + TResult? Function(Deleted value)? deleted, + }) { + return sent?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(Sent value)? sent, + TResult Function(Updated value)? updated, + TResult Function(Deleted value)? deleted, + required TResult orElse(), + }) { + if (sent != null) { + return sent(this); + } + return orElse(); + } + + @override + Map toJson() { + return _$$SentToJson( + this, + ); + } +} + +abstract class Sent implements CompletedState { + const factory Sent() = _$Sent; + + factory Sent.fromJson(Map json) = _$Sent.fromJson; +} + +/// @nodoc +abstract class _$$UpdatedCopyWith<$Res> { + factory _$$UpdatedCopyWith(_$Updated value, $Res Function(_$Updated) then) = + __$$UpdatedCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$UpdatedCopyWithImpl<$Res> + extends _$CompletedStateCopyWithImpl<$Res, _$Updated> + implements _$$UpdatedCopyWith<$Res> { + __$$UpdatedCopyWithImpl(_$Updated _value, $Res Function(_$Updated) _then) + : super(_value, _then); +} + +/// @nodoc +@JsonSerializable() +class _$Updated implements Updated { + const _$Updated({final String? $type}) : $type = $type ?? 'updated'; + + factory _$Updated.fromJson(Map json) => + _$$UpdatedFromJson(json); + + @JsonKey(name: 'runtimeType') + final String $type; + + @override + String toString() { + return 'CompletedState.updated()'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$Updated); + } + + @JsonKey(ignore: true) + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() sent, + required TResult Function() updated, + required TResult Function(bool hard) deleted, + }) { + return updated(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? sent, + TResult? Function()? updated, + TResult? Function(bool hard)? deleted, + }) { + return updated?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? sent, + TResult Function()? updated, + TResult Function(bool hard)? deleted, + required TResult orElse(), + }) { + if (updated != null) { + return updated(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(Sent value) sent, + required TResult Function(Updated value) updated, + required TResult Function(Deleted value) deleted, + }) { + return updated(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(Sent value)? sent, + TResult? Function(Updated value)? updated, + TResult? Function(Deleted value)? deleted, + }) { + return updated?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(Sent value)? sent, + TResult Function(Updated value)? updated, + TResult Function(Deleted value)? deleted, + required TResult orElse(), + }) { + if (updated != null) { + return updated(this); + } + return orElse(); + } + + @override + Map toJson() { + return _$$UpdatedToJson( + this, + ); + } +} + +abstract class Updated implements CompletedState { + const factory Updated() = _$Updated; + + factory Updated.fromJson(Map json) = _$Updated.fromJson; +} + +/// @nodoc +abstract class _$$DeletedCopyWith<$Res> { + factory _$$DeletedCopyWith(_$Deleted value, $Res Function(_$Deleted) then) = + __$$DeletedCopyWithImpl<$Res>; + @useResult + $Res call({bool hard}); +} + +/// @nodoc +class __$$DeletedCopyWithImpl<$Res> + extends _$CompletedStateCopyWithImpl<$Res, _$Deleted> + implements _$$DeletedCopyWith<$Res> { + __$$DeletedCopyWithImpl(_$Deleted _value, $Res Function(_$Deleted) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? hard = null, + }) { + return _then(_$Deleted( + hard: null == hard + ? _value.hard + : hard // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$Deleted implements Deleted { + const _$Deleted({this.hard = false, final String? $type}) + : $type = $type ?? 'deleted'; + + factory _$Deleted.fromJson(Map json) => + _$$DeletedFromJson(json); + + @override + @JsonKey() + final bool hard; + + @JsonKey(name: 'runtimeType') + final String $type; + + @override + String toString() { + return 'CompletedState.deleted(hard: $hard)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$Deleted && + (identical(other.hard, hard) || other.hard == hard)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, hard); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$DeletedCopyWith<_$Deleted> get copyWith => + __$$DeletedCopyWithImpl<_$Deleted>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() sent, + required TResult Function() updated, + required TResult Function(bool hard) deleted, + }) { + return deleted(hard); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? sent, + TResult? Function()? updated, + TResult? Function(bool hard)? deleted, + }) { + return deleted?.call(hard); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? sent, + TResult Function()? updated, + TResult Function(bool hard)? deleted, + required TResult orElse(), + }) { + if (deleted != null) { + return deleted(hard); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(Sent value) sent, + required TResult Function(Updated value) updated, + required TResult Function(Deleted value) deleted, + }) { + return deleted(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(Sent value)? sent, + TResult? Function(Updated value)? updated, + TResult? Function(Deleted value)? deleted, + }) { + return deleted?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(Sent value)? sent, + TResult Function(Updated value)? updated, + TResult Function(Deleted value)? deleted, + required TResult orElse(), + }) { + if (deleted != null) { + return deleted(this); + } + return orElse(); + } + + @override + Map toJson() { + return _$$DeletedToJson( + this, + ); + } +} + +abstract class Deleted implements CompletedState { + const factory Deleted({final bool hard}) = _$Deleted; + + factory Deleted.fromJson(Map json) = _$Deleted.fromJson; + + bool get hard; + @JsonKey(ignore: true) + _$$DeletedCopyWith<_$Deleted> get copyWith => + throw _privateConstructorUsedError; +} + +FailedState _$FailedStateFromJson(Map json) { + switch (json['runtimeType']) { + case 'sendingFailed': + return SendingFailed.fromJson(json); + case 'updatingFailed': + return UpdatingFailed.fromJson(json); + case 'deletingFailed': + return DeletingFailed.fromJson(json); + + default: + throw CheckedFromJsonException(json, 'runtimeType', 'FailedState', + 'Invalid union type "${json['runtimeType']}"!'); + } +} + +/// @nodoc +mixin _$FailedState { + @optionalTypeArgs + TResult when({ + required TResult Function() sendingFailed, + required TResult Function() updatingFailed, + required TResult Function(bool hard) deletingFailed, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? sendingFailed, + TResult? Function()? updatingFailed, + TResult? Function(bool hard)? deletingFailed, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? sendingFailed, + TResult Function()? updatingFailed, + TResult Function(bool hard)? deletingFailed, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(SendingFailed value) sendingFailed, + required TResult Function(UpdatingFailed value) updatingFailed, + required TResult Function(DeletingFailed value) deletingFailed, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(SendingFailed value)? sendingFailed, + TResult? Function(UpdatingFailed value)? updatingFailed, + TResult? Function(DeletingFailed value)? deletingFailed, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(SendingFailed value)? sendingFailed, + TResult Function(UpdatingFailed value)? updatingFailed, + TResult Function(DeletingFailed value)? deletingFailed, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + Map toJson() => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $FailedStateCopyWith<$Res> { + factory $FailedStateCopyWith( + FailedState value, $Res Function(FailedState) then) = + _$FailedStateCopyWithImpl<$Res, FailedState>; +} + +/// @nodoc +class _$FailedStateCopyWithImpl<$Res, $Val extends FailedState> + implements $FailedStateCopyWith<$Res> { + _$FailedStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; +} + +/// @nodoc +abstract class _$$SendingFailedCopyWith<$Res> { + factory _$$SendingFailedCopyWith( + _$SendingFailed value, $Res Function(_$SendingFailed) then) = + __$$SendingFailedCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$SendingFailedCopyWithImpl<$Res> + extends _$FailedStateCopyWithImpl<$Res, _$SendingFailed> + implements _$$SendingFailedCopyWith<$Res> { + __$$SendingFailedCopyWithImpl( + _$SendingFailed _value, $Res Function(_$SendingFailed) _then) + : super(_value, _then); +} + +/// @nodoc +@JsonSerializable() +class _$SendingFailed implements SendingFailed { + const _$SendingFailed({final String? $type}) + : $type = $type ?? 'sendingFailed'; + + factory _$SendingFailed.fromJson(Map json) => + _$$SendingFailedFromJson(json); + + @JsonKey(name: 'runtimeType') + final String $type; + + @override + String toString() { + return 'FailedState.sendingFailed()'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$SendingFailed); + } + + @JsonKey(ignore: true) + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() sendingFailed, + required TResult Function() updatingFailed, + required TResult Function(bool hard) deletingFailed, + }) { + return sendingFailed(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? sendingFailed, + TResult? Function()? updatingFailed, + TResult? Function(bool hard)? deletingFailed, + }) { + return sendingFailed?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? sendingFailed, + TResult Function()? updatingFailed, + TResult Function(bool hard)? deletingFailed, + required TResult orElse(), + }) { + if (sendingFailed != null) { + return sendingFailed(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(SendingFailed value) sendingFailed, + required TResult Function(UpdatingFailed value) updatingFailed, + required TResult Function(DeletingFailed value) deletingFailed, + }) { + return sendingFailed(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(SendingFailed value)? sendingFailed, + TResult? Function(UpdatingFailed value)? updatingFailed, + TResult? Function(DeletingFailed value)? deletingFailed, + }) { + return sendingFailed?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(SendingFailed value)? sendingFailed, + TResult Function(UpdatingFailed value)? updatingFailed, + TResult Function(DeletingFailed value)? deletingFailed, + required TResult orElse(), + }) { + if (sendingFailed != null) { + return sendingFailed(this); + } + return orElse(); + } + + @override + Map toJson() { + return _$$SendingFailedToJson( + this, + ); + } +} + +abstract class SendingFailed implements FailedState { + const factory SendingFailed() = _$SendingFailed; + + factory SendingFailed.fromJson(Map json) = + _$SendingFailed.fromJson; +} + +/// @nodoc +abstract class _$$UpdatingFailedCopyWith<$Res> { + factory _$$UpdatingFailedCopyWith( + _$UpdatingFailed value, $Res Function(_$UpdatingFailed) then) = + __$$UpdatingFailedCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$UpdatingFailedCopyWithImpl<$Res> + extends _$FailedStateCopyWithImpl<$Res, _$UpdatingFailed> + implements _$$UpdatingFailedCopyWith<$Res> { + __$$UpdatingFailedCopyWithImpl( + _$UpdatingFailed _value, $Res Function(_$UpdatingFailed) _then) + : super(_value, _then); +} + +/// @nodoc +@JsonSerializable() +class _$UpdatingFailed implements UpdatingFailed { + const _$UpdatingFailed({final String? $type}) + : $type = $type ?? 'updatingFailed'; + + factory _$UpdatingFailed.fromJson(Map json) => + _$$UpdatingFailedFromJson(json); + + @JsonKey(name: 'runtimeType') + final String $type; + + @override + String toString() { + return 'FailedState.updatingFailed()'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$UpdatingFailed); + } + + @JsonKey(ignore: true) + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() sendingFailed, + required TResult Function() updatingFailed, + required TResult Function(bool hard) deletingFailed, + }) { + return updatingFailed(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? sendingFailed, + TResult? Function()? updatingFailed, + TResult? Function(bool hard)? deletingFailed, + }) { + return updatingFailed?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? sendingFailed, + TResult Function()? updatingFailed, + TResult Function(bool hard)? deletingFailed, + required TResult orElse(), + }) { + if (updatingFailed != null) { + return updatingFailed(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(SendingFailed value) sendingFailed, + required TResult Function(UpdatingFailed value) updatingFailed, + required TResult Function(DeletingFailed value) deletingFailed, + }) { + return updatingFailed(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(SendingFailed value)? sendingFailed, + TResult? Function(UpdatingFailed value)? updatingFailed, + TResult? Function(DeletingFailed value)? deletingFailed, + }) { + return updatingFailed?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(SendingFailed value)? sendingFailed, + TResult Function(UpdatingFailed value)? updatingFailed, + TResult Function(DeletingFailed value)? deletingFailed, + required TResult orElse(), + }) { + if (updatingFailed != null) { + return updatingFailed(this); + } + return orElse(); + } + + @override + Map toJson() { + return _$$UpdatingFailedToJson( + this, + ); + } +} + +abstract class UpdatingFailed implements FailedState { + const factory UpdatingFailed() = _$UpdatingFailed; + + factory UpdatingFailed.fromJson(Map json) = + _$UpdatingFailed.fromJson; +} + +/// @nodoc +abstract class _$$DeletingFailedCopyWith<$Res> { + factory _$$DeletingFailedCopyWith( + _$DeletingFailed value, $Res Function(_$DeletingFailed) then) = + __$$DeletingFailedCopyWithImpl<$Res>; + @useResult + $Res call({bool hard}); +} + +/// @nodoc +class __$$DeletingFailedCopyWithImpl<$Res> + extends _$FailedStateCopyWithImpl<$Res, _$DeletingFailed> + implements _$$DeletingFailedCopyWith<$Res> { + __$$DeletingFailedCopyWithImpl( + _$DeletingFailed _value, $Res Function(_$DeletingFailed) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? hard = null, + }) { + return _then(_$DeletingFailed( + hard: null == hard + ? _value.hard + : hard // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$DeletingFailed implements DeletingFailed { + const _$DeletingFailed({this.hard = false, final String? $type}) + : $type = $type ?? 'deletingFailed'; + + factory _$DeletingFailed.fromJson(Map json) => + _$$DeletingFailedFromJson(json); + + @override + @JsonKey() + final bool hard; + + @JsonKey(name: 'runtimeType') + final String $type; + + @override + String toString() { + return 'FailedState.deletingFailed(hard: $hard)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$DeletingFailed && + (identical(other.hard, hard) || other.hard == hard)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, hard); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$DeletingFailedCopyWith<_$DeletingFailed> get copyWith => + __$$DeletingFailedCopyWithImpl<_$DeletingFailed>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() sendingFailed, + required TResult Function() updatingFailed, + required TResult Function(bool hard) deletingFailed, + }) { + return deletingFailed(hard); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? sendingFailed, + TResult? Function()? updatingFailed, + TResult? Function(bool hard)? deletingFailed, + }) { + return deletingFailed?.call(hard); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? sendingFailed, + TResult Function()? updatingFailed, + TResult Function(bool hard)? deletingFailed, + required TResult orElse(), + }) { + if (deletingFailed != null) { + return deletingFailed(hard); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(SendingFailed value) sendingFailed, + required TResult Function(UpdatingFailed value) updatingFailed, + required TResult Function(DeletingFailed value) deletingFailed, + }) { + return deletingFailed(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(SendingFailed value)? sendingFailed, + TResult? Function(UpdatingFailed value)? updatingFailed, + TResult? Function(DeletingFailed value)? deletingFailed, + }) { + return deletingFailed?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(SendingFailed value)? sendingFailed, + TResult Function(UpdatingFailed value)? updatingFailed, + TResult Function(DeletingFailed value)? deletingFailed, + required TResult orElse(), + }) { + if (deletingFailed != null) { + return deletingFailed(this); + } + return orElse(); + } + + @override + Map toJson() { + return _$$DeletingFailedToJson( + this, + ); + } +} + +abstract class DeletingFailed implements FailedState { + const factory DeletingFailed({final bool hard}) = _$DeletingFailed; + + factory DeletingFailed.fromJson(Map json) = + _$DeletingFailed.fromJson; + + bool get hard; + @JsonKey(ignore: true) + _$$DeletingFailedCopyWith<_$DeletingFailed> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/stream_chat/lib/src/core/models/message_state.g.dart b/packages/stream_chat/lib/src/core/models/message_state.g.dart new file mode 100644 index 000000000..708222bf5 --- /dev/null +++ b/packages/stream_chat/lib/src/core/models/message_state.g.dart @@ -0,0 +1,141 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'message_state.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$MessageInitial _$$MessageInitialFromJson(Map json) => + _$MessageInitial( + $type: json['runtimeType'] as String?, + ); + +Map _$$MessageInitialToJson(_$MessageInitial instance) => + { + 'runtimeType': instance.$type, + }; + +_$MessageOutgoing _$$MessageOutgoingFromJson(Map json) => + _$MessageOutgoing( + state: OutgoingState.fromJson(json['state'] as Map), + $type: json['runtimeType'] as String?, + ); + +Map _$$MessageOutgoingToJson(_$MessageOutgoing instance) => + { + 'state': instance.state.toJson(), + 'runtimeType': instance.$type, + }; + +_$MessageCompleted _$$MessageCompletedFromJson(Map json) => + _$MessageCompleted( + state: CompletedState.fromJson(json['state'] as Map), + $type: json['runtimeType'] as String?, + ); + +Map _$$MessageCompletedToJson(_$MessageCompleted instance) => + { + 'state': instance.state.toJson(), + 'runtimeType': instance.$type, + }; + +_$MessageFailed _$$MessageFailedFromJson(Map json) => + _$MessageFailed( + state: FailedState.fromJson(json['state'] as Map), + reason: json['reason'], + $type: json['runtimeType'] as String?, + ); + +Map _$$MessageFailedToJson(_$MessageFailed instance) => + { + 'state': instance.state.toJson(), + 'reason': instance.reason, + 'runtimeType': instance.$type, + }; + +_$Sending _$$SendingFromJson(Map json) => _$Sending( + $type: json['runtimeType'] as String?, + ); + +Map _$$SendingToJson(_$Sending instance) => { + 'runtimeType': instance.$type, + }; + +_$Updating _$$UpdatingFromJson(Map json) => _$Updating( + $type: json['runtimeType'] as String?, + ); + +Map _$$UpdatingToJson(_$Updating instance) => + { + 'runtimeType': instance.$type, + }; + +_$Deleting _$$DeletingFromJson(Map json) => _$Deleting( + hard: json['hard'] as bool? ?? false, + $type: json['runtimeType'] as String?, + ); + +Map _$$DeletingToJson(_$Deleting instance) => + { + 'hard': instance.hard, + 'runtimeType': instance.$type, + }; + +_$Sent _$$SentFromJson(Map json) => _$Sent( + $type: json['runtimeType'] as String?, + ); + +Map _$$SentToJson(_$Sent instance) => { + 'runtimeType': instance.$type, + }; + +_$Updated _$$UpdatedFromJson(Map json) => _$Updated( + $type: json['runtimeType'] as String?, + ); + +Map _$$UpdatedToJson(_$Updated instance) => { + 'runtimeType': instance.$type, + }; + +_$Deleted _$$DeletedFromJson(Map json) => _$Deleted( + hard: json['hard'] as bool? ?? false, + $type: json['runtimeType'] as String?, + ); + +Map _$$DeletedToJson(_$Deleted instance) => { + 'hard': instance.hard, + 'runtimeType': instance.$type, + }; + +_$SendingFailed _$$SendingFailedFromJson(Map json) => + _$SendingFailed( + $type: json['runtimeType'] as String?, + ); + +Map _$$SendingFailedToJson(_$SendingFailed instance) => + { + 'runtimeType': instance.$type, + }; + +_$UpdatingFailed _$$UpdatingFailedFromJson(Map json) => + _$UpdatingFailed( + $type: json['runtimeType'] as String?, + ); + +Map _$$UpdatingFailedToJson(_$UpdatingFailed instance) => + { + 'runtimeType': instance.$type, + }; + +_$DeletingFailed _$$DeletingFailedFromJson(Map json) => + _$DeletingFailed( + hard: json['hard'] as bool? ?? false, + $type: json['runtimeType'] as String?, + ); + +Map _$$DeletingFailedToJson(_$DeletingFailed instance) => + { + 'hard': instance.hard, + 'runtimeType': instance.$type, + }; diff --git a/packages/stream_chat/lib/stream_chat.dart b/packages/stream_chat/lib/stream_chat.dart index 2dbf1afdb..71a217dce 100644 --- a/packages/stream_chat/lib/stream_chat.dart +++ b/packages/stream_chat/lib/stream_chat.dart @@ -38,6 +38,7 @@ export 'src/core/models/event.dart'; export 'src/core/models/filter.dart' show Filter; export 'src/core/models/member.dart'; export 'src/core/models/message.dart'; +export 'src/core/models/message_state.dart'; export 'src/core/models/mute.dart'; export 'src/core/models/own_user.dart'; export 'src/core/models/reaction.dart'; diff --git a/packages/stream_chat/lib/version.dart b/packages/stream_chat/lib/version.dart index 6b1c214db..199e32009 100644 --- a/packages/stream_chat/lib/version.dart +++ b/packages/stream_chat/lib/version.dart @@ -3,4 +3,4 @@ import 'package:stream_chat/src/client/client.dart'; /// Current package version /// Used in [StreamChatClient] to build the `x-stream-client` header // ignore: constant_identifier_names -const PACKAGE_VERSION = '6.5.0'; +const PACKAGE_VERSION = '6.6.0'; diff --git a/packages/stream_chat/pubspec.yaml b/packages/stream_chat/pubspec.yaml index 469f6b19a..6b35b723b 100644 --- a/packages/stream_chat/pubspec.yaml +++ b/packages/stream_chat/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat homepage: https://getstream.io/ description: The official Dart client for Stream Chat, a service for building chat applications. -version: 6.5.0 +version: 6.6.0 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues diff --git a/packages/stream_chat/test/src/client/channel_test.dart b/packages/stream_chat/test/src/client/channel_test.dart index 3b4a1b6a1..17cb7679a 100644 --- a/packages/stream_chat/test/src/client/channel_test.dart +++ b/packages/stream_chat/test/src/client/channel_test.dart @@ -133,7 +133,7 @@ void main() { final retryPolicy = RetryPolicy( shouldRetry: (_, __, ___) => false, - retryTimeout: (_, __, ___) => Duration.zero, + delayFactor: Duration.zero, ); when(() => client.retryPolicy).thenReturn(retryPolicy); @@ -191,7 +191,7 @@ void main() { final retryPolicy = RetryPolicy( shouldRetry: (_, __, ___) => false, - retryTimeout: (_, __, ___) => Duration.zero, + delayFactor: Duration.zero, ); when(() => client.retryPolicy).thenReturn(retryPolicy); @@ -253,7 +253,7 @@ void main() { ); final sendMessageResponse = SendMessageResponse() - ..message = message.copyWith(status: MessageSendingStatus.sent); + ..message = message.copyWith(state: MessageState.sent); when(() => client.sendMessage( any(that: isSameMessageAs(message)), @@ -267,14 +267,14 @@ void main() { emitsInOrder([ [ isSameMessageAs( - message.copyWith(status: MessageSendingStatus.sending), - matchSendingStatus: true, + message.copyWith(state: MessageState.sending), + matchMessageState: true, ), ], [ isSameMessageAs( - message.copyWith(status: MessageSendingStatus.sent), - matchSendingStatus: true, + message.copyWith(state: MessageState.sent), + matchMessageState: true, ), ], ]), @@ -338,7 +338,7 @@ void main() { .map((it) => it.copyWith(uploadState: const UploadState.success())) .toList(growable: false), - status: MessageSendingStatus.sent, + state: MessageState.sent, )); expectLater( @@ -350,13 +350,13 @@ void main() { [ isSameMessageAs( message.copyWith( - status: MessageSendingStatus.sending, + state: MessageState.sending, attachments: [ ...attachments.map((it) => it.copyWith( uploadState: const UploadState.preparing())) ], ), - matchSendingStatus: true, + matchMessageState: true, matchAttachments: true, matchAttachmentsUploadState: true, ), @@ -365,13 +365,13 @@ void main() { [ isSameMessageAs( message.copyWith( - status: MessageSendingStatus.sending, + state: MessageState.sending, attachments: [...attachments]..[0] = attachments[0].copyWith( uploadState: const UploadState.success(), ), ), - matchSendingStatus: true, + matchMessageState: true, matchAttachments: true, matchAttachmentsUploadState: true, ), @@ -380,7 +380,7 @@ void main() { [ isSameMessageAs( message.copyWith( - status: MessageSendingStatus.sending, + state: MessageState.sending, attachments: [...attachments] ..[0] = attachments[0].copyWith( uploadState: const UploadState.success(), @@ -389,7 +389,7 @@ void main() { uploadState: const UploadState.success(), ), ), - matchSendingStatus: true, + matchMessageState: true, matchAttachments: true, matchAttachmentsUploadState: true, ), @@ -398,13 +398,13 @@ void main() { [ isSameMessageAs( message.copyWith( - status: MessageSendingStatus.sending, + state: MessageState.sending, attachments: [ ...attachments.map((it) => it.copyWith(uploadState: const UploadState.success())) ], ), - matchSendingStatus: true, + matchMessageState: true, matchAttachments: true, matchAttachmentsUploadState: true, ), @@ -412,13 +412,13 @@ void main() { [ isSameMessageAs( message.copyWith( - status: MessageSendingStatus.sent, + state: MessageState.sent, attachments: [ ...attachments.map((it) => it.copyWith(uploadState: const UploadState.success())) ], ), - matchSendingStatus: true, + matchMessageState: true, matchAttachments: true, matchAttachmentsUploadState: true, ), @@ -469,7 +469,7 @@ void main() { test('should work fine', () async { final message = Message( id: 'test-message-id', - status: MessageSendingStatus.sent, + state: MessageState.sent, ); final updateMessageResponse = UpdateMessageResponse() @@ -484,14 +484,14 @@ void main() { emitsInOrder([ [ isSameMessageAs( - message.copyWith(status: MessageSendingStatus.updating), - matchSendingStatus: true, + message.copyWith(state: MessageState.updating), + matchMessageState: true, ), ], [ isSameMessageAs( - message.copyWith(status: MessageSendingStatus.sent), - matchSendingStatus: true, + message.copyWith(state: MessageState.updated), + matchMessageState: true, ), ], ]), @@ -547,7 +547,7 @@ void main() { any(that: isSameMessageAs(message)), )).thenAnswer((_) async => UpdateMessageResponse() ..message = message.copyWith( - status: MessageSendingStatus.sent, + state: MessageState.sent, attachments: attachments .map((it) => it.copyWith(uploadState: const UploadState.success())) @@ -563,13 +563,13 @@ void main() { [ isSameMessageAs( message.copyWith( - status: MessageSendingStatus.updating, + state: MessageState.updating, attachments: [ ...attachments.map((it) => it.copyWith( uploadState: const UploadState.preparing())) ], ), - matchSendingStatus: true, + matchMessageState: true, matchAttachments: true, matchAttachmentsUploadState: true, ), @@ -578,13 +578,13 @@ void main() { [ isSameMessageAs( message.copyWith( - status: MessageSendingStatus.updating, + state: MessageState.updating, attachments: [...attachments]..[0] = attachments[0].copyWith( uploadState: const UploadState.success(), ), ), - matchSendingStatus: true, + matchMessageState: true, matchAttachments: true, matchAttachmentsUploadState: true, ), @@ -593,7 +593,7 @@ void main() { [ isSameMessageAs( message.copyWith( - status: MessageSendingStatus.updating, + state: MessageState.updating, attachments: [...attachments] ..[0] = attachments[0].copyWith( uploadState: const UploadState.success(), @@ -602,7 +602,7 @@ void main() { uploadState: const UploadState.success(), ), ), - matchSendingStatus: true, + matchMessageState: true, matchAttachments: true, matchAttachmentsUploadState: true, ), @@ -611,13 +611,13 @@ void main() { [ isSameMessageAs( message.copyWith( - status: MessageSendingStatus.updating, + state: MessageState.updating, attachments: [ ...attachments.map((it) => it.copyWith(uploadState: const UploadState.success())) ], ), - matchSendingStatus: true, + matchMessageState: true, matchAttachments: true, matchAttachmentsUploadState: true, ), @@ -625,13 +625,13 @@ void main() { [ isSameMessageAs( message.copyWith( - status: MessageSendingStatus.sent, + state: MessageState.updated, attachments: [ ...attachments.map((it) => it.copyWith(uploadState: const UploadState.success())) ], ), - matchSendingStatus: true, + matchMessageState: true, matchAttachments: true, matchAttachmentsUploadState: true, ), @@ -677,7 +677,10 @@ void main() { }); test('`.partialUpdateMessage`', () async { - final message = Message(id: 'test-message-id'); + final message = Message( + id: 'test-message-id', + state: MessageState.sent, + ); const set = {'text': 'Update Message text'}; const unset = ['pinExpires']; @@ -689,19 +692,26 @@ void main() { () => client.partialUpdateMessage(message.id, set: set, unset: unset), ).thenAnswer((_) async => updateMessageResponse); - channel.state?.messagesStream.skip(1).listen(print); - expectLater( // skipping first seed message list -> [] messages channel.state?.messagesStream.skip(1), emitsInOrder([ + [ + isSameMessageAs( + message.copyWith( + state: MessageState.updating, + ), + matchText: true, + matchMessageState: true, + ), + ], [ isSameMessageAs( updateMessageResponse.message.copyWith( - status: MessageSendingStatus.sending, + state: MessageState.updated, ), matchText: true, - matchSendingStatus: true, + matchMessageState: true, ), ], ]), @@ -729,7 +739,8 @@ void main() { const messageId = 'test-message-id'; final message = Message( id: messageId, - status: MessageSendingStatus.sent, + createdAt: DateTime.now(), + state: MessageState.sent, ); when(() => client.deleteMessage(messageId)) @@ -741,14 +752,14 @@ void main() { emitsInOrder([ [ isSameMessageAs( - message.copyWith(status: MessageSendingStatus.deleting), - matchSendingStatus: true, + message.copyWith(state: MessageState.softDeleting), + matchMessageState: true, ), ], [ isSameMessageAs( - message.copyWith(status: MessageSendingStatus.sent), - matchSendingStatus: true, + message.copyWith(state: MessageState.softDeleted), + matchMessageState: true, ), ], ]), @@ -775,8 +786,8 @@ void main() { emitsInOrder([ [ isSameMessageAs( - message.copyWith(status: MessageSendingStatus.sent), - matchSendingStatus: true, + message.copyWith(state: MessageState.softDeleted), + matchMessageState: true, ), ], ]), @@ -785,6 +796,7 @@ void main() { final res = await channel.deleteMessage(message); expect(res, isNotNull); + verifyNever(() => client.deleteMessage(messageId)); }, ); }); @@ -802,7 +814,6 @@ void main() { ..message = message.copyWith( pinned: true, pinExpires: null, - status: MessageSendingStatus.sent, )); expectLater( @@ -811,8 +822,14 @@ void main() { emitsInOrder([ [ isSameMessageAs( - message.copyWith(status: MessageSendingStatus.sent), - matchSendingStatus: true, + message.copyWith(state: MessageState.updating), + matchMessageState: true, + ), + ], + [ + isSameMessageAs( + message.copyWith(state: MessageState.updated), + matchMessageState: true, ), ], ]), @@ -847,7 +864,6 @@ void main() { pinExpires: DateTime.now().add( const Duration(seconds: timeoutOrExpirationDate), ), - status: MessageSendingStatus.sent, )); expectLater( @@ -856,8 +872,14 @@ void main() { emitsInOrder([ [ isSameMessageAs( - message.copyWith(status: MessageSendingStatus.sent), - matchSendingStatus: true, + message.copyWith(state: MessageState.updating), + matchMessageState: true, + ), + ], + [ + isSameMessageAs( + message.copyWith(state: MessageState.updated), + matchMessageState: true, ), ], ]), @@ -895,7 +917,6 @@ void main() { ..message = message.copyWith( pinned: true, pinExpires: timeoutOrExpirationDate, - status: MessageSendingStatus.sent, )); expectLater( @@ -904,8 +925,14 @@ void main() { emitsInOrder([ [ isSameMessageAs( - message.copyWith(status: MessageSendingStatus.sent), - matchSendingStatus: true, + message.copyWith(state: MessageState.updating), + matchMessageState: true, + ), + ], + [ + isSameMessageAs( + message.copyWith(state: MessageState.updated), + matchMessageState: true, ), ], ]), @@ -954,10 +981,7 @@ void main() { message.id, set: {'pinned': false}, )).thenAnswer((_) async => UpdateMessageResponse() - ..message = message.copyWith( - pinned: false, - status: MessageSendingStatus.sent, - )); + ..message = message.copyWith(pinned: false)); expectLater( // skipping first seed message list -> [] messages @@ -965,8 +989,14 @@ void main() { emitsInOrder([ [ isSameMessageAs( - message.copyWith(status: MessageSendingStatus.sent), - matchSendingStatus: true, + message.copyWith(state: MessageState.updating), + matchMessageState: true, + ), + ], + [ + isSameMessageAs( + message.copyWith(state: MessageState.updated), + matchMessageState: true, ), ], ]), @@ -1101,7 +1131,7 @@ void main() { const type = 'test-reaction-type'; final message = Message( id: 'test-message-id', - status: MessageSendingStatus.sent, + state: MessageState.sent, ); final reaction = Reaction(type: type, messageId: message.id); @@ -1119,14 +1149,14 @@ void main() { [ isSameMessageAs( message.copyWith( - status: MessageSendingStatus.sent, + state: MessageState.sent, reactionCounts: {type: 1}, reactionScores: {type: 1}, latestReactions: [reaction], ownReactions: [reaction], ), matchReactions: true, - matchSendingStatus: true, + matchMessageState: true, ), ], ]), @@ -1145,7 +1175,7 @@ void main() { const type = 'test-reaction-type'; final message = Message( id: 'test-message-id', - status: MessageSendingStatus.sent, + state: MessageState.sent, ); const score = 5; @@ -1172,14 +1202,14 @@ void main() { [ isSameMessageAs( message.copyWith( - status: MessageSendingStatus.sent, + state: MessageState.sent, reactionCounts: {type: 1}, reactionScores: {type: score}, latestReactions: [reaction], ownReactions: [reaction], ), matchReactions: true, - matchSendingStatus: true, + matchMessageState: true, ), ], ]), @@ -1208,7 +1238,7 @@ void main() { const type = 'test-reaction-type'; final message = Message( id: 'test-message-id', - status: MessageSendingStatus.sent, + state: MessageState.sent, ); const score = 5; @@ -1240,14 +1270,14 @@ void main() { [ isSameMessageAs( message.copyWith( - status: MessageSendingStatus.sent, + state: MessageState.sent, reactionCounts: {type: 1}, reactionScores: {type: extraDataScore}, latestReactions: [reaction], ownReactions: [reaction], ), matchReactions: true, - matchSendingStatus: true, + matchMessageState: true, ), ], ]), @@ -1282,7 +1312,7 @@ void main() { const type = 'test-reaction-type'; final message = Message( id: 'test-message-id', - status: MessageSendingStatus.sent, + state: MessageState.sent, ); final reaction = Reaction(type: type, messageId: message.id); @@ -1297,21 +1327,21 @@ void main() { [ isSameMessageAs( message.copyWith( - status: MessageSendingStatus.sent, + state: MessageState.sent, reactionCounts: {type: 1}, reactionScores: {type: 1}, latestReactions: [reaction], ownReactions: [reaction], ), matchReactions: true, - matchSendingStatus: true, + matchMessageState: true, ), ], [ isSameMessageAs( message, matchReactions: true, - matchSendingStatus: true, + matchMessageState: true, ), ], ]), @@ -1344,7 +1374,7 @@ void main() { latestReactions: [prevReaction], reactionScores: const {prevType: 1}, reactionCounts: const {prevType: 1}, - status: MessageSendingStatus.sent, + state: MessageState.sent, ); const type = 'test-reaction-type-2'; @@ -1378,7 +1408,7 @@ void main() { isSameMessageAs( newMessage, matchReactions: true, - matchSendingStatus: true, + matchMessageState: true, ), ], ]), @@ -1409,7 +1439,7 @@ void main() { final message = Message( id: 'test-message-id', parentId: 'test-parent-id', // is thread message - status: MessageSendingStatus.sent, + state: MessageState.sent, ); final reaction = Reaction(type: type, messageId: message.id); @@ -1429,14 +1459,14 @@ void main() { [ isSameMessageAs( message.copyWith( - status: MessageSendingStatus.sent, + state: MessageState.sent, reactionCounts: {type: 1}, reactionScores: {type: 1}, latestReactions: [reaction], ownReactions: [reaction], ), matchReactions: true, - matchSendingStatus: true, + matchMessageState: true, matchParentId: true, ), ], @@ -1459,7 +1489,7 @@ void main() { final message = Message( id: 'test-message-id', parentId: 'test-parent-id', // is thread message - status: MessageSendingStatus.sent, + state: MessageState.sent, ); final reaction = Reaction(type: type, messageId: message.id); @@ -1476,14 +1506,14 @@ void main() { [ isSameMessageAs( message.copyWith( - status: MessageSendingStatus.sent, + state: MessageState.sent, reactionCounts: {type: 1}, reactionScores: {type: 1}, latestReactions: [reaction], ownReactions: [reaction], ), matchReactions: true, - matchSendingStatus: true, + matchMessageState: true, matchParentId: true, ), ], @@ -1491,7 +1521,7 @@ void main() { isSameMessageAs( message, matchReactions: true, - matchSendingStatus: true, + matchMessageState: true, matchParentId: true, ), ], @@ -1527,7 +1557,7 @@ void main() { latestReactions: [prevReaction], reactionScores: const {prevType: 1}, reactionCounts: const {prevType: 1}, - status: MessageSendingStatus.sent, + state: MessageState.sent, ); const type = 'test-reaction-type-2'; @@ -1561,9 +1591,9 @@ void main() { emitsInOrder([ [ isSameMessageAs( - newMessage.copyWith(status: MessageSendingStatus.sent), + newMessage.copyWith(state: MessageState.sent), matchReactions: true, - matchSendingStatus: true, + matchMessageState: true, matchParentId: true, ), ], @@ -1605,7 +1635,7 @@ void main() { latestReactions: [reaction], reactionScores: const {type: 1}, reactionCounts: const {type: 1}, - status: MessageSendingStatus.sent, + state: MessageState.sent, ); when(() => client.deleteReaction(messageId, type)) @@ -1618,12 +1648,12 @@ void main() { [ isSameMessageAs( message.copyWith( - status: MessageSendingStatus.sent, + state: MessageState.sent, latestReactions: [], ownReactions: [], ), matchReactions: true, - matchSendingStatus: true, + matchMessageState: true, ), ], ]), @@ -1653,7 +1683,7 @@ void main() { latestReactions: [reaction], reactionScores: const {type: 1}, reactionCounts: const {type: 1}, - status: MessageSendingStatus.sent, + state: MessageState.sent, ); when(() => client.deleteReaction(messageId, type)) @@ -1666,19 +1696,19 @@ void main() { [ isSameMessageAs( message.copyWith( - status: MessageSendingStatus.sent, + state: MessageState.sent, latestReactions: [], ownReactions: [], ), matchReactions: true, - matchSendingStatus: true, + matchMessageState: true, ), ], [ isSameMessageAs( message, matchReactions: true, - matchSendingStatus: true, + matchMessageState: true, ), ], ]), @@ -1714,7 +1744,7 @@ void main() { latestReactions: [reaction], reactionScores: const {type: 1}, reactionCounts: const {type: 1}, - status: MessageSendingStatus.sent, + state: MessageState.sent, ); when(() => client.deleteReaction(messageId, type)) @@ -1729,12 +1759,12 @@ void main() { [ isSameMessageAs( message.copyWith( - status: MessageSendingStatus.sent, + state: MessageState.sent, latestReactions: [], ownReactions: [], ), matchReactions: true, - matchSendingStatus: true, + matchMessageState: true, matchParentId: true, ), ], @@ -1767,7 +1797,7 @@ void main() { latestReactions: [reaction], reactionScores: const {type: 1}, reactionCounts: const {type: 1}, - status: MessageSendingStatus.sent, + state: MessageState.sent, ); when(() => client.deleteReaction(messageId, type)) @@ -1782,12 +1812,12 @@ void main() { [ isSameMessageAs( message.copyWith( - status: MessageSendingStatus.sent, + state: MessageState.sent, latestReactions: [], ownReactions: [], ), matchReactions: true, - matchSendingStatus: true, + matchMessageState: true, matchParentId: true, ), ], @@ -1795,7 +1825,7 @@ void main() { isSameMessageAs( message, matchReactions: true, - matchSendingStatus: true, + matchMessageState: true, matchParentId: true, ), ], @@ -2144,7 +2174,7 @@ void main() { [ isSameMessageAs( message, - matchSendingStatus: true, + matchMessageState: true, ), ], ]), diff --git a/packages/stream_chat/test/src/client/client_test.dart b/packages/stream_chat/test/src/client/client_test.dart index 9ff0c69f7..df167b235 100644 --- a/packages/stream_chat/test/src/client/client_test.dart +++ b/packages/stream_chat/test/src/client/client_test.dart @@ -2236,13 +2236,13 @@ void main() { test('`.deleteMessage`', () async { const messageId = 'test-message-id'; - when(() => api.message.deleteMessage(messageId)) + when(() => api.message.deleteMessage(messageId, hard: false)) .thenAnswer((_) async => EmptyResponse()); final res = await client.deleteMessage(messageId); expect(res, isNotNull); - verify(() => api.message.deleteMessage(messageId)).called(1); + verify(() => api.message.deleteMessage(messageId, hard: false)).called(1); verifyNoMoreInteractions(api.message); }); @@ -2359,7 +2359,7 @@ void main() { ..message = message.copyWith( pinned: true, pinExpires: null, - status: MessageSendingStatus.sent, + state: MessageState.sent, )); final res = await client.pinMessage(messageId); @@ -2393,7 +2393,7 @@ void main() { pinExpires: DateTime.now().add( const Duration(seconds: timeoutOrExpirationDate), ), - status: MessageSendingStatus.sent, + state: MessageState.sent, )); final res = await client.pinMessage( @@ -2430,7 +2430,7 @@ void main() { ..message = message.copyWith( pinned: true, pinExpires: timeoutOrExpirationDate, - status: MessageSendingStatus.sent, + state: MessageState.sent, )); final res = await client.pinMessage( @@ -2480,7 +2480,7 @@ void main() { )).thenAnswer((_) async => UpdateMessageResponse() ..message = message.copyWith( pinned: false, - status: MessageSendingStatus.sent, + state: MessageState.sent, )); final res = await client.unpinMessage(messageId); diff --git a/packages/stream_chat/test/src/client/retry_queue_test.dart b/packages/stream_chat/test/src/client/retry_queue_test.dart index ddc5364ee..e28f6da48 100644 --- a/packages/stream_chat/test/src/client/retry_queue_test.dart +++ b/packages/stream_chat/test/src/client/retry_queue_test.dart @@ -1,9 +1,7 @@ import 'package:mocktail/mocktail.dart'; import 'package:stream_chat/src/client/retry_policy.dart'; import 'package:stream_chat/src/client/retry_queue.dart'; -import 'package:stream_chat/src/core/models/event.dart'; -import 'package:stream_chat/src/core/models/message.dart'; -import 'package:stream_chat/src/event_type.dart'; +import 'package:stream_chat/stream_chat.dart'; import 'package:test/test.dart'; import '../mocks.dart'; @@ -15,8 +13,9 @@ void main() { setUpAll(() { final retryPolicy = RetryPolicy( - shouldRetry: (_, attempt, __) => attempt < 5, - retryTimeout: (_, attempt, __) => Duration(seconds: attempt), + shouldRetry: (_, __, error) { + return error is StreamChatNetworkError && error.isRetriable; + }, ); when(() => channel.client.retryPolicy).thenReturn(retryPolicy); @@ -48,21 +47,32 @@ void main() { verifyNever(() => logger.info(any())); }); + test('should throw if message state is not failed', () { + final message = Message( + id: 'test-message-id', + text: 'Sample message test', + state: MessageState.sent, + ); + expect(() => retryQueue.add([message]), throwsA(isA())); + }); + test('should return if queue already contains the message', () { final message = Message( id: 'test-message-id', text: 'Sample message test', + state: MessageState.sendingFailed, ); retryQueue.add([message]); expect(() => retryQueue.add([message]), returnsNormally); // Called only for the first message - verify(() => logger.info('Adding 1 messages')).called(1); + verify(() => logger.info('Adding 1 messages to the queue')).called(1); }); test('`.add` should add failed request to the queue', () async { final message = Message( id: 'test-message-id', text: 'Sample message test', + state: MessageState.sendingFailed, ); retryQueue.add([message]); expect(retryQueue.hasMessages, isTrue); diff --git a/packages/stream_chat/test/src/core/models/message_state_test.dart b/packages/stream_chat/test/src/core/models/message_state_test.dart new file mode 100644 index 000000000..9bdb250af --- /dev/null +++ b/packages/stream_chat/test/src/core/models/message_state_test.dart @@ -0,0 +1,308 @@ +// ignore_for_file: use_named_constants, lines_longer_than_80_chars + +import 'package:stream_chat/src/core/models/message_state.dart'; +import 'package:test/test.dart'; + +void main() { + group( + 'Message State Extensions', + () { + test( + 'isInitial should return true if the message state is MessageInitial', + () { + const messageState = MessageState.initial(); + expect(messageState.isInitial, true); + }, + ); + + test( + 'isOutgoing should return true if the message state is MessageOutgoing', + () { + const messageState = MessageState.outgoing( + state: OutgoingState.sending(), + ); + expect(messageState.isOutgoing, true); + }, + ); + + test( + 'isCompleted should return true if the message state is MessageCompleted', + () { + const messageState = MessageState.completed( + state: CompletedState.sent(), + ); + expect(messageState.isCompleted, true); + }, + ); + + test( + 'isFailed should return true if the message state is MessageFailed', + () { + const messageState = MessageState.failed( + state: FailedState.sendingFailed(), + ); + expect(messageState.isFailed, true); + }, + ); + + test( + 'isSending should return true if the message state is MessageOutgoing with Sending state', + () { + const messageState = MessageState.outgoing( + state: OutgoingState.sending(), + ); + expect(messageState.isSending, true); + }, + ); + + test( + 'isUpdating should return true if the message state is MessageOutgoing with Updating state', + () { + const messageState = MessageState.outgoing( + state: OutgoingState.updating(), + ); + expect(messageState.isUpdating, true); + }, + ); + + test( + 'isDeleting should return true if the message state is either isSoftDeleting or isHardDeleting', + () { + const messageState = MessageState.softDeleting; + expect(messageState.isDeleting, true); + }, + ); + + test( + 'isSoftDeleting should return true if the message state is MessageOutgoing with Deleting state and not hard deleting', + () { + const messageState = MessageState.outgoing( + state: OutgoingState.deleting(), + ); + expect(messageState.isSoftDeleting, true); + }, + ); + + test( + 'isHardDeleting should return true if the message state is MessageOutgoing with Deleting state and hard deleting', + () { + const messageState = MessageState.outgoing( + state: OutgoingState.deleting(hard: true), + ); + expect(messageState.isHardDeleting, true); + }, + ); + + test( + 'isSent should return true if the message state is MessageCompleted with Sent state', + () { + const messageState = + MessageState.completed(state: CompletedState.sent()); + expect(messageState.isSent, true); + }, + ); + + test( + 'isUpdated should return true if the message state is MessageCompleted with Updated state', + () { + const messageState = MessageState.completed( + state: CompletedState.updated(), + ); + expect(messageState.isUpdated, true); + }, + ); + + test( + 'isDeleted should return true if the message state is either isSoftDeleted or isHardDeleted', + () { + const messageState = MessageState.softDeleted; + expect(messageState.isDeleted, true); + }, + ); + + test( + 'isSoftDeleted should return true if the message state is MessageCompleted with Deleted state and not hard deleting', + () { + const messageState = MessageState.completed( + state: CompletedState.deleted(), + ); + expect(messageState.isSoftDeleted, true); + }, + ); + + test( + 'isHardDeleted should return true if the message state is MessageCompleted with Deleted state and hard deleting', + () { + const messageState = MessageState.completed( + state: CompletedState.deleted(hard: true), + ); + expect(messageState.isHardDeleted, true); + }, + ); + + test( + 'isSendingFailed should return true if the message state is MessageFailed with SendingFailed state', + () { + const messageState = MessageState.failed( + state: FailedState.sendingFailed(), + ); + expect(messageState.isSendingFailed, true); + }, + ); + + test( + 'isUpdatingFailed should return true if the message state is MessageFailed with UpdatingFailed state', + () { + const messageState = MessageState.failed( + state: FailedState.updatingFailed(), + ); + expect(messageState.isUpdatingFailed, true); + }, + ); + + test( + 'isDeletingFailed should return true if the message state is either isSoftDeletingFailed or isHardDeletingFailed', + () { + const messageState = MessageState.softDeletingFailed; + expect(messageState.isDeletingFailed, true); + }, + ); + + test( + 'isSoftDeletingFailed should return true if the message state is MessageFailed with DeletingFailed state and not hard deleting', + () { + const messageState = MessageState.failed( + state: FailedState.deletingFailed(), + ); + expect(messageState.isSoftDeletingFailed, true); + }, + ); + + test( + 'isHardDeletingFailed should return true if the message state is MessageFailed with DeletingFailed state and hard deleting', + () { + const messageState = MessageState.failed( + state: FailedState.deletingFailed(hard: true), + ); + expect(messageState.isHardDeletingFailed, true); + }, + ); + }, + ); + + group('Message State Classes', () { + test( + 'MessageState.sending should create a MessageOutgoing instance with Sending state', + () { + const messageState = MessageState.sending; + expect(messageState, isA()); + expect((messageState as MessageOutgoing).state, isA()); + }, + ); + + test( + 'MessageState.updating should create a MessageOutgoing instance with Updating state', + () { + const messageState = MessageState.updating; + expect(messageState, isA()); + expect((messageState as MessageOutgoing).state, isA()); + }, + ); + + test( + 'MessageState.softDeleting should create a MessageOutgoing instance with Deleting state and not hard deleting', + () { + const messageState = MessageState.softDeleting; + expect(messageState, isA()); + expect((messageState as MessageOutgoing).state, isA()); + expect((messageState.state as Deleting).hard, false); + }, + ); + + test( + 'MessageState.hardDeleting should create a MessageOutgoing instance with Deleting state and hard deleting', + () { + const messageState = MessageState.hardDeleting; + expect(messageState, isA()); + expect((messageState as MessageOutgoing).state, isA()); + expect((messageState.state as Deleting).hard, true); + }, + ); + + test( + 'MessageState.sent should create a MessageCompleted instance with Sent state', + () { + const messageState = MessageState.sent; + expect(messageState, isA()); + expect((messageState as MessageCompleted).state, isA()); + }, + ); + + test( + 'MessageState.updated should create a MessageCompleted instance with Updated state', + () { + const messageState = MessageState.updated; + expect(messageState, isA()); + expect((messageState as MessageCompleted).state, isA()); + }, + ); + + test( + 'MessageState.softDeleted should create a MessageCompleted instance with Deleted state and not hard deleting', + () { + const messageState = MessageState.softDeleted; + expect(messageState, isA()); + expect((messageState as MessageCompleted).state, isA()); + expect((messageState.state as Deleted).hard, false); + }, + ); + + test( + 'MessageState.hardDeleted should create a MessageCompleted instance with Deleted state and hard deleting', + () { + const messageState = MessageState.hardDeleted; + expect(messageState, isA()); + expect((messageState as MessageCompleted).state, isA()); + expect((messageState.state as Deleted).hard, true); + }, + ); + + test( + 'MessageState.sendingFailed should create a MessageFailed instance with SendingFailed state', + () { + const messageState = MessageState.sendingFailed; + expect(messageState, isA()); + expect((messageState as MessageFailed).state, isA()); + }, + ); + + test( + 'MessageState.updatingFailed should create a MessageFailed instance with UpdatingFailed state', + () { + const messageState = MessageState.updatingFailed; + expect(messageState, isA()); + expect((messageState as MessageFailed).state, isA()); + }, + ); + + test( + 'MessageState.softDeletingFailed should create a MessageFailed instance with DeletingFailed state and not hard deleting', + () { + const messageState = MessageState.softDeletingFailed; + expect(messageState, isA()); + expect((messageState as MessageFailed).state, isA()); + expect((messageState.state as DeletingFailed).hard, false); + }, + ); + + test( + 'MessageState.hardDeletingFailed should create a MessageFailed instance with DeletingFailed state and hard deleting', + () { + const messageState = MessageState.hardDeletingFailed; + expect(messageState, isA()); + expect((messageState as MessageFailed).state, isA()); + expect((messageState.state as DeletingFailed).hard, true); + }, + ); + }); +} diff --git a/packages/stream_chat/test/src/matchers.dart b/packages/stream_chat/test/src/matchers.dart index 102480cd7..eeac94c35 100644 --- a/packages/stream_chat/test/src/matchers.dart +++ b/packages/stream_chat/test/src/matchers.dart @@ -46,7 +46,7 @@ Matcher isSameMessageAs( Message targetMessage, { bool matchText = false, bool matchReactions = false, - bool matchSendingStatus = false, + bool matchMessageState = false, bool matchAttachments = false, bool matchAttachmentsUploadState = false, bool matchParentId = false, @@ -55,7 +55,7 @@ Matcher isSameMessageAs( targetMessage: targetMessage, matchText: matchText, matchReactions: matchReactions, - matchSendingStatus: matchSendingStatus, + matchMessageState: matchMessageState, matchAttachments: matchAttachments, matchAttachmentsUploadState: matchAttachmentsUploadState, matchParentId: matchParentId, @@ -66,7 +66,7 @@ class _IsSameMessageAs extends Matcher { required this.targetMessage, this.matchText = false, this.matchReactions = false, - this.matchSendingStatus = false, + this.matchMessageState = false, this.matchAttachments = false, this.matchAttachmentsUploadState = false, this.matchParentId = false, @@ -75,7 +75,7 @@ class _IsSameMessageAs extends Matcher { final Message targetMessage; final bool matchText; final bool matchReactions; - final bool matchSendingStatus; + final bool matchMessageState; final bool matchAttachments; final bool matchAttachmentsUploadState; final bool matchParentId; @@ -90,8 +90,8 @@ class _IsSameMessageAs extends Matcher { if (matchText) { matches &= message.text == targetMessage.text; } - if (matchSendingStatus) { - matches &= message.status == targetMessage.status; + if (matchMessageState) { + matches &= message.state == targetMessage.state; } if (matchReactions) { matches &= const ListEquality().equals( diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 1263c4fe9..349457e8a 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,3 +1,10 @@ +## 6.7.0 + +🔄 Changed + +- Updated `stream_chat_flutter_core` dependency + to [`6.6.0`](https://pub.dev/packages/stream_chat_flutter_core/changelog). + ## 6.6.0 🔄 Changed diff --git a/packages/stream_chat_flutter/lib/src/attachment/attachment_upload_state_builder.dart b/packages/stream_chat_flutter/lib/src/attachment/attachment_upload_state_builder.dart index 53491edaa..e05ba3971 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/attachment_upload_state_builder.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/attachment_upload_state_builder.dart @@ -36,7 +36,7 @@ class StreamAttachmentUploadStateBuilder extends StatelessWidget { @override Widget build(BuildContext context) { - if (message.status == MessageSendingStatus.sent) { + if (message.state.isCompleted) { return const Offstage(); } diff --git a/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart index efebaf692..9e9d9e551 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart @@ -236,7 +236,7 @@ class _Trailing extends StatelessWidget { final channel = StreamChannel.of(context).channel; final attachmentId = attachment.id; - if (message.status == MessageSendingStatus.sent) { + if (message.state.isCompleted) { return IconButton( icon: StreamSvgIcon.cloudDownload( color: theme.colorTheme.textHighEmphasis, diff --git a/packages/stream_chat_flutter/lib/src/indicators/sending_indicator.dart b/packages/stream_chat_flutter/lib/src/indicators/sending_indicator.dart index f4f1d4c87..2e0d46a3d 100644 --- a/packages/stream_chat_flutter/lib/src/indicators/sending_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/indicators/sending_indicator.dart @@ -30,14 +30,13 @@ class StreamSendingIndicator extends StatelessWidget { color: StreamChatTheme.of(context).colorTheme.accentPrimary, ); } - if (message.status == MessageSendingStatus.sent) { + if (message.state.isCompleted) { return StreamSvgIcon.check( size: size, color: StreamChatTheme.of(context).colorTheme.textLowEmphasis, ); } - if (message.status == MessageSendingStatus.sending || - message.status == MessageSendingStatus.updating) { + if (message.state.isOutgoing) { return Icon( Icons.access_time, size: size, diff --git a/packages/stream_chat_flutter/lib/src/message_actions_modal/message_actions_modal.dart b/packages/stream_chat_flutter/lib/src/message_actions_modal/message_actions_modal.dart index 2af8e5e6d..c9ff55953 100644 --- a/packages/stream_chat_flutter/lib/src/message_actions_modal/message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_actions_modal/message_actions_modal.dart @@ -98,9 +98,7 @@ class _MessageActionsModalState extends State { bool _showActions = true; @override - Widget build(BuildContext context) => _showMessageOptionsModal(); - - Widget _showMessageOptionsModal() { + Widget build(BuildContext context) { final mediaQueryData = MediaQuery.of(context); final user = StreamChat.of(context).currentUser; final orientation = mediaQueryData.orientation; @@ -163,7 +161,7 @@ class _MessageActionsModalState extends State { crossAxisAlignment: CrossAxisAlignment.stretch, children: [ if (widget.showReplyMessage && - widget.message.status == MessageSendingStatus.sent) + widget.message.state.isCompleted) ReplyButton( onTap: () { Navigator.of(context).pop(); @@ -173,8 +171,7 @@ class _MessageActionsModalState extends State { }, ), if (widget.showThreadReplyMessage && - (widget.message.status == - MessageSendingStatus.sent) && + (widget.message.state.isCompleted) && widget.message.parentId == null) ThreadReplyButton( message: widget.message, @@ -210,8 +207,8 @@ class _MessageActionsModalState extends State { ), if (widget.showDeleteMessage) DeleteMessageButton( - isDeleteFailed: widget.message.status == - MessageSendingStatus.failed_delete, + isDeleteFailed: + widget.message.state.isDeletingFailed, onTap: _showDeleteBottomSheet, ), ...widget.customActions diff --git a/packages/stream_chat_flutter/lib/src/message_actions_modal/resend_message_button.dart b/packages/stream_chat_flutter/lib/src/message_actions_modal/resend_message_button.dart index 73087f39e..612fe2111 100644 --- a/packages/stream_chat_flutter/lib/src/message_actions_modal/resend_message_button.dart +++ b/packages/stream_chat_flutter/lib/src/message_actions_modal/resend_message_button.dart @@ -22,16 +22,12 @@ class ResendMessageButton extends StatelessWidget { @override Widget build(BuildContext context) { - final isUpdateFailed = message.status == MessageSendingStatus.failed_update; + final isUpdateFailed = message.state.isUpdatingFailed; final streamChatThemeData = StreamChatTheme.of(context); return InkWell( onTap: () { Navigator.of(context).pop(); - if (isUpdateFailed) { - channel.updateMessage(message); - } else { - channel.sendMessage(message); - } + channel.retryMessage(message); }, child: Padding( padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), diff --git a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart index ad5790b42..ab36c3be3 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart @@ -400,8 +400,7 @@ class StreamMessageInputState extends State bool get _hasQuotedMessage => _effectiveController.message.quotedMessage != null; - bool get _isEditing => - _effectiveController.message.status != MessageSendingStatus.sending; + bool get _isEditing => !_effectiveController.message.state.isInitial; BoxBorder? _draggingBorder; @@ -587,7 +586,7 @@ class StreamMessageInputState extends State child: Column( mainAxisSize: MainAxisSize.min, children: [ - if (_hasQuotedMessage) + if (_hasQuotedMessage && !_isEditing) // Ensure this doesn't show on web & desktop PlatformWidgetBuilder( mobile: (context, child) => child, @@ -1386,10 +1385,12 @@ class StreamMessageInputState extends State skipEnrichUrl: skipEnrichUrl, ); - if (shouldKeepFocus) { - FocusScope.of(context).requestFocus(_effectiveFocusNode); - } else { - FocusScope.of(context).unfocus(); + if (mounted) { + if (shouldKeepFocus) { + FocusScope.of(context).requestFocus(_effectiveFocusNode); + } else { + FocusScope.of(context).unfocus(); + } } } diff --git a/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart index 614a0f38c..f62adf841 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart @@ -783,13 +783,11 @@ class _StreamMessageWidgetState extends State /// {@endtemplate} bool get hasQuotedMessage => widget.message.quotedMessage != null; - bool get isSendFailed => widget.message.status == MessageSendingStatus.failed; + bool get isSendFailed => widget.message.state.isSendingFailed; - bool get isUpdateFailed => - widget.message.status == MessageSendingStatus.failed_update; + bool get isUpdateFailed => widget.message.state.isUpdatingFailed; - bool get isDeleteFailed => - widget.message.status == MessageSendingStatus.failed_delete; + bool get isDeleteFailed => widget.message.state.isDeletingFailed; /// {@template isFailedState} /// Whether the message has failed to be sent, updated, or deleted. @@ -905,7 +903,7 @@ class _StreamMessageWidgetState extends State return ConditionalParentBuilder( builder: (context, child) { - if (!widget.message.isDeleted) { + if (!widget.message.state.isDeleted) { return ContextMenuArea( verticalPadding: 0, builder: (_) => _buildContextMenu(), @@ -927,7 +925,7 @@ class _StreamMessageWidgetState extends State mobile: (context, child) { return InkWell( onTap: () => widget.onMessageTap!(widget.message), - onLongPress: widget.message.isDeleted && !isFailedState + onLongPress: widget.message.state.isDeleted ? null : () => onLongPress(context), child: child, @@ -1114,14 +1112,12 @@ class _StreamMessageWidgetState extends State leading: StreamSvgIcon.iconSendMessage(), title: Text( context.translations.toggleResendOrResendEditedMessage( - isUpdateFailed: - widget.message.status == MessageSendingStatus.failed, + isUpdateFailed: widget.message.state.isUpdatingFailed, ), ), onClick: () { Navigator.of(context, rootNavigator: true).pop(); - final isUpdateFailed = - widget.message.status == MessageSendingStatus.failed_update; + final isUpdateFailed = widget.message.state.isUpdatingFailed; final channel = StreamChannel.of(context).channel; if (isUpdateFailed) { channel.updateMessage(widget.message); @@ -1216,8 +1212,7 @@ class _StreamMessageWidgetState extends State } void onLongPress(BuildContext context) { - if (widget.message.isEphemeral || - widget.message.status == MessageSendingStatus.sending) { + if (widget.message.isEphemeral || widget.message.state.isOutgoing) { return; } diff --git a/packages/stream_chat_flutter/lib/src/message_widget/sending_indicator_builder.dart b/packages/stream_chat_flutter/lib/src/message_widget/sending_indicator_builder.dart index 616737521..0a7b6c412 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/sending_indicator_builder.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/sending_indicator_builder.dart @@ -42,25 +42,21 @@ class SendingIndicatorBuilder extends StatelessWidget { final channel = this.channel ?? StreamChannel.of(context).channel; final memberCount = channel.memberCount ?? 0; - if (hasNonUrlAttachments && - (message.status == MessageSendingStatus.sending || - message.status == MessageSendingStatus.updating)) { + if (hasNonUrlAttachments && message.state.isOutgoing) { final totalAttachments = message.attachments.length; - final uploadRemaining = - message.attachments.where((it) => !it.uploadState.isSuccess).length; - if (uploadRemaining == 0) { - return StreamSvgIcon.check( - size: style!.fontSize, - color: IconTheme.of(context).color!.withOpacity(0.5), + final attachmentsToUpload = message.attachments.where((it) { + return !it.uploadState.isSuccess; + }); + + if (attachmentsToUpload.isNotEmpty) { + return Text( + context.translations.attachmentsUploadProgressText( + remaining: attachmentsToUpload.length, + total: totalAttachments, + ), + style: style, ); } - return Text( - context.translations.attachmentsUploadProgressText( - remaining: uploadRemaining, - total: totalAttachments, - ), - style: style, - ); } return BetterStreamBuilder>( diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart index 6b2a9b4fe..39951d53a 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart @@ -91,7 +91,7 @@ class StreamChannelListTile extends StatelessWidget { /// The widget builder for the sending indicator. /// /// `Message` is the last message in the channel, Use it to determine the - /// status using [Message.status]. + /// status using [Message.state]. final Widget Function(BuildContext, Message)? sendingIndicatorBuilder; /// True if the tile is in a selected state. diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index f120f931c..4d8f6e970 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 6.6.0 +version: 6.7.0 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -38,7 +38,7 @@ dependencies: rxdart: ^0.27.7 share_plus: ^7.0.2 shimmer: ^3.0.0 - stream_chat_flutter_core: ^6.5.0 + stream_chat_flutter_core: ^6.6.0 synchronized: ^3.1.0 thumblr: ^0.0.4 url_launcher: ^6.1.11 diff --git a/packages/stream_chat_flutter/test/src/indicators/sending_indicator_test.dart b/packages/stream_chat_flutter/test/src/indicators/sending_indicator_test.dart index e47b43cd7..dd84aef9f 100644 --- a/packages/stream_chat_flutter/test/src/indicators/sending_indicator_test.dart +++ b/packages/stream_chat_flutter/test/src/indicators/sending_indicator_test.dart @@ -4,24 +4,51 @@ import 'package:golden_toolkit/golden_toolkit.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; void main() { - testWidgets('StreamSendingIndicator shows an Icon', (tester) async { - await tester.pumpWidget( - MaterialApp( - home: StreamChatTheme( - data: StreamChatThemeData.light(), - child: Scaffold( - body: Center( - child: StreamSendingIndicator( - message: Message(), + testWidgets( + 'StreamSendingIndicator shows sizedBox if messsage state is initial', + (tester) async { + await tester.pumpWidget( + MaterialApp( + home: StreamChatTheme( + data: StreamChatThemeData.light(), + child: Scaffold( + body: Center( + child: StreamSendingIndicator( + message: Message(), + ), ), ), ), ), - ), - ); + ); - expect(find.byType(Icon), findsOneWidget); - }); + expect(find.byType(SizedBox), findsOneWidget); + }, + ); + + testWidgets( + 'StreamSendingIndicator shows an Icon if message state is sending', + (tester) async { + await tester.pumpWidget( + MaterialApp( + home: StreamChatTheme( + data: StreamChatThemeData.light(), + child: Scaffold( + body: Center( + child: StreamSendingIndicator( + message: Message( + state: MessageState.sending, + ), + ), + ), + ), + ), + ), + ); + + expect(find.byType(Icon), findsOneWidget); + }, + ); testGoldens( 'golden test for StreamSendingIndicator with StreamSvgIcon.checkAll', diff --git a/packages/stream_chat_flutter/test/src/message_actions_modal/message_actions_modal_test.dart b/packages/stream_chat_flutter/test/src/message_actions_modal/message_actions_modal_test.dart index 590778179..0dd0e1186 100644 --- a/packages/stream_chat_flutter/test/src/message_actions_modal/message_actions_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/message_actions_modal/message_actions_modal_test.dart @@ -40,7 +40,7 @@ void main() { user: User( id: 'user-id', ), - status: MessageSendingStatus.sent, + state: MessageState.sent, ), messageWidget: const Text( 'test', @@ -211,7 +211,7 @@ void main() { user: User( id: 'user-id', ), - status: MessageSendingStatus.sent, + state: MessageState.sent, ), messageTheme: streamTheme.ownMessageTheme, ), @@ -262,7 +262,7 @@ void main() { user: User( id: 'user-id', ), - status: MessageSendingStatus.sent, + state: MessageState.sent, ), messageTheme: streamTheme.ownMessageTheme, ), @@ -431,66 +431,24 @@ void main() { ); testWidgets( - 'tapping on resend should call send message', + 'tapping on resend should call retry message', (WidgetTester tester) async { final client = MockClient(); final clientState = MockClientState(); final channel = MockChannel(); - when(() => client.state).thenReturn(clientState); - when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); - when(() => channel.sendMessage(any())) - .thenAnswer((_) async => SendMessageResponse()); - - final themeData = ThemeData(); - final streamTheme = StreamChatThemeData.fromTheme(themeData); - - await tester.pumpWidget( - MaterialApp( - builder: (context, child) => StreamChat( - client: client, - streamChatThemeData: streamTheme, - child: child, - ), - theme: themeData, - home: StreamChannel( - showLoading: false, - channel: channel, - child: SizedBox( - child: MessageActionsModal( - messageWidget: const Text('test'), - message: Message( - status: MessageSendingStatus.failed, - text: 'test', - user: User( - id: 'user-id', - ), - ), - messageTheme: streamTheme.ownMessageTheme, - ), - ), - ), + final message = Message( + state: MessageState.sendingFailed, + text: 'test', + user: User( + id: 'user-id', ), ); - await tester.pumpAndSettle(); - - await tester.tap(find.text('Resend')); - - verify(() => channel.sendMessage(any())).called(1); - }, - ); - - testWidgets( - 'tapping on resend should call update message if editing the message', - (WidgetTester tester) async { - final client = MockClient(); - final clientState = MockClientState(); - final channel = MockChannel(); when(() => client.state).thenReturn(clientState); when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); - when(() => channel.updateMessage(any())) - .thenAnswer((_) async => UpdateMessageResponse()); + when(() => channel.retryMessage(message)) + .thenAnswer((_) async => SendMessageResponse()); final themeData = ThemeData(); final streamTheme = StreamChatThemeData.fromTheme(themeData); @@ -509,13 +467,7 @@ void main() { child: SizedBox( child: MessageActionsModal( messageWidget: const Text('test'), - message: Message( - status: MessageSendingStatus.failed_update, - text: 'test', - user: User( - id: 'user-id', - ), - ), + message: message, messageTheme: streamTheme.ownMessageTheme, ), ), @@ -524,9 +476,9 @@ void main() { ); await tester.pumpAndSettle(); - await tester.tap(find.text('Resend Edited Message')); + await tester.tap(find.text('Resend')); - verify(() => channel.updateMessage(any())).called(1); + verify(() => channel.retryMessage(message)).called(1); }, ); diff --git a/packages/stream_chat_flutter_core/CHANGELOG.md b/packages/stream_chat_flutter_core/CHANGELOG.md index edb10b9aa..22d26ec8f 100644 --- a/packages/stream_chat_flutter_core/CHANGELOG.md +++ b/packages/stream_chat_flutter_core/CHANGELOG.md @@ -1,6 +1,11 @@ +## 6.6.0 + +- Updated `stream_chat` dependency to [`6.6.0`](https://pub.dev/packages/stream_chat/changelog). + ## 6.5.0 - Updated minimum supported `SDK` version to Flutter 3.7/Dart 2.19 +- Updated `stream_chat` dependency to [`6.5.0`](https://pub.dev/packages/stream_chat/changelog). ## 6.4.0 diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index 5f30475c6..399f986f0 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter_core homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK Core. Build your own chat experience using Dart and Flutter. -version: 6.5.0 +version: 6.6.0 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -17,7 +17,7 @@ dependencies: freezed_annotation: ^2.2.0 meta: ^1.8.0 rxdart: ^0.27.7 - stream_chat: ^6.5.0 + stream_chat: ^6.6.0 dev_dependencies: build_runner: ^2.3.3 diff --git a/packages/stream_chat_localizations/CHANGELOG.md b/packages/stream_chat_localizations/CHANGELOG.md index 85d391961..527cf5140 100644 --- a/packages/stream_chat_localizations/CHANGELOG.md +++ b/packages/stream_chat_localizations/CHANGELOG.md @@ -1,6 +1,11 @@ +## 5.7.0 + +* Updated `stream_chat_flutter` dependency to [`6.7.0`](https://pub.dev/packages/stream_chat_flutter/changelog). + ## 5.6.0 -- Updated minimum supported `SDK` version to Flutter 3.7/Dart 2.19 +* Updated minimum supported `SDK` version to Flutter 3.7/Dart 2.19 +* Updated `stream_chat_flutter` dependency to [`6.6.0`](https://pub.dev/packages/stream_chat_flutter/changelog). ## 5.5.0 diff --git a/packages/stream_chat_localizations/pubspec.yaml b/packages/stream_chat_localizations/pubspec.yaml index a933f4925..00a53a934 100644 --- a/packages/stream_chat_localizations/pubspec.yaml +++ b/packages/stream_chat_localizations/pubspec.yaml @@ -1,6 +1,6 @@ name: stream_chat_localizations description: The Official localizations for Stream Chat Flutter, a service for building chat applications -version: 5.6.0 +version: 5.7.0 homepage: https://github.com/GetStream/stream-chat-flutter repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -14,7 +14,7 @@ dependencies: sdk: flutter flutter_localizations: sdk: flutter - stream_chat_flutter: ^6.6.0 + stream_chat_flutter: ^6.7.0 dev_dependencies: flutter_test: diff --git a/packages/stream_chat_persistence/CHANGELOG.md b/packages/stream_chat_persistence/CHANGELOG.md index 245797b79..056ab3c83 100644 --- a/packages/stream_chat_persistence/CHANGELOG.md +++ b/packages/stream_chat_persistence/CHANGELOG.md @@ -1,3 +1,7 @@ +## 6.6.0 + +- Updated `stream_chat` dependency to [`6.6.0`](https://pub.dev/packages/stream_chat/changelog). + ## 6.5.0 - Updated minimum supported `SDK` version to Flutter 3.7/Dart 2.19 diff --git a/packages/stream_chat_persistence/lib/src/converter/converter.dart b/packages/stream_chat_persistence/lib/src/converter/converter.dart index a5d7bbdc5..98d34e93c 100644 --- a/packages/stream_chat_persistence/lib/src/converter/converter.dart +++ b/packages/stream_chat_persistence/lib/src/converter/converter.dart @@ -1,3 +1,2 @@ export 'list_converter.dart'; export 'map_converter.dart'; -export 'message_sending_status_converter.dart'; diff --git a/packages/stream_chat_persistence/lib/src/converter/message_sending_status_converter.dart b/packages/stream_chat_persistence/lib/src/converter/message_sending_status_converter.dart deleted file mode 100644 index 7eb194a3c..000000000 --- a/packages/stream_chat_persistence/lib/src/converter/message_sending_status_converter.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'package:drift/drift.dart'; -import 'package:stream_chat/stream_chat.dart'; - -/// Maps a [MessageSendingStatus] into a [int] understood -/// by the sqlite backend. -class MessageSendingStatusConverter - extends TypeConverter { - @override - MessageSendingStatus fromSql(int fromDb) { - switch (fromDb) { - case 0: - return MessageSendingStatus.sending; - case 1: - return MessageSendingStatus.sent; - case 2: - return MessageSendingStatus.failed; - case 3: - return MessageSendingStatus.updating; - case 4: - return MessageSendingStatus.failed_update; - case 5: - return MessageSendingStatus.deleting; - case 6: - return MessageSendingStatus.failed_delete; - } - return MessageSendingStatus.sending; - } - - @override - int toSql(MessageSendingStatus value) { - switch (value) { - case MessageSendingStatus.sending: - return 0; - case MessageSendingStatus.sent: - return 1; - case MessageSendingStatus.failed: - return 2; - case MessageSendingStatus.updating: - return 3; - case MessageSendingStatus.failed_update: - return 4; - case MessageSendingStatus.deleting: - return 5; - case MessageSendingStatus.failed_delete: - return 6; - } - } -} diff --git a/packages/stream_chat_persistence/lib/src/db/drift_chat_database.dart b/packages/stream_chat_persistence/lib/src/db/drift_chat_database.dart index f0b07095c..7dd71b25a 100644 --- a/packages/stream_chat_persistence/lib/src/db/drift_chat_database.dart +++ b/packages/stream_chat_persistence/lib/src/db/drift_chat_database.dart @@ -1,5 +1,4 @@ import 'package:drift/drift.dart'; -import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_persistence/src/converter/converter.dart'; import 'package:stream_chat_persistence/src/dao/dao.dart'; import 'package:stream_chat_persistence/src/entity/entity.dart'; diff --git a/packages/stream_chat_persistence/lib/src/db/drift_chat_database.g.dart b/packages/stream_chat_persistence/lib/src/db/drift_chat_database.g.dart index 04572675d..0ee57dd0c 100644 --- a/packages/stream_chat_persistence/lib/src/db/drift_chat_database.g.dart +++ b/packages/stream_chat_persistence/lib/src/db/drift_chat_database.g.dart @@ -667,14 +667,11 @@ class $MessagesTable extends Messages attachments = GeneratedColumn('attachments', aliasedName, false, type: DriftSqlType.string, requiredDuringInsert: true) .withConverter>($MessagesTable.$converterattachments); - static const VerificationMeta _statusMeta = const VerificationMeta('status'); - @override - late final GeneratedColumnWithTypeConverter - status = GeneratedColumn('status', aliasedName, false, - type: DriftSqlType.int, - requiredDuringInsert: false, - defaultValue: const Constant(1)) - .withConverter($MessagesTable.$converterstatus); + static const VerificationMeta _stateMeta = const VerificationMeta('state'); + @override + late final GeneratedColumn state = GeneratedColumn( + 'state', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); static const VerificationMeta _typeMeta = const VerificationMeta('type'); @override late final GeneratedColumn type = GeneratedColumn( @@ -856,7 +853,7 @@ class $MessagesTable extends Messages id, messageText, attachments, - status, + state, type, mentionedUsers, reactionCounts, @@ -903,7 +900,12 @@ class $MessagesTable extends Messages data['message_text']!, _messageTextMeta)); } context.handle(_attachmentsMeta, const VerificationResult.success()); - context.handle(_statusMeta, const VerificationResult.success()); + if (data.containsKey('state')) { + context.handle( + _stateMeta, state.isAcceptableOrUnknown(data['state']!, _stateMeta)); + } else if (isInserting) { + context.missing(_stateMeta); + } if (data.containsKey('type')) { context.handle( _typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); @@ -1027,9 +1029,8 @@ class $MessagesTable extends Messages attachments: $MessagesTable.$converterattachments.fromSql(attachedDatabase .typeMapping .read(DriftSqlType.string, data['${effectivePrefix}attachments'])!), - status: $MessagesTable.$converterstatus.fromSql(attachedDatabase - .typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}status'])!), + state: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}state'])!, type: attachedDatabase.typeMapping .read(DriftSqlType.string, data['${effectivePrefix}type'])!, mentionedUsers: $MessagesTable.$convertermentionedUsers.fromSql( @@ -1092,8 +1093,6 @@ class $MessagesTable extends Messages static TypeConverter, String> $converterattachments = ListConverter(); - static TypeConverter $converterstatus = - MessageSendingStatusConverter(); static TypeConverter, String> $convertermentionedUsers = ListConverter(); static TypeConverter, String> $converterreactionCounts = @@ -1123,8 +1122,8 @@ class MessageEntity extends DataClass implements Insertable { /// or generated from a command or as a result of URL scraping. final List attachments; - /// The status of a sending message - final MessageSendingStatus status; + /// The current state of the message. + final String state; /// The message type final String type; @@ -1201,7 +1200,7 @@ class MessageEntity extends DataClass implements Insertable { {required this.id, this.messageText, required this.attachments, - required this.status, + required this.state, required this.type, required this.mentionedUsers, this.reactionCounts, @@ -1237,10 +1236,7 @@ class MessageEntity extends DataClass implements Insertable { final converter = $MessagesTable.$converterattachments; map['attachments'] = Variable(converter.toSql(attachments)); } - { - final converter = $MessagesTable.$converterstatus; - map['status'] = Variable(converter.toSql(status)); - } + map['state'] = Variable(state); map['type'] = Variable(type); { final converter = $MessagesTable.$convertermentionedUsers; @@ -1323,7 +1319,7 @@ class MessageEntity extends DataClass implements Insertable { id: serializer.fromJson(json['id']), messageText: serializer.fromJson(json['messageText']), attachments: serializer.fromJson>(json['attachments']), - status: serializer.fromJson(json['status']), + state: serializer.fromJson(json['state']), type: serializer.fromJson(json['type']), mentionedUsers: serializer.fromJson>(json['mentionedUsers']), reactionCounts: @@ -1359,7 +1355,7 @@ class MessageEntity extends DataClass implements Insertable { 'id': serializer.toJson(id), 'messageText': serializer.toJson(messageText), 'attachments': serializer.toJson>(attachments), - 'status': serializer.toJson(status), + 'state': serializer.toJson(state), 'type': serializer.toJson(type), 'mentionedUsers': serializer.toJson>(mentionedUsers), 'reactionCounts': serializer.toJson?>(reactionCounts), @@ -1391,7 +1387,7 @@ class MessageEntity extends DataClass implements Insertable { {String? id, Value messageText = const Value.absent(), List? attachments, - MessageSendingStatus? status, + String? state, String? type, List? mentionedUsers, Value?> reactionCounts = const Value.absent(), @@ -1420,7 +1416,7 @@ class MessageEntity extends DataClass implements Insertable { id: id ?? this.id, messageText: messageText.present ? messageText.value : this.messageText, attachments: attachments ?? this.attachments, - status: status ?? this.status, + state: state ?? this.state, type: type ?? this.type, mentionedUsers: mentionedUsers ?? this.mentionedUsers, reactionCounts: @@ -1467,7 +1463,7 @@ class MessageEntity extends DataClass implements Insertable { ..write('id: $id, ') ..write('messageText: $messageText, ') ..write('attachments: $attachments, ') - ..write('status: $status, ') + ..write('state: $state, ') ..write('type: $type, ') ..write('mentionedUsers: $mentionedUsers, ') ..write('reactionCounts: $reactionCounts, ') @@ -1501,7 +1497,7 @@ class MessageEntity extends DataClass implements Insertable { id, messageText, attachments, - status, + state, type, mentionedUsers, reactionCounts, @@ -1534,7 +1530,7 @@ class MessageEntity extends DataClass implements Insertable { other.id == this.id && other.messageText == this.messageText && other.attachments == this.attachments && - other.status == this.status && + other.state == this.state && other.type == this.type && other.mentionedUsers == this.mentionedUsers && other.reactionCounts == this.reactionCounts && @@ -1565,7 +1561,7 @@ class MessagesCompanion extends UpdateCompanion { final Value id; final Value messageText; final Value> attachments; - final Value status; + final Value state; final Value type; final Value> mentionedUsers; final Value?> reactionCounts; @@ -1595,7 +1591,7 @@ class MessagesCompanion extends UpdateCompanion { this.id = const Value.absent(), this.messageText = const Value.absent(), this.attachments = const Value.absent(), - this.status = const Value.absent(), + this.state = const Value.absent(), this.type = const Value.absent(), this.mentionedUsers = const Value.absent(), this.reactionCounts = const Value.absent(), @@ -1626,7 +1622,7 @@ class MessagesCompanion extends UpdateCompanion { required String id, this.messageText = const Value.absent(), required List attachments, - this.status = const Value.absent(), + required String state, this.type = const Value.absent(), required List mentionedUsers, this.reactionCounts = const Value.absent(), @@ -1654,13 +1650,14 @@ class MessagesCompanion extends UpdateCompanion { this.rowid = const Value.absent(), }) : id = Value(id), attachments = Value(attachments), + state = Value(state), mentionedUsers = Value(mentionedUsers), channelCid = Value(channelCid); static Insertable custom({ Expression? id, Expression? messageText, Expression? attachments, - Expression? status, + Expression? state, Expression? type, Expression? mentionedUsers, Expression? reactionCounts, @@ -1691,7 +1688,7 @@ class MessagesCompanion extends UpdateCompanion { if (id != null) 'id': id, if (messageText != null) 'message_text': messageText, if (attachments != null) 'attachments': attachments, - if (status != null) 'status': status, + if (state != null) 'state': state, if (type != null) 'type': type, if (mentionedUsers != null) 'mentioned_users': mentionedUsers, if (reactionCounts != null) 'reaction_counts': reactionCounts, @@ -1724,7 +1721,7 @@ class MessagesCompanion extends UpdateCompanion { {Value? id, Value? messageText, Value>? attachments, - Value? status, + Value? state, Value? type, Value>? mentionedUsers, Value?>? reactionCounts, @@ -1754,7 +1751,7 @@ class MessagesCompanion extends UpdateCompanion { id: id ?? this.id, messageText: messageText ?? this.messageText, attachments: attachments ?? this.attachments, - status: status ?? this.status, + state: state ?? this.state, type: type ?? this.type, mentionedUsers: mentionedUsers ?? this.mentionedUsers, reactionCounts: reactionCounts ?? this.reactionCounts, @@ -1796,9 +1793,8 @@ class MessagesCompanion extends UpdateCompanion { final converter = $MessagesTable.$converterattachments; map['attachments'] = Variable(converter.toSql(attachments.value)); } - if (status.present) { - final converter = $MessagesTable.$converterstatus; - map['status'] = Variable(converter.toSql(status.value)); + if (state.present) { + map['state'] = Variable(state.value); } if (type.present) { map['type'] = Variable(type.value); @@ -1892,7 +1888,7 @@ class MessagesCompanion extends UpdateCompanion { ..write('id: $id, ') ..write('messageText: $messageText, ') ..write('attachments: $attachments, ') - ..write('status: $status, ') + ..write('state: $state, ') ..write('type: $type, ') ..write('mentionedUsers: $mentionedUsers, ') ..write('reactionCounts: $reactionCounts, ') @@ -1948,15 +1944,11 @@ class $PinnedMessagesTable extends PinnedMessages type: DriftSqlType.string, requiredDuringInsert: true) .withConverter>( $PinnedMessagesTable.$converterattachments); - static const VerificationMeta _statusMeta = const VerificationMeta('status'); - @override - late final GeneratedColumnWithTypeConverter - status = GeneratedColumn('status', aliasedName, false, - type: DriftSqlType.int, - requiredDuringInsert: false, - defaultValue: const Constant(1)) - .withConverter( - $PinnedMessagesTable.$converterstatus); + static const VerificationMeta _stateMeta = const VerificationMeta('state'); + @override + late final GeneratedColumn state = GeneratedColumn( + 'state', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); static const VerificationMeta _typeMeta = const VerificationMeta('type'); @override late final GeneratedColumn type = GeneratedColumn( @@ -2140,7 +2132,7 @@ class $PinnedMessagesTable extends PinnedMessages id, messageText, attachments, - status, + state, type, mentionedUsers, reactionCounts, @@ -2188,7 +2180,12 @@ class $PinnedMessagesTable extends PinnedMessages data['message_text']!, _messageTextMeta)); } context.handle(_attachmentsMeta, const VerificationResult.success()); - context.handle(_statusMeta, const VerificationResult.success()); + if (data.containsKey('state')) { + context.handle( + _stateMeta, state.isAcceptableOrUnknown(data['state']!, _stateMeta)); + } else if (isInserting) { + context.missing(_stateMeta); + } if (data.containsKey('type')) { context.handle( _typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); @@ -2312,9 +2309,8 @@ class $PinnedMessagesTable extends PinnedMessages attachments: $PinnedMessagesTable.$converterattachments.fromSql( attachedDatabase.typeMapping.read( DriftSqlType.string, data['${effectivePrefix}attachments'])!), - status: $PinnedMessagesTable.$converterstatus.fromSql(attachedDatabase - .typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}status'])!), + state: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}state'])!, type: attachedDatabase.typeMapping .read(DriftSqlType.string, data['${effectivePrefix}type'])!, mentionedUsers: $PinnedMessagesTable.$convertermentionedUsers.fromSql( @@ -2378,8 +2374,6 @@ class $PinnedMessagesTable extends PinnedMessages static TypeConverter, String> $converterattachments = ListConverter(); - static TypeConverter $converterstatus = - MessageSendingStatusConverter(); static TypeConverter, String> $convertermentionedUsers = ListConverter(); static TypeConverter, String> $converterreactionCounts = @@ -2410,8 +2404,8 @@ class PinnedMessageEntity extends DataClass /// or generated from a command or as a result of URL scraping. final List attachments; - /// The status of a sending message - final MessageSendingStatus status; + /// The current state of the message. + final String state; /// The message type final String type; @@ -2488,7 +2482,7 @@ class PinnedMessageEntity extends DataClass {required this.id, this.messageText, required this.attachments, - required this.status, + required this.state, required this.type, required this.mentionedUsers, this.reactionCounts, @@ -2524,10 +2518,7 @@ class PinnedMessageEntity extends DataClass final converter = $PinnedMessagesTable.$converterattachments; map['attachments'] = Variable(converter.toSql(attachments)); } - { - final converter = $PinnedMessagesTable.$converterstatus; - map['status'] = Variable(converter.toSql(status)); - } + map['state'] = Variable(state); map['type'] = Variable(type); { final converter = $PinnedMessagesTable.$convertermentionedUsers; @@ -2610,7 +2601,7 @@ class PinnedMessageEntity extends DataClass id: serializer.fromJson(json['id']), messageText: serializer.fromJson(json['messageText']), attachments: serializer.fromJson>(json['attachments']), - status: serializer.fromJson(json['status']), + state: serializer.fromJson(json['state']), type: serializer.fromJson(json['type']), mentionedUsers: serializer.fromJson>(json['mentionedUsers']), reactionCounts: @@ -2646,7 +2637,7 @@ class PinnedMessageEntity extends DataClass 'id': serializer.toJson(id), 'messageText': serializer.toJson(messageText), 'attachments': serializer.toJson>(attachments), - 'status': serializer.toJson(status), + 'state': serializer.toJson(state), 'type': serializer.toJson(type), 'mentionedUsers': serializer.toJson>(mentionedUsers), 'reactionCounts': serializer.toJson?>(reactionCounts), @@ -2678,7 +2669,7 @@ class PinnedMessageEntity extends DataClass {String? id, Value messageText = const Value.absent(), List? attachments, - MessageSendingStatus? status, + String? state, String? type, List? mentionedUsers, Value?> reactionCounts = const Value.absent(), @@ -2707,7 +2698,7 @@ class PinnedMessageEntity extends DataClass id: id ?? this.id, messageText: messageText.present ? messageText.value : this.messageText, attachments: attachments ?? this.attachments, - status: status ?? this.status, + state: state ?? this.state, type: type ?? this.type, mentionedUsers: mentionedUsers ?? this.mentionedUsers, reactionCounts: @@ -2754,7 +2745,7 @@ class PinnedMessageEntity extends DataClass ..write('id: $id, ') ..write('messageText: $messageText, ') ..write('attachments: $attachments, ') - ..write('status: $status, ') + ..write('state: $state, ') ..write('type: $type, ') ..write('mentionedUsers: $mentionedUsers, ') ..write('reactionCounts: $reactionCounts, ') @@ -2788,7 +2779,7 @@ class PinnedMessageEntity extends DataClass id, messageText, attachments, - status, + state, type, mentionedUsers, reactionCounts, @@ -2821,7 +2812,7 @@ class PinnedMessageEntity extends DataClass other.id == this.id && other.messageText == this.messageText && other.attachments == this.attachments && - other.status == this.status && + other.state == this.state && other.type == this.type && other.mentionedUsers == this.mentionedUsers && other.reactionCounts == this.reactionCounts && @@ -2852,7 +2843,7 @@ class PinnedMessagesCompanion extends UpdateCompanion { final Value id; final Value messageText; final Value> attachments; - final Value status; + final Value state; final Value type; final Value> mentionedUsers; final Value?> reactionCounts; @@ -2882,7 +2873,7 @@ class PinnedMessagesCompanion extends UpdateCompanion { this.id = const Value.absent(), this.messageText = const Value.absent(), this.attachments = const Value.absent(), - this.status = const Value.absent(), + this.state = const Value.absent(), this.type = const Value.absent(), this.mentionedUsers = const Value.absent(), this.reactionCounts = const Value.absent(), @@ -2913,7 +2904,7 @@ class PinnedMessagesCompanion extends UpdateCompanion { required String id, this.messageText = const Value.absent(), required List attachments, - this.status = const Value.absent(), + required String state, this.type = const Value.absent(), required List mentionedUsers, this.reactionCounts = const Value.absent(), @@ -2941,13 +2932,14 @@ class PinnedMessagesCompanion extends UpdateCompanion { this.rowid = const Value.absent(), }) : id = Value(id), attachments = Value(attachments), + state = Value(state), mentionedUsers = Value(mentionedUsers), channelCid = Value(channelCid); static Insertable custom({ Expression? id, Expression? messageText, Expression? attachments, - Expression? status, + Expression? state, Expression? type, Expression? mentionedUsers, Expression? reactionCounts, @@ -2978,7 +2970,7 @@ class PinnedMessagesCompanion extends UpdateCompanion { if (id != null) 'id': id, if (messageText != null) 'message_text': messageText, if (attachments != null) 'attachments': attachments, - if (status != null) 'status': status, + if (state != null) 'state': state, if (type != null) 'type': type, if (mentionedUsers != null) 'mentioned_users': mentionedUsers, if (reactionCounts != null) 'reaction_counts': reactionCounts, @@ -3011,7 +3003,7 @@ class PinnedMessagesCompanion extends UpdateCompanion { {Value? id, Value? messageText, Value>? attachments, - Value? status, + Value? state, Value? type, Value>? mentionedUsers, Value?>? reactionCounts, @@ -3041,7 +3033,7 @@ class PinnedMessagesCompanion extends UpdateCompanion { id: id ?? this.id, messageText: messageText ?? this.messageText, attachments: attachments ?? this.attachments, - status: status ?? this.status, + state: state ?? this.state, type: type ?? this.type, mentionedUsers: mentionedUsers ?? this.mentionedUsers, reactionCounts: reactionCounts ?? this.reactionCounts, @@ -3083,9 +3075,8 @@ class PinnedMessagesCompanion extends UpdateCompanion { final converter = $PinnedMessagesTable.$converterattachments; map['attachments'] = Variable(converter.toSql(attachments.value)); } - if (status.present) { - final converter = $PinnedMessagesTable.$converterstatus; - map['status'] = Variable(converter.toSql(status.value)); + if (state.present) { + map['state'] = Variable(state.value); } if (type.present) { map['type'] = Variable(type.value); @@ -3179,7 +3170,7 @@ class PinnedMessagesCompanion extends UpdateCompanion { ..write('id: $id, ') ..write('messageText: $messageText, ') ..write('attachments: $attachments, ') - ..write('status: $status, ') + ..write('state: $state, ') ..write('type: $type, ') ..write('mentionedUsers: $mentionedUsers, ') ..write('reactionCounts: $reactionCounts, ') diff --git a/packages/stream_chat_persistence/lib/src/entity/messages.dart b/packages/stream_chat_persistence/lib/src/entity/messages.dart index 5d5856192..e37d4de1d 100644 --- a/packages/stream_chat_persistence/lib/src/entity/messages.dart +++ b/packages/stream_chat_persistence/lib/src/entity/messages.dart @@ -2,7 +2,6 @@ import 'package:drift/drift.dart'; import 'package:stream_chat_persistence/src/converter/list_converter.dart'; import 'package:stream_chat_persistence/src/converter/map_converter.dart'; -import 'package:stream_chat_persistence/src/converter/message_sending_status_converter.dart'; import 'package:stream_chat_persistence/src/entity/channels.dart'; /// Represents a [Messages] table in [MoorChatDatabase]. @@ -18,10 +17,8 @@ class Messages extends Table { /// or generated from a command or as a result of URL scraping. TextColumn get attachments => text().map(ListConverter())(); - /// The status of a sending message - IntColumn get status => integer() - .withDefault(const Constant(1)) - .map(MessageSendingStatusConverter())(); + /// The current state of the message. + TextColumn get state => text()(); /// The message type TextColumn get type => text().withDefault(const Constant('regular'))(); diff --git a/packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart index 0fd69db88..6e656b207 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/message_mapper.dart @@ -30,7 +30,7 @@ extension MessageEntityX on MessageEntity { localDeletedAt: localDeletedAt, id: id, type: type, - status: status, + state: MessageState.fromJson(jsonDecode(state)), command: command, parentId: parentId, quotedMessageId: quotedMessageId, @@ -70,7 +70,7 @@ extension MessageX on Message { reactionScores: reactionScores, reactionCounts: reactionCounts, mentionedUsers: mentionedUsers.map(jsonEncode).toList(), - status: status, + state: jsonEncode(state), remoteUpdatedAt: remoteUpdatedAt, localUpdatedAt: localUpdatedAt, extraData: extraData, diff --git a/packages/stream_chat_persistence/lib/src/mapper/pinned_message_mapper.dart b/packages/stream_chat_persistence/lib/src/mapper/pinned_message_mapper.dart index d4089a7c5..77ee78541 100644 --- a/packages/stream_chat_persistence/lib/src/mapper/pinned_message_mapper.dart +++ b/packages/stream_chat_persistence/lib/src/mapper/pinned_message_mapper.dart @@ -30,7 +30,7 @@ extension PinnedMessageEntityX on PinnedMessageEntity { localDeletedAt: localDeletedAt, id: id, type: type, - status: status, + state: MessageState.fromJson(jsonDecode(state)), command: command, parentId: parentId, quotedMessageId: quotedMessageId, @@ -71,7 +71,7 @@ extension PMessageX on Message { reactionScores: reactionScores, reactionCounts: reactionCounts, mentionedUsers: mentionedUsers.map(jsonEncode).toList(), - status: status, + state: jsonEncode(state), remoteUpdatedAt: remoteUpdatedAt, localUpdatedAt: localUpdatedAt, extraData: extraData, diff --git a/packages/stream_chat_persistence/pubspec.yaml b/packages/stream_chat_persistence/pubspec.yaml index aa4ae55ff..8dea75836 100644 --- a/packages/stream_chat_persistence/pubspec.yaml +++ b/packages/stream_chat_persistence/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_persistence homepage: https://github.com/GetStream/stream-chat-flutter description: Official Stream Chat Persistence library. Build your own chat experience using Dart and Flutter. -version: 6.5.0 +version: 6.6.0 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -18,7 +18,7 @@ dependencies: path: ^1.8.2 path_provider: ^2.0.15 sqlite3_flutter_libs: ^0.5.15 - stream_chat: ^6.5.0 + stream_chat: ^6.6.0 dev_dependencies: build_runner: ^2.3.3 diff --git a/packages/stream_chat_persistence/test/src/converter/message_sending_status_converter_test.dart b/packages/stream_chat_persistence/test/src/converter/message_sending_status_converter_test.dart deleted file mode 100644 index 9961f9171..000000000 --- a/packages/stream_chat_persistence/test/src/converter/message_sending_status_converter_test.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:stream_chat/stream_chat.dart'; -import 'package:stream_chat_persistence/src/converter/message_sending_status_converter.dart'; - -void main() { - group('fromSql', () { - final statusConverter = MessageSendingStatusConverter(); - - test('should return expected status if status code is provided', () { - final res = statusConverter.fromSql(6); - expect(res, MessageSendingStatus.failed_delete); - }); - }); - - group('toSql', () { - final statusConverter = MessageSendingStatusConverter(); - - test('should return expected code if the status is provided', () { - final res = statusConverter.toSql(MessageSendingStatus.failed_delete); - expect(res, 6); - }); - }); -} diff --git a/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart index b6524861a..1134d0ee7 100644 --- a/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart @@ -52,7 +52,7 @@ void main() { mentionedUsers: [ jsonEncode(User(id: 'testuser')), ], - status: MessageSendingStatus.sent, + state: jsonEncode(MessageState.sent), localUpdatedAt: DateTime.now(), remoteUpdatedAt: DateTime.now().add(const Duration(seconds: 1)), extraData: {'extra_test_data': 'extraData'}, @@ -96,7 +96,7 @@ void main() { expect(message.replyCount, entity.replyCount); expect(message.reactionScores, entity.reactionScores); expect(message.reactionCounts, entity.reactionCounts); - expect(message.status, entity.status); + expect(message.state, MessageState.fromJson(jsonDecode(entity.state))); expect(message.localUpdatedAt, isSameDateAs(entity.localUpdatedAt)); expect(message.remoteUpdatedAt, isSameDateAs(entity.remoteUpdatedAt)); expect(message.extraData, entity.extraData); @@ -197,7 +197,7 @@ void main() { entity.mentionedUsers, message.mentionedUsers.map(jsonEncode).toList()); expect(entity.reactionScores, message.reactionScores); expect(entity.reactionCounts, message.reactionCounts); - expect(entity.status, message.status); + expect(entity.state, jsonEncode(message.state)); expect(entity.localUpdatedAt, isSameDateAs(message.localUpdatedAt)); expect(entity.remoteUpdatedAt, isSameDateAs(message.remoteUpdatedAt)); expect(entity.extraData, message.extraData); diff --git a/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart index 490af8ca6..569b75e0c 100644 --- a/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart @@ -52,7 +52,7 @@ void main() { mentionedUsers: [ jsonEncode(User(id: 'testuser')), ], - status: MessageSendingStatus.sent, + state: jsonEncode(MessageState.sent), localUpdatedAt: DateTime.now(), remoteUpdatedAt: DateTime.now().add(const Duration(seconds: 1)), extraData: {'extra_test_data': 'extraData'}, @@ -96,7 +96,7 @@ void main() { expect(message.replyCount, entity.replyCount); expect(message.reactionScores, entity.reactionScores); expect(message.reactionCounts, entity.reactionCounts); - expect(message.status, entity.status); + expect(message.state, MessageState.fromJson(jsonDecode(entity.state))); expect(message.localUpdatedAt, isSameDateAs(entity.localUpdatedAt)); expect(message.remoteUpdatedAt, isSameDateAs(entity.remoteUpdatedAt)); expect(message.extraData, entity.extraData); @@ -197,7 +197,7 @@ void main() { entity.mentionedUsers, message.mentionedUsers.map(jsonEncode).toList()); expect(entity.reactionScores, message.reactionScores); expect(entity.reactionCounts, message.reactionCounts); - expect(entity.status, message.status); + expect(entity.state, jsonEncode(message.state)); expect(entity.localUpdatedAt, isSameDateAs(message.localUpdatedAt)); expect(entity.remoteUpdatedAt, isSameDateAs(message.remoteUpdatedAt)); expect(entity.extraData, message.extraData);