import 'dart:convert'; import 'package:drift/drift.dart'; import 'package:island/models/chat.dart'; import 'package:island/models/file.dart'; class MapConverter extends TypeConverter, String> { const MapConverter(); @override Map fromSql(String fromDb) => json.decode(fromDb); @override String toSql(Map value) => json.encode(value); } class ListStringConverter extends TypeConverter, String> { const ListStringConverter(); @override List fromSql(String fromDb) => List.from(json.decode(fromDb)); @override String toSql(List value) => json.encode(value); } class ListMapConverter extends TypeConverter>, String> { const ListMapConverter(); @override List> fromSql(String fromDb) => List>.from(json.decode(fromDb)); @override String toSql(List> value) => json.encode(value); } class ChatRooms extends Table { TextColumn get id => text()(); TextColumn get name => text().nullable()(); TextColumn get description => text().nullable()(); IntColumn get type => integer()(); BoolColumn get isPublic => boolean().nullable().withDefault(const Constant(false))(); BoolColumn get isCommunity => boolean().nullable().withDefault(const Constant(false))(); TextColumn get picture => text().map(const MapConverter()).nullable()(); TextColumn get background => text().map(const MapConverter()).nullable()(); TextColumn get realmId => text().nullable()(); DateTimeColumn get createdAt => dateTime()(); DateTimeColumn get updatedAt => dateTime()(); DateTimeColumn get deletedAt => dateTime().nullable()(); @override Set get primaryKey => {id}; } class ChatMembers extends Table { TextColumn get id => text()(); TextColumn get chatRoomId => text().references(ChatRooms, #id)(); TextColumn get accountId => text()(); TextColumn get account => text().map(const MapConverter())(); TextColumn get nick => text().nullable()(); IntColumn get role => integer()(); IntColumn get notify => integer()(); DateTimeColumn get joinedAt => dateTime().nullable()(); DateTimeColumn get breakUntil => dateTime().nullable()(); DateTimeColumn get timeoutUntil => dateTime().nullable()(); BoolColumn get isBot => boolean()(); TextColumn get status => text().nullable()(); DateTimeColumn get lastTyped => dateTime().nullable()(); DateTimeColumn get createdAt => dateTime()(); DateTimeColumn get updatedAt => dateTime()(); DateTimeColumn get deletedAt => dateTime().nullable()(); @override Set get primaryKey => {id}; } class ChatMessages extends Table { TextColumn get id => text()(); TextColumn get roomId => text().references(ChatRooms, #id)(); TextColumn get senderId => text().references(ChatMembers, #id)(); TextColumn get content => text().nullable()(); TextColumn get nonce => text().nullable()(); TextColumn get data => text()(); DateTimeColumn get createdAt => dateTime()(); IntColumn get status => intEnum()(); BoolColumn get isDeleted => boolean().nullable().withDefault(const Constant(false))(); DateTimeColumn get updatedAt => dateTime().nullable()(); DateTimeColumn get deletedAt => dateTime().nullable()(); TextColumn get type => text().withDefault(const Constant('text'))(); TextColumn get meta => text().map(const MapConverter()).withDefault(const Constant('{}'))(); TextColumn get membersMentioned => text() .map(const ListStringConverter()) .withDefault(const Constant('[]'))(); DateTimeColumn get editedAt => dateTime().nullable()(); TextColumn get attachments => text().map(const ListMapConverter()).withDefault(const Constant('[]'))(); TextColumn get reactions => text().map(const ListMapConverter()).withDefault(const Constant('[]'))(); TextColumn get repliedMessageId => text().nullable()(); TextColumn get forwardedMessageId => text().nullable()(); @override Set get primaryKey => {id}; } class LocalChatMessage { final String id; final String roomId; final String senderId; final SnChatMember? sender; final Map data; final DateTime createdAt; MessageStatus status; final String? nonce; final String? content; final bool? isDeleted; final DateTime? updatedAt; final DateTime? deletedAt; final String type; final Map meta; final List membersMentioned; final DateTime? editedAt; final List> attachments; final List> reactions; final String? repliedMessageId; final String? forwardedMessageId; List? localAttachments; LocalChatMessage({ required this.id, required this.roomId, required this.senderId, required this.sender, required this.data, required this.createdAt, required this.nonce, required this.status, this.content, this.isDeleted, this.updatedAt, this.deletedAt, required this.type, required this.meta, required this.membersMentioned, this.editedAt, required this.attachments, required this.reactions, this.repliedMessageId, this.forwardedMessageId, this.localAttachments, }); SnChatMessage toRemoteMessage() { if (sender == null) { throw Exception('Cannot create remote message without sender'); } final msgData = Map.from(data); msgData['sender'] = sender!.toJson(); return SnChatMessage.fromJson(msgData); } static LocalChatMessage fromRemoteMessage( SnChatMessage message, MessageStatus status, { String? nonce, }) { final jsonData = message.toJson(); jsonData.remove('sender'); // Ensure proper defaults for collections to prevent type cast errors if (jsonData['meta'] == null) jsonData['meta'] = {}; if (jsonData['members_mentioned'] == null) { jsonData['members_mentioned'] = []; } if (jsonData['attachments'] == null) { jsonData['attachments'] = >[]; } if (jsonData['reactions'] == null) { jsonData['reactions'] = >[]; } final msgData = Map.from(jsonData); return LocalChatMessage( id: message.id, roomId: message.chatRoomId, senderId: message.senderId, sender: message.sender, data: msgData, createdAt: message.createdAt, status: status, nonce: nonce ?? message.nonce, content: message.content, isDeleted: false, updatedAt: message.updatedAt, deletedAt: null, type: message.type, meta: message.meta, membersMentioned: message.membersMentioned, editedAt: message.editedAt, attachments: message.attachments.map((e) => e.toJson()).toList(), reactions: message.reactions.map((e) => e.toJson()).toList(), repliedMessageId: message.repliedMessageId, forwardedMessageId: message.forwardedMessageId, ); } } enum MessageStatus { pending, sent, failed }