mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-05-25 07:02:12 +00:00
add shortcuts
This commit is contained in:
parent
ba06126a3c
commit
f2b27e19f2
25 changed files with 5948 additions and 24 deletions
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
## 0.2.11
|
## 0.2.11
|
||||||
|
|
||||||
|
- New: Create custom shortcuts to quickly share images with pre-selected groups
|
||||||
- New: Seamless recovery for iOS reinstallations
|
- New: Seamless recovery for iOS reinstallations
|
||||||
- Improved: Redesigned snackbar notifications
|
- Improved: Redesigned snackbar notifications
|
||||||
- Improved: New backup mechanism to allow larger backup files
|
- Improved: New backup mechanism to allow larger backup files
|
||||||
|
|
|
||||||
1
assets/animated_icons/distorted_face.json
Normal file
1
assets/animated_icons/distorted_face.json
Normal file
File diff suppressed because one or more lines are too long
83
lib/src/database/daos/shortcuts.dao.dart
Normal file
83
lib/src/database/daos/shortcuts.dao.dart
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
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<TwonlyDB> with _$ShortcutsDaoMixin {
|
||||||
|
ShortcutsDao(super.db);
|
||||||
|
|
||||||
|
Stream<List<Shortcut>> watchAllShortcuts() {
|
||||||
|
return (select(shortcuts)..orderBy([
|
||||||
|
(t) =>
|
||||||
|
OrderingTerm(expression: t.usageCounter, mode: OrderingMode.desc),
|
||||||
|
]))
|
||||||
|
.watch();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Shortcut?> getShortcutByEmoji(String emoji) {
|
||||||
|
return (select(
|
||||||
|
shortcuts,
|
||||||
|
)..where((t) => t.emoji.equals(emoji))).getSingleOrNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> createShortcut(String emoji) async {
|
||||||
|
try {
|
||||||
|
await into(shortcuts).insert(
|
||||||
|
ShortcutsCompanion.insert(emoji: emoji),
|
||||||
|
);
|
||||||
|
// ignore: empty_catches
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> addShortcutMembers(int shortcutId, List<String> groupIds) async {
|
||||||
|
await batch((b) {
|
||||||
|
b.insertAll(
|
||||||
|
shortcutMembers,
|
||||||
|
groupIds.map(
|
||||||
|
(gId) => ShortcutMembersCompanion.insert(
|
||||||
|
shortcutId: shortcutId,
|
||||||
|
groupId: gId,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<ShortcutMember>> getShortcutMembers(int shortcutId) {
|
||||||
|
return (select(
|
||||||
|
shortcutMembers,
|
||||||
|
)..where((t) => t.shortcutId.equals(shortcutId))).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> 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<void> updateShortcut(int shortcutId, String emoji) async {
|
||||||
|
await (update(shortcuts)..where((t) => t.id.equals(shortcutId))).write(
|
||||||
|
ShortcutsCompanion(emoji: Value(emoji)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteShortcutMembers(int shortcutId) async {
|
||||||
|
await (delete(
|
||||||
|
shortcutMembers,
|
||||||
|
)..where((t) => t.shortcutId.equals(shortcutId))).go();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteShortcut(int shortcutId) async {
|
||||||
|
await (delete(shortcuts)..where((t) => t.id.equals(shortcutId))).go();
|
||||||
|
}
|
||||||
|
}
|
||||||
25
lib/src/database/daos/shortcuts.dao.g.dart
Normal file
25
lib/src/database/daos/shortcuts.dao.g.dart
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'shortcuts.dao.dart';
|
||||||
|
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
mixin _$ShortcutsDaoMixin on DatabaseAccessor<TwonlyDB> {
|
||||||
|
$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,
|
||||||
|
);
|
||||||
|
}
|
||||||
2901
lib/src/database/schemas/twonly_db/drift_schema_v13.json
Normal file
2901
lib/src/database/schemas/twonly_db/drift_schema_v13.json
Normal file
File diff suppressed because it is too large
Load diff
26
lib/src/database/tables/shortcuts.table.dart
Normal file
26
lib/src/database/tables/shortcuts.table.dart
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
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<Column> get primaryKey => {shortcutId, groupId};
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,7 @@ import 'package:twonly/src/database/daos/mediafiles.dao.dart';
|
||||||
import 'package:twonly/src/database/daos/messages.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/reactions.dao.dart';
|
||||||
import 'package:twonly/src/database/daos/receipts.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/daos/user_discovery.dao.dart';
|
||||||
import 'package:twonly/src/database/tables/contacts.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/groups.table.dart';
|
||||||
|
|
@ -17,6 +18,7 @@ import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||||
import 'package:twonly/src/database/tables/messages.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/reactions.table.dart';
|
||||||
import 'package:twonly/src/database/tables/receipts.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/signal_identity_key_store.table.dart';
|
import 'package:twonly/src/database/tables/signal_identity_key_store.table.dart';
|
||||||
import 'package:twonly/src/database/tables/signal_pre_key_store.table.dart';
|
import 'package:twonly/src/database/tables/signal_pre_key_store.table.dart';
|
||||||
import 'package:twonly/src/database/tables/signal_sender_key_store.table.dart';
|
import 'package:twonly/src/database/tables/signal_sender_key_store.table.dart';
|
||||||
|
|
@ -52,6 +54,8 @@ part 'twonly.db.g.dart';
|
||||||
UserDiscoveryOtherPromotions,
|
UserDiscoveryOtherPromotions,
|
||||||
UserDiscoveryOwnPromotions,
|
UserDiscoveryOwnPromotions,
|
||||||
UserDiscoveryShares,
|
UserDiscoveryShares,
|
||||||
|
Shortcuts,
|
||||||
|
ShortcutMembers,
|
||||||
],
|
],
|
||||||
daos: [
|
daos: [
|
||||||
MessagesDao,
|
MessagesDao,
|
||||||
|
|
@ -62,6 +66,7 @@ part 'twonly.db.g.dart';
|
||||||
MediaFilesDao,
|
MediaFilesDao,
|
||||||
UserDiscoveryDao,
|
UserDiscoveryDao,
|
||||||
KeyVerificationDao,
|
KeyVerificationDao,
|
||||||
|
ShortcutsDao,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
class TwonlyDB extends _$TwonlyDB {
|
class TwonlyDB extends _$TwonlyDB {
|
||||||
|
|
@ -74,7 +79,7 @@ class TwonlyDB extends _$TwonlyDB {
|
||||||
TwonlyDB.forTesting(DatabaseConnection super.connection);
|
TwonlyDB.forTesting(DatabaseConnection super.connection);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get schemaVersion => 12;
|
int get schemaVersion => 13;
|
||||||
|
|
||||||
static QueryExecutor _openConnection() {
|
static QueryExecutor _openConnection() {
|
||||||
return driftDatabase(
|
return driftDatabase(
|
||||||
|
|
@ -186,6 +191,10 @@ class TwonlyDB extends _$TwonlyDB {
|
||||||
await m.addColumn(schema.contacts, column);
|
await m.addColumn(schema.contacts, column);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
from12To13: (m, schema) async {
|
||||||
|
await m.createTable(schema.shortcuts);
|
||||||
|
await m.createTable(schema.shortcutMembers);
|
||||||
|
},
|
||||||
)(m, from, to);
|
)(m, from, to);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -6582,6 +6582,480 @@ i1.GeneratedColumn<i2.Uint8List> _column_235(String aliasedName) =>
|
||||||
type: i1.DriftSqlType.blob,
|
type: i1.DriftSqlType.blob,
|
||||||
$customConstraints: 'NOT NULL',
|
$customConstraints: 'NOT NULL',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final class Schema13 extends i0.VersionedSchema {
|
||||||
|
Schema13({required super.database}) : super(version: 13);
|
||||||
|
@override
|
||||||
|
late final List<i1.DatabaseSchemaEntity> entities = [
|
||||||
|
contacts,
|
||||||
|
groups,
|
||||||
|
mediaFiles,
|
||||||
|
messages,
|
||||||
|
messageHistories,
|
||||||
|
reactions,
|
||||||
|
groupMembers,
|
||||||
|
receipts,
|
||||||
|
receivedReceipts,
|
||||||
|
signalIdentityKeyStores,
|
||||||
|
signalPreKeyStores,
|
||||||
|
signalSenderKeyStores,
|
||||||
|
signalSessionStores,
|
||||||
|
messageActions,
|
||||||
|
groupHistories,
|
||||||
|
keyVerifications,
|
||||||
|
verificationTokens,
|
||||||
|
userDiscoveryAnnouncedUsers,
|
||||||
|
userDiscoveryUserRelations,
|
||||||
|
userDiscoveryOtherPromotions,
|
||||||
|
userDiscoveryOwnPromotions,
|
||||||
|
userDiscoveryShares,
|
||||||
|
shortcuts,
|
||||||
|
shortcutMembers,
|
||||||
|
];
|
||||||
|
late final Shape39 contacts = Shape39(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'contacts',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(user_id)'],
|
||||||
|
columns: [
|
||||||
|
_column_106,
|
||||||
|
_column_107,
|
||||||
|
_column_108,
|
||||||
|
_column_109,
|
||||||
|
_column_110,
|
||||||
|
_column_111,
|
||||||
|
_column_112,
|
||||||
|
_column_113,
|
||||||
|
_column_114,
|
||||||
|
_column_115,
|
||||||
|
_column_116,
|
||||||
|
_column_117,
|
||||||
|
_column_118,
|
||||||
|
_column_211,
|
||||||
|
_column_212,
|
||||||
|
_column_213,
|
||||||
|
_column_214,
|
||||||
|
_column_215,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape23 groups = Shape23(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'groups',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(group_id)'],
|
||||||
|
columns: [
|
||||||
|
_column_119,
|
||||||
|
_column_120,
|
||||||
|
_column_121,
|
||||||
|
_column_122,
|
||||||
|
_column_123,
|
||||||
|
_column_124,
|
||||||
|
_column_125,
|
||||||
|
_column_126,
|
||||||
|
_column_127,
|
||||||
|
_column_128,
|
||||||
|
_column_129,
|
||||||
|
_column_130,
|
||||||
|
_column_131,
|
||||||
|
_column_132,
|
||||||
|
_column_133,
|
||||||
|
_column_134,
|
||||||
|
_column_118,
|
||||||
|
_column_135,
|
||||||
|
_column_136,
|
||||||
|
_column_137,
|
||||||
|
_column_138,
|
||||||
|
_column_139,
|
||||||
|
_column_140,
|
||||||
|
_column_141,
|
||||||
|
_column_142,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape36 mediaFiles = Shape36(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'media_files',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(media_id)'],
|
||||||
|
columns: [
|
||||||
|
_column_143,
|
||||||
|
_column_144,
|
||||||
|
_column_145,
|
||||||
|
_column_146,
|
||||||
|
_column_147,
|
||||||
|
_column_148,
|
||||||
|
_column_149,
|
||||||
|
_column_207,
|
||||||
|
_column_150,
|
||||||
|
_column_151,
|
||||||
|
_column_152,
|
||||||
|
_column_153,
|
||||||
|
_column_154,
|
||||||
|
_column_155,
|
||||||
|
_column_156,
|
||||||
|
_column_157,
|
||||||
|
_column_118,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape25 messages = Shape25(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'messages',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(message_id)'],
|
||||||
|
columns: [
|
||||||
|
_column_158,
|
||||||
|
_column_159,
|
||||||
|
_column_160,
|
||||||
|
_column_144,
|
||||||
|
_column_161,
|
||||||
|
_column_162,
|
||||||
|
_column_163,
|
||||||
|
_column_164,
|
||||||
|
_column_165,
|
||||||
|
_column_153,
|
||||||
|
_column_166,
|
||||||
|
_column_167,
|
||||||
|
_column_168,
|
||||||
|
_column_169,
|
||||||
|
_column_118,
|
||||||
|
_column_170,
|
||||||
|
_column_171,
|
||||||
|
_column_172,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape26 messageHistories = Shape26(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'message_histories',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [
|
||||||
|
_column_173,
|
||||||
|
_column_174,
|
||||||
|
_column_175,
|
||||||
|
_column_161,
|
||||||
|
_column_118,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape27 reactions = Shape27(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'reactions',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(message_id, sender_id, emoji)'],
|
||||||
|
columns: [_column_174, _column_176, _column_177, _column_118],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape38 groupMembers = Shape38(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'group_members',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(group_id, contact_id)'],
|
||||||
|
columns: [
|
||||||
|
_column_158,
|
||||||
|
_column_178,
|
||||||
|
_column_179,
|
||||||
|
_column_180,
|
||||||
|
_column_209,
|
||||||
|
_column_210,
|
||||||
|
_column_181,
|
||||||
|
_column_118,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape37 receipts = Shape37(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'receipts',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(receipt_id)'],
|
||||||
|
columns: [
|
||||||
|
_column_182,
|
||||||
|
_column_183,
|
||||||
|
_column_184,
|
||||||
|
_column_185,
|
||||||
|
_column_186,
|
||||||
|
_column_208,
|
||||||
|
_column_187,
|
||||||
|
_column_188,
|
||||||
|
_column_189,
|
||||||
|
_column_190,
|
||||||
|
_column_191,
|
||||||
|
_column_118,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape30 receivedReceipts = Shape30(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'received_receipts',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(receipt_id)'],
|
||||||
|
columns: [_column_182, _column_118],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape31 signalIdentityKeyStores = Shape31(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'signal_identity_key_stores',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(device_id, name)'],
|
||||||
|
columns: [_column_192, _column_193, _column_194, _column_118],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape32 signalPreKeyStores = Shape32(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'signal_pre_key_stores',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(pre_key_id)'],
|
||||||
|
columns: [_column_195, _column_196, _column_118],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape11 signalSenderKeyStores = Shape11(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'signal_sender_key_stores',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(sender_key_name)'],
|
||||||
|
columns: [_column_197, _column_198],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape33 signalSessionStores = Shape33(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'signal_session_stores',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(device_id, name)'],
|
||||||
|
columns: [_column_192, _column_193, _column_199, _column_118],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape34 messageActions = Shape34(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'message_actions',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(message_id, contact_id, type)'],
|
||||||
|
columns: [_column_174, _column_183, _column_144, _column_200],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape35 groupHistories = Shape35(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'group_histories',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(group_history_id)'],
|
||||||
|
columns: [
|
||||||
|
_column_201,
|
||||||
|
_column_158,
|
||||||
|
_column_202,
|
||||||
|
_column_203,
|
||||||
|
_column_204,
|
||||||
|
_column_205,
|
||||||
|
_column_206,
|
||||||
|
_column_144,
|
||||||
|
_column_200,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape40 keyVerifications = Shape40(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'key_verifications',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [_column_216, _column_183, _column_144, _column_118],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape41 verificationTokens = Shape41(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'verification_tokens',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [_column_217, _column_218, _column_118],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape42 userDiscoveryAnnouncedUsers = Shape42(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'user_discovery_announced_users',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(announced_user_id)'],
|
||||||
|
columns: [
|
||||||
|
_column_219,
|
||||||
|
_column_220,
|
||||||
|
_column_221,
|
||||||
|
_column_222,
|
||||||
|
_column_223,
|
||||||
|
_column_224,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape43 userDiscoveryUserRelations = Shape43(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'user_discovery_user_relations',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(announced_user_id, from_contact_id)'],
|
||||||
|
columns: [_column_225, _column_226, _column_227],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape44 userDiscoveryOtherPromotions = Shape44(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'user_discovery_other_promotions',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(from_contact_id, public_id)'],
|
||||||
|
columns: [
|
||||||
|
_column_226,
|
||||||
|
_column_228,
|
||||||
|
_column_229,
|
||||||
|
_column_230,
|
||||||
|
_column_231,
|
||||||
|
_column_227,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape45 userDiscoveryOwnPromotions = Shape45(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'user_discovery_own_promotions',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [_column_232, _column_183, _column_233],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape46 userDiscoveryShares = Shape46(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'user_discovery_shares',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [_column_234, _column_235, _column_175],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape47 shortcuts = Shape47(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'shortcuts',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [_column_173, _column_236, _column_237],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape48 shortcutMembers = Shape48(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'shortcut_members',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: ['PRIMARY KEY(shortcut_id, group_id)'],
|
||||||
|
columns: [_column_238, _column_158],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Shape47 extends i0.VersionedTable {
|
||||||
|
Shape47({required super.source, required super.alias}) : super.aliased();
|
||||||
|
i1.GeneratedColumn<int> get id =>
|
||||||
|
columnsByName['id']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<String> get emoji =>
|
||||||
|
columnsByName['emoji']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<int> get usageCounter =>
|
||||||
|
columnsByName['usage_counter']! as i1.GeneratedColumn<int>;
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.GeneratedColumn<String> _column_236(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>(
|
||||||
|
'emoji',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i1.DriftSqlType.string,
|
||||||
|
$customConstraints: 'NOT NULL UNIQUE',
|
||||||
|
);
|
||||||
|
i1.GeneratedColumn<int> _column_237(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<int>(
|
||||||
|
'usage_counter',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i1.DriftSqlType.int,
|
||||||
|
$customConstraints: 'NOT NULL DEFAULT 0',
|
||||||
|
defaultValue: const i1.CustomExpression('0'),
|
||||||
|
);
|
||||||
|
|
||||||
|
class Shape48 extends i0.VersionedTable {
|
||||||
|
Shape48({required super.source, required super.alias}) : super.aliased();
|
||||||
|
i1.GeneratedColumn<int> get shortcutId =>
|
||||||
|
columnsByName['shortcut_id']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<String> get groupId =>
|
||||||
|
columnsByName['group_id']! as i1.GeneratedColumn<String>;
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.GeneratedColumn<int> _column_238(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<int>(
|
||||||
|
'shortcut_id',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i1.DriftSqlType.int,
|
||||||
|
$customConstraints: 'NOT NULL REFERENCES shortcuts(id)ON DELETE CASCADE',
|
||||||
|
);
|
||||||
i0.MigrationStepWithVersion migrationSteps({
|
i0.MigrationStepWithVersion migrationSteps({
|
||||||
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
||||||
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
||||||
|
|
@ -6594,6 +7068,7 @@ i0.MigrationStepWithVersion migrationSteps({
|
||||||
required Future<void> Function(i1.Migrator m, Schema10 schema) from9To10,
|
required Future<void> Function(i1.Migrator m, Schema10 schema) from9To10,
|
||||||
required Future<void> Function(i1.Migrator m, Schema11 schema) from10To11,
|
required Future<void> Function(i1.Migrator m, Schema11 schema) from10To11,
|
||||||
required Future<void> Function(i1.Migrator m, Schema12 schema) from11To12,
|
required Future<void> Function(i1.Migrator m, Schema12 schema) from11To12,
|
||||||
|
required Future<void> Function(i1.Migrator m, Schema13 schema) from12To13,
|
||||||
}) {
|
}) {
|
||||||
return (currentVersion, database) async {
|
return (currentVersion, database) async {
|
||||||
switch (currentVersion) {
|
switch (currentVersion) {
|
||||||
|
|
@ -6652,6 +7127,11 @@ i0.MigrationStepWithVersion migrationSteps({
|
||||||
final migrator = i1.Migrator(database, schema);
|
final migrator = i1.Migrator(database, schema);
|
||||||
await from11To12(migrator, schema);
|
await from11To12(migrator, schema);
|
||||||
return 12;
|
return 12;
|
||||||
|
case 12:
|
||||||
|
final schema = Schema13(database: database);
|
||||||
|
final migrator = i1.Migrator(database, schema);
|
||||||
|
await from12To13(migrator, schema);
|
||||||
|
return 13;
|
||||||
default:
|
default:
|
||||||
throw ArgumentError.value('Unknown migration from $currentVersion');
|
throw ArgumentError.value('Unknown migration from $currentVersion');
|
||||||
}
|
}
|
||||||
|
|
@ -6670,6 +7150,7 @@ i1.OnUpgrade stepByStep({
|
||||||
required Future<void> Function(i1.Migrator m, Schema10 schema) from9To10,
|
required Future<void> Function(i1.Migrator m, Schema10 schema) from9To10,
|
||||||
required Future<void> Function(i1.Migrator m, Schema11 schema) from10To11,
|
required Future<void> Function(i1.Migrator m, Schema11 schema) from10To11,
|
||||||
required Future<void> Function(i1.Migrator m, Schema12 schema) from11To12,
|
required Future<void> Function(i1.Migrator m, Schema12 schema) from11To12,
|
||||||
|
required Future<void> Function(i1.Migrator m, Schema13 schema) from12To13,
|
||||||
}) => i0.VersionedSchema.stepByStepHelper(
|
}) => i0.VersionedSchema.stepByStepHelper(
|
||||||
step: migrationSteps(
|
step: migrationSteps(
|
||||||
from1To2: from1To2,
|
from1To2: from1To2,
|
||||||
|
|
@ -6683,5 +7164,6 @@ i1.OnUpgrade stepByStep({
|
||||||
from9To10: from9To10,
|
from9To10: from9To10,
|
||||||
from10To11: from10To11,
|
from10To11: from10To11,
|
||||||
from11To12: from11To12,
|
from11To12: from11To12,
|
||||||
|
from12To13: from12To13,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -3115,6 +3115,48 @@ abstract class AppLocalizations {
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Registering new account'**
|
/// **'Registering new account'**
|
||||||
String get registeringNewAccount;
|
String get registeringNewAccount;
|
||||||
|
|
||||||
|
/// No description provided for @createShortcut.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Create shortcut'**
|
||||||
|
String get createShortcut;
|
||||||
|
|
||||||
|
/// No description provided for @editShortcut.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Edit shortcut'**
|
||||||
|
String get editShortcut;
|
||||||
|
|
||||||
|
/// No description provided for @deleteShortcut.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Delete shortcut'**
|
||||||
|
String get deleteShortcut;
|
||||||
|
|
||||||
|
/// No description provided for @deleteShortcutBody.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Are you sure you want to delete this shortcut?'**
|
||||||
|
String get deleteShortcutBody;
|
||||||
|
|
||||||
|
/// No description provided for @updateShortcut.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Update shortcut'**
|
||||||
|
String get updateShortcut;
|
||||||
|
|
||||||
|
/// No description provided for @selectEmoji.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Select Emoji'**
|
||||||
|
String get selectEmoji;
|
||||||
|
|
||||||
|
/// No description provided for @errorEmojiUsedOrInvalid.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Emoji already used or invalid'**
|
||||||
|
String get errorEmojiUsedOrInvalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppLocalizationsDelegate
|
class _AppLocalizationsDelegate
|
||||||
|
|
|
||||||
|
|
@ -1757,4 +1757,27 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get registeringNewAccount => 'Neues Konto wird registriert';
|
String get registeringNewAccount => 'Neues Konto wird registriert';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get createShortcut => 'Shortcut erstellen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get editShortcut => 'Shortcut bearbeiten';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get deleteShortcut => 'Shortcut löschen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get deleteShortcutBody =>
|
||||||
|
'Bist du sicher, dass du diesen Shortcut löschen möchtest?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get updateShortcut => 'Shortcut aktualisieren';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get selectEmoji => 'Emoji auswählen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get errorEmojiUsedOrInvalid =>
|
||||||
|
'Emoji wird bereits verwendet oder ist ungültig';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1742,4 +1742,26 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get registeringNewAccount => 'Registering new account';
|
String get registeringNewAccount => 'Registering new account';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get createShortcut => 'Create shortcut';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get editShortcut => 'Edit shortcut';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get deleteShortcut => 'Delete shortcut';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get deleteShortcutBody =>
|
||||||
|
'Are you sure you want to delete this shortcut?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get updateShortcut => 'Update shortcut';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get selectEmoji => 'Select Emoji';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get errorEmojiUsedOrInvalid => 'Emoji already used or invalid';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 75b97e912f2e72a8e2a5da65e8ad12f0d1091855
|
Subproject commit 9218abf0961c072edd2f8aa5035d06a331b853c6
|
||||||
|
|
@ -417,6 +417,7 @@ class ApiService {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
await twonlyDB.receiptsDao.deleteReceiptForUser(contactId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ Future<void> createThumbnailsForVideo(
|
||||||
'It took ${stopwatch.elapsedMilliseconds}ms to create the thumbnail.',
|
'It took ${stopwatch.elapsedMilliseconds}ms to create the thumbnail.',
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
Log.error(
|
Log.warn(
|
||||||
'Thumbnail creation failed for the video with exit code.',
|
'Thumbnail creation failed for the video with exit code.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,10 @@ class Log {
|
||||||
static String filterLogMessage(String msg) {
|
static String filterLogMessage(String msg) {
|
||||||
if (msg.contains('SqliteException')) {
|
if (msg.contains('SqliteException')) {
|
||||||
// Do not log data which would be inserted into the DB.
|
// Do not log data which would be inserted into the DB.
|
||||||
return msg.substring(0, msg.indexOf('parameters: '));
|
final paramIndex = msg.indexOf('parameters: ');
|
||||||
|
if (paramIndex != -1) {
|
||||||
|
return msg.substring(0, paramIndex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,7 @@ class EmojiAnimationComp extends StatelessWidget {
|
||||||
'😴': 'sleep.lottie',
|
'😴': 'sleep.lottie',
|
||||||
'🤒': 'thermometer-face.lottie',
|
'🤒': 'thermometer-face.lottie',
|
||||||
'🤕': 'bandage-face.lottie',
|
'🤕': 'bandage-face.lottie',
|
||||||
|
'': 'distorted_face.json',
|
||||||
'🤥': 'liar.lottie',
|
'🤥': 'liar.lottie',
|
||||||
'😇': 'halo.lottie',
|
'😇': 'halo.lottie',
|
||||||
'🤠': 'cowboy.lottie',
|
'🤠': 'cowboy.lottie',
|
||||||
|
|
|
||||||
292
lib/src/visual/views/camera/add_new_shortcut.view.dart
Normal file
292
lib/src/visual/views/camera/add_new_shortcut.view.dart
Normal file
|
|
@ -0,0 +1,292 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:collection';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
import 'package:twonly/locator.dart';
|
||||||
|
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||||
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
|
||||||
|
import 'package:twonly/src/visual/components/emoji_picker.bottom.dart';
|
||||||
|
import 'package:twonly/src/visual/components/flame_counter.comp.dart';
|
||||||
|
import 'package:twonly/src/visual/components/snackbar.dart';
|
||||||
|
import 'package:twonly/src/visual/decorations/input_text.decoration.dart';
|
||||||
|
import 'package:twonly/src/visual/views/camera/share_image_editor_components/layer_data.dart';
|
||||||
|
|
||||||
|
class AddNewShortcutView extends StatefulWidget {
|
||||||
|
const AddNewShortcutView({this.shortcut, super.key});
|
||||||
|
final Shortcut? shortcut;
|
||||||
|
@override
|
||||||
|
State<AddNewShortcutView> createState() => _StartNewChatView();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _StartNewChatView extends State<AddNewShortcutView> {
|
||||||
|
List<Group> _groups = [];
|
||||||
|
List<Group> _allGroups = [];
|
||||||
|
final TextEditingController _searchGroupName = TextEditingController();
|
||||||
|
late StreamSubscription<List<Group>> _groupSub;
|
||||||
|
|
||||||
|
final HashSet<String> _selectedGroups = HashSet();
|
||||||
|
String? shortcutEmoji;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
if (widget.shortcut != null) {
|
||||||
|
shortcutEmoji = widget.shortcut!.emoji;
|
||||||
|
twonlyDB.shortcutsDao.getShortcutMembers(widget.shortcut!.id).then((
|
||||||
|
members,
|
||||||
|
) {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
for (final m in members) {
|
||||||
|
_selectedGroups.add(m.groupId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
final stream = twonlyDB.groupsDao.watchGroupsForChatList();
|
||||||
|
|
||||||
|
_groupSub = stream.listen((update) async {
|
||||||
|
update.sort(
|
||||||
|
(a, b) => a.groupName.compareTo(b.groupName),
|
||||||
|
);
|
||||||
|
setState(() {
|
||||||
|
_allGroups = update;
|
||||||
|
});
|
||||||
|
await filterUsers();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
unawaited(_groupSub.cancel());
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> filterUsers() async {
|
||||||
|
if (_searchGroupName.value.text.isEmpty) {
|
||||||
|
setState(() {
|
||||||
|
_groups = _allGroups;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final usersFiltered = _allGroups
|
||||||
|
.where(
|
||||||
|
(group) => group.groupName.toLowerCase().contains(
|
||||||
|
_searchGroupName.value.text.toLowerCase(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
setState(() {
|
||||||
|
_groups = usersFiltered;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void toggleSelectedGroup(String groupId) {
|
||||||
|
if (!_selectedGroups.contains(groupId)) {
|
||||||
|
if (_selectedGroups.length > 256) {
|
||||||
|
showSnackbar(context, context.lang.groupSizeLimitError(256));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_selectedGroups.add(groupId);
|
||||||
|
} else {
|
||||||
|
_selectedGroups.remove(groupId);
|
||||||
|
}
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> submitChanges() async {
|
||||||
|
try {
|
||||||
|
if (widget.shortcut != null) {
|
||||||
|
await twonlyDB.shortcutsDao.updateShortcut(
|
||||||
|
widget.shortcut!.id,
|
||||||
|
shortcutEmoji!,
|
||||||
|
);
|
||||||
|
await twonlyDB.shortcutsDao.deleteShortcutMembers(widget.shortcut!.id);
|
||||||
|
await twonlyDB.shortcutsDao.addShortcutMembers(
|
||||||
|
widget.shortcut!.id,
|
||||||
|
_selectedGroups.toList(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await twonlyDB.shortcutsDao.createShortcut(
|
||||||
|
shortcutEmoji!,
|
||||||
|
);
|
||||||
|
final shortcutId = (await twonlyDB.shortcutsDao.getShortcutByEmoji(
|
||||||
|
shortcutEmoji!,
|
||||||
|
))!.id;
|
||||||
|
await twonlyDB.shortcutsDao.deleteShortcutMembers(shortcutId);
|
||||||
|
await twonlyDB.shortcutsDao.addShortcutMembers(
|
||||||
|
shortcutId,
|
||||||
|
_selectedGroups.toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (mounted) Navigator.pop(context);
|
||||||
|
} catch (e) {
|
||||||
|
Log.error(e);
|
||||||
|
if (mounted) {
|
||||||
|
showSnackbar(context, context.lang.errorEmojiUsedOrInvalid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () => FocusScope.of(context).unfocus(),
|
||||||
|
child: Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(
|
||||||
|
widget.shortcut == null
|
||||||
|
? context.lang.createShortcut
|
||||||
|
: context.lang.editShortcut,
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
if (widget.shortcut != null)
|
||||||
|
IconButton(
|
||||||
|
icon: const FaIcon(
|
||||||
|
FontAwesomeIcons.trashCan,
|
||||||
|
size: 18,
|
||||||
|
color: Colors.red,
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
final confirm = await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: Text(context.lang.deleteShortcut),
|
||||||
|
content: Text(context.lang.deleteShortcutBody),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context, false),
|
||||||
|
child: Text(context.lang.cancel),
|
||||||
|
),
|
||||||
|
FilledButton(
|
||||||
|
onPressed: () => Navigator.pop(context, true),
|
||||||
|
child: Text(context.lang.delete),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (confirm == true) {
|
||||||
|
await twonlyDB.shortcutsDao.deleteShortcut(
|
||||||
|
widget.shortcut!.id,
|
||||||
|
);
|
||||||
|
if (context.mounted) Navigator.pop(context);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
// ignore: inference_failure_on_function_invocation
|
||||||
|
final result = await showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
backgroundColor: Colors.black,
|
||||||
|
builder: (context) => const EmojiPickerBottom(),
|
||||||
|
);
|
||||||
|
if (result is EmojiLayerData) {
|
||||||
|
setState(() {
|
||||||
|
shortcutEmoji = result.text;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
shortcutEmoji ?? context.lang.selectEmoji,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: shortcutEmoji == null ? 14 : 22,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
floatingActionButton: FilledButton.icon(
|
||||||
|
onPressed: (_selectedGroups.isEmpty || shortcutEmoji == null)
|
||||||
|
? null
|
||||||
|
: submitChanges,
|
||||||
|
label: Text(
|
||||||
|
widget.shortcut == null
|
||||||
|
? context.lang.createShortcut
|
||||||
|
: context.lang.updateShortcut,
|
||||||
|
),
|
||||||
|
icon: const FaIcon(FontAwesomeIcons.check),
|
||||||
|
),
|
||||||
|
body: SafeArea(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
bottom: 40,
|
||||||
|
left: 10,
|
||||||
|
top: 20,
|
||||||
|
right: 10,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||||
|
child: TextField(
|
||||||
|
onChanged: (_) async {
|
||||||
|
await filterUsers();
|
||||||
|
},
|
||||||
|
controller: _searchGroupName,
|
||||||
|
decoration: getInputDecoration(
|
||||||
|
context,
|
||||||
|
context.lang.shareImageSearchAllContacts,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
|
restorationId: 'new_message_users_list',
|
||||||
|
itemCount: _groups.length,
|
||||||
|
itemBuilder: (context, i) {
|
||||||
|
final group = _groups[i];
|
||||||
|
return ListTile(
|
||||||
|
key: ValueKey(group.groupId),
|
||||||
|
title: Row(
|
||||||
|
children: [
|
||||||
|
Text(substringBy(group.groupName, 12)),
|
||||||
|
FlameCounterWidget(
|
||||||
|
groupId: group.groupId,
|
||||||
|
prefix: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
leading: AvatarIcon(
|
||||||
|
group: group,
|
||||||
|
fontSize: 15,
|
||||||
|
),
|
||||||
|
trailing: Checkbox(
|
||||||
|
value: _selectedGroups.contains(group.groupId),
|
||||||
|
side: WidgetStateBorderSide.resolveWith(
|
||||||
|
(states) {
|
||||||
|
if (states.contains(WidgetState.selected)) {
|
||||||
|
return const BorderSide(width: 0);
|
||||||
|
}
|
||||||
|
return BorderSide(
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
onChanged: (value) {
|
||||||
|
toggleSelectedGroup(group.groupId);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
toggleSelectedGroup(group.groupId);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -98,9 +98,12 @@ class MainCameraController {
|
||||||
final cameraControllerTemp = cameraController;
|
final cameraControllerTemp = cameraController;
|
||||||
cameraController = null;
|
cameraController = null;
|
||||||
// prevents: CameraException(Disposed CameraController, buildPreview() was called on a disposed CameraController.)
|
// prevents: CameraException(Disposed CameraController, buildPreview() was called on a disposed CameraController.)
|
||||||
_pendingDisposal = Future.delayed(const Duration(milliseconds: 100), () async {
|
_pendingDisposal = Future.delayed(
|
||||||
|
const Duration(milliseconds: 100),
|
||||||
|
() async {
|
||||||
await cameraControllerTemp?.dispose();
|
await cameraControllerTemp?.dispose();
|
||||||
});
|
},
|
||||||
|
);
|
||||||
initCameraStarted = false;
|
initCameraStarted = false;
|
||||||
selectedCameraDetails = SelectedCameraDetails();
|
selectedCameraDetails = SelectedCameraDetails();
|
||||||
}
|
}
|
||||||
|
|
@ -226,7 +229,7 @@ class MainCameraController {
|
||||||
(e.code == 'setFocusPointFailed' || e.code == 'setFocusModeFailed')) {
|
(e.code == 'setFocusPointFailed' || e.code == 'setFocusModeFailed')) {
|
||||||
Log.info('Focus point or mode not supported on this device');
|
Log.info('Focus point or mode not supported on this device');
|
||||||
} else {
|
} else {
|
||||||
Log.error(e);
|
Log.warn(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import 'package:twonly/src/visual/decorations/input_text.decoration.dart';
|
||||||
import 'package:twonly/src/visual/elements/headline.element.dart';
|
import 'package:twonly/src/visual/elements/headline.element.dart';
|
||||||
import 'package:twonly/src/visual/helpers/screenshot.helper.dart';
|
import 'package:twonly/src/visual/helpers/screenshot.helper.dart';
|
||||||
import 'package:twonly/src/visual/views/camera/share_image_contact_selection_components/best_friends_selector.dart';
|
import 'package:twonly/src/visual/views/camera/share_image_contact_selection_components/best_friends_selector.dart';
|
||||||
|
import 'package:twonly/src/visual/views/camera/share_image_contact_selection_components/shortcut_row.comp.dart';
|
||||||
import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/background.layer.dart';
|
import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/background.layer.dart';
|
||||||
|
|
||||||
class ShareImageView extends StatefulWidget {
|
class ShareImageView extends StatefulWidget {
|
||||||
|
|
@ -194,6 +195,11 @@ class _ShareImageView extends State<ShareImageView> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
ShortcutRowComp(
|
||||||
|
selectedGroupIds: widget.selectedGroupIds,
|
||||||
|
updateSelectedGroupIds: updateSelectedGroupIds,
|
||||||
|
),
|
||||||
if (_pinnedContacts.isNotEmpty) const SizedBox(height: 10),
|
if (_pinnedContacts.isNotEmpty) const SizedBox(height: 10),
|
||||||
BestFriendsSelector(
|
BestFriendsSelector(
|
||||||
groups: _pinnedContacts,
|
groups: _pinnedContacts,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
import 'dart:collection';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:twonly/locator.dart';
|
||||||
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
import 'package:twonly/src/visual/views/camera/add_new_shortcut.view.dart';
|
||||||
|
|
||||||
|
class ShortcutRowComp extends StatefulWidget {
|
||||||
|
const ShortcutRowComp({
|
||||||
|
required this.selectedGroupIds,
|
||||||
|
required this.updateSelectedGroupIds,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final HashSet<String> selectedGroupIds;
|
||||||
|
final void Function(String, bool) updateSelectedGroupIds;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ShortcutRowComp> createState() => _ShortcutRowCompState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ShortcutRowCompState extends State<ShortcutRowComp> {
|
||||||
|
Future<void> _openCreateDialog() async {
|
||||||
|
await context.navPush(const AddNewShortcutView());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _applyShortcut(Shortcut shortcut) async {
|
||||||
|
await twonlyDB.shortcutsDao.incrementUsage(shortcut.id);
|
||||||
|
final members = await twonlyDB.shortcutsDao.getShortcutMembers(shortcut.id);
|
||||||
|
for (final m in members) {
|
||||||
|
widget.updateSelectedGroupIds(m.groupId, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: StreamBuilder<List<Shortcut>>(
|
||||||
|
stream: twonlyDB.shortcutsDao.watchAllShortcuts(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
final shortcuts = snapshot.data ?? [];
|
||||||
|
return ListView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
ActionChip(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
onPressed: _openCreateDialog,
|
||||||
|
label: shortcuts.isEmpty
|
||||||
|
? Text(
|
||||||
|
context.lang.createShortcut,
|
||||||
|
style: const TextStyle(fontSize: 9),
|
||||||
|
)
|
||||||
|
: const Icon(Icons.add_reaction_outlined, size: 20),
|
||||||
|
shape: const StadiumBorder(),
|
||||||
|
),
|
||||||
|
for (final shortcut in shortcuts)
|
||||||
|
GestureDetector(
|
||||||
|
onLongPress: () {
|
||||||
|
context.navPush(AddNewShortcutView(shortcut: shortcut));
|
||||||
|
},
|
||||||
|
child: ActionChip(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
onPressed: () => _applyShortcut(shortcut),
|
||||||
|
label: Text(
|
||||||
|
shortcut.emoji,
|
||||||
|
style: const TextStyle(fontSize: 18),
|
||||||
|
),
|
||||||
|
shape: const StadiumBorder(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -443,8 +443,8 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: HEAD
|
ref: "23a0595f7dde50728afce917c1c58f284ccbb495"
|
||||||
resolved-ref: c5bffd3414c1e640389b41165b831df7df1cf517
|
resolved-ref: "23a0595f7dde50728afce917c1c58f284ccbb495"
|
||||||
url: "https://github.com/otsmr/emoji_picker_flutter.git"
|
url: "https://github.com/otsmr/emoji_picker_flutter.git"
|
||||||
source: git
|
source: git
|
||||||
version: "4.4.0"
|
version: "4.4.0"
|
||||||
|
|
|
||||||
|
|
@ -167,6 +167,7 @@ dependency_overrides:
|
||||||
# Using override until this gets merged.
|
# Using override until this gets merged.
|
||||||
git:
|
git:
|
||||||
url: https://github.com/otsmr/emoji_picker_flutter.git
|
url: https://github.com/otsmr/emoji_picker_flutter.git
|
||||||
|
ref: 23a0595f7dde50728afce917c1c58f284ccbb495
|
||||||
flutter_android_volume_keydown:
|
flutter_android_volume_keydown:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/yenchieh/flutter_android_volume_keydown.git
|
url: https://github.com/yenchieh/flutter_android_volume_keydown.git
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import 'schema_v9.dart' as v9;
|
||||||
import 'schema_v10.dart' as v10;
|
import 'schema_v10.dart' as v10;
|
||||||
import 'schema_v11.dart' as v11;
|
import 'schema_v11.dart' as v11;
|
||||||
import 'schema_v12.dart' as v12;
|
import 'schema_v12.dart' as v12;
|
||||||
|
import 'schema_v13.dart' as v13;
|
||||||
|
|
||||||
class GeneratedHelper implements SchemaInstantiationHelper {
|
class GeneratedHelper implements SchemaInstantiationHelper {
|
||||||
@override
|
@override
|
||||||
|
|
@ -45,10 +46,12 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
||||||
return v11.DatabaseAtV11(db);
|
return v11.DatabaseAtV11(db);
|
||||||
case 12:
|
case 12:
|
||||||
return v12.DatabaseAtV12(db);
|
return v12.DatabaseAtV12(db);
|
||||||
|
case 13:
|
||||||
|
return v13.DatabaseAtV13(db);
|
||||||
default:
|
default:
|
||||||
throw MissingSchemaException(version, versions);
|
throw MissingSchemaException(version, versions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
|
static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue