refactor: /simplify — 22 fixes from 3-agent code review
Critical: - Fix MXC URI resolution: all avatars/images now resolve mxc:// to HTTP - Sync persistence: only write changed rooms, batch message upserts - lastActivityAt uses room.lastEvent.originServerTs, not creation time High: - Shared MatrixAvatar widget replaces 6 duplicate implementations - CallScreen decodes roomId before LiveKit JWT fetch - Decline button actually dismisses incoming call overlay - EventTypes constants replace raw string literals - LiveKitService uses lazy auth reads, onDispose disconnects Medium: - CallController is keepAlive with timer/room cleanup - authRepository is keepAlive (used from keepAlive notifier) - StreamController not closed in stopListening (crash fix) - Index on messages.roomId for query performance - 400ms debounce on user search - Static DateFormat in MessageBubble - Hardcoded strings replaced with AppConfig refs - Duplicate isDirectMessage field removed from RoomModel - E2EE profile claim corrected to Phase 3 Shared utilities: - lib/shared/widgets/matrix_avatar.dart - lib/shared/utils/mxc_url.dart - lib/shared/utils/room_preview.dart - lib/shared/utils/matrix_id.dart rawJson column removed (unused, caused main-thread jsonEncode) Schema migrated to v2 with roomId index. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
// Version: 1.1.0 | Created: 2026-04-01
|
||||
// Version: 1.2.0 | Created: 2026-04-01 | Updated: 2026-04-02
|
||||
// Drift database — rooms and messages tables.
|
||||
// Uses driftDatabase() from drift_flutter for cross-platform support:
|
||||
// - Mobile/desktop: SQLite file via sqlite3_flutter_libs
|
||||
@@ -33,6 +33,7 @@ class RoomsTable extends Table {
|
||||
}
|
||||
|
||||
/// Persisted message events. Upserted when received from sync.
|
||||
@TableIndex(name: 'idx_messages_room_id', columns: {#roomId})
|
||||
class MessagesTable extends Table {
|
||||
@override
|
||||
String get tableName => 'messages';
|
||||
@@ -43,7 +44,6 @@ class MessagesTable extends Table {
|
||||
TextColumn get body => text().nullable()();
|
||||
TextColumn get type => text()(); // MessageType name
|
||||
IntColumn get timestamp => integer()(); // milliseconds since epoch
|
||||
TextColumn get rawJson => text()(); // full event JSON for future use
|
||||
|
||||
@override
|
||||
Set<Column<Object>> get primaryKey => {id};
|
||||
@@ -61,7 +61,41 @@ class AppDatabase extends _$AppDatabase {
|
||||
AppDatabase.forTesting(super.executor);
|
||||
|
||||
@override
|
||||
int get schemaVersion => 1;
|
||||
int get schemaVersion => 2;
|
||||
|
||||
@override
|
||||
MigrationStrategy get migration => MigrationStrategy(
|
||||
onUpgrade: (migrator, from, to) async {
|
||||
if (from < 2) {
|
||||
// v2: drop rawJson column, add index on roomId.
|
||||
// Drift cannot drop columns — recreate the table.
|
||||
await customStatement(
|
||||
'CREATE TABLE IF NOT EXISTS messages_new ('
|
||||
' id TEXT NOT NULL PRIMARY KEY, '
|
||||
' room_id TEXT NOT NULL, '
|
||||
' sender_id TEXT NOT NULL, '
|
||||
' body TEXT, '
|
||||
' type TEXT NOT NULL, '
|
||||
' timestamp INTEGER NOT NULL'
|
||||
')',
|
||||
);
|
||||
await customStatement(
|
||||
'INSERT OR IGNORE INTO messages_new '
|
||||
'(id, room_id, sender_id, body, type, timestamp) '
|
||||
'SELECT id, room_id, sender_id, body, type, timestamp FROM messages',
|
||||
);
|
||||
await customStatement('DROP TABLE IF EXISTS messages');
|
||||
await customStatement('ALTER TABLE messages_new RENAME TO messages');
|
||||
await customStatement(
|
||||
'CREATE INDEX IF NOT EXISTS idx_messages_room_id '
|
||||
'ON messages (room_id)',
|
||||
);
|
||||
}
|
||||
},
|
||||
onCreate: (migrator) async {
|
||||
await migrator.createAll();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -99,6 +133,15 @@ extension MessagesDao on AppDatabase {
|
||||
Future<void> upsertMessage(MessagesTableCompanion message) =>
|
||||
into(messagesTable).insertOnConflictUpdate(message);
|
||||
|
||||
/// Insert or replace multiple message rows in a single batch transaction.
|
||||
Future<void> upsertMessages(List<MessagesTableCompanion> messages) async {
|
||||
await batch((b) {
|
||||
for (final msg in messages) {
|
||||
b.insert(messagesTable, msg, onConflict: DoUpdate((_) => msg));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Watch all messages for [roomId] ordered oldest-first.
|
||||
Stream<List<MessagesTableData>> watchByRoom(String roomId) {
|
||||
return (select(messagesTable)
|
||||
|
||||
Reference in New Issue
Block a user