175 lines
4.9 KiB
Dart
175 lines
4.9 KiB
Dart
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<ChatState, Map<String, dynamic>> {
|
|
const ConversationChatStateConverter();
|
|
|
|
@override
|
|
ChatState fromJson(Map<String, dynamic> json) =>
|
|
ChatState.fromName(json['chatState'] as String);
|
|
|
|
@override
|
|
Map<String, dynamic> toJson(ChatState state) => <String, String>{
|
|
'chatState': state.toName(),
|
|
};
|
|
}
|
|
|
|
class ConversationMessageConverter
|
|
implements JsonConverter<Message?, Map<String, dynamic>> {
|
|
const ConversationMessageConverter();
|
|
|
|
@override
|
|
Message? fromJson(Map<String, dynamic> json) {
|
|
if (json['message'] == null) return null;
|
|
|
|
return Message.fromJson(json['message']! as Map<String, dynamic>);
|
|
}
|
|
|
|
@override
|
|
Map<String, dynamic> toJson(Message? message) => <String, dynamic>{
|
|
'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<String, dynamic> json) =>
|
|
_$ConversationFromJson(json);
|
|
|
|
factory Conversation.fromDatabaseJson(
|
|
Map<String, dynamic> 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<String, dynamic> 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<PreferencesBloc>().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<PreferencesBloc>().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);
|
|
}
|