import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:get_it/get_it.dart'; import 'package:moxxmpp/moxxmpp.dart'; import 'package:moxxyv2/service/database/helpers.dart'; import 'package:moxxyv2/shared/models/message.dart'; import 'package:moxxyv2/ui/bloc/preferences_bloc.dart'; part 'conversation.freezed.dart'; part 'conversation.g.dart'; class ConversationChatStateConverter implements JsonConverter> { const ConversationChatStateConverter(); @override ChatState fromJson(Map json) => ChatState.fromName(json['chatState'] as String); @override Map toJson(ChatState state) => { 'chatState': state.toName(), }; } class ConversationMessageConverter implements JsonConverter> { const ConversationMessageConverter(); @override Message? fromJson(Map json) { if (json['message'] == null) return null; return Message.fromJson(json['message']! as Map); } @override Map toJson(Message? message) => { 'message': message?.toJson(), }; } enum ConversationType { @JsonValue('chat') chat, @JsonValue('note') note } @freezed class Conversation with _$Conversation { factory Conversation( /// The title of the chat. String title, // The newest message in the chat. @ConversationMessageConverter() Message? lastMessage, // The path to the avatar. String avatarPath, // The hash of the avatar. String? avatarHash, // The JID of the entity we're having a chat with... String jid, // The number of unread messages. int unreadCounter, // The kind of chat this conversation is representing. ConversationType type, // The timestamp the conversation was last changed. // NOTE: In milliseconds since Epoch or -1 if none has ever happened int lastChangeTimestamp, // Indicates if the conversation should be shown on the homescreen. bool open, /// Flag indicating whether the "add to roster" button should be shown. bool showAddToRoster, // Whether the chat is muted (true = muted, false = not muted) bool muted, // Whether the conversation is encrypted or not (true = encrypted, false = unencrypted) bool encrypted, // The current chat state @ConversationChatStateConverter() ChatState chatState, { // The id of the contact in the device's phonebook if it exists String? contactId, // The path to the contact avatar, if available String? contactAvatarPath, // The contact's display name, if it exists String? contactDisplayName, }) = _Conversation; const Conversation._(); /// JSON factory Conversation.fromJson(Map json) => _$ConversationFromJson(json); factory Conversation.fromDatabaseJson( Map json, bool showAddToRoster, Message? lastMessage, ) { return Conversation.fromJson({ ...json, 'muted': intToBool(json['muted']! as int), 'open': intToBool(json['open']! as int), 'showAddToRoster': showAddToRoster, 'encrypted': intToBool(json['encrypted']! as int), 'chatState': const ConversationChatStateConverter().toJson(ChatState.gone), }).copyWith( lastMessage: lastMessage, ); } Map toDatabaseJson() { final map = toJson() ..remove('id') ..remove('chatState') ..remove('showAddToRoster') ..remove('lastMessage'); return { ...map, 'open': boolToInt(open), 'muted': boolToInt(muted), 'encrypted': boolToInt(encrypted), 'lastMessageId': lastMessage?.id, }; } /// True, when the chat state of the conversation indicates typing. False, if not. bool get isTyping => chatState == ChatState.composing; /// The path to the avatar. This returns, if enabled, first the contact's avatar /// path, then the XMPP avatar's path. If not enabled, just returns the regular /// XMPP avatar's path. String? get avatarPathWithOptionalContact { if (GetIt.I.get().state.enableContactIntegration) { return contactAvatarPath ?? avatarPath; } return avatarPath; } /// The title of the chat. This returns, if enabled, first the contact's display /// name, then the XMPP chat title. If not enabled, just returns the XMPP chat /// title. String get titleWithOptionalContact { if (GetIt.I.get().state.enableContactIntegration) { return contactDisplayName ?? title; } return title; } /// The amount of items that are shown in the context menu. int get numberContextMenuOptions => 1 + (unreadCounter != 0 ? 1 : 0); } /// Sorts conversations in descending order by their last change timestamp. int compareConversation(Conversation a, Conversation b) { return -1 * Comparable.compare(a.lastChangeTimestamp, b.lastChangeTimestamp); }