import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:stream_chat/src/models/attachment.dart'; import 'package:stream_chat/src/models/reaction.dart'; import 'package:stream_chat/src/models/serialization.dart'; import 'package:stream_chat/src/models/user.dart'; import 'package:uuid/uuid.dart'; part 'message.g.dart'; class _PinExpires { const _PinExpires(); } const _pinExpires = _PinExpires(); /// Enum defining the status of a sending message enum MessageSendingStatus { /// Message is being sent sending, /// Message is being updated updating, /// Message is being deleted deleting, /// Message failed to send failed, /// Message failed to updated // ignore: constant_identifier_names failed_update, /// Message failed to delete // ignore: constant_identifier_names failed_delete, /// Message correctly sent sent, } /// The class that contains the information about a message @JsonSerializable() class Message extends Equatable { /// Constructor used for json serialization Message({ String? id, this.text, this.type = 'regular', this.attachments = const [], this.mentionedUsers = const [], this.silent = false, this.shadowed = false, this.reactionCounts, this.reactionScores, this.latestReactions, this.ownReactions, this.parentId, this.quotedMessage, this.quotedMessageId, this.replyCount = 0, this.threadParticipants, this.showInChannel, this.command, DateTime? createdAt, DateTime? updatedAt, this.user, this.pinned = false, this.pinnedAt, DateTime? pinExpires, this.pinnedBy, this.extraData = const {}, this.deletedAt, this.status = MessageSendingStatus.sent, this.skipPush = false, }) : id = id ?? const Uuid().v4(), pinExpires = pinExpires?.toUtc(), createdAt = createdAt ?? DateTime.now(), updatedAt = updatedAt ?? DateTime.now(); /// Create a new instance from a json factory Message.fromJson(Map json) => _$MessageFromJson( Serialization.moveToExtraDataFromRoot(json, topLevelFields)); /// The message ID. This is either created by Stream or set client side when /// the message is added. final String id; /// The text of this message final String? text; /// The status of a sending message @JsonKey(ignore: true) final MessageSendingStatus status; /// The message type @JsonKey( includeIfNull: false, toJson: Serialization.readOnly, defaultValue: 'regular', ) final String type; /// The list of attachments, either provided by the user or generated from a /// command or as a result of URL scraping. @JsonKey( includeIfNull: false, defaultValue: [], ) final List attachments; /// The list of user mentioned in the message @JsonKey( toJson: Serialization.userIds, defaultValue: [], ) final List mentionedUsers; /// A map describing the count of number of every reaction @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) final Map? reactionCounts; /// A map describing the count of score of every reaction @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) final Map? reactionScores; /// The latest reactions to the message created by any user. @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) final List? latestReactions; /// The reactions added to the message by the current user. @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) final List? ownReactions; /// The ID of the parent message, if the message is a thread reply. final String? parentId; /// A quoted reply message @JsonKey(toJson: Serialization.readOnly) final Message? quotedMessage; /// The ID of the quoted message, if the message is a quoted reply. final String? quotedMessageId; /// Reserved field indicating the number of replies for this message. @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) final int? replyCount; /// Reserved field indicating the thread participants for this message. @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) final List? threadParticipants; /// Check if this message needs to show in the channel. final bool? showInChannel; /// If true the message is silent @JsonKey(defaultValue: false) final bool silent; /// If true the message will not send a push notification @JsonKey(defaultValue: false) final bool skipPush; /// If true the message is shadowed @JsonKey( includeIfNull: false, toJson: Serialization.readOnly, defaultValue: false, ) final bool shadowed; /// A used command name. @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) final String? command; /// Reserved field indicating when the message was created. @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) final DateTime createdAt; /// Reserved field indicating when the message was updated last time. @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) final DateTime updatedAt; /// User who sent the message @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) final User? user; /// If true the message is pinned @JsonKey(defaultValue: false) final bool pinned; /// Reserved field indicating when the message was pinned @JsonKey(toJson: Serialization.readOnly) final DateTime? pinnedAt; /// Reserved field indicating when the message will expire /// /// if `null` message has no expiry final DateTime? pinExpires; /// Reserved field indicating who pinned the message @JsonKey(toJson: Serialization.readOnly) final User? pinnedBy; /// Message custom extraData @JsonKey( includeIfNull: false, defaultValue: {}, ) final Map extraData; /// True if the message is a system info bool get isSystem => type == 'system'; /// True if the message has been deleted bool get isDeleted => type == 'deleted'; /// True if the message is ephemeral bool get isEphemeral => type == 'ephemeral'; /// Reserved field indicating when the message was deleted. @JsonKey(includeIfNull: false, toJson: Serialization.readOnly) final DateTime? deletedAt; /// Known top level fields. /// Useful for [Serialization] methods. static const topLevelFields = [ 'id', 'text', 'type', 'silent', 'attachments', 'latest_reactions', 'shadowed', 'own_reactions', 'mentioned_users', 'reaction_counts', 'reaction_scores', 'silent', 'parent_id', 'quoted_message', 'quoted_message_id', 'reply_count', 'thread_participants', 'show_in_channel', 'command', 'created_at', 'updated_at', 'deleted_at', 'user', 'pinned', 'pinned_at', 'pin_expires', 'pinned_by', 'skip_push', ]; /// Serialize to json Map toJson() => Serialization.moveFromExtraDataToRoot( _$MessageToJson(this), ); /// Creates a copy of [Message] with specified attributes overridden. Message copyWith({ String? id, String? text, String? type, List? attachments, List? mentionedUsers, Map? reactionCounts, Map? reactionScores, List? latestReactions, List? ownReactions, String? parentId, Message? quotedMessage, String? quotedMessageId, int? replyCount, List? threadParticipants, bool? showInChannel, bool? shadowed, bool? silent, String? command, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, User? user, bool? pinned, DateTime? pinnedAt, Object? pinExpires = _pinExpires, User? pinnedBy, Map? extraData, MessageSendingStatus? status, bool? skipPush, }) { assert(() { if (pinExpires is! DateTime && pinExpires != null && pinExpires is! _PinExpires) { throw ArgumentError('`pinExpires` can only be set as DateTime or null'); } return true; }(), 'Validate type for pinExpires'); return Message( id: id ?? this.id, text: text ?? this.text, type: type ?? this.type, attachments: attachments ?? this.attachments, mentionedUsers: mentionedUsers ?? this.mentionedUsers, reactionCounts: reactionCounts ?? this.reactionCounts, reactionScores: reactionScores ?? this.reactionScores, latestReactions: latestReactions ?? this.latestReactions, ownReactions: ownReactions ?? this.ownReactions, parentId: parentId ?? this.parentId, quotedMessage: quotedMessage ?? this.quotedMessage, quotedMessageId: quotedMessageId ?? this.quotedMessageId, replyCount: replyCount ?? this.replyCount, threadParticipants: threadParticipants ?? this.threadParticipants, showInChannel: showInChannel ?? this.showInChannel, command: command ?? this.command, createdAt: createdAt ?? this.createdAt, silent: silent ?? this.silent, extraData: extraData ?? this.extraData, user: user ?? this.user, shadowed: shadowed ?? this.shadowed, updatedAt: updatedAt ?? this.updatedAt, deletedAt: deletedAt ?? this.deletedAt, status: status ?? this.status, pinned: pinned ?? this.pinned, pinnedAt: pinnedAt ?? this.pinnedAt, pinnedBy: pinnedBy ?? this.pinnedBy, pinExpires: pinExpires == _pinExpires ? this.pinExpires : pinExpires as DateTime?, skipPush: skipPush ?? this.skipPush, ); } /// 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, 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.createdAt, silent: other.silent, extraData: other.extraData, user: other.user, shadowed: other.shadowed, updatedAt: other.updatedAt, deletedAt: other.deletedAt, status: other.status, pinned: other.pinned, pinnedAt: other.pinnedAt, pinExpires: other.pinExpires, pinnedBy: other.pinnedBy, ); @override List get props => [ id, text, type, attachments, mentionedUsers, reactionCounts, reactionScores, latestReactions, ownReactions, parentId, quotedMessage, quotedMessageId, replyCount, threadParticipants, showInChannel, shadowed, silent, command, createdAt, updatedAt, deletedAt, user, pinned, pinnedAt, pinExpires, pinnedBy, extraData, status, skipPush, ]; } /// A translated message /// It has an additional property called [i18n] @JsonSerializable() class TranslatedMessage extends Message { /// Constructor used for json serialization TranslatedMessage(this.i18n) : super(); /// Create a new instance from a json factory TranslatedMessage.fromJson(Map json) => _$TranslatedMessageFromJson( Serialization.moveToExtraDataFromRoot(json, topLevelFields), ); /// A Map of final Map? i18n; /// Known top level fields. /// Useful for [Serialization] methods. static final topLevelFields = [ 'i18n', ...Message.topLevelFields, ]; /// Serialize to json @override Map toJson() => Serialization.moveFromExtraDataToRoot( _$TranslatedMessageToJson(this), ); }