diff --git a/assets/i18n/en-US.json b/assets/i18n/en-US.json index 9670498..35e9e72 100644 --- a/assets/i18n/en-US.json +++ b/assets/i18n/en-US.json @@ -75,5 +75,6 @@ "editChatRoom": "Edit a Room", "chat": "Chat", "chatMessageHint": "Message in {}", - "chatDirectMessageHint": "Message to {}" + "chatDirectMessageHint": "Message to {}", + "loading": "Loading..." } diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 5923683..3d847f0 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -142,6 +142,28 @@ PODS: - sqflite_darwin (0.0.4): - Flutter - FlutterMacOS + - sqlite3 (3.49.1): + - sqlite3/common (= 3.49.1) + - sqlite3/common (3.49.1) + - sqlite3/dbstatvtab (3.49.1): + - sqlite3/common + - sqlite3/fts5 (3.49.1): + - sqlite3/common + - sqlite3/math (3.49.1): + - sqlite3/common + - sqlite3/perf-threadsafe (3.49.1): + - sqlite3/common + - sqlite3/rtree (3.49.1): + - sqlite3/common + - sqlite3_flutter_libs (0.0.1): + - Flutter + - FlutterMacOS + - sqlite3 (~> 3.49.1) + - sqlite3/dbstatvtab + - sqlite3/fts5 + - sqlite3/math + - sqlite3/perf-threadsafe + - sqlite3/rtree - super_native_extensions (0.0.1): - Flutter - SwiftyGif (5.4.5) @@ -172,6 +194,7 @@ DEPENDENCIES: - quill_native_bridge_ios (from `.symlinks/plugins/quill_native_bridge_ios/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) + - sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`) - super_native_extensions (from `.symlinks/plugins/super_native_extensions/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - volume_controller (from `.symlinks/plugins/volume_controller/ios`) @@ -194,6 +217,7 @@ SPEC REPOS: - PromisesObjC - SAMKeychain - SDWebImage + - sqlite3 - SwiftyGif EXTERNAL SOURCES: @@ -233,6 +257,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/shared_preferences_foundation/darwin" sqflite_darwin: :path: ".symlinks/plugins/sqflite_darwin/darwin" + sqlite3_flutter_libs: + :path: ".symlinks/plugins/sqlite3_flutter_libs/darwin" super_native_extensions: :path: ".symlinks/plugins/super_native_extensions/ios" url_launcher_ios: @@ -276,6 +302,8 @@ SPEC CHECKSUMS: SDWebImage: f84b0feeb08d2d11e6a9b843cb06d75ebf5b8868 shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 + sqlite3: fc1400008a9b3525f5914ed715a5d1af0b8f4983 + sqlite3_flutter_libs: f6acaa2172e6bb3e2e70c771661905080e8ebcf2 super_native_extensions: b763c02dc3a8fd078389f410bf15149179020cb4 SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 url_launcher_ios: 694010445543906933d732453a59da0a173ae33d diff --git a/lib/database/drift_db.dart b/lib/database/drift_db.dart new file mode 100644 index 0000000..1e822e6 --- /dev/null +++ b/lib/database/drift_db.dart @@ -0,0 +1,81 @@ +import 'dart:convert'; +import 'dart:io'; +import 'package:drift/drift.dart'; +import 'package:drift/native.dart'; +import 'package:island/database/message.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:path/path.dart' as p; + +part 'drift_db.g.dart'; + +// Define the database +@DriftDatabase(tables: [ChatMessages]) +class AppDatabase extends _$AppDatabase { + AppDatabase() : super(_openConnection()); + + @override + int get schemaVersion => 1; + + // Methods for chat messages + Future> getMessagesForRoom( + int roomId, { + int offset = 0, + int limit = 20, + }) { + return (select(chatMessages) + ..where((m) => m.roomId.equals(roomId)) + ..orderBy([(m) => OrderingTerm.desc(m.createdAt)]) + ..limit(limit, offset: offset)) + .get(); + } + + Future saveMessage(ChatMessagesCompanion message) { + return into(chatMessages).insert(message, mode: InsertMode.insertOrReplace); + } + + Future updateMessageStatus(String id, MessageStatus status) { + return (update(chatMessages)..where( + (m) => m.id.equals(id), + )).write(ChatMessagesCompanion(status: Value(status))); + } + + Future deleteMessage(String id) { + return (delete(chatMessages)..where((m) => m.id.equals(id))).go(); + } + + // Convert between Drift and model objects + ChatMessagesCompanion messageToCompanion(LocalChatMessage message) { + return ChatMessagesCompanion( + id: Value(message.id), + roomId: Value(message.roomId), + senderId: Value(message.senderId), + content: Value(message.toRemoteMessage().content), + nonce: Value(message.nonce), + data: Value(jsonEncode(message.data)), + createdAt: Value(message.createdAt), + status: Value(message.status), + ); + } + + LocalChatMessage companionToMessage(ChatMessage dbMessage) { + final data = jsonDecode(dbMessage.data); + return LocalChatMessage( + id: dbMessage.id, + roomId: dbMessage.roomId, + senderId: dbMessage.senderId, + data: data, + createdAt: dbMessage.createdAt, + status: dbMessage.status, + nonce: dbMessage.nonce, + ); + } +} + +// Helper to open the database connection +LazyDatabase _openConnection() { + return LazyDatabase(() async { + final dbFolder = await getApplicationDocumentsDirectory(); + final file = File(p.join(dbFolder.path, 'island_chat.sqlite')); + return NativeDatabase(file); + }); +} diff --git a/lib/database/drift_db.g.dart b/lib/database/drift_db.g.dart new file mode 100644 index 0000000..ab80f8e --- /dev/null +++ b/lib/database/drift_db.g.dart @@ -0,0 +1,807 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'drift_db.dart'; + +// ignore_for_file: type=lint +class $ChatMessagesTable extends ChatMessages + with TableInfo<$ChatMessagesTable, ChatMessage> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $ChatMessagesTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _roomIdMeta = const VerificationMeta('roomId'); + @override + late final GeneratedColumn roomId = GeneratedColumn( + 'room_id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + static const VerificationMeta _senderIdMeta = const VerificationMeta( + 'senderId', + ); + @override + late final GeneratedColumn senderId = GeneratedColumn( + 'sender_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _contentMeta = const VerificationMeta( + 'content', + ); + @override + late final GeneratedColumn content = GeneratedColumn( + 'content', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _nonceMeta = const VerificationMeta('nonce'); + @override + late final GeneratedColumn nonce = GeneratedColumn( + 'nonce', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _dataMeta = const VerificationMeta('data'); + @override + late final GeneratedColumn data = GeneratedColumn( + 'data', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _createdAtMeta = const VerificationMeta( + 'createdAt', + ); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: true, + ); + @override + late final GeneratedColumnWithTypeConverter status = + GeneratedColumn( + 'status', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ).withConverter($ChatMessagesTable.$converterstatus); + @override + List get $columns => [ + id, + roomId, + senderId, + content, + nonce, + data, + createdAt, + status, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'chat_messages'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('room_id')) { + context.handle( + _roomIdMeta, + roomId.isAcceptableOrUnknown(data['room_id']!, _roomIdMeta), + ); + } else if (isInserting) { + context.missing(_roomIdMeta); + } + if (data.containsKey('sender_id')) { + context.handle( + _senderIdMeta, + senderId.isAcceptableOrUnknown(data['sender_id']!, _senderIdMeta), + ); + } else if (isInserting) { + context.missing(_senderIdMeta); + } + if (data.containsKey('content')) { + context.handle( + _contentMeta, + content.isAcceptableOrUnknown(data['content']!, _contentMeta), + ); + } + if (data.containsKey('nonce')) { + context.handle( + _nonceMeta, + nonce.isAcceptableOrUnknown(data['nonce']!, _nonceMeta), + ); + } + if (data.containsKey('data')) { + context.handle( + _dataMeta, + this.data.isAcceptableOrUnknown(data['data']!, _dataMeta), + ); + } else if (isInserting) { + context.missing(_dataMeta); + } + if (data.containsKey('created_at')) { + context.handle( + _createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta), + ); + } else if (isInserting) { + context.missing(_createdAtMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + ChatMessage map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return ChatMessage( + id: + attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + roomId: + attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}room_id'], + )!, + senderId: + attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}sender_id'], + )!, + content: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}content'], + ), + nonce: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}nonce'], + ), + data: + attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}data'], + )!, + createdAt: + attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + status: $ChatMessagesTable.$converterstatus.fromSql( + attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}status'], + )!, + ), + ); + } + + @override + $ChatMessagesTable createAlias(String alias) { + return $ChatMessagesTable(attachedDatabase, alias); + } + + static JsonTypeConverter2 $converterstatus = + const EnumIndexConverter(MessageStatus.values); +} + +class ChatMessage extends DataClass implements Insertable { + final String id; + final int roomId; + final String senderId; + final String? content; + final String? nonce; + final String data; + final DateTime createdAt; + final MessageStatus status; + const ChatMessage({ + required this.id, + required this.roomId, + required this.senderId, + this.content, + this.nonce, + required this.data, + required this.createdAt, + required this.status, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['room_id'] = Variable(roomId); + map['sender_id'] = Variable(senderId); + if (!nullToAbsent || content != null) { + map['content'] = Variable(content); + } + if (!nullToAbsent || nonce != null) { + map['nonce'] = Variable(nonce); + } + map['data'] = Variable(data); + map['created_at'] = Variable(createdAt); + { + map['status'] = Variable( + $ChatMessagesTable.$converterstatus.toSql(status), + ); + } + return map; + } + + ChatMessagesCompanion toCompanion(bool nullToAbsent) { + return ChatMessagesCompanion( + id: Value(id), + roomId: Value(roomId), + senderId: Value(senderId), + content: + content == null && nullToAbsent + ? const Value.absent() + : Value(content), + nonce: + nonce == null && nullToAbsent ? const Value.absent() : Value(nonce), + data: Value(data), + createdAt: Value(createdAt), + status: Value(status), + ); + } + + factory ChatMessage.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return ChatMessage( + id: serializer.fromJson(json['id']), + roomId: serializer.fromJson(json['roomId']), + senderId: serializer.fromJson(json['senderId']), + content: serializer.fromJson(json['content']), + nonce: serializer.fromJson(json['nonce']), + data: serializer.fromJson(json['data']), + createdAt: serializer.fromJson(json['createdAt']), + status: $ChatMessagesTable.$converterstatus.fromJson( + serializer.fromJson(json['status']), + ), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'roomId': serializer.toJson(roomId), + 'senderId': serializer.toJson(senderId), + 'content': serializer.toJson(content), + 'nonce': serializer.toJson(nonce), + 'data': serializer.toJson(data), + 'createdAt': serializer.toJson(createdAt), + 'status': serializer.toJson( + $ChatMessagesTable.$converterstatus.toJson(status), + ), + }; + } + + ChatMessage copyWith({ + String? id, + int? roomId, + String? senderId, + Value content = const Value.absent(), + Value nonce = const Value.absent(), + String? data, + DateTime? createdAt, + MessageStatus? status, + }) => ChatMessage( + id: id ?? this.id, + roomId: roomId ?? this.roomId, + senderId: senderId ?? this.senderId, + content: content.present ? content.value : this.content, + nonce: nonce.present ? nonce.value : this.nonce, + data: data ?? this.data, + createdAt: createdAt ?? this.createdAt, + status: status ?? this.status, + ); + ChatMessage copyWithCompanion(ChatMessagesCompanion data) { + return ChatMessage( + id: data.id.present ? data.id.value : this.id, + roomId: data.roomId.present ? data.roomId.value : this.roomId, + senderId: data.senderId.present ? data.senderId.value : this.senderId, + content: data.content.present ? data.content.value : this.content, + nonce: data.nonce.present ? data.nonce.value : this.nonce, + data: data.data.present ? data.data.value : this.data, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + status: data.status.present ? data.status.value : this.status, + ); + } + + @override + String toString() { + return (StringBuffer('ChatMessage(') + ..write('id: $id, ') + ..write('roomId: $roomId, ') + ..write('senderId: $senderId, ') + ..write('content: $content, ') + ..write('nonce: $nonce, ') + ..write('data: $data, ') + ..write('createdAt: $createdAt, ') + ..write('status: $status') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + roomId, + senderId, + content, + nonce, + data, + createdAt, + status, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is ChatMessage && + other.id == this.id && + other.roomId == this.roomId && + other.senderId == this.senderId && + other.content == this.content && + other.nonce == this.nonce && + other.data == this.data && + other.createdAt == this.createdAt && + other.status == this.status); +} + +class ChatMessagesCompanion extends UpdateCompanion { + final Value id; + final Value roomId; + final Value senderId; + final Value content; + final Value nonce; + final Value data; + final Value createdAt; + final Value status; + final Value rowid; + const ChatMessagesCompanion({ + this.id = const Value.absent(), + this.roomId = const Value.absent(), + this.senderId = const Value.absent(), + this.content = const Value.absent(), + this.nonce = const Value.absent(), + this.data = const Value.absent(), + this.createdAt = const Value.absent(), + this.status = const Value.absent(), + this.rowid = const Value.absent(), + }); + ChatMessagesCompanion.insert({ + required String id, + required int roomId, + required String senderId, + this.content = const Value.absent(), + this.nonce = const Value.absent(), + required String data, + required DateTime createdAt, + required MessageStatus status, + this.rowid = const Value.absent(), + }) : id = Value(id), + roomId = Value(roomId), + senderId = Value(senderId), + data = Value(data), + createdAt = Value(createdAt), + status = Value(status); + static Insertable custom({ + Expression? id, + Expression? roomId, + Expression? senderId, + Expression? content, + Expression? nonce, + Expression? data, + Expression? createdAt, + Expression? status, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (roomId != null) 'room_id': roomId, + if (senderId != null) 'sender_id': senderId, + if (content != null) 'content': content, + if (nonce != null) 'nonce': nonce, + if (data != null) 'data': data, + if (createdAt != null) 'created_at': createdAt, + if (status != null) 'status': status, + if (rowid != null) 'rowid': rowid, + }); + } + + ChatMessagesCompanion copyWith({ + Value? id, + Value? roomId, + Value? senderId, + Value? content, + Value? nonce, + Value? data, + Value? createdAt, + Value? status, + Value? rowid, + }) { + return ChatMessagesCompanion( + id: id ?? this.id, + roomId: roomId ?? this.roomId, + senderId: senderId ?? this.senderId, + content: content ?? this.content, + nonce: nonce ?? this.nonce, + data: data ?? this.data, + createdAt: createdAt ?? this.createdAt, + status: status ?? this.status, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (roomId.present) { + map['room_id'] = Variable(roomId.value); + } + if (senderId.present) { + map['sender_id'] = Variable(senderId.value); + } + if (content.present) { + map['content'] = Variable(content.value); + } + if (nonce.present) { + map['nonce'] = Variable(nonce.value); + } + if (data.present) { + map['data'] = Variable(data.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (status.present) { + map['status'] = Variable( + $ChatMessagesTable.$converterstatus.toSql(status.value), + ); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('ChatMessagesCompanion(') + ..write('id: $id, ') + ..write('roomId: $roomId, ') + ..write('senderId: $senderId, ') + ..write('content: $content, ') + ..write('nonce: $nonce, ') + ..write('data: $data, ') + ..write('createdAt: $createdAt, ') + ..write('status: $status, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +abstract class _$AppDatabase extends GeneratedDatabase { + _$AppDatabase(QueryExecutor e) : super(e); + $AppDatabaseManager get managers => $AppDatabaseManager(this); + late final $ChatMessagesTable chatMessages = $ChatMessagesTable(this); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [chatMessages]; +} + +typedef $$ChatMessagesTableCreateCompanionBuilder = + ChatMessagesCompanion Function({ + required String id, + required int roomId, + required String senderId, + Value content, + Value nonce, + required String data, + required DateTime createdAt, + required MessageStatus status, + Value rowid, + }); +typedef $$ChatMessagesTableUpdateCompanionBuilder = + ChatMessagesCompanion Function({ + Value id, + Value roomId, + Value senderId, + Value content, + Value nonce, + Value data, + Value createdAt, + Value status, + Value rowid, + }); + +class $$ChatMessagesTableFilterComposer + extends Composer<_$AppDatabase, $ChatMessagesTable> { + $$ChatMessagesTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get roomId => $composableBuilder( + column: $table.roomId, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get senderId => $composableBuilder( + column: $table.senderId, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get content => $composableBuilder( + column: $table.content, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get nonce => $composableBuilder( + column: $table.nonce, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get data => $composableBuilder( + column: $table.data, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => ColumnFilters(column), + ); + + ColumnWithTypeConverterFilters + get status => $composableBuilder( + column: $table.status, + builder: (column) => ColumnWithTypeConverterFilters(column), + ); +} + +class $$ChatMessagesTableOrderingComposer + extends Composer<_$AppDatabase, $ChatMessagesTable> { + $$ChatMessagesTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get roomId => $composableBuilder( + column: $table.roomId, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get senderId => $composableBuilder( + column: $table.senderId, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get content => $composableBuilder( + column: $table.content, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get nonce => $composableBuilder( + column: $table.nonce, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get data => $composableBuilder( + column: $table.data, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get status => $composableBuilder( + column: $table.status, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$ChatMessagesTableAnnotationComposer + extends Composer<_$AppDatabase, $ChatMessagesTable> { + $$ChatMessagesTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get roomId => + $composableBuilder(column: $table.roomId, builder: (column) => column); + + GeneratedColumn get senderId => + $composableBuilder(column: $table.senderId, builder: (column) => column); + + GeneratedColumn get content => + $composableBuilder(column: $table.content, builder: (column) => column); + + GeneratedColumn get nonce => + $composableBuilder(column: $table.nonce, builder: (column) => column); + + GeneratedColumn get data => + $composableBuilder(column: $table.data, builder: (column) => column); + + GeneratedColumn get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => column); + + GeneratedColumnWithTypeConverter get status => + $composableBuilder(column: $table.status, builder: (column) => column); +} + +class $$ChatMessagesTableTableManager + extends + RootTableManager< + _$AppDatabase, + $ChatMessagesTable, + ChatMessage, + $$ChatMessagesTableFilterComposer, + $$ChatMessagesTableOrderingComposer, + $$ChatMessagesTableAnnotationComposer, + $$ChatMessagesTableCreateCompanionBuilder, + $$ChatMessagesTableUpdateCompanionBuilder, + ( + ChatMessage, + BaseReferences<_$AppDatabase, $ChatMessagesTable, ChatMessage>, + ), + ChatMessage, + PrefetchHooks Function() + > { + $$ChatMessagesTableTableManager(_$AppDatabase db, $ChatMessagesTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: + () => $$ChatMessagesTableFilterComposer($db: db, $table: table), + createOrderingComposer: + () => $$ChatMessagesTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: + () => + $$ChatMessagesTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value roomId = const Value.absent(), + Value senderId = const Value.absent(), + Value content = const Value.absent(), + Value nonce = const Value.absent(), + Value data = const Value.absent(), + Value createdAt = const Value.absent(), + Value status = const Value.absent(), + Value rowid = const Value.absent(), + }) => ChatMessagesCompanion( + id: id, + roomId: roomId, + senderId: senderId, + content: content, + nonce: nonce, + data: data, + createdAt: createdAt, + status: status, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String id, + required int roomId, + required String senderId, + Value content = const Value.absent(), + Value nonce = const Value.absent(), + required String data, + required DateTime createdAt, + required MessageStatus status, + Value rowid = const Value.absent(), + }) => ChatMessagesCompanion.insert( + id: id, + roomId: roomId, + senderId: senderId, + content: content, + nonce: nonce, + data: data, + createdAt: createdAt, + status: status, + rowid: rowid, + ), + withReferenceMapper: + (p0) => + p0 + .map( + (e) => ( + e.readTable(table), + BaseReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: null, + ), + ); +} + +typedef $$ChatMessagesTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $ChatMessagesTable, + ChatMessage, + $$ChatMessagesTableFilterComposer, + $$ChatMessagesTableOrderingComposer, + $$ChatMessagesTableAnnotationComposer, + $$ChatMessagesTableCreateCompanionBuilder, + $$ChatMessagesTableUpdateCompanionBuilder, + ( + ChatMessage, + BaseReferences<_$AppDatabase, $ChatMessagesTable, ChatMessage>, + ), + ChatMessage, + PrefetchHooks Function() + >; + +class $AppDatabaseManager { + final _$AppDatabase _db; + $AppDatabaseManager(this._db); + $$ChatMessagesTableTableManager get chatMessages => + $$ChatMessagesTableTableManager(_db, _db.chatMessages); +} diff --git a/lib/database/message.dart b/lib/database/message.dart new file mode 100644 index 0000000..1b25ca4 --- /dev/null +++ b/lib/database/message.dart @@ -0,0 +1,58 @@ +import 'package:drift/drift.dart'; +import 'package:island/models/chat.dart'; + +class ChatMessages extends Table { + TextColumn get id => text()(); + IntColumn get roomId => integer()(); + TextColumn get senderId => text()(); + TextColumn get content => text().nullable()(); + TextColumn get nonce => text().nullable()(); + TextColumn get data => text()(); + DateTimeColumn get createdAt => dateTime()(); + IntColumn get status => intEnum()(); + + @override + Set get primaryKey => {id}; +} + +class LocalChatMessage { + final String id; + final int roomId; + final String senderId; + final Map data; + final DateTime createdAt; + MessageStatus status; + final String? nonce; + + LocalChatMessage({ + required this.id, + required this.roomId, + required this.senderId, + required this.data, + required this.createdAt, + required this.status, + this.nonce, + }); + + SnChatMessage toRemoteMessage() { + return SnChatMessage.fromJson(data); + } + + static LocalChatMessage fromRemoteMessage( + SnChatMessage message, + MessageStatus status, { + String? nonce, + }) { + return LocalChatMessage( + id: message.id, + roomId: message.chatRoomId, + senderId: message.senderId, + data: message.toJson(), + createdAt: message.createdAt, + status: status, + nonce: nonce ?? message.nonce, + ); + } +} + +enum MessageStatus { pending, sent, failed } diff --git a/lib/database/message_repository.dart b/lib/database/message_repository.dart new file mode 100644 index 0000000..24498af --- /dev/null +++ b/lib/database/message_repository.dart @@ -0,0 +1,263 @@ +import 'package:dio/dio.dart'; +import 'package:island/database/drift_db.dart'; +import 'package:island/database/message.dart'; +import 'package:island/models/chat.dart'; +import 'package:island/models/file.dart'; +import 'package:uuid/uuid.dart'; + +class MessageRepository { + final SnChat room; + final Dio _apiClient; + final AppDatabase _database; + + SnChatMember? _identity; + + final Map _pendingMessages = {}; + + MessageRepository(this.room, this._apiClient, this._database) { + initialize(); + } + + bool initialized = false; + + Future initialize() async { + if (initialized) return; + + try { + final response = await _apiClient.get('/chat/${room.id}/members/me'); + _identity = SnChatMember.fromJson(response.data); + initialized = true; + } catch (e) { + rethrow; + } + } + + Future> listMessages({ + int offset = 0, + int take = 20, + }) async { + try { + final localMessages = await _getCachedMessages( + room.id, + offset: offset, + take: take, + ); + + if (offset == 0) { + // Always fetch latest messages in background if we're loading the first page + _fetchAndCacheMessages(room.id, offset: offset, take: take); + + if (localMessages.isNotEmpty) { + return localMessages; + } + } + + return await _fetchAndCacheMessages(room.id, offset: offset, take: take); + } catch (e) { + // If API fails but we have local messages, return them + final localMessages = await _getCachedMessages( + room.id, + offset: offset, + take: take, + ); + + if (localMessages.isNotEmpty) { + return localMessages; + } + rethrow; + } + } + + Future> _getCachedMessages( + int roomId, { + int offset = 0, + int take = 20, + }) async { + // Get messages from local database + final dbMessages = await _database.getMessagesForRoom( + roomId, + offset: offset, + limit: take, + ); + final dbLocalMessages = + dbMessages.map(_database.companionToMessage).toList(); + + // Combine with pending messages + final pendingForRoom = + _pendingMessages.values.where((msg) => msg.roomId == roomId).toList(); + + // Sort by timestamp descending (newest first) + final allMessages = [...pendingForRoom, ...dbLocalMessages]; + allMessages.sort((a, b) => b.createdAt.compareTo(a.createdAt)); + + // Apply pagination + if (offset >= allMessages.length) { + return []; + } + + final end = + (offset + take) > allMessages.length + ? allMessages.length + : (offset + take); + return allMessages.sublist(offset, end); + } + + Future> _fetchAndCacheMessages( + int roomId, { + int offset = 0, + int take = 20, + }) async { + final response = await _apiClient.get( + '/chat/$roomId/messages', + queryParameters: {'offset': offset, 'take': take}, + ); + + final total = int.parse(response.headers.value('X-Total') ?? '0'); + final List data = response.data; + + final messages = + data.map((json) { + final remoteMessage = SnChatMessage.fromJson(json); + return LocalChatMessage.fromRemoteMessage( + remoteMessage, + MessageStatus.sent, + ); + }).toList(); + + for (final message in messages) { + await _database.saveMessage(_database.messageToCompanion(message)); + if (message.nonce != null) { + _pendingMessages.removeWhere( + (_, pendingMsg) => pendingMsg.nonce == message.nonce, + ); + } + } + + return messages; + } + + Future sendMessage( + int roomId, + String content, { + List? attachments, + Map? meta, + }) async { + if (!initialized) { + throw UnsupportedError( + "The message repository is not ready for send message.", + ); + } + + // Generate a unique nonce for this message + final nonce = const Uuid().v4(); + + // Create a local message with pending status + final mockMessage = SnChatMessage( + id: 'pending_$nonce', + chatRoomId: roomId, + senderId: _identity!.id, + content: content, + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + nonce: nonce, + sender: _identity!, + ); + + final localMessage = LocalChatMessage.fromRemoteMessage( + mockMessage, + MessageStatus.pending, + ); + + // Store in memory and database + _pendingMessages[localMessage.id] = localMessage; + await _database.saveMessage(_database.messageToCompanion(localMessage)); + + try { + // Send to server + final response = await _apiClient.post( + '/chat/$roomId/messages', + data: { + 'content': content, + 'attachments_id': attachments, + 'meta': meta, + 'nonce': nonce, + }, + ); + + // Update with server response + final remoteMessage = SnChatMessage.fromJson(response.data); + final updatedMessage = LocalChatMessage.fromRemoteMessage( + remoteMessage, + MessageStatus.sent, + ); + + // Remove from pending and update in database + _pendingMessages.remove(localMessage.id); + await _database.deleteMessage(localMessage.id); + await _database.saveMessage(_database.messageToCompanion(updatedMessage)); + + return updatedMessage; + } catch (e) { + // Update status to failed + localMessage.status = MessageStatus.failed; + _pendingMessages[localMessage.id] = localMessage; + await _database.updateMessageStatus( + localMessage.id, + MessageStatus.failed, + ); + rethrow; + } + } + + Future retryMessage(String pendingMessageId) async { + final message = _pendingMessages[pendingMessageId]; + if (message == null) { + throw Exception('Message not found'); + } + + // Update status back to pending + message.status = MessageStatus.pending; + _pendingMessages[pendingMessageId] = message; + await _database.updateMessageStatus( + pendingMessageId, + MessageStatus.pending, + ); + + try { + // Send to server + var remoteMessage = message.toRemoteMessage(); + final response = await _apiClient.post( + '/chat/${message.roomId}/messages', + data: { + 'content': remoteMessage.content, + 'attachments_id': remoteMessage.attachments, + 'meta': remoteMessage.meta, + 'nonce': message.nonce, + }, + ); + + // Update with server response + remoteMessage = SnChatMessage.fromJson(response.data); + final updatedMessage = LocalChatMessage.fromRemoteMessage( + remoteMessage, + MessageStatus.sent, + ); + + // Remove from pending and update in database + _pendingMessages.remove(pendingMessageId); + await _database.deleteMessage(pendingMessageId); + await _database.saveMessage(_database.messageToCompanion(updatedMessage)); + + return updatedMessage; + } catch (e) { + // Update status to failed + message.status = MessageStatus.failed; + _pendingMessages[pendingMessageId] = message; + await _database.updateMessageStatus( + pendingMessageId, + MessageStatus.failed, + ); + rethrow; + } + } +} diff --git a/lib/models/chat.dart b/lib/models/chat.dart index 7cbbf04..652b531 100644 --- a/lib/models/chat.dart +++ b/lib/models/chat.dart @@ -24,3 +24,68 @@ abstract class SnChat with _$SnChat { factory SnChat.fromJson(Map json) => _$SnChatFromJson(json); } + +@freezed +abstract class SnChatMessage with _$SnChatMessage { + const factory SnChatMessage({ + required DateTime createdAt, + required DateTime updatedAt, + DateTime? deletedAt, + required String id, + String? content, + String? nonce, + @Default({}) Map meta, + @Default([]) List membersMetioned, + DateTime? editedAt, + @Default([]) List attachments, + @Default([]) List reactions, + String? repliedMessageId, + SnChatMessage? repliedMessage, + String? forwardedMessageId, + SnChatMessage? forwardedMessage, + required String senderId, + required SnChatMember sender, + required int chatRoomId, + }) = _SnChatMessage; + + factory SnChatMessage.fromJson(Map json) => + _$SnChatMessageFromJson(json); +} + +@freezed +abstract class SnChatReaction with _$SnChatReaction { + const factory SnChatReaction({ + required DateTime createdAt, + required DateTime updatedAt, + required DateTime? deletedAt, + required String id, + required String messageId, + required String senderId, + required SnChatMember sender, + required String symbol, + required int attitude, + }) = _SnChatReaction; + + factory SnChatReaction.fromJson(Map json) => + _$SnChatReactionFromJson(json); +} + +@freezed +abstract class SnChatMember with _$SnChatMember { + const factory SnChatMember({ + required DateTime createdAt, + required DateTime updatedAt, + required DateTime? deletedAt, + required String id, + required int chatRoomId, + required int accountId, + required String? nick, + required int role, + required int notify, + required DateTime? joinedAt, + required bool isBot, + }) = _SnChatMember; + + factory SnChatMember.fromJson(Map json) => + _$SnChatMemberFromJson(json); +} diff --git a/lib/models/chat.freezed.dart b/lib/models/chat.freezed.dart index 42ca4c9..0fd4724 100644 --- a/lib/models/chat.freezed.dart +++ b/lib/models/chat.freezed.dart @@ -250,4 +250,616 @@ $SnRealmCopyWith<$Res>? get realm { } } + +/// @nodoc +mixin _$SnChatMessage { + + DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; String get id; String? get content; String? get nonce; Map get meta; List get membersMetioned; DateTime? get editedAt; List get attachments; List get reactions; String? get repliedMessageId; SnChatMessage? get repliedMessage; String? get forwardedMessageId; SnChatMessage? get forwardedMessage; String get senderId; SnChatMember get sender; int get chatRoomId; +/// Create a copy of SnChatMessage +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$SnChatMessageCopyWith get copyWith => _$SnChatMessageCopyWithImpl(this as SnChatMessage, _$identity); + + /// Serializes this SnChatMessage to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is SnChatMessage&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.id, id) || other.id == id)&&(identical(other.content, content) || other.content == content)&&(identical(other.nonce, nonce) || other.nonce == nonce)&&const DeepCollectionEquality().equals(other.meta, meta)&&const DeepCollectionEquality().equals(other.membersMetioned, membersMetioned)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&const DeepCollectionEquality().equals(other.attachments, attachments)&&const DeepCollectionEquality().equals(other.reactions, reactions)&&(identical(other.repliedMessageId, repliedMessageId) || other.repliedMessageId == repliedMessageId)&&(identical(other.repliedMessage, repliedMessage) || other.repliedMessage == repliedMessage)&&(identical(other.forwardedMessageId, forwardedMessageId) || other.forwardedMessageId == forwardedMessageId)&&(identical(other.forwardedMessage, forwardedMessage) || other.forwardedMessage == forwardedMessage)&&(identical(other.senderId, senderId) || other.senderId == senderId)&&(identical(other.sender, sender) || other.sender == sender)&&(identical(other.chatRoomId, chatRoomId) || other.chatRoomId == chatRoomId)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,content,nonce,const DeepCollectionEquality().hash(meta),const DeepCollectionEquality().hash(membersMetioned),editedAt,const DeepCollectionEquality().hash(attachments),const DeepCollectionEquality().hash(reactions),repliedMessageId,repliedMessage,forwardedMessageId,forwardedMessage,senderId,sender,chatRoomId); + +@override +String toString() { + return 'SnChatMessage(createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, id: $id, content: $content, nonce: $nonce, meta: $meta, membersMetioned: $membersMetioned, editedAt: $editedAt, attachments: $attachments, reactions: $reactions, repliedMessageId: $repliedMessageId, repliedMessage: $repliedMessage, forwardedMessageId: $forwardedMessageId, forwardedMessage: $forwardedMessage, senderId: $senderId, sender: $sender, chatRoomId: $chatRoomId)'; +} + + +} + +/// @nodoc +abstract mixin class $SnChatMessageCopyWith<$Res> { + factory $SnChatMessageCopyWith(SnChatMessage value, $Res Function(SnChatMessage) _then) = _$SnChatMessageCopyWithImpl; +@useResult +$Res call({ + DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, String? content, String? nonce, Map meta, List membersMetioned, DateTime? editedAt, List attachments, List reactions, String? repliedMessageId, SnChatMessage? repliedMessage, String? forwardedMessageId, SnChatMessage? forwardedMessage, String senderId, SnChatMember sender, int chatRoomId +}); + + +$SnChatMessageCopyWith<$Res>? get repliedMessage;$SnChatMessageCopyWith<$Res>? get forwardedMessage;$SnChatMemberCopyWith<$Res> get sender; + +} +/// @nodoc +class _$SnChatMessageCopyWithImpl<$Res> + implements $SnChatMessageCopyWith<$Res> { + _$SnChatMessageCopyWithImpl(this._self, this._then); + + final SnChatMessage _self; + final $Res Function(SnChatMessage) _then; + +/// Create a copy of SnChatMessage +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? id = null,Object? content = freezed,Object? nonce = freezed,Object? meta = null,Object? membersMetioned = null,Object? editedAt = freezed,Object? attachments = null,Object? reactions = null,Object? repliedMessageId = freezed,Object? repliedMessage = freezed,Object? forwardedMessageId = freezed,Object? forwardedMessage = freezed,Object? senderId = null,Object? sender = null,Object? chatRoomId = null,}) { + return _then(_self.copyWith( +createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable +as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable +as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable +as DateTime?,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,content: freezed == content ? _self.content : content // ignore: cast_nullable_to_non_nullable +as String?,nonce: freezed == nonce ? _self.nonce : nonce // ignore: cast_nullable_to_non_nullable +as String?,meta: null == meta ? _self.meta : meta // ignore: cast_nullable_to_non_nullable +as Map,membersMetioned: null == membersMetioned ? _self.membersMetioned : membersMetioned // ignore: cast_nullable_to_non_nullable +as List,editedAt: freezed == editedAt ? _self.editedAt : editedAt // ignore: cast_nullable_to_non_nullable +as DateTime?,attachments: null == attachments ? _self.attachments : attachments // ignore: cast_nullable_to_non_nullable +as List,reactions: null == reactions ? _self.reactions : reactions // ignore: cast_nullable_to_non_nullable +as List,repliedMessageId: freezed == repliedMessageId ? _self.repliedMessageId : repliedMessageId // ignore: cast_nullable_to_non_nullable +as String?,repliedMessage: freezed == repliedMessage ? _self.repliedMessage : repliedMessage // ignore: cast_nullable_to_non_nullable +as SnChatMessage?,forwardedMessageId: freezed == forwardedMessageId ? _self.forwardedMessageId : forwardedMessageId // ignore: cast_nullable_to_non_nullable +as String?,forwardedMessage: freezed == forwardedMessage ? _self.forwardedMessage : forwardedMessage // ignore: cast_nullable_to_non_nullable +as SnChatMessage?,senderId: null == senderId ? _self.senderId : senderId // ignore: cast_nullable_to_non_nullable +as String,sender: null == sender ? _self.sender : sender // ignore: cast_nullable_to_non_nullable +as SnChatMember,chatRoomId: null == chatRoomId ? _self.chatRoomId : chatRoomId // ignore: cast_nullable_to_non_nullable +as int, + )); +} +/// Create a copy of SnChatMessage +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$SnChatMessageCopyWith<$Res>? get repliedMessage { + if (_self.repliedMessage == null) { + return null; + } + + return $SnChatMessageCopyWith<$Res>(_self.repliedMessage!, (value) { + return _then(_self.copyWith(repliedMessage: value)); + }); +}/// Create a copy of SnChatMessage +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$SnChatMessageCopyWith<$Res>? get forwardedMessage { + if (_self.forwardedMessage == null) { + return null; + } + + return $SnChatMessageCopyWith<$Res>(_self.forwardedMessage!, (value) { + return _then(_self.copyWith(forwardedMessage: value)); + }); +}/// Create a copy of SnChatMessage +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$SnChatMemberCopyWith<$Res> get sender { + + return $SnChatMemberCopyWith<$Res>(_self.sender, (value) { + return _then(_self.copyWith(sender: value)); + }); +} +} + + +/// @nodoc +@JsonSerializable() + +class _SnChatMessage implements SnChatMessage { + const _SnChatMessage({required this.createdAt, required this.updatedAt, this.deletedAt, required this.id, this.content, this.nonce, final Map meta = const {}, final List membersMetioned = const [], this.editedAt, final List attachments = const [], final List reactions = const [], this.repliedMessageId, this.repliedMessage, this.forwardedMessageId, this.forwardedMessage, required this.senderId, required this.sender, required this.chatRoomId}): _meta = meta,_membersMetioned = membersMetioned,_attachments = attachments,_reactions = reactions; + factory _SnChatMessage.fromJson(Map json) => _$SnChatMessageFromJson(json); + +@override final DateTime createdAt; +@override final DateTime updatedAt; +@override final DateTime? deletedAt; +@override final String id; +@override final String? content; +@override final String? nonce; + final Map _meta; +@override@JsonKey() Map get meta { + if (_meta is EqualUnmodifiableMapView) return _meta; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_meta); +} + + final List _membersMetioned; +@override@JsonKey() List get membersMetioned { + if (_membersMetioned is EqualUnmodifiableListView) return _membersMetioned; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_membersMetioned); +} + +@override final DateTime? editedAt; + final List _attachments; +@override@JsonKey() List get attachments { + if (_attachments is EqualUnmodifiableListView) return _attachments; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_attachments); +} + + final List _reactions; +@override@JsonKey() List get reactions { + if (_reactions is EqualUnmodifiableListView) return _reactions; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_reactions); +} + +@override final String? repliedMessageId; +@override final SnChatMessage? repliedMessage; +@override final String? forwardedMessageId; +@override final SnChatMessage? forwardedMessage; +@override final String senderId; +@override final SnChatMember sender; +@override final int chatRoomId; + +/// Create a copy of SnChatMessage +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$SnChatMessageCopyWith<_SnChatMessage> get copyWith => __$SnChatMessageCopyWithImpl<_SnChatMessage>(this, _$identity); + +@override +Map toJson() { + return _$SnChatMessageToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnChatMessage&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.id, id) || other.id == id)&&(identical(other.content, content) || other.content == content)&&(identical(other.nonce, nonce) || other.nonce == nonce)&&const DeepCollectionEquality().equals(other._meta, _meta)&&const DeepCollectionEquality().equals(other._membersMetioned, _membersMetioned)&&(identical(other.editedAt, editedAt) || other.editedAt == editedAt)&&const DeepCollectionEquality().equals(other._attachments, _attachments)&&const DeepCollectionEquality().equals(other._reactions, _reactions)&&(identical(other.repliedMessageId, repliedMessageId) || other.repliedMessageId == repliedMessageId)&&(identical(other.repliedMessage, repliedMessage) || other.repliedMessage == repliedMessage)&&(identical(other.forwardedMessageId, forwardedMessageId) || other.forwardedMessageId == forwardedMessageId)&&(identical(other.forwardedMessage, forwardedMessage) || other.forwardedMessage == forwardedMessage)&&(identical(other.senderId, senderId) || other.senderId == senderId)&&(identical(other.sender, sender) || other.sender == sender)&&(identical(other.chatRoomId, chatRoomId) || other.chatRoomId == chatRoomId)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,content,nonce,const DeepCollectionEquality().hash(_meta),const DeepCollectionEquality().hash(_membersMetioned),editedAt,const DeepCollectionEquality().hash(_attachments),const DeepCollectionEquality().hash(_reactions),repliedMessageId,repliedMessage,forwardedMessageId,forwardedMessage,senderId,sender,chatRoomId); + +@override +String toString() { + return 'SnChatMessage(createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, id: $id, content: $content, nonce: $nonce, meta: $meta, membersMetioned: $membersMetioned, editedAt: $editedAt, attachments: $attachments, reactions: $reactions, repliedMessageId: $repliedMessageId, repliedMessage: $repliedMessage, forwardedMessageId: $forwardedMessageId, forwardedMessage: $forwardedMessage, senderId: $senderId, sender: $sender, chatRoomId: $chatRoomId)'; +} + + +} + +/// @nodoc +abstract mixin class _$SnChatMessageCopyWith<$Res> implements $SnChatMessageCopyWith<$Res> { + factory _$SnChatMessageCopyWith(_SnChatMessage value, $Res Function(_SnChatMessage) _then) = __$SnChatMessageCopyWithImpl; +@override @useResult +$Res call({ + DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, String? content, String? nonce, Map meta, List membersMetioned, DateTime? editedAt, List attachments, List reactions, String? repliedMessageId, SnChatMessage? repliedMessage, String? forwardedMessageId, SnChatMessage? forwardedMessage, String senderId, SnChatMember sender, int chatRoomId +}); + + +@override $SnChatMessageCopyWith<$Res>? get repliedMessage;@override $SnChatMessageCopyWith<$Res>? get forwardedMessage;@override $SnChatMemberCopyWith<$Res> get sender; + +} +/// @nodoc +class __$SnChatMessageCopyWithImpl<$Res> + implements _$SnChatMessageCopyWith<$Res> { + __$SnChatMessageCopyWithImpl(this._self, this._then); + + final _SnChatMessage _self; + final $Res Function(_SnChatMessage) _then; + +/// Create a copy of SnChatMessage +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? id = null,Object? content = freezed,Object? nonce = freezed,Object? meta = null,Object? membersMetioned = null,Object? editedAt = freezed,Object? attachments = null,Object? reactions = null,Object? repliedMessageId = freezed,Object? repliedMessage = freezed,Object? forwardedMessageId = freezed,Object? forwardedMessage = freezed,Object? senderId = null,Object? sender = null,Object? chatRoomId = null,}) { + return _then(_SnChatMessage( +createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable +as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable +as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable +as DateTime?,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,content: freezed == content ? _self.content : content // ignore: cast_nullable_to_non_nullable +as String?,nonce: freezed == nonce ? _self.nonce : nonce // ignore: cast_nullable_to_non_nullable +as String?,meta: null == meta ? _self._meta : meta // ignore: cast_nullable_to_non_nullable +as Map,membersMetioned: null == membersMetioned ? _self._membersMetioned : membersMetioned // ignore: cast_nullable_to_non_nullable +as List,editedAt: freezed == editedAt ? _self.editedAt : editedAt // ignore: cast_nullable_to_non_nullable +as DateTime?,attachments: null == attachments ? _self._attachments : attachments // ignore: cast_nullable_to_non_nullable +as List,reactions: null == reactions ? _self._reactions : reactions // ignore: cast_nullable_to_non_nullable +as List,repliedMessageId: freezed == repliedMessageId ? _self.repliedMessageId : repliedMessageId // ignore: cast_nullable_to_non_nullable +as String?,repliedMessage: freezed == repliedMessage ? _self.repliedMessage : repliedMessage // ignore: cast_nullable_to_non_nullable +as SnChatMessage?,forwardedMessageId: freezed == forwardedMessageId ? _self.forwardedMessageId : forwardedMessageId // ignore: cast_nullable_to_non_nullable +as String?,forwardedMessage: freezed == forwardedMessage ? _self.forwardedMessage : forwardedMessage // ignore: cast_nullable_to_non_nullable +as SnChatMessage?,senderId: null == senderId ? _self.senderId : senderId // ignore: cast_nullable_to_non_nullable +as String,sender: null == sender ? _self.sender : sender // ignore: cast_nullable_to_non_nullable +as SnChatMember,chatRoomId: null == chatRoomId ? _self.chatRoomId : chatRoomId // ignore: cast_nullable_to_non_nullable +as int, + )); +} + +/// Create a copy of SnChatMessage +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$SnChatMessageCopyWith<$Res>? get repliedMessage { + if (_self.repliedMessage == null) { + return null; + } + + return $SnChatMessageCopyWith<$Res>(_self.repliedMessage!, (value) { + return _then(_self.copyWith(repliedMessage: value)); + }); +}/// Create a copy of SnChatMessage +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$SnChatMessageCopyWith<$Res>? get forwardedMessage { + if (_self.forwardedMessage == null) { + return null; + } + + return $SnChatMessageCopyWith<$Res>(_self.forwardedMessage!, (value) { + return _then(_self.copyWith(forwardedMessage: value)); + }); +}/// Create a copy of SnChatMessage +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$SnChatMemberCopyWith<$Res> get sender { + + return $SnChatMemberCopyWith<$Res>(_self.sender, (value) { + return _then(_self.copyWith(sender: value)); + }); +} +} + + +/// @nodoc +mixin _$SnChatReaction { + + DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; String get id; String get messageId; String get senderId; SnChatMember get sender; String get symbol; int get attitude; +/// Create a copy of SnChatReaction +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$SnChatReactionCopyWith get copyWith => _$SnChatReactionCopyWithImpl(this as SnChatReaction, _$identity); + + /// Serializes this SnChatReaction to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is SnChatReaction&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.id, id) || other.id == id)&&(identical(other.messageId, messageId) || other.messageId == messageId)&&(identical(other.senderId, senderId) || other.senderId == senderId)&&(identical(other.sender, sender) || other.sender == sender)&&(identical(other.symbol, symbol) || other.symbol == symbol)&&(identical(other.attitude, attitude) || other.attitude == attitude)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,messageId,senderId,sender,symbol,attitude); + +@override +String toString() { + return 'SnChatReaction(createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, id: $id, messageId: $messageId, senderId: $senderId, sender: $sender, symbol: $symbol, attitude: $attitude)'; +} + + +} + +/// @nodoc +abstract mixin class $SnChatReactionCopyWith<$Res> { + factory $SnChatReactionCopyWith(SnChatReaction value, $Res Function(SnChatReaction) _then) = _$SnChatReactionCopyWithImpl; +@useResult +$Res call({ + DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, String messageId, String senderId, SnChatMember sender, String symbol, int attitude +}); + + +$SnChatMemberCopyWith<$Res> get sender; + +} +/// @nodoc +class _$SnChatReactionCopyWithImpl<$Res> + implements $SnChatReactionCopyWith<$Res> { + _$SnChatReactionCopyWithImpl(this._self, this._then); + + final SnChatReaction _self; + final $Res Function(SnChatReaction) _then; + +/// Create a copy of SnChatReaction +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? id = null,Object? messageId = null,Object? senderId = null,Object? sender = null,Object? symbol = null,Object? attitude = null,}) { + return _then(_self.copyWith( +createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable +as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable +as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable +as DateTime?,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,messageId: null == messageId ? _self.messageId : messageId // ignore: cast_nullable_to_non_nullable +as String,senderId: null == senderId ? _self.senderId : senderId // ignore: cast_nullable_to_non_nullable +as String,sender: null == sender ? _self.sender : sender // ignore: cast_nullable_to_non_nullable +as SnChatMember,symbol: null == symbol ? _self.symbol : symbol // ignore: cast_nullable_to_non_nullable +as String,attitude: null == attitude ? _self.attitude : attitude // ignore: cast_nullable_to_non_nullable +as int, + )); +} +/// Create a copy of SnChatReaction +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$SnChatMemberCopyWith<$Res> get sender { + + return $SnChatMemberCopyWith<$Res>(_self.sender, (value) { + return _then(_self.copyWith(sender: value)); + }); +} +} + + +/// @nodoc +@JsonSerializable() + +class _SnChatReaction implements SnChatReaction { + const _SnChatReaction({required this.createdAt, required this.updatedAt, required this.deletedAt, required this.id, required this.messageId, required this.senderId, required this.sender, required this.symbol, required this.attitude}); + factory _SnChatReaction.fromJson(Map json) => _$SnChatReactionFromJson(json); + +@override final DateTime createdAt; +@override final DateTime updatedAt; +@override final DateTime? deletedAt; +@override final String id; +@override final String messageId; +@override final String senderId; +@override final SnChatMember sender; +@override final String symbol; +@override final int attitude; + +/// Create a copy of SnChatReaction +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$SnChatReactionCopyWith<_SnChatReaction> get copyWith => __$SnChatReactionCopyWithImpl<_SnChatReaction>(this, _$identity); + +@override +Map toJson() { + return _$SnChatReactionToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnChatReaction&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.id, id) || other.id == id)&&(identical(other.messageId, messageId) || other.messageId == messageId)&&(identical(other.senderId, senderId) || other.senderId == senderId)&&(identical(other.sender, sender) || other.sender == sender)&&(identical(other.symbol, symbol) || other.symbol == symbol)&&(identical(other.attitude, attitude) || other.attitude == attitude)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,messageId,senderId,sender,symbol,attitude); + +@override +String toString() { + return 'SnChatReaction(createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, id: $id, messageId: $messageId, senderId: $senderId, sender: $sender, symbol: $symbol, attitude: $attitude)'; +} + + +} + +/// @nodoc +abstract mixin class _$SnChatReactionCopyWith<$Res> implements $SnChatReactionCopyWith<$Res> { + factory _$SnChatReactionCopyWith(_SnChatReaction value, $Res Function(_SnChatReaction) _then) = __$SnChatReactionCopyWithImpl; +@override @useResult +$Res call({ + DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, String messageId, String senderId, SnChatMember sender, String symbol, int attitude +}); + + +@override $SnChatMemberCopyWith<$Res> get sender; + +} +/// @nodoc +class __$SnChatReactionCopyWithImpl<$Res> + implements _$SnChatReactionCopyWith<$Res> { + __$SnChatReactionCopyWithImpl(this._self, this._then); + + final _SnChatReaction _self; + final $Res Function(_SnChatReaction) _then; + +/// Create a copy of SnChatReaction +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? id = null,Object? messageId = null,Object? senderId = null,Object? sender = null,Object? symbol = null,Object? attitude = null,}) { + return _then(_SnChatReaction( +createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable +as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable +as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable +as DateTime?,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,messageId: null == messageId ? _self.messageId : messageId // ignore: cast_nullable_to_non_nullable +as String,senderId: null == senderId ? _self.senderId : senderId // ignore: cast_nullable_to_non_nullable +as String,sender: null == sender ? _self.sender : sender // ignore: cast_nullable_to_non_nullable +as SnChatMember,symbol: null == symbol ? _self.symbol : symbol // ignore: cast_nullable_to_non_nullable +as String,attitude: null == attitude ? _self.attitude : attitude // ignore: cast_nullable_to_non_nullable +as int, + )); +} + +/// Create a copy of SnChatReaction +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$SnChatMemberCopyWith<$Res> get sender { + + return $SnChatMemberCopyWith<$Res>(_self.sender, (value) { + return _then(_self.copyWith(sender: value)); + }); +} +} + + +/// @nodoc +mixin _$SnChatMember { + + DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; String get id; int get chatRoomId; int get accountId; String? get nick; int get role; int get notify; DateTime? get joinedAt; bool get isBot; +/// Create a copy of SnChatMember +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$SnChatMemberCopyWith get copyWith => _$SnChatMemberCopyWithImpl(this as SnChatMember, _$identity); + + /// Serializes this SnChatMember to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is SnChatMember&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.id, id) || other.id == id)&&(identical(other.chatRoomId, chatRoomId) || other.chatRoomId == chatRoomId)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.role, role) || other.role == role)&&(identical(other.notify, notify) || other.notify == notify)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.isBot, isBot) || other.isBot == isBot)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,chatRoomId,accountId,nick,role,notify,joinedAt,isBot); + +@override +String toString() { + return 'SnChatMember(createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, id: $id, chatRoomId: $chatRoomId, accountId: $accountId, nick: $nick, role: $role, notify: $notify, joinedAt: $joinedAt, isBot: $isBot)'; +} + + +} + +/// @nodoc +abstract mixin class $SnChatMemberCopyWith<$Res> { + factory $SnChatMemberCopyWith(SnChatMember value, $Res Function(SnChatMember) _then) = _$SnChatMemberCopyWithImpl; +@useResult +$Res call({ + DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, int chatRoomId, int accountId, String? nick, int role, int notify, DateTime? joinedAt, bool isBot +}); + + + + +} +/// @nodoc +class _$SnChatMemberCopyWithImpl<$Res> + implements $SnChatMemberCopyWith<$Res> { + _$SnChatMemberCopyWithImpl(this._self, this._then); + + final SnChatMember _self; + final $Res Function(SnChatMember) _then; + +/// Create a copy of SnChatMember +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? id = null,Object? chatRoomId = null,Object? accountId = null,Object? nick = freezed,Object? role = null,Object? notify = null,Object? joinedAt = freezed,Object? isBot = null,}) { + return _then(_self.copyWith( +createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable +as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable +as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable +as DateTime?,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,chatRoomId: null == chatRoomId ? _self.chatRoomId : chatRoomId // ignore: cast_nullable_to_non_nullable +as int,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable +as int,nick: freezed == nick ? _self.nick : nick // ignore: cast_nullable_to_non_nullable +as String?,role: null == role ? _self.role : role // ignore: cast_nullable_to_non_nullable +as int,notify: null == notify ? _self.notify : notify // ignore: cast_nullable_to_non_nullable +as int,joinedAt: freezed == joinedAt ? _self.joinedAt : joinedAt // ignore: cast_nullable_to_non_nullable +as DateTime?,isBot: null == isBot ? _self.isBot : isBot // ignore: cast_nullable_to_non_nullable +as bool, + )); +} + +} + + +/// @nodoc +@JsonSerializable() + +class _SnChatMember implements SnChatMember { + const _SnChatMember({required this.createdAt, required this.updatedAt, required this.deletedAt, required this.id, required this.chatRoomId, required this.accountId, required this.nick, required this.role, required this.notify, required this.joinedAt, required this.isBot}); + factory _SnChatMember.fromJson(Map json) => _$SnChatMemberFromJson(json); + +@override final DateTime createdAt; +@override final DateTime updatedAt; +@override final DateTime? deletedAt; +@override final String id; +@override final int chatRoomId; +@override final int accountId; +@override final String? nick; +@override final int role; +@override final int notify; +@override final DateTime? joinedAt; +@override final bool isBot; + +/// Create a copy of SnChatMember +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$SnChatMemberCopyWith<_SnChatMember> get copyWith => __$SnChatMemberCopyWithImpl<_SnChatMember>(this, _$identity); + +@override +Map toJson() { + return _$SnChatMemberToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnChatMember&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.id, id) || other.id == id)&&(identical(other.chatRoomId, chatRoomId) || other.chatRoomId == chatRoomId)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.role, role) || other.role == role)&&(identical(other.notify, notify) || other.notify == notify)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.isBot, isBot) || other.isBot == isBot)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,createdAt,updatedAt,deletedAt,id,chatRoomId,accountId,nick,role,notify,joinedAt,isBot); + +@override +String toString() { + return 'SnChatMember(createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, id: $id, chatRoomId: $chatRoomId, accountId: $accountId, nick: $nick, role: $role, notify: $notify, joinedAt: $joinedAt, isBot: $isBot)'; +} + + +} + +/// @nodoc +abstract mixin class _$SnChatMemberCopyWith<$Res> implements $SnChatMemberCopyWith<$Res> { + factory _$SnChatMemberCopyWith(_SnChatMember value, $Res Function(_SnChatMember) _then) = __$SnChatMemberCopyWithImpl; +@override @useResult +$Res call({ + DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, String id, int chatRoomId, int accountId, String? nick, int role, int notify, DateTime? joinedAt, bool isBot +}); + + + + +} +/// @nodoc +class __$SnChatMemberCopyWithImpl<$Res> + implements _$SnChatMemberCopyWith<$Res> { + __$SnChatMemberCopyWithImpl(this._self, this._then); + + final _SnChatMember _self; + final $Res Function(_SnChatMember) _then; + +/// Create a copy of SnChatMember +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? id = null,Object? chatRoomId = null,Object? accountId = null,Object? nick = freezed,Object? role = null,Object? notify = null,Object? joinedAt = freezed,Object? isBot = null,}) { + return _then(_SnChatMember( +createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable +as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable +as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable +as DateTime?,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,chatRoomId: null == chatRoomId ? _self.chatRoomId : chatRoomId // ignore: cast_nullable_to_non_nullable +as int,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable +as int,nick: freezed == nick ? _self.nick : nick // ignore: cast_nullable_to_non_nullable +as String?,role: null == role ? _self.role : role // ignore: cast_nullable_to_non_nullable +as int,notify: null == notify ? _self.notify : notify // ignore: cast_nullable_to_non_nullable +as int,joinedAt: freezed == joinedAt ? _self.joinedAt : joinedAt // ignore: cast_nullable_to_non_nullable +as DateTime?,isBot: null == isBot ? _self.isBot : isBot // ignore: cast_nullable_to_non_nullable +as bool, + )); +} + + +} + // dart format on diff --git a/lib/models/chat.g.dart b/lib/models/chat.g.dart index 128f21e..a09e382 100644 --- a/lib/models/chat.g.dart +++ b/lib/models/chat.g.dart @@ -47,3 +47,140 @@ Map _$SnChatToJson(_SnChat instance) => { 'updated_at': instance.updatedAt.toIso8601String(), 'deleted_at': instance.deletedAt?.toIso8601String(), }; + +_SnChatMessage _$SnChatMessageFromJson(Map json) => + _SnChatMessage( + createdAt: DateTime.parse(json['created_at'] as String), + updatedAt: DateTime.parse(json['updated_at'] as String), + deletedAt: + json['deleted_at'] == null + ? null + : DateTime.parse(json['deleted_at'] as String), + id: json['id'] as String, + content: json['content'] as String?, + nonce: json['nonce'] as String?, + meta: json['meta'] as Map? ?? const {}, + membersMetioned: + (json['members_metioned'] as List?) + ?.map((e) => e as String) + .toList() ?? + const [], + editedAt: + json['edited_at'] == null + ? null + : DateTime.parse(json['edited_at'] as String), + attachments: + (json['attachments'] as List?) + ?.map((e) => SnCloudFile.fromJson(e as Map)) + .toList() ?? + const [], + reactions: + (json['reactions'] as List?) + ?.map((e) => SnChatReaction.fromJson(e as Map)) + .toList() ?? + const [], + repliedMessageId: json['replied_message_id'] as String?, + repliedMessage: + json['replied_message'] == null + ? null + : SnChatMessage.fromJson( + json['replied_message'] as Map, + ), + forwardedMessageId: json['forwarded_message_id'] as String?, + forwardedMessage: + json['forwarded_message'] == null + ? null + : SnChatMessage.fromJson( + json['forwarded_message'] as Map, + ), + senderId: json['sender_id'] as String, + sender: SnChatMember.fromJson(json['sender'] as Map), + chatRoomId: (json['chat_room_id'] as num).toInt(), + ); + +Map _$SnChatMessageToJson(_SnChatMessage instance) => + { + 'created_at': instance.createdAt.toIso8601String(), + 'updated_at': instance.updatedAt.toIso8601String(), + 'deleted_at': instance.deletedAt?.toIso8601String(), + 'id': instance.id, + 'content': instance.content, + 'nonce': instance.nonce, + 'meta': instance.meta, + 'members_metioned': instance.membersMetioned, + 'edited_at': instance.editedAt?.toIso8601String(), + 'attachments': instance.attachments.map((e) => e.toJson()).toList(), + 'reactions': instance.reactions.map((e) => e.toJson()).toList(), + 'replied_message_id': instance.repliedMessageId, + 'replied_message': instance.repliedMessage?.toJson(), + 'forwarded_message_id': instance.forwardedMessageId, + 'forwarded_message': instance.forwardedMessage?.toJson(), + 'sender_id': instance.senderId, + 'sender': instance.sender.toJson(), + 'chat_room_id': instance.chatRoomId, + }; + +_SnChatReaction _$SnChatReactionFromJson(Map json) => + _SnChatReaction( + createdAt: DateTime.parse(json['created_at'] as String), + updatedAt: DateTime.parse(json['updated_at'] as String), + deletedAt: + json['deleted_at'] == null + ? null + : DateTime.parse(json['deleted_at'] as String), + id: json['id'] as String, + messageId: json['message_id'] as String, + senderId: json['sender_id'] as String, + sender: SnChatMember.fromJson(json['sender'] as Map), + symbol: json['symbol'] as String, + attitude: (json['attitude'] as num).toInt(), + ); + +Map _$SnChatReactionToJson(_SnChatReaction instance) => + { + 'created_at': instance.createdAt.toIso8601String(), + 'updated_at': instance.updatedAt.toIso8601String(), + 'deleted_at': instance.deletedAt?.toIso8601String(), + 'id': instance.id, + 'message_id': instance.messageId, + 'sender_id': instance.senderId, + 'sender': instance.sender.toJson(), + 'symbol': instance.symbol, + 'attitude': instance.attitude, + }; + +_SnChatMember _$SnChatMemberFromJson(Map json) => + _SnChatMember( + createdAt: DateTime.parse(json['created_at'] as String), + updatedAt: DateTime.parse(json['updated_at'] as String), + deletedAt: + json['deleted_at'] == null + ? null + : DateTime.parse(json['deleted_at'] as String), + id: json['id'] as String, + chatRoomId: (json['chat_room_id'] as num).toInt(), + accountId: (json['account_id'] as num).toInt(), + nick: json['nick'] as String?, + role: (json['role'] as num).toInt(), + notify: (json['notify'] as num).toInt(), + joinedAt: + json['joined_at'] == null + ? null + : DateTime.parse(json['joined_at'] as String), + isBot: json['is_bot'] as bool, + ); + +Map _$SnChatMemberToJson(_SnChatMember instance) => + { + 'created_at': instance.createdAt.toIso8601String(), + 'updated_at': instance.updatedAt.toIso8601String(), + 'deleted_at': instance.deletedAt?.toIso8601String(), + 'id': instance.id, + 'chat_room_id': instance.chatRoomId, + 'account_id': instance.accountId, + 'nick': instance.nick, + 'role': instance.role, + 'notify': instance.notify, + 'joined_at': instance.joinedAt?.toIso8601String(), + 'is_bot': instance.isBot, + }; diff --git a/lib/pods/message.dart b/lib/pods/message.dart new file mode 100644 index 0000000..d230ed1 --- /dev/null +++ b/lib/pods/message.dart @@ -0,0 +1,100 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:island/database/drift_db.dart'; +import 'package:island/database/message.dart'; +import 'package:island/database/message_repository.dart'; +import 'package:island/models/chat.dart'; +import 'package:island/pods/network.dart'; +import 'package:island/widgets/alert.dart'; + +// Global database instance +final databaseProvider = Provider((ref) { + final db = AppDatabase(); + ref.onDispose(() => db.close()); + return db; +}); + +final messageRepositoryProvider = + FutureProvider.family((ref, chat) async { + final apiClient = ref.watch(apiClientProvider); + final database = ref.watch(databaseProvider); + return MessageRepository(chat, apiClient, database); + }); + +final chatMessagesProvider = + FutureProvider.family, SnChat>((ref, room) async { + final repository = await ref.watch( + messageRepositoryProvider(room).future, + ); + return repository.listMessages(); + }); + +class ChatMessageNotifier + extends StateNotifier>> { + final MessageRepository _repository; + final int roomId; + int _currentOffset = 0; + final int _pageSize = 20; + bool _hasMore = true; + + ChatMessageNotifier(this._repository, this.roomId) + : super(const AsyncValue.loading()) { + loadInitial(); + } + + Future loadInitial() async { + state = const AsyncValue.loading(); + try { + final messages = await _repository.listMessages( + offset: 0, + take: _pageSize, + ); + _currentOffset = messages.length; + _hasMore = messages.length >= _pageSize; + state = AsyncValue.data(messages); + } catch (e, stack) { + state = AsyncValue.error(e, stack); + } + } + + Future loadMore() async { + if (!_hasMore) return; + + try { + final newMessages = await _repository.listMessages( + offset: _currentOffset, + take: _pageSize, + ); + + if (newMessages.isEmpty) { + _hasMore = false; + return; + } + + _currentOffset += newMessages.length; + _hasMore = newMessages.length >= _pageSize; + + state = AsyncValue.data([...state.value ?? [], ...newMessages]); + } catch (err) { + showErrorAlert(err); + } + } + + Future sendMessage(String content) async { + try { + final message = await _repository.sendMessage(roomId, content); + + final currentMessages = state.value ?? []; + state = AsyncValue.data([message, ...currentMessages]); + } catch (err) { + showErrorAlert(err); + } + } + + bool get hasMore => _hasMore; +} + +final chatMessageNotifierProvider = StateNotifierProvider.family< + ChatMessageNotifier, + AsyncValue>, + MessageRepository +>((ref, repository) => ChatMessageNotifier(repository, repository.room.id)); diff --git a/lib/screens/chat/new_chat.dart b/lib/screens/chat/new_chat.dart deleted file mode 100644 index e69de29..0000000 diff --git a/lib/screens/chat/room.dart b/lib/screens/chat/room.dart index 6968936..11ce5c0 100644 --- a/lib/screens/chat/room.dart +++ b/lib/screens/chat/room.dart @@ -4,11 +4,123 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:island/database/message.dart'; +import 'package:island/database/message_repository.dart'; +import 'package:island/pods/message.dart'; +import 'package:island/pods/network.dart'; +import 'package:island/widgets/alert.dart'; import 'package:island/widgets/content/cloud_files.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart'; import 'package:styled_widget/styled_widget.dart'; import 'chat.dart'; +final messageRepositoryProvider = FutureProvider.family( + (ref, roomId) async { + final room = ref.watch(chatroomProvider(roomId)).value; + final apiClient = ref.watch(apiClientProvider); + final database = ref.watch(databaseProvider); + return MessageRepository(room!, apiClient, database); + }, +); + +// Provider for messages with pagination +final messagesProvider = StateNotifierProvider.family< + MessagesNotifier, + AsyncValue>, + int +>((ref, roomId) => MessagesNotifier(ref, roomId)); + +class MessagesNotifier + extends StateNotifier>> { + final Ref _ref; + final int _roomId; + int _currentPage = 0; + static const int _pageSize = 20; + bool _hasMore = true; + + MessagesNotifier(this._ref, this._roomId) + : super(const AsyncValue.loading()) { + loadInitial(); + } + + Future loadInitial() async { + try { + final repository = await _ref.read( + messageRepositoryProvider(_roomId).future, + ); + final messages = await repository.listMessages( + offset: 0, + take: _pageSize, + ); + state = AsyncValue.data(messages); + _currentPage = 0; + _hasMore = messages.length == _pageSize; + } catch (e, stack) { + state = AsyncValue.error(e, stack); + } + } + + Future loadMore() async { + if (!_hasMore || state is AsyncLoading) return; + + try { + final currentMessages = state.value ?? []; + _currentPage++; + final repository = await _ref.read( + messageRepositoryProvider(_roomId).future, + ); + final newMessages = await repository.listMessages( + offset: _currentPage * _pageSize, + take: _pageSize, + ); + + if (newMessages.isEmpty || newMessages.length < _pageSize) { + _hasMore = false; + } + + state = AsyncValue.data([...currentMessages, ...newMessages]); + } catch (err) { + showErrorAlert(err); + _currentPage--; + } + } + + Future sendMessage(String content) async { + try { + final repository = await _ref.read( + messageRepositoryProvider(_roomId).future, + ); + final message = await repository.sendMessage(_roomId, content); + + // Add the new message to the list + final currentMessages = state.value ?? []; + state = AsyncValue.data([message, ...currentMessages]); + } catch (err) { + showErrorAlert(err); + } + } + + Future retryMessage(String pendingMessageId) async { + try { + final repository = await _ref.read( + messageRepositoryProvider(_roomId).future, + ); + final updatedMessage = await repository.retryMessage(pendingMessageId); + + // Update the message in the list + final currentMessages = state.value ?? []; + final index = currentMessages.indexWhere((m) => m.id == pendingMessageId); + if (index >= 0) { + final newList = [...currentMessages]; + newList[index] = updatedMessage; + state = AsyncValue.data(newList); + } + } catch (err) { + showErrorAlert(err); + } + } +} + @RoutePage() class ChatRoomScreen extends HookConsumerWidget { final int id; @@ -17,8 +129,24 @@ class ChatRoomScreen extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final chatRoom = ref.watch(chatroomProvider(id)); + final messages = ref.watch(messagesProvider(id)); + final messagesNotifier = ref.read(messagesProvider(id).notifier); final messageController = useTextEditingController(); + final scrollController = useScrollController(); + + // Add scroll listener for pagination + useEffect(() { + void onScroll() { + if (scrollController.position.pixels >= + scrollController.position.maxScrollExtent - 200) { + messagesNotifier.loadMore(); + } + } + + scrollController.addListener(onScroll); + return () => scrollController.removeListener(onScroll); + }, [scrollController]); return Scaffold( appBar: AppBar( @@ -59,10 +187,34 @@ class ChatRoomScreen extends HookConsumerWidget { body: Column( children: [ Expanded( - child: chatRoom.when( - data: (room) => SizedBox.expand(), + child: messages.when( + data: + (messageList) => + messageList.isEmpty + ? Center(child: Text('No messages yet'.tr())) + : ListView.builder( + controller: scrollController, + reverse: true, // Show newest messages at the bottom + itemCount: messageList.length, + itemBuilder: (context, index) { + final message = messageList[index]; + return MessageBubble(message: message); + }, + ), loading: () => const Center(child: CircularProgressIndicator()), - error: (error, stack) => Center(child: Text('Error: $error')), + error: + (error, stack) => Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text('Error: $error'), + ElevatedButton( + onPressed: () => messagesNotifier.loadInitial(), + child: Text('Retry'.tr()), + ), + ], + ), + ), ), ), Material( @@ -105,7 +257,14 @@ class ChatRoomScreen extends HookConsumerWidget { IconButton( icon: const Icon(Icons.send), color: Theme.of(context).colorScheme.primary, - onPressed: () {}, + onPressed: () { + if (messageController.text.trim().isNotEmpty) { + messagesNotifier.sendMessage( + messageController.text.trim(), + ); + messageController.clear(); + } + }, ), ], ).padding(bottom: MediaQuery.of(context).padding.bottom), @@ -116,3 +275,99 @@ class ChatRoomScreen extends HookConsumerWidget { ); } } + +class MessageBubble extends StatelessWidget { + final LocalChatMessage message; + + const MessageBubble({Key? key, required this.message}) : super(key: key); + + @override + Widget build(BuildContext context) { + final isCurrentUser = + message.senderId == 'current_user_id'; // Replace with actual check + + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), + child: Row( + mainAxisAlignment: + isCurrentUser ? MainAxisAlignment.end : MainAxisAlignment.start, + children: [ + if (!isCurrentUser) + CircleAvatar( + radius: 16, + child: Text(message.senderId[0].toUpperCase()), + ), + const Gap(8), + Flexible( + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: + isCurrentUser + ? Theme.of(context).colorScheme.primary.withOpacity(0.8) + : Colors.grey.shade200, + borderRadius: BorderRadius.circular(16), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + message.toRemoteMessage().content ?? '', + style: TextStyle( + color: isCurrentUser ? Colors.white : Colors.black, + ), + ), + const Gap(4), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + DateFormat.Hm().format(message.createdAt), + style: TextStyle( + fontSize: 10, + color: + isCurrentUser ? Colors.white70 : Colors.black54, + ), + ), + const Gap(4), + if (isCurrentUser) + _buildStatusIcon(context, message.status), + ], + ), + ], + ), + ), + ), + const Gap(8), + if (isCurrentUser) + const SizedBox(width: 32), // Balance with avatar on the other side + ], + ), + ); + } + + Widget _buildStatusIcon(BuildContext context, MessageStatus status) { + switch (status) { + case MessageStatus.pending: + return const Icon(Icons.access_time, size: 12, color: Colors.white70); + case MessageStatus.sent: + return const Icon(Icons.check, size: 12, color: Colors.white70); + case MessageStatus.failed: + return Consumer( + builder: + (context, ref, _) => GestureDetector( + onTap: () { + ref + .read(messagesProvider(message.roomId).notifier) + .retryMessage(message.id); + }, + child: const Icon( + Icons.error_outline, + size: 12, + color: Colors.red, + ), + ), + ); + } + } +} diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index aebd246..d55b720 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -38,6 +39,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) media_kit_video_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "MediaKitVideoPlugin"); media_kit_video_plugin_register_with_registrar(media_kit_video_registrar); + g_autoptr(FlPluginRegistrar) sqlite3_flutter_libs_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "Sqlite3FlutterLibsPlugin"); + sqlite3_flutter_libs_plugin_register_with_registrar(sqlite3_flutter_libs_registrar); g_autoptr(FlPluginRegistrar) super_native_extensions_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "SuperNativeExtensionsPlugin"); super_native_extensions_plugin_register_with_registrar(super_native_extensions_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 3460c6a..1c913d8 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -10,6 +10,7 @@ list(APPEND FLUTTER_PLUGIN_LIST irondash_engine_context media_kit_libs_linux media_kit_video + sqlite3_flutter_libs super_native_extensions url_launcher_linux ) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 150250d..7f9cef8 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -22,6 +22,7 @@ import path_provider_foundation import quill_native_bridge_macos import shared_preferences_foundation import sqflite_darwin +import sqlite3_flutter_libs import super_native_extensions import url_launcher_macos import volume_controller @@ -45,6 +46,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { QuillNativeBridgePlugin.register(with: registry.registrar(forPlugin: "QuillNativeBridgePlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) + Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin")) SuperNativeExtensionsPlugin.register(with: registry.registrar(forPlugin: "SuperNativeExtensionsPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) VolumeControllerPlugin.register(with: registry.registrar(forPlugin: "VolumeControllerPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 33ee8e8..9649f8a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -425,6 +425,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + drift: + dependency: "direct main" + description: + name: drift + sha256: "14a61af39d4584faf1d73b5b35e4b758a43008cf4c0fdb0576ec8e7032c0d9a5" + url: "https://pub.dev" + source: hosted + version: "2.26.0" + drift_dev: + dependency: "direct dev" + description: + name: drift_dev + sha256: "0d3f8b33b76cf1c6a82ee34d9511c40957549c4674b8f1688609e6d6c7306588" + url: "https://pub.dev" + source: hosted + version: "2.26.0" + drift_flutter: + dependency: "direct main" + description: + name: drift_flutter + sha256: "0cadbf3b8733409a6cf61d18ba2e94e149df81df7de26f48ae0695b48fd71922" + url: "https://pub.dev" + source: hosted + version: "0.2.4" easy_localization: dependency: "direct main" description: @@ -1270,7 +1294,7 @@ packages: source: hosted version: "3.2.0" path: - dependency: transitive + dependency: "direct main" description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" @@ -1477,6 +1501,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.2" + recase: + dependency: transitive + description: + name: recase + sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213 + url: "https://pub.dev" + source: hosted + version: "4.1.0" responsive_framework: dependency: "direct main" description: @@ -1714,6 +1746,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.4.0" + sqlite3: + dependency: transitive + description: + name: sqlite3 + sha256: "310af39c40dd0bb2058538333c9d9840a2725ae0b9f77e4fd09ad6696aa8f66e" + url: "https://pub.dev" + source: hosted + version: "2.7.5" + sqlite3_flutter_libs: + dependency: transitive + description: + name: sqlite3_flutter_libs + sha256: "1a96b59227828d9eb1463191d684b37a27d66ee5ed7597fcf42eee6452c88a14" + url: "https://pub.dev" + source: hosted + version: "0.5.32" + sqlparser: + dependency: transitive + description: + name: sqlparser + sha256: "27dd0a9f0c02e22ac0eb42a23df9ea079ce69b52bb4a3b478d64e0ef34a263ee" + url: "https://pub.dev" + source: hosted + version: "0.41.0" stack_trace: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index cdc428c..257fcbf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -85,6 +85,9 @@ dependencies: web_socket_channel: ^3.0.3 flutter_quill: ^11.4.0 material_symbols_icons: ^4.2815.0 + drift: ^2.26.0 + drift_flutter: ^0.2.4 + path: ^1.9.1 dev_dependencies: flutter_test: @@ -103,6 +106,7 @@ dev_dependencies: riverpod_generator: ^2.6.5 custom_lint: ^0.7.5 riverpod_lint: ^2.6.5 + drift_dev: ^2.26.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index c368584..c0ff750 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -38,6 +39,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("MediaKitLibsWindowsVideoPluginCApi")); MediaKitVideoPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("MediaKitVideoPluginCApi")); + Sqlite3FlutterLibsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("Sqlite3FlutterLibsPlugin")); SuperNativeExtensionsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("SuperNativeExtensionsPluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 7b03623..4df7e22 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -12,6 +12,7 @@ list(APPEND FLUTTER_PLUGIN_LIST irondash_engine_context media_kit_libs_windows_video media_kit_video + sqlite3_flutter_libs super_native_extensions url_launcher_windows volume_controller