diff --git a/lib/globals.dart b/lib/globals.dart index 49c7ad7e..5419eeae 100644 --- a/lib/globals.dart +++ b/lib/globals.dart @@ -47,7 +47,7 @@ class AppEnvironment { ) async { await destination.create(recursive: true); final marker = File('${destination.path}/.runtime_storage_migrated_v1'); - if (await marker.exists() || !await source.exists()) return; + if (marker.existsSync() || !source.existsSync()) return; await for (final entity in source.list(followLinks: false)) { await _copyEntity(entity, destination.path); @@ -62,7 +62,9 @@ class AppEnvironment { FileSystemEntity entity, String destinationDirectory, ) async { - final name = entity.uri.pathSegments.where((value) => value.isNotEmpty).last; + final name = entity.uri.pathSegments + .where((value) => value.isNotEmpty) + .last; final destinationPath = '$destinationDirectory/$name'; if (entity is Directory) { final destination = Directory(destinationPath); @@ -75,7 +77,7 @@ class AppEnvironment { if (entity is! File) return; final destination = File(destinationPath); - if (await destination.exists()) return; + if (destination.existsSync()) return; final temporary = File('$destinationPath.migrating'); await entity.copy(temporary.path); await temporary.rename(destination.path); diff --git a/lib/src/database/daos/contact_groups.dao.dart b/lib/src/database/daos/contact_groups.dao.dart new file mode 100644 index 00000000..fef78b26 --- /dev/null +++ b/lib/src/database/daos/contact_groups.dao.dart @@ -0,0 +1,295 @@ +import 'package:drift/drift.dart'; +import 'package:twonly/src/database/tables/contact_groups.table.dart'; +import 'package:twonly/src/database/twonly.db.dart'; + +part 'contact_groups.dao.g.dart'; + +@DriftAccessor(tables: [ContactGroups, ContactGroupMembers]) +class ContactGroupsDao extends DatabaseAccessor + with _$ContactGroupsDaoMixin { + ContactGroupsDao(super.db); + + Stream> watchAllContactGroups() { + return (select( + contactGroups, + )..orderBy([(t) => OrderingTerm.asc(t.name)])).watch(); + } + + Stream> watchShortcutContactGroups() { + return (select(contactGroups) + ..where((t) => t.showAsShortcut.equals(true)) + ..orderBy([(t) => OrderingTerm.desc(t.usageCounter)])) + .watch(); + } + + Stream> watchVisibleGroupsForUser(int userId) { + final query = + select(contactGroupMembers).join([ + innerJoin( + contactGroups, + contactGroups.id.equalsExp(contactGroupMembers.contactGroupId), + ), + ]) + ..where( + contactGroupMembers.userId.equals(userId) & + contactGroups.showAsLabel.equals(true), + ) + ..orderBy([OrderingTerm.asc(contactGroups.name)]); + return query.map((row) => row.readTable(contactGroups)).watch(); + } + + Stream> watchVisibleGroupsForGroup(String groupId) { + final query = + select(contactGroupMembers).join([ + innerJoin( + contactGroups, + contactGroups.id.equalsExp(contactGroupMembers.contactGroupId), + ), + ]) + ..where( + contactGroupMembers.groupId.equals(groupId) & + contactGroups.showAsLabel.equals(true), + ) + ..orderBy([OrderingTerm.asc(contactGroups.name)]); + return query.map((row) => row.readTable(contactGroups)).watch(); + } + + Stream> watchAllVisibleUserGroups() { + final query = + select(contactGroupMembers).join([ + innerJoin( + contactGroups, + contactGroups.id.equalsExp(contactGroupMembers.contactGroupId), + ), + ])..where( + contactGroupMembers.userId.isNotNull() & + contactGroups.showAsLabel.equals(true), + ); + return query.watch().map( + (rows) => rows + .map( + (row) => ( + row.readTable(contactGroupMembers).userId!, + row.readTable(contactGroups), + ), + ) + .toList(), + ); + } + + Stream> watchAllVisibleChatGroups() { + final query = + select(contactGroupMembers).join([ + innerJoin( + contactGroups, + contactGroups.id.equalsExp(contactGroupMembers.contactGroupId), + ), + ])..where( + contactGroupMembers.groupId.isNotNull() & + contactGroups.showAsLabel.equals(true), + ); + return query.watch().map( + (rows) => rows + .map( + (row) => ( + row.readTable(contactGroupMembers).groupId!, + row.readTable(contactGroups), + ), + ) + .toList(), + ); + } + + Stream> watchContactGroupIdsForUser(int userId) { + return (select(contactGroupMembers)..where((t) => t.userId.equals(userId))) + .watch() + .map((rows) => rows.map((row) => row.contactGroupId).toSet()); + } + + Stream> watchContactGroupIdsForGroup(String groupId) { + return (select( + contactGroupMembers, + )..where((t) => t.groupId.equals(groupId))).watch().map( + (rows) => rows.map((row) => row.contactGroupId).toSet(), + ); + } + + Future getContactGroup(int id) { + return (select( + contactGroups, + )..where((t) => t.id.equals(id))).getSingleOrNull(); + } + + Future> getMembers(int contactGroupId) { + return (select( + contactGroupMembers, + )..where((t) => t.contactGroupId.equals(contactGroupId))).get(); + } + + Future createContactGroup({ + required String name, + required int textColor, + required int backgroundColor, + required bool showAsShortcut, + required bool showAsLabel, + String? emoji, + }) { + return into(contactGroups).insert( + ContactGroupsCompanion.insert( + name: _sanitizeName(name), + emoji: Value(_sanitizeEmoji(emoji)), + textColor: textColor, + backgroundColor: backgroundColor, + showAsShortcut: Value(showAsShortcut), + showAsLabel: Value(showAsLabel), + ), + ); + } + + Future updateContactGroup({ + required int id, + required String name, + required int textColor, + required int backgroundColor, + required bool showAsShortcut, + required bool showAsLabel, + String? emoji, + }) { + return (update(contactGroups)..where((t) => t.id.equals(id))) + .write( + ContactGroupsCompanion( + name: Value(_sanitizeName(name)), + emoji: Value(_sanitizeEmoji(emoji)), + textColor: Value(textColor), + backgroundColor: Value(backgroundColor), + showAsShortcut: Value(showAsShortcut), + showAsLabel: Value(showAsLabel), + ), + ) + .then((rows) => rows > 0); + } + + Future setUserMembership( + int contactGroupId, + int userId, + bool selected, + ) async { + await transaction(() async { + await (delete(contactGroupMembers)..where( + (t) => + t.contactGroupId.equals(contactGroupId) & + t.userId.equals(userId), + )) + .go(); + if (selected) { + await into(contactGroupMembers).insert( + ContactGroupMembersCompanion.insert( + contactGroupId: contactGroupId, + userId: Value(userId), + ), + ); + } + }); + } + + Future setGroupMembership( + int contactGroupId, + String groupId, + bool selected, + ) async { + if (selected) { + final group = + await (select(attachedDatabase.groups)..where( + (group) => + group.groupId.equals(groupId) & + group.isDirectChat.equals(false), + )) + .getSingleOrNull(); + // Direct chats carry their labels through the contact instead. + if (group == null) return; + } + await transaction(() async { + await (delete(contactGroupMembers)..where( + (t) => + t.contactGroupId.equals(contactGroupId) & + t.groupId.equals(groupId), + )) + .go(); + if (selected) { + await into(contactGroupMembers).insert( + ContactGroupMembersCompanion.insert( + contactGroupId: contactGroupId, + groupId: Value(groupId), + ), + ); + } + }); + } + + Future replaceMembers( + int contactGroupId, { + required Iterable userIds, + required Iterable groupIds, + }) async { + final requestedUserIds = userIds.toSet(); + final requestedGroupIds = groupIds.toSet(); + final validGroupIds = requestedGroupIds.isEmpty + ? const [] + : await (select(attachedDatabase.groups)..where( + (group) => + group.groupId.isIn(requestedGroupIds) & + group.isDirectChat.equals(false), + )) + .map((group) => group.groupId) + .get(); + await transaction(() async { + await (delete( + contactGroupMembers, + )..where((t) => t.contactGroupId.equals(contactGroupId))).go(); + final members = [ + ...requestedUserIds.map( + (userId) => ContactGroupMembersCompanion.insert( + contactGroupId: contactGroupId, + userId: Value(userId), + ), + ), + ...validGroupIds.map( + (groupId) => ContactGroupMembersCompanion.insert( + contactGroupId: contactGroupId, + groupId: Value(groupId), + ), + ), + ]; + if (members.isNotEmpty) { + await batch( + (batch) => batch.insertAll(contactGroupMembers, members), + ); + } + }); + } + + Future incrementUsage(int contactGroupId) async { + await customStatement( + 'UPDATE contact_groups ' + 'SET usage_counter = usage_counter + 1 WHERE id = ?', + [contactGroupId], + ); + notifyUpdates({ + TableUpdate.onTable(contactGroups, kind: UpdateKind.update), + }); + } + + Future deleteContactGroup(int id) { + return (delete(contactGroups)..where((t) => t.id.equals(id))).go(); + } + + static String _sanitizeName(String name) { + final trimmed = name.trim(); + return trimmed.length > 24 ? trimmed.substring(0, 24) : trimmed; + } + + static String? _sanitizeEmoji(String? emoji) { + final trimmed = emoji?.trim(); + return trimmed == null || trimmed.isEmpty ? null : trimmed; + } +} diff --git a/lib/src/database/daos/contact_groups.dao.g.dart b/lib/src/database/daos/contact_groups.dao.g.dart new file mode 100644 index 00000000..6df68ff0 --- /dev/null +++ b/lib/src/database/daos/contact_groups.dao.g.dart @@ -0,0 +1,29 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'contact_groups.dao.dart'; + +// ignore_for_file: type=lint +mixin _$ContactGroupsDaoMixin on DatabaseAccessor { + $ContactGroupsTable get contactGroups => attachedDatabase.contactGroups; + $ContactsTable get contacts => attachedDatabase.contacts; + $GroupsTable get groups => attachedDatabase.groups; + $ContactGroupMembersTable get contactGroupMembers => + attachedDatabase.contactGroupMembers; + ContactGroupsDaoManager get managers => ContactGroupsDaoManager(this); +} + +class ContactGroupsDaoManager { + final _$ContactGroupsDaoMixin _db; + ContactGroupsDaoManager(this._db); + $$ContactGroupsTableTableManager get contactGroups => + $$ContactGroupsTableTableManager(_db.attachedDatabase, _db.contactGroups); + $$ContactsTableTableManager get contacts => + $$ContactsTableTableManager(_db.attachedDatabase, _db.contacts); + $$GroupsTableTableManager get groups => + $$GroupsTableTableManager(_db.attachedDatabase, _db.groups); + $$ContactGroupMembersTableTableManager get contactGroupMembers => + $$ContactGroupMembersTableTableManager( + _db.attachedDatabase, + _db.contactGroupMembers, + ); +} diff --git a/lib/src/database/daos/labels.dao.dart b/lib/src/database/daos/labels.dao.dart deleted file mode 100644 index 8782495b..00000000 --- a/lib/src/database/daos/labels.dao.dart +++ /dev/null @@ -1,100 +0,0 @@ -import 'package:drift/drift.dart'; -import 'package:twonly/src/database/tables/labels.table.dart'; -import 'package:twonly/src/database/twonly.db.dart'; - -part 'labels.dao.g.dart'; - -@DriftAccessor( - tables: [ - Labels, - ContactLabels, - ], -) -class LabelsDao extends DatabaseAccessor with _$LabelsDaoMixin { - LabelsDao(super.db); - - Stream> watchAllLabels() { - return (select( - labels, - )..orderBy([(t) => OrderingTerm(expression: t.name)])).watch(); - } -Stream> watchContactLabels(int contactId) { - final query = select(contactLabels).join([ - innerJoin(labels, labels.id.equalsExp(contactLabels.labelId)), - ])..where(contactLabels.contactId.equals(contactId)); - - return query.watch().map( - (rows) => rows.map((row) => row.readTable(labels)).toList(), - ); - } - - Stream> watchAllContactLabels() { - final query = select(contactLabels).join([ - innerJoin(labels, labels.id.equalsExp(contactLabels.labelId)), - ]); - return query.watch().map( - (rows) => rows - .map( - (row) => ( - row.readTable(contactLabels).contactId, - row.readTable(labels), - ), - ) - .toList(), - ); - } -Future setContactLabels(int contactId, List labelIds) async { - final sanitizedLabelIds = labelIds.take(3).toList(); - await transaction(() async { - await (delete( - contactLabels, - )..where((t) => t.contactId.equals(contactId))).go(); - if (sanitizedLabelIds.isNotEmpty) { - await batch((b) { - b.insertAll( - contactLabels, - sanitizedLabelIds.map( - (lId) => ContactLabelsCompanion.insert( - contactId: contactId, - labelId: lId, - ), - ), - ); - }); - } - }); - } - - Future createLabel(String name, int textColor, int backgroundColor) { - final sanitizedName = name.length > 8 ? name.substring(0, 8) : name; - return into(labels).insert( - LabelsCompanion.insert( - name: sanitizedName, - textColor: textColor, - backgroundColor: backgroundColor, - ), - ); - } - - Future updateLabel( - int id, - String name, - int textColor, - int backgroundColor, - ) { - final sanitizedName = name.length > 8 ? name.substring(0, 8) : name; - return (update(labels)..where((t) => t.id.equals(id))) - .write( - LabelsCompanion( - name: Value(sanitizedName), - textColor: Value(textColor), - backgroundColor: Value(backgroundColor), - ), - ) - .then((rows) => rows > 0); - } - - Future deleteLabel(int id) { - return (delete(labels)..where((t) => t.id.equals(id))).go(); - } -} diff --git a/lib/src/database/daos/labels.dao.g.dart b/lib/src/database/daos/labels.dao.g.dart deleted file mode 100644 index 6723f9c1..00000000 --- a/lib/src/database/daos/labels.dao.g.dart +++ /dev/null @@ -1,22 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'labels.dao.dart'; - -// ignore_for_file: type=lint -mixin _$LabelsDaoMixin on DatabaseAccessor { - $LabelsTable get labels => attachedDatabase.labels; - $ContactsTable get contacts => attachedDatabase.contacts; - $ContactLabelsTable get contactLabels => attachedDatabase.contactLabels; - LabelsDaoManager get managers => LabelsDaoManager(this); -} - -class LabelsDaoManager { - final _$LabelsDaoMixin _db; - LabelsDaoManager(this._db); - $$LabelsTableTableManager get labels => - $$LabelsTableTableManager(_db.attachedDatabase, _db.labels); - $$ContactsTableTableManager get contacts => - $$ContactsTableTableManager(_db.attachedDatabase, _db.contacts); - $$ContactLabelsTableTableManager get contactLabels => - $$ContactLabelsTableTableManager(_db.attachedDatabase, _db.contactLabels); -} diff --git a/lib/src/database/daos/shortcuts.dao.dart b/lib/src/database/daos/shortcuts.dao.dart deleted file mode 100644 index 66938956..00000000 --- a/lib/src/database/daos/shortcuts.dao.dart +++ /dev/null @@ -1,79 +0,0 @@ -import 'package:drift/drift.dart'; -import 'package:twonly/src/database/tables/shortcuts.table.dart'; -import 'package:twonly/src/database/twonly.db.dart'; - -part 'shortcuts.dao.g.dart'; - -@DriftAccessor( - tables: [ - Shortcuts, - ShortcutMembers, - ], -) -class ShortcutsDao extends DatabaseAccessor with _$ShortcutsDaoMixin { - ShortcutsDao(super.db); - - Stream> watchAllShortcuts() { - return select(shortcuts).watch(); - } - - Future getShortcutByEmoji(String emoji) { - return (select( - shortcuts, - )..where((t) => t.emoji.equals(emoji))).getSingleOrNull(); - } - - Future createShortcut(String emoji) async { - try { - await into(shortcuts).insert( - ShortcutsCompanion.insert(emoji: emoji), - ); - // ignore: empty_catches - } catch (e) {} - } - - Future addShortcutMembers(int shortcutId, List groupIds) async { - await batch((b) { - b.insertAll( - shortcutMembers, - groupIds.map( - (gId) => ShortcutMembersCompanion.insert( - shortcutId: shortcutId, - groupId: gId, - ), - ), - ); - }); - } - - Future> getShortcutMembers(int shortcutId) { - return (select( - shortcutMembers, - )..where((t) => t.shortcutId.equals(shortcutId))).get(); - } - - Future incrementUsage(int shortcutId) async { - await customStatement( - 'UPDATE shortcuts SET usage_counter = usage_counter + 1 WHERE id = ?', - [shortcutId], - ); - // Notify updates to trigger streams - notifyUpdates({TableUpdate.onTable(shortcuts, kind: UpdateKind.update)}); - } - - Future updateShortcut(int shortcutId, String emoji) async { - await (update(shortcuts)..where((t) => t.id.equals(shortcutId))).write( - ShortcutsCompanion(emoji: Value(emoji)), - ); - } - - Future deleteShortcutMembers(int shortcutId) async { - await (delete( - shortcutMembers, - )..where((t) => t.shortcutId.equals(shortcutId))).go(); - } - - Future deleteShortcut(int shortcutId) async { - await (delete(shortcuts)..where((t) => t.id.equals(shortcutId))).go(); - } -} diff --git a/lib/src/database/daos/shortcuts.dao.g.dart b/lib/src/database/daos/shortcuts.dao.g.dart deleted file mode 100644 index 36e0a8d5..00000000 --- a/lib/src/database/daos/shortcuts.dao.g.dart +++ /dev/null @@ -1,25 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'shortcuts.dao.dart'; - -// ignore_for_file: type=lint -mixin _$ShortcutsDaoMixin on DatabaseAccessor { - $ShortcutsTable get shortcuts => attachedDatabase.shortcuts; - $GroupsTable get groups => attachedDatabase.groups; - $ShortcutMembersTable get shortcutMembers => attachedDatabase.shortcutMembers; - ShortcutsDaoManager get managers => ShortcutsDaoManager(this); -} - -class ShortcutsDaoManager { - final _$ShortcutsDaoMixin _db; - ShortcutsDaoManager(this._db); - $$ShortcutsTableTableManager get shortcuts => - $$ShortcutsTableTableManager(_db.attachedDatabase, _db.shortcuts); - $$GroupsTableTableManager get groups => - $$GroupsTableTableManager(_db.attachedDatabase, _db.groups); - $$ShortcutMembersTableTableManager get shortcutMembers => - $$ShortcutMembersTableTableManager( - _db.attachedDatabase, - _db.shortcutMembers, - ); -} diff --git a/lib/src/database/tables/contact_groups.table.dart b/lib/src/database/tables/contact_groups.table.dart new file mode 100644 index 00000000..063853a9 --- /dev/null +++ b/lib/src/database/tables/contact_groups.table.dart @@ -0,0 +1,48 @@ +import 'package:drift/drift.dart'; +import 'package:twonly/src/database/tables/contacts.table.dart'; +import 'package:twonly/src/database/tables/groups.table.dart'; + +@DataClassName('ContactGroup') +class ContactGroups extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get name => text()(); + TextColumn get emoji => text().nullable()(); + IntColumn get textColor => integer()(); + IntColumn get backgroundColor => integer()(); + BoolColumn get showAsShortcut => + boolean().withDefault(const Constant(false))(); + BoolColumn get showAsLabel => boolean().withDefault(const Constant(true))(); + IntColumn get usageCounter => integer().withDefault(const Constant(0))(); + DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); +} + +@DataClassName('ContactGroupMember') +class ContactGroupMembers extends Table { + IntColumn get id => integer().autoIncrement()(); + IntColumn get contactGroupId => integer().references( + ContactGroups, + #id, + onDelete: KeyAction.cascade, + )(); + IntColumn get userId => integer().nullable().references( + Contacts, + #userId, + onDelete: KeyAction.cascade, + )(); + TextColumn get groupId => text().nullable().references( + Groups, + #groupId, + onDelete: KeyAction.cascade, + )(); + + @override + List>> get uniqueKeys => [ + {contactGroupId, userId}, + {contactGroupId, groupId}, + ]; + + @override + List get customConstraints => [ + 'CHECK ((user_id IS NOT NULL) != (group_id IS NOT NULL))', + ]; +} diff --git a/lib/src/database/tables/labels.table.dart b/lib/src/database/tables/labels.table.dart deleted file mode 100644 index 4824de07..00000000 --- a/lib/src/database/tables/labels.table.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:drift/drift.dart'; -import 'package:twonly/src/database/tables/contacts.table.dart'; - -@DataClassName('Label') -class Labels extends Table { - IntColumn get id => integer().autoIncrement()(); - TextColumn get name => text()(); - IntColumn get textColor => integer()(); - IntColumn get backgroundColor => integer()(); - DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); -} - -@DataClassName('ContactLabel') -class ContactLabels extends Table { - IntColumn get contactId => integer().references( - Contacts, - #userId, - onDelete: KeyAction.cascade, - )(); - IntColumn get labelId => integer().references( - Labels, - #id, - onDelete: KeyAction.cascade, - )(); - - @override - Set get primaryKey => {contactId, labelId}; -} diff --git a/lib/src/database/tables/shortcuts.table.dart b/lib/src/database/tables/shortcuts.table.dart deleted file mode 100644 index 43cda3a7..00000000 --- a/lib/src/database/tables/shortcuts.table.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:drift/drift.dart'; -import 'package:twonly/src/database/tables/groups.table.dart'; - -@DataClassName('Shortcut') -class Shortcuts extends Table { - IntColumn get id => integer().autoIncrement()(); - TextColumn get emoji => text().unique()(); - IntColumn get usageCounter => integer().withDefault(const Constant(0))(); -} - -@DataClassName('ShortcutMember') -class ShortcutMembers extends Table { - IntColumn get shortcutId => integer().references( - Shortcuts, - #id, - onDelete: KeyAction.cascade, - )(); - TextColumn get groupId => text().references( - Groups, - #groupId, - onDelete: KeyAction.cascade, - )(); - - @override - Set get primaryKey => {shortcutId, groupId}; -} diff --git a/lib/src/database/twonly.db.dart b/lib/src/database/twonly.db.dart index 74d0c6ce..1369c5e7 100644 --- a/lib/src/database/twonly.db.dart +++ b/lib/src/database/twonly.db.dart @@ -1,26 +1,24 @@ import 'dart:async'; import 'package:drift/drift.dart'; +import 'package:twonly/src/database/daos/contact_groups.dao.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/daos/groups.dao.dart'; import 'package:twonly/src/database/daos/key_verification.dao.dart'; -import 'package:twonly/src/database/daos/labels.dao.dart'; import 'package:twonly/src/database/daos/mediafiles.dao.dart'; import 'package:twonly/src/database/daos/messages.dao.dart'; import 'package:twonly/src/database/daos/reactions.dao.dart'; import 'package:twonly/src/database/daos/receipts.dao.dart'; -import 'package:twonly/src/database/daos/shortcuts.dao.dart'; import 'package:twonly/src/database/daos/user_discovery.dao.dart'; import 'package:twonly/src/database/rust_change_notifier.dart'; import 'package:twonly/src/database/rust_query_executor.dart'; +import 'package:twonly/src/database/tables/contact_groups.table.dart'; import 'package:twonly/src/database/tables/contacts.table.dart'; import 'package:twonly/src/database/tables/groups.table.dart'; -import 'package:twonly/src/database/tables/labels.table.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/database/tables/messages.table.dart'; import 'package:twonly/src/database/tables/reactions.table.dart'; import 'package:twonly/src/database/tables/receipts.table.dart'; -import 'package:twonly/src/database/tables/shortcuts.table.dart'; import 'package:twonly/src/database/tables/user_discovery.table.dart'; import 'package:twonly/src/database/twonly.db.steps.dart'; import 'package:twonly/src/utils/log.dart'; @@ -48,10 +46,8 @@ part 'twonly.db.g.dart'; UserDiscoveryOtherPromotions, UserDiscoveryOwnPromotions, UserDiscoveryShares, - Shortcuts, - ShortcutMembers, - Labels, - ContactLabels, + ContactGroups, + ContactGroupMembers, ], daos: [ MessagesDao, @@ -62,8 +58,7 @@ part 'twonly.db.g.dart'; MediaFilesDao, UserDiscoveryDao, KeyVerificationDao, - ShortcutsDao, - LabelsDao, + ContactGroupsDao, ], ) class TwonlyDB extends _$TwonlyDB { @@ -93,6 +88,8 @@ class TwonlyDB extends _$TwonlyDB { @override MigrationStrategy get migration { + // Frozen legacy-Drift upgrade chain used only before Rust imports schema + // v25. All current application schema migrations are owned by Rust. return MigrationStrategy( beforeOpen: (details) async { await customStatement('PRAGMA foreign_keys = ON'); diff --git a/lib/src/database/twonly.db.g.dart b/lib/src/database/twonly.db.g.dart index a03fcb28..d44a5a43 100644 --- a/lib/src/database/twonly.db.g.dart +++ b/lib/src/database/twonly.db.g.dart @@ -11427,488 +11427,12 @@ class UserDiscoverySharesCompanion extends UpdateCompanion { } } -class $ShortcutsTable extends Shortcuts - with TableInfo<$ShortcutsTable, Shortcut> { +class $ContactGroupsTable extends ContactGroups + with TableInfo<$ContactGroupsTable, ContactGroup> { @override final GeneratedDatabase attachedDatabase; final String? _alias; - $ShortcutsTable(this.attachedDatabase, [this._alias]); - static const VerificationMeta _idMeta = const VerificationMeta('id'); - @override - late final GeneratedColumn id = GeneratedColumn( - 'id', - aliasedName, - false, - hasAutoIncrement: true, - type: DriftSqlType.int, - requiredDuringInsert: false, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'PRIMARY KEY AUTOINCREMENT', - ), - ); - static const VerificationMeta _emojiMeta = const VerificationMeta('emoji'); - @override - late final GeneratedColumn emoji = GeneratedColumn( - 'emoji', - aliasedName, - false, - type: DriftSqlType.string, - requiredDuringInsert: true, - defaultConstraints: GeneratedColumn.constraintIsAlways('UNIQUE'), - ); - static const VerificationMeta _usageCounterMeta = const VerificationMeta( - 'usageCounter', - ); - @override - late final GeneratedColumn usageCounter = GeneratedColumn( - 'usage_counter', - aliasedName, - false, - type: DriftSqlType.int, - requiredDuringInsert: false, - defaultValue: const Constant(0), - ); - @override - List get $columns => [id, emoji, usageCounter]; - @override - String get aliasedName => _alias ?? actualTableName; - @override - String get actualTableName => $name; - static const String $name = 'shortcuts'; - @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)); - } - if (data.containsKey('emoji')) { - context.handle( - _emojiMeta, - emoji.isAcceptableOrUnknown(data['emoji']!, _emojiMeta), - ); - } else if (isInserting) { - context.missing(_emojiMeta); - } - if (data.containsKey('usage_counter')) { - context.handle( - _usageCounterMeta, - usageCounter.isAcceptableOrUnknown( - data['usage_counter']!, - _usageCounterMeta, - ), - ); - } - return context; - } - - @override - Set get $primaryKey => {id}; - @override - Shortcut map(Map data, {String? tablePrefix}) { - final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; - return Shortcut( - id: attachedDatabase.typeMapping.read( - DriftSqlType.int, - data['${effectivePrefix}id'], - )!, - emoji: attachedDatabase.typeMapping.read( - DriftSqlType.string, - data['${effectivePrefix}emoji'], - )!, - usageCounter: attachedDatabase.typeMapping.read( - DriftSqlType.int, - data['${effectivePrefix}usage_counter'], - )!, - ); - } - - @override - $ShortcutsTable createAlias(String alias) { - return $ShortcutsTable(attachedDatabase, alias); - } -} - -class Shortcut extends DataClass implements Insertable { - final int id; - final String emoji; - final int usageCounter; - const Shortcut({ - required this.id, - required this.emoji, - required this.usageCounter, - }); - @override - Map toColumns(bool nullToAbsent) { - final map = {}; - map['id'] = Variable(id); - map['emoji'] = Variable(emoji); - map['usage_counter'] = Variable(usageCounter); - return map; - } - - ShortcutsCompanion toCompanion(bool nullToAbsent) { - return ShortcutsCompanion( - id: Value(id), - emoji: Value(emoji), - usageCounter: Value(usageCounter), - ); - } - - factory Shortcut.fromJson( - Map json, { - ValueSerializer? serializer, - }) { - serializer ??= driftRuntimeOptions.defaultSerializer; - return Shortcut( - id: serializer.fromJson(json['id']), - emoji: serializer.fromJson(json['emoji']), - usageCounter: serializer.fromJson(json['usageCounter']), - ); - } - @override - Map toJson({ValueSerializer? serializer}) { - serializer ??= driftRuntimeOptions.defaultSerializer; - return { - 'id': serializer.toJson(id), - 'emoji': serializer.toJson(emoji), - 'usageCounter': serializer.toJson(usageCounter), - }; - } - - Shortcut copyWith({int? id, String? emoji, int? usageCounter}) => Shortcut( - id: id ?? this.id, - emoji: emoji ?? this.emoji, - usageCounter: usageCounter ?? this.usageCounter, - ); - Shortcut copyWithCompanion(ShortcutsCompanion data) { - return Shortcut( - id: data.id.present ? data.id.value : this.id, - emoji: data.emoji.present ? data.emoji.value : this.emoji, - usageCounter: data.usageCounter.present - ? data.usageCounter.value - : this.usageCounter, - ); - } - - @override - String toString() { - return (StringBuffer('Shortcut(') - ..write('id: $id, ') - ..write('emoji: $emoji, ') - ..write('usageCounter: $usageCounter') - ..write(')')) - .toString(); - } - - @override - int get hashCode => Object.hash(id, emoji, usageCounter); - @override - bool operator ==(Object other) => - identical(this, other) || - (other is Shortcut && - other.id == this.id && - other.emoji == this.emoji && - other.usageCounter == this.usageCounter); -} - -class ShortcutsCompanion extends UpdateCompanion { - final Value id; - final Value emoji; - final Value usageCounter; - const ShortcutsCompanion({ - this.id = const Value.absent(), - this.emoji = const Value.absent(), - this.usageCounter = const Value.absent(), - }); - ShortcutsCompanion.insert({ - this.id = const Value.absent(), - required String emoji, - this.usageCounter = const Value.absent(), - }) : emoji = Value(emoji); - static Insertable custom({ - Expression? id, - Expression? emoji, - Expression? usageCounter, - }) { - return RawValuesInsertable({ - if (id != null) 'id': id, - if (emoji != null) 'emoji': emoji, - if (usageCounter != null) 'usage_counter': usageCounter, - }); - } - - ShortcutsCompanion copyWith({ - Value? id, - Value? emoji, - Value? usageCounter, - }) { - return ShortcutsCompanion( - id: id ?? this.id, - emoji: emoji ?? this.emoji, - usageCounter: usageCounter ?? this.usageCounter, - ); - } - - @override - Map toColumns(bool nullToAbsent) { - final map = {}; - if (id.present) { - map['id'] = Variable(id.value); - } - if (emoji.present) { - map['emoji'] = Variable(emoji.value); - } - if (usageCounter.present) { - map['usage_counter'] = Variable(usageCounter.value); - } - return map; - } - - @override - String toString() { - return (StringBuffer('ShortcutsCompanion(') - ..write('id: $id, ') - ..write('emoji: $emoji, ') - ..write('usageCounter: $usageCounter') - ..write(')')) - .toString(); - } -} - -class $ShortcutMembersTable extends ShortcutMembers - with TableInfo<$ShortcutMembersTable, ShortcutMember> { - @override - final GeneratedDatabase attachedDatabase; - final String? _alias; - $ShortcutMembersTable(this.attachedDatabase, [this._alias]); - static const VerificationMeta _shortcutIdMeta = const VerificationMeta( - 'shortcutId', - ); - @override - late final GeneratedColumn shortcutId = GeneratedColumn( - 'shortcut_id', - aliasedName, - false, - type: DriftSqlType.int, - requiredDuringInsert: true, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES shortcuts (id) ON DELETE CASCADE', - ), - ); - static const VerificationMeta _groupIdMeta = const VerificationMeta( - 'groupId', - ); - @override - late final GeneratedColumn groupId = GeneratedColumn( - 'group_id', - aliasedName, - false, - type: DriftSqlType.string, - requiredDuringInsert: true, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES "groups" (group_id) ON DELETE CASCADE', - ), - ); - @override - List get $columns => [shortcutId, groupId]; - @override - String get aliasedName => _alias ?? actualTableName; - @override - String get actualTableName => $name; - static const String $name = 'shortcut_members'; - @override - VerificationContext validateIntegrity( - Insertable instance, { - bool isInserting = false, - }) { - final context = VerificationContext(); - final data = instance.toColumns(true); - if (data.containsKey('shortcut_id')) { - context.handle( - _shortcutIdMeta, - shortcutId.isAcceptableOrUnknown(data['shortcut_id']!, _shortcutIdMeta), - ); - } else if (isInserting) { - context.missing(_shortcutIdMeta); - } - if (data.containsKey('group_id')) { - context.handle( - _groupIdMeta, - groupId.isAcceptableOrUnknown(data['group_id']!, _groupIdMeta), - ); - } else if (isInserting) { - context.missing(_groupIdMeta); - } - return context; - } - - @override - Set get $primaryKey => {shortcutId, groupId}; - @override - ShortcutMember map(Map data, {String? tablePrefix}) { - final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; - return ShortcutMember( - shortcutId: attachedDatabase.typeMapping.read( - DriftSqlType.int, - data['${effectivePrefix}shortcut_id'], - )!, - groupId: attachedDatabase.typeMapping.read( - DriftSqlType.string, - data['${effectivePrefix}group_id'], - )!, - ); - } - - @override - $ShortcutMembersTable createAlias(String alias) { - return $ShortcutMembersTable(attachedDatabase, alias); - } -} - -class ShortcutMember extends DataClass implements Insertable { - final int shortcutId; - final String groupId; - const ShortcutMember({required this.shortcutId, required this.groupId}); - @override - Map toColumns(bool nullToAbsent) { - final map = {}; - map['shortcut_id'] = Variable(shortcutId); - map['group_id'] = Variable(groupId); - return map; - } - - ShortcutMembersCompanion toCompanion(bool nullToAbsent) { - return ShortcutMembersCompanion( - shortcutId: Value(shortcutId), - groupId: Value(groupId), - ); - } - - factory ShortcutMember.fromJson( - Map json, { - ValueSerializer? serializer, - }) { - serializer ??= driftRuntimeOptions.defaultSerializer; - return ShortcutMember( - shortcutId: serializer.fromJson(json['shortcutId']), - groupId: serializer.fromJson(json['groupId']), - ); - } - @override - Map toJson({ValueSerializer? serializer}) { - serializer ??= driftRuntimeOptions.defaultSerializer; - return { - 'shortcutId': serializer.toJson(shortcutId), - 'groupId': serializer.toJson(groupId), - }; - } - - ShortcutMember copyWith({int? shortcutId, String? groupId}) => ShortcutMember( - shortcutId: shortcutId ?? this.shortcutId, - groupId: groupId ?? this.groupId, - ); - ShortcutMember copyWithCompanion(ShortcutMembersCompanion data) { - return ShortcutMember( - shortcutId: data.shortcutId.present - ? data.shortcutId.value - : this.shortcutId, - groupId: data.groupId.present ? data.groupId.value : this.groupId, - ); - } - - @override - String toString() { - return (StringBuffer('ShortcutMember(') - ..write('shortcutId: $shortcutId, ') - ..write('groupId: $groupId') - ..write(')')) - .toString(); - } - - @override - int get hashCode => Object.hash(shortcutId, groupId); - @override - bool operator ==(Object other) => - identical(this, other) || - (other is ShortcutMember && - other.shortcutId == this.shortcutId && - other.groupId == this.groupId); -} - -class ShortcutMembersCompanion extends UpdateCompanion { - final Value shortcutId; - final Value groupId; - final Value rowid; - const ShortcutMembersCompanion({ - this.shortcutId = const Value.absent(), - this.groupId = const Value.absent(), - this.rowid = const Value.absent(), - }); - ShortcutMembersCompanion.insert({ - required int shortcutId, - required String groupId, - this.rowid = const Value.absent(), - }) : shortcutId = Value(shortcutId), - groupId = Value(groupId); - static Insertable custom({ - Expression? shortcutId, - Expression? groupId, - Expression? rowid, - }) { - return RawValuesInsertable({ - if (shortcutId != null) 'shortcut_id': shortcutId, - if (groupId != null) 'group_id': groupId, - if (rowid != null) 'rowid': rowid, - }); - } - - ShortcutMembersCompanion copyWith({ - Value? shortcutId, - Value? groupId, - Value? rowid, - }) { - return ShortcutMembersCompanion( - shortcutId: shortcutId ?? this.shortcutId, - groupId: groupId ?? this.groupId, - rowid: rowid ?? this.rowid, - ); - } - - @override - Map toColumns(bool nullToAbsent) { - final map = {}; - if (shortcutId.present) { - map['shortcut_id'] = Variable(shortcutId.value); - } - if (groupId.present) { - map['group_id'] = Variable(groupId.value); - } - if (rowid.present) { - map['rowid'] = Variable(rowid.value); - } - return map; - } - - @override - String toString() { - return (StringBuffer('ShortcutMembersCompanion(') - ..write('shortcutId: $shortcutId, ') - ..write('groupId: $groupId, ') - ..write('rowid: $rowid') - ..write(')')) - .toString(); - } -} - -class $LabelsTable extends Labels with TableInfo<$LabelsTable, Label> { - @override - final GeneratedDatabase attachedDatabase; - final String? _alias; - $LabelsTable(this.attachedDatabase, [this._alias]); + $ContactGroupsTable(this.attachedDatabase, [this._alias]); static const VerificationMeta _idMeta = const VerificationMeta('id'); @override late final GeneratedColumn id = GeneratedColumn( @@ -11931,6 +11455,15 @@ class $LabelsTable extends Labels with TableInfo<$LabelsTable, Label> { type: DriftSqlType.string, requiredDuringInsert: true, ); + static const VerificationMeta _emojiMeta = const VerificationMeta('emoji'); + @override + late final GeneratedColumn emoji = GeneratedColumn( + 'emoji', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); static const VerificationMeta _textColorMeta = const VerificationMeta( 'textColor', ); @@ -11953,6 +11486,48 @@ class $LabelsTable extends Labels with TableInfo<$LabelsTable, Label> { type: DriftSqlType.int, requiredDuringInsert: true, ); + static const VerificationMeta _showAsShortcutMeta = const VerificationMeta( + 'showAsShortcut', + ); + @override + late final GeneratedColumn showAsShortcut = GeneratedColumn( + 'show_as_shortcut', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("show_as_shortcut" IN (0, 1))', + ), + defaultValue: const Constant(false), + ); + static const VerificationMeta _showAsLabelMeta = const VerificationMeta( + 'showAsLabel', + ); + @override + late final GeneratedColumn showAsLabel = GeneratedColumn( + 'show_as_label', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("show_as_label" IN (0, 1))', + ), + defaultValue: const Constant(true), + ); + static const VerificationMeta _usageCounterMeta = const VerificationMeta( + 'usageCounter', + ); + @override + late final GeneratedColumn usageCounter = GeneratedColumn( + 'usage_counter', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(0), + ); static const VerificationMeta _createdAtMeta = const VerificationMeta( 'createdAt', ); @@ -11969,18 +11544,22 @@ class $LabelsTable extends Labels with TableInfo<$LabelsTable, Label> { List get $columns => [ id, name, + emoji, textColor, backgroundColor, + showAsShortcut, + showAsLabel, + usageCounter, createdAt, ]; @override String get aliasedName => _alias ?? actualTableName; @override String get actualTableName => $name; - static const String $name = 'labels'; + static const String $name = 'contact_groups'; @override VerificationContext validateIntegrity( - Insertable