From cc3a7b8b64725cd093420d53dc392cffd85ab024 Mon Sep 17 00:00:00 2001 From: otsmr Date: Fri, 10 Apr 2026 21:21:54 +0200 Subject: [PATCH 1/5] fix wrong increase of last message --- lib/src/services/api/messages.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/src/services/api/messages.dart b/lib/src/services/api/messages.dart index d0cca10..e4fe6d9 100644 --- a/lib/src/services/api/messages.dart +++ b/lib/src/services/api/messages.dart @@ -304,7 +304,9 @@ Future sendCipherTextToGroup( }) async { final groupMembers = await twonlyDB.groupsDao.getGroupNonLeftMembers(groupId); - await twonlyDB.groupsDao.increaseLastMessageExchange(groupId, clock.now()); + if (!onlySendIfNoReceiptsAreOpen) { + await twonlyDB.groupsDao.increaseLastMessageExchange(groupId, clock.now()); + } encryptedContent.groupId = groupId; From 0d975db3e44cfe05027648efb89a5156bbd61040 Mon Sep 17 00:00:00 2001 From: otsmr Date: Sat, 11 Apr 2026 00:09:14 +0200 Subject: [PATCH 2/5] fmt --- lib/firebase_options.dart | 3 +- lib/globals.dart | 7 +- lib/src/database/daos/contacts.dao.dart | 48 ++++++------ lib/src/database/daos/signal.dao.g.dart | 12 ++- .../signal/connect_identity_key_store.dart | 43 ++++++----- .../signal/connect_pre_key_store.dart | 18 ++--- .../signal/connect_sender_key_store.dart | 11 ++- .../signal/connect_session_store.dart | 66 ++++++++-------- .../signal/connect_signal_protocol_store.dart | 9 ++- lib/src/database/tables/mediafiles.table.dart | 2 +- lib/src/database/tables/messages.table.dart | 16 ++-- lib/src/database/tables/reactions.table.dart | 8 +- lib/src/database/tables/receipts.table.dart | 8 +- .../api/http/http_requests.pbserver.dart | 1 - .../websocket/client_to_server.pbserver.dart | 1 - .../api/websocket/error.pbserver.dart | 1 - .../websocket/server_to_client.pbserver.dart | 1 - .../client/generated/backup.pbserver.dart | 1 - .../client/generated/groups.pbserver.dart | 1 - .../client/generated/messages.pbserver.dart | 1 - .../generated/push_notification.pbserver.dart | 1 - lib/src/providers/purchases.provider.dart | 6 +- .../client2client/additional_data.c2c.dart | 5 +- .../api/client2client/errors.c2c.dart | 2 +- .../api/client2client/messages.c2c.dart | 5 +- .../api/client2client/pushkeys.c2c.dart | 5 +- lib/src/services/api/utils.dart | 5 +- lib/src/services/backup/common.backup.dart | 6 +- lib/src/services/group.services.dart | 76 +++++++++++-------- .../notifications/setup.notifications.dart | 13 ++-- .../services/signal/encryption.signal.dart | 2 +- lib/src/services/signal/identity.signal.dart | 22 ++++-- lib/src/services/signal/session.signal.dart | 5 +- lib/src/services/signal/utils.signal.dart | 5 +- lib/src/utils/misc.dart | 57 ++++++++------ lib/src/utils/qr.dart | 5 +- lib/src/utils/storage.dart | 11 ++- .../camera_preview.dart | 20 ++++- .../face_filters.dart | 3 +- .../face_filters/beard_filter_painter.dart | 8 +- .../face_filters/dog_filter_painter.dart | 13 +++- .../video_recording_time.dart | 3 +- .../zoom_selector.dart | 26 ++++--- .../share_image_contact_selection.view.dart | 34 ++++++--- .../best_friends_selector.dart | 16 ++-- .../layers/emoji.layer.dart | 34 +++++---- .../layers/filters/location_filter.dart | 5 +- .../link_preview/parser/mastodon.parser.dart | 6 +- .../link_preview/parser/twitter.parser.dart | 4 +- .../share_image_editor/layers/text.layer.dart | 9 ++- .../last_message_time.dart | 16 ++-- .../blink.component.dart | 3 +- .../all_reactions.bottom_sheet.dart | 10 ++- .../chat_group_action.dart | 21 +++-- .../chat_reaction_row.dart | 13 ++-- .../entries/chat_audio_entry.dart | 5 +- .../entries/chat_contacts.entry.dart | 18 +++-- .../entries/chat_flame_restored.entry.dart | 5 +- .../additional_message_content.dart | 5 +- .../reaction_buttons.component.dart | 37 +++++---- lib/src/views/chats/message_info.view.dart | 15 ++-- lib/src/views/components/animate_icon.dart | 4 +- lib/src/views/components/app_outdated.dart | 24 +++--- lib/src/views/components/better_text.dart | 5 +- .../views/components/emoji_picker.bottom.dart | 5 +- .../views/components/fingerprint_text.dart | 6 +- .../views/components/media_view_sizing.dart | 3 +- .../select_chat_deletion_time.comp.dart | 46 +++++------ lib/src/views/components/svg_icon.dart | 5 +- lib/src/views/groups/group.view.dart | 23 +++--- .../group_create_select_group_name.view.dart | 14 +++- .../group_create_select_members.view.dart | 22 ++++-- lib/src/views/onboarding/onboarding.view.dart | 12 ++- lib/src/views/onboarding/recover.view.dart | 11 ++- lib/src/views/onboarding/register.view.dart | 11 ++- lib/src/views/settings/account.view.dart | 34 +++++---- lib/src/views/settings/appearance.view.dart | 6 +- .../views/settings/backup/backup.view.dart | 21 ++--- .../settings/backup/setup_backup.view.dart | 20 +++-- .../settings/chat/chat_reactions.view.dart | 6 +- .../views/settings/data_and_storage.view.dart | 15 ++-- .../data_and_storage/export_media.view.dart | 6 +- .../data_and_storage/import_media.view.dart | 5 +- .../developer/automated_testing.view.dart | 30 +++++--- .../views/settings/help/contact_us.view.dart | 5 +- .../help/contact_us/submit_message.view.dart | 6 +- lib/src/views/settings/help/faq.view.dart | 19 +++-- lib/src/views/settings/help/help.view.dart | 18 +++-- .../settings/privacy_view_block.view.dart | 14 ++-- .../settings/profile/modify_avatar.view.dart | 25 +++--- .../views/settings/profile/profile.view.dart | 5 +- .../settings/share_with_friends.view.dart | 5 +- .../subscription/additional_users.view.dart | 42 +++++----- .../select_additional_users.view.dart | 22 ++++-- .../subscription/subscription.view.dart | 12 ++- .../subscription_custom/checkout.view.dart | 24 +++--- .../select_payment.view.dart | 14 ++-- .../subscription.view.dart | 19 +++-- .../views/shared/select_contacts.view.dart | 22 ++++-- .../user_study_questionnaire.view.dart | 42 +++++----- .../user_study/user_study_welcome.view.dart | 5 +- 101 files changed, 854 insertions(+), 597 deletions(-) diff --git a/lib/firebase_options.dart b/lib/firebase_options.dart index dce01b6..d205b5c 100644 --- a/lib/firebase_options.dart +++ b/lib/firebase_options.dart @@ -65,5 +65,4 @@ class DefaultFirebaseOptions { storageBucket: 'twonly-ff605.firebasestorage.app', iosBundleId: 'eu.twonly', ); - -} \ No newline at end of file +} diff --git a/lib/globals.dart b/lib/globals.dart index 804a918..f446d5a 100644 --- a/lib/globals.dart +++ b/lib/globals.dart @@ -21,9 +21,10 @@ late UserData gUser; // App widget. // This callback called by the apiProvider -void Function({required bool isConnected}) globalCallbackConnectionState = ({ - required isConnected, -}) {}; +void Function({required bool isConnected}) globalCallbackConnectionState = + ({ + required isConnected, + }) {}; void Function() globalCallbackAppIsOutdated = () {}; void Function() globalCallbackNewDeviceRegistered = () {}; void Function(SubscriptionPlan plan) globalCallbackUpdatePlan = (plan) {}; diff --git a/lib/src/database/daos/contacts.dao.dart b/lib/src/database/daos/contacts.dao.dart index 912c4eb..ce26eb2 100644 --- a/lib/src/database/daos/contacts.dao.dart +++ b/lib/src/database/daos/contacts.dao.dart @@ -37,18 +37,18 @@ class ContactsDao extends DatabaseAccessor with _$ContactsDaoMixin { } Future getContactById(int userId) async { - return (select(contacts)..where((t) => t.userId.equals(userId))) - .getSingleOrNull(); + return (select( + contacts, + )..where((t) => t.userId.equals(userId))).getSingleOrNull(); } Future> getContactsByUsername( String username, { String username2 = '_______', }) async { - return (select(contacts) - ..where( - (t) => t.username.equals(username) | t.username.equals(username2), - )) + return (select(contacts)..where( + (t) => t.username.equals(username) | t.username.equals(username2), + )) .get(); } @@ -60,8 +60,9 @@ class ContactsDao extends DatabaseAccessor with _$ContactsDaoMixin { int userId, ContactsCompanion updatedValues, ) async { - await (update(contacts)..where((c) => c.userId.equals(userId))) - .write(updatedValues); + await (update( + contacts, + )..where((c) => c.userId.equals(userId))).write(updatedValues); if (updatedValues.blocked.present || updatedValues.displayName.present || updatedValues.nickName.present || @@ -83,19 +84,19 @@ class ContactsDao extends DatabaseAccessor with _$ContactsDaoMixin { } Stream> watchNotAcceptedContacts() { - return (select(contacts) - ..where( - (t) => - t.accepted.equals(false) & - t.blocked.equals(false) & - t.deletedByUser.equals(false), - )) + return (select(contacts)..where( + (t) => + t.accepted.equals(false) & + t.blocked.equals(false) & + t.deletedByUser.equals(false), + )) .watch(); } Stream watchContact(int userid) { - return (select(contacts)..where((t) => t.userId.equals(userid))) - .watchSingleOrNull(); + return (select( + contacts, + )..where((t) => t.userId.equals(userid))).watchSingleOrNull(); } Future> getAllContacts() { @@ -124,13 +125,12 @@ class ContactsDao extends DatabaseAccessor with _$ContactsDaoMixin { } Stream> watchAllAcceptedContacts() { - return (select(contacts) - ..where( - (t) => - t.blocked.equals(false) & - t.accepted.equals(true) & - t.accountDeleted.equals(false), - )) + return (select(contacts)..where( + (t) => + t.blocked.equals(false) & + t.accepted.equals(true) & + t.accountDeleted.equals(false), + )) .watch(); } diff --git a/lib/src/database/daos/signal.dao.g.dart b/lib/src/database/daos/signal.dao.g.dart index 68c90c1..8cba9e4 100644 --- a/lib/src/database/daos/signal.dao.g.dart +++ b/lib/src/database/daos/signal.dao.g.dart @@ -19,9 +19,13 @@ class SignalDaoManager { $$ContactsTableTableManager(_db.attachedDatabase, _db.contacts); $$SignalContactPreKeysTableTableManager get signalContactPreKeys => $$SignalContactPreKeysTableTableManager( - _db.attachedDatabase, _db.signalContactPreKeys); + _db.attachedDatabase, + _db.signalContactPreKeys, + ); $$SignalContactSignedPreKeysTableTableManager - get signalContactSignedPreKeys => - $$SignalContactSignedPreKeysTableTableManager( - _db.attachedDatabase, _db.signalContactSignedPreKeys); + get signalContactSignedPreKeys => + $$SignalContactSignedPreKeysTableTableManager( + _db.attachedDatabase, + _db.signalContactSignedPreKeys, + ); } diff --git a/lib/src/database/signal/connect_identity_key_store.dart b/lib/src/database/signal/connect_identity_key_store.dart index 2b36b13..2fa8ce3 100644 --- a/lib/src/database/signal/connect_identity_key_store.dart +++ b/lib/src/database/signal/connect_identity_key_store.dart @@ -12,13 +12,13 @@ class ConnectIdentityKeyStore extends IdentityKeyStore { @override Future getIdentity(SignalProtocolAddress address) async { - final identity = await (twonlyDB.select(twonlyDB.signalIdentityKeyStores) - ..where( - (t) => - t.deviceId.equals(address.getDeviceId()) & - t.name.equals(address.getName()), - )) - .getSingleOrNull(); + final identity = + await (twonlyDB.select(twonlyDB.signalIdentityKeyStores)..where( + (t) => + t.deviceId.equals(address.getDeviceId()) & + t.name.equals(address.getName()), + )) + .getSingleOrNull(); if (identity == null) return null; return IdentityKey.fromBytes(identity.identityKey, 0); } @@ -40,8 +40,10 @@ class ConnectIdentityKeyStore extends IdentityKeyStore { return false; } return trusted == null || - const ListEquality() - .equals(trusted.serialize(), identityKey.serialize()); + const ListEquality().equals( + trusted.serialize(), + identityKey.serialize(), + ); } @override @@ -53,7 +55,9 @@ class ConnectIdentityKeyStore extends IdentityKeyStore { return false; } if (await getIdentity(address) == null) { - await twonlyDB.into(twonlyDB.signalIdentityKeyStores).insert( + await twonlyDB + .into(twonlyDB.signalIdentityKeyStores) + .insert( SignalIdentityKeyStoresCompanion( deviceId: Value(address.getDeviceId()), name: Value(address.getName()), @@ -61,17 +65,16 @@ class ConnectIdentityKeyStore extends IdentityKeyStore { ), ); } else { - await (twonlyDB.update(twonlyDB.signalIdentityKeyStores) - ..where( - (t) => - t.deviceId.equals(address.getDeviceId()) & - t.name.equals(address.getName()), - )) + await (twonlyDB.update(twonlyDB.signalIdentityKeyStores)..where( + (t) => + t.deviceId.equals(address.getDeviceId()) & + t.name.equals(address.getName()), + )) .write( - SignalIdentityKeyStoresCompanion( - identityKey: Value(identityKey.serialize()), - ), - ); + SignalIdentityKeyStoresCompanion( + identityKey: Value(identityKey.serialize()), + ), + ); } return true; } diff --git a/lib/src/database/signal/connect_pre_key_store.dart b/lib/src/database/signal/connect_pre_key_store.dart index aa8ccd9..2a49b92 100644 --- a/lib/src/database/signal/connect_pre_key_store.dart +++ b/lib/src/database/signal/connect_pre_key_store.dart @@ -7,17 +7,17 @@ import 'package:twonly/src/utils/log.dart'; class ConnectPreKeyStore extends PreKeyStore { @override Future containsPreKey(int preKeyId) async { - final preKeyRecord = await (twonlyDB.select(twonlyDB.signalPreKeyStores) - ..where((tbl) => tbl.preKeyId.equals(preKeyId))) - .get(); + final preKeyRecord = await (twonlyDB.select( + twonlyDB.signalPreKeyStores, + )..where((tbl) => tbl.preKeyId.equals(preKeyId))).get(); return preKeyRecord.isNotEmpty; } @override Future loadPreKey(int preKeyId) async { - final preKeyRecord = await (twonlyDB.select(twonlyDB.signalPreKeyStores) - ..where((tbl) => tbl.preKeyId.equals(preKeyId))) - .get(); + final preKeyRecord = await (twonlyDB.select( + twonlyDB.signalPreKeyStores, + )..where((tbl) => tbl.preKeyId.equals(preKeyId))).get(); if (preKeyRecord.isEmpty) { throw InvalidKeyIdException( '[PREKEY] No such preKey record!', @@ -29,9 +29,9 @@ class ConnectPreKeyStore extends PreKeyStore { @override Future removePreKey(int preKeyId) async { - await (twonlyDB.delete(twonlyDB.signalPreKeyStores) - ..where((tbl) => tbl.preKeyId.equals(preKeyId))) - .go(); + await (twonlyDB.delete( + twonlyDB.signalPreKeyStores, + )..where((tbl) => tbl.preKeyId.equals(preKeyId))).go(); } @override diff --git a/lib/src/database/signal/connect_sender_key_store.dart b/lib/src/database/signal/connect_sender_key_store.dart index 69b25c7..c637f88 100644 --- a/lib/src/database/signal/connect_sender_key_store.dart +++ b/lib/src/database/signal/connect_sender_key_store.dart @@ -6,9 +6,10 @@ import 'package:twonly/src/database/twonly.db.dart'; class ConnectSenderKeyStore extends SenderKeyStore { @override Future loadSenderKey(SenderKeyName senderKeyName) async { - final identity = await (twonlyDB.select(twonlyDB.signalSenderKeyStores) - ..where((t) => t.senderKeyName.equals(senderKeyName.serialize()))) - .getSingleOrNull(); + final identity = + await (twonlyDB.select(twonlyDB.signalSenderKeyStores) + ..where((t) => t.senderKeyName.equals(senderKeyName.serialize()))) + .getSingleOrNull(); if (identity == null) { throw InvalidKeyIdException( 'No such sender key record! - $senderKeyName', @@ -22,7 +23,9 @@ class ConnectSenderKeyStore extends SenderKeyStore { SenderKeyName senderKeyName, SenderKeyRecord record, ) async { - await twonlyDB.into(twonlyDB.signalSenderKeyStores).insert( + await twonlyDB + .into(twonlyDB.signalSenderKeyStores) + .insert( SignalSenderKeyStoresCompanion( senderKey: Value(record.serialize()), senderKeyName: Value(senderKeyName.serialize()), diff --git a/lib/src/database/signal/connect_session_store.dart b/lib/src/database/signal/connect_session_store.dart index bb738c4..92208e3 100644 --- a/lib/src/database/signal/connect_session_store.dart +++ b/lib/src/database/signal/connect_session_store.dart @@ -6,53 +6,52 @@ import 'package:twonly/src/database/twonly.db.dart'; class ConnectSessionStore extends SessionStore { @override Future containsSession(SignalProtocolAddress address) async { - final sessions = await (twonlyDB.select(twonlyDB.signalSessionStores) - ..where( - (tbl) => - tbl.deviceId.equals(address.getDeviceId()) & - tbl.name.equals(address.getName()), - )) - .get(); + final sessions = + await (twonlyDB.select(twonlyDB.signalSessionStores)..where( + (tbl) => + tbl.deviceId.equals(address.getDeviceId()) & + tbl.name.equals(address.getName()), + )) + .get(); return sessions.isNotEmpty; } @override Future deleteAllSessions(String name) async { - await (twonlyDB.delete(twonlyDB.signalSessionStores) - ..where((tbl) => tbl.name.equals(name))) - .go(); + await (twonlyDB.delete( + twonlyDB.signalSessionStores, + )..where((tbl) => tbl.name.equals(name))).go(); } @override Future deleteSession(SignalProtocolAddress address) async { - await (twonlyDB.delete(twonlyDB.signalSessionStores) - ..where( - (tbl) => - tbl.deviceId.equals(address.getDeviceId()) & - tbl.name.equals(address.getName()), - )) + await (twonlyDB.delete(twonlyDB.signalSessionStores)..where( + (tbl) => + tbl.deviceId.equals(address.getDeviceId()) & + tbl.name.equals(address.getName()), + )) .go(); } @override Future> getSubDeviceSessions(String name) async { - final deviceIds = await (twonlyDB.select(twonlyDB.signalSessionStores) - ..where( - (tbl) => tbl.deviceId.equals(1).not() & tbl.name.equals(name), - )) - .get(); + final deviceIds = + await (twonlyDB.select(twonlyDB.signalSessionStores)..where( + (tbl) => tbl.deviceId.equals(1).not() & tbl.name.equals(name), + )) + .get(); return deviceIds.map((row) => row.deviceId).toList(); } @override Future loadSession(SignalProtocolAddress address) async { - final dbSession = await (twonlyDB.select(twonlyDB.signalSessionStores) - ..where( - (tbl) => - tbl.deviceId.equals(address.getDeviceId()) & - tbl.name.equals(address.getName()), - )) - .get(); + final dbSession = + await (twonlyDB.select(twonlyDB.signalSessionStores)..where( + (tbl) => + tbl.deviceId.equals(address.getDeviceId()) & + tbl.name.equals(address.getName()), + )) + .get(); if (dbSession.isEmpty) { return SessionRecord(); @@ -77,12 +76,11 @@ class ConnectSessionStore extends SessionStore { .into(twonlyDB.signalSessionStores) .insert(sessionCompanion); } else { - await (twonlyDB.update(twonlyDB.signalSessionStores) - ..where( - (tbl) => - tbl.deviceId.equals(address.getDeviceId()) & - tbl.name.equals(address.getName()), - )) + await (twonlyDB.update(twonlyDB.signalSessionStores)..where( + (tbl) => + tbl.deviceId.equals(address.getDeviceId()) & + tbl.name.equals(address.getName()), + )) .write(sessionCompanion); } } diff --git a/lib/src/database/signal/connect_signal_protocol_store.dart b/lib/src/database/signal/connect_signal_protocol_store.dart index f84774d..ea07739 100644 --- a/lib/src/database/signal/connect_signal_protocol_store.dart +++ b/lib/src/database/signal/connect_signal_protocol_store.dart @@ -9,8 +9,10 @@ class ConnectSignalProtocolStore implements SignalProtocolStore { IdentityKeyPair identityKeyPair, int registrationId, ) { - _identityKeyStore = - ConnectIdentityKeyStore(identityKeyPair, registrationId); + _identityKeyStore = ConnectIdentityKeyStore( + identityKeyPair, + registrationId, + ); } final preKeyStore = ConnectPreKeyStore(); @@ -31,8 +33,7 @@ class ConnectSignalProtocolStore implements SignalProtocolStore { Future saveIdentity( SignalProtocolAddress address, IdentityKey? identityKey, - ) async => - _identityKeyStore.saveIdentity(address, identityKey); + ) async => _identityKeyStore.saveIdentity(address, identityKey); @override Future isTrustedIdentity( diff --git a/lib/src/database/tables/mediafiles.table.dart b/lib/src/database/tables/mediafiles.table.dart index 50308a9..2cbb45c 100644 --- a/lib/src/database/tables/mediafiles.table.dart +++ b/lib/src/database/tables/mediafiles.table.dart @@ -33,7 +33,7 @@ enum DownloadState { downloading, downloaded, ready, - reuploadRequested + reuploadRequested, } @DataClassName('MediaFile') diff --git a/lib/src/database/tables/messages.table.dart b/lib/src/database/tables/messages.table.dart index 71cf0b0..42c779f 100644 --- a/lib/src/database/tables/messages.table.dart +++ b/lib/src/database/tables/messages.table.dart @@ -18,9 +18,11 @@ class Messages extends Table { TextColumn get type => text()(); TextColumn get content => text().nullable()(); - TextColumn get mediaId => text() - .nullable() - .references(MediaFiles, #mediaId, onDelete: KeyAction.setNull)(); + TextColumn get mediaId => text().nullable().references( + MediaFiles, + #mediaId, + onDelete: KeyAction.setNull, + )(); BlobColumn get additionalMessageData => blob().nullable()(); @@ -75,9 +77,11 @@ class MessageHistories extends Table { TextColumn get messageId => text().references(Messages, #messageId, onDelete: KeyAction.cascade)(); - IntColumn get contactId => integer() - .nullable() - .references(Contacts, #userId, onDelete: KeyAction.cascade)(); + IntColumn get contactId => integer().nullable().references( + Contacts, + #userId, + onDelete: KeyAction.cascade, + )(); TextColumn get content => text().nullable()(); diff --git a/lib/src/database/tables/reactions.table.dart b/lib/src/database/tables/reactions.table.dart index 5c7a671..1ffb178 100644 --- a/lib/src/database/tables/reactions.table.dart +++ b/lib/src/database/tables/reactions.table.dart @@ -10,9 +10,11 @@ class Reactions extends Table { TextColumn get emoji => text()(); // in case senderId is null, it was send by user itself - IntColumn get senderId => integer() - .nullable() - .references(Contacts, #userId, onDelete: KeyAction.cascade)(); + IntColumn get senderId => integer().nullable().references( + Contacts, + #userId, + onDelete: KeyAction.cascade, + )(); DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); diff --git a/lib/src/database/tables/receipts.table.dart b/lib/src/database/tables/receipts.table.dart index 9c9f7be..691cc32 100644 --- a/lib/src/database/tables/receipts.table.dart +++ b/lib/src/database/tables/receipts.table.dart @@ -10,9 +10,11 @@ class Receipts extends Table { integer().references(Contacts, #userId, onDelete: KeyAction.cascade)(); // in case a message is deleted, it should be also deleted from the receipts table - TextColumn get messageId => text() - .nullable() - .references(Messages, #messageId, onDelete: KeyAction.cascade)(); + TextColumn get messageId => text().nullable().references( + Messages, + #messageId, + onDelete: KeyAction.cascade, + )(); /// This is the protobuf 'Message' BlobColumn get message => blob()(); diff --git a/lib/src/model/protobuf/api/http/http_requests.pbserver.dart b/lib/src/model/protobuf/api/http/http_requests.pbserver.dart index 38180fc..51f279b 100644 --- a/lib/src/model/protobuf/api/http/http_requests.pbserver.dart +++ b/lib/src/model/protobuf/api/http/http_requests.pbserver.dart @@ -11,4 +11,3 @@ // ignore_for_file: unnecessary_import, unnecessary_this, unused_import export 'http_requests.pb.dart'; - diff --git a/lib/src/model/protobuf/api/websocket/client_to_server.pbserver.dart b/lib/src/model/protobuf/api/websocket/client_to_server.pbserver.dart index e071285..60eb113 100644 --- a/lib/src/model/protobuf/api/websocket/client_to_server.pbserver.dart +++ b/lib/src/model/protobuf/api/websocket/client_to_server.pbserver.dart @@ -11,4 +11,3 @@ // ignore_for_file: unnecessary_import, unnecessary_this, unused_import export 'client_to_server.pb.dart'; - diff --git a/lib/src/model/protobuf/api/websocket/error.pbserver.dart b/lib/src/model/protobuf/api/websocket/error.pbserver.dart index 0586ca9..a35b42e 100644 --- a/lib/src/model/protobuf/api/websocket/error.pbserver.dart +++ b/lib/src/model/protobuf/api/websocket/error.pbserver.dart @@ -11,4 +11,3 @@ // ignore_for_file: unnecessary_import, unnecessary_this, unused_import export 'error.pb.dart'; - diff --git a/lib/src/model/protobuf/api/websocket/server_to_client.pbserver.dart b/lib/src/model/protobuf/api/websocket/server_to_client.pbserver.dart index 66482f4..61c867e 100644 --- a/lib/src/model/protobuf/api/websocket/server_to_client.pbserver.dart +++ b/lib/src/model/protobuf/api/websocket/server_to_client.pbserver.dart @@ -11,4 +11,3 @@ // ignore_for_file: unnecessary_import, unnecessary_this, unused_import export 'server_to_client.pb.dart'; - diff --git a/lib/src/model/protobuf/client/generated/backup.pbserver.dart b/lib/src/model/protobuf/client/generated/backup.pbserver.dart index 1df01c1..53d3324 100644 --- a/lib/src/model/protobuf/client/generated/backup.pbserver.dart +++ b/lib/src/model/protobuf/client/generated/backup.pbserver.dart @@ -11,4 +11,3 @@ // ignore_for_file: unnecessary_import, unnecessary_this, unused_import export 'backup.pb.dart'; - diff --git a/lib/src/model/protobuf/client/generated/groups.pbserver.dart b/lib/src/model/protobuf/client/generated/groups.pbserver.dart index 087f85c..880b981 100644 --- a/lib/src/model/protobuf/client/generated/groups.pbserver.dart +++ b/lib/src/model/protobuf/client/generated/groups.pbserver.dart @@ -11,4 +11,3 @@ // ignore_for_file: unnecessary_import, unnecessary_this, unused_import export 'groups.pb.dart'; - diff --git a/lib/src/model/protobuf/client/generated/messages.pbserver.dart b/lib/src/model/protobuf/client/generated/messages.pbserver.dart index 956b01d..3085f97 100644 --- a/lib/src/model/protobuf/client/generated/messages.pbserver.dart +++ b/lib/src/model/protobuf/client/generated/messages.pbserver.dart @@ -11,4 +11,3 @@ // ignore_for_file: unnecessary_import, unnecessary_this, unused_import export 'messages.pb.dart'; - diff --git a/lib/src/model/protobuf/client/generated/push_notification.pbserver.dart b/lib/src/model/protobuf/client/generated/push_notification.pbserver.dart index a77f69b..4dd1ced 100644 --- a/lib/src/model/protobuf/client/generated/push_notification.pbserver.dart +++ b/lib/src/model/protobuf/client/generated/push_notification.pbserver.dart @@ -11,4 +11,3 @@ // ignore_for_file: unnecessary_import, unnecessary_this, unused_import export 'push_notification.pb.dart'; - diff --git a/lib/src/providers/purchases.provider.dart b/lib/src/providers/purchases.provider.dart index 3895c18..fd37b17 100644 --- a/lib/src/providers/purchases.provider.dart +++ b/lib/src/providers/purchases.provider.dart @@ -133,8 +133,10 @@ class PurchasesProvider with ChangeNotifier, DiagnosticableTreeMixin { final jsonData = base64Decode(b64Data); final data = jsonDecode(utf8.decode(jsonData)) as Map; final expiresDate = data['expiresDate'] as int; - final dt = - DateTime.fromMillisecondsSinceEpoch(expiresDate, isUtc: true); + final dt = DateTime.fromMillisecondsSinceEpoch( + expiresDate, + isUtc: true, + ); if (dt.isBefore(DateTime.now())) { Log.warn('ExpiresDate is in the past: $dt'); if (_userTriggeredBuyButton && Platform.isIOS) { diff --git a/lib/src/services/api/client2client/additional_data.c2c.dart b/lib/src/services/api/client2client/additional_data.c2c.dart index fdea032..6f42093 100644 --- a/lib/src/services/api/client2client/additional_data.c2c.dart +++ b/lib/src/services/api/client2client/additional_data.c2c.dart @@ -20,8 +20,9 @@ Future handleAdditionalDataMessage( senderId: Value(fromUserId), groupId: Value(groupId), type: Value(message.type), - additionalMessageData: - Value(Uint8List.fromList(message.additionalMessageData)), + additionalMessageData: Value( + Uint8List.fromList(message.additionalMessageData), + ), createdAt: Value(fromTimestamp(message.timestamp)), ackByServer: Value(clock.now()), ), diff --git a/lib/src/services/api/client2client/errors.c2c.dart b/lib/src/services/api/client2client/errors.c2c.dart index 598424e..84dc8c8 100644 --- a/lib/src/services/api/client2client/errors.c2c.dart +++ b/lib/src/services/api/client2client/errors.c2c.dart @@ -13,7 +13,7 @@ Future handleErrorMessage( switch (error.type) { case EncryptedContent_ErrorMessages_Type - .ERROR_PROCESSING_MESSAGE_CREATED_ACCOUNT_REQUEST_INSTEAD: + .ERROR_PROCESSING_MESSAGE_CREATED_ACCOUNT_REQUEST_INSTEAD: await twonlyDB.receiptsDao.updateReceiptWidthUserId( fromUserId, error.relatedReceiptId, diff --git a/lib/src/services/api/client2client/messages.c2c.dart b/lib/src/services/api/client2client/messages.c2c.dart index e193ed3..d2f810f 100644 --- a/lib/src/services/api/client2client/messages.c2c.dart +++ b/lib/src/services/api/client2client/messages.c2c.dart @@ -54,8 +54,9 @@ Future handleMessageUpdate( } Future isSender(int fromUserId, String messageId) async { - final message = - await twonlyDB.messagesDao.getMessageById(messageId).getSingleOrNull(); + final message = await twonlyDB.messagesDao + .getMessageById(messageId) + .getSingleOrNull(); if (message == null) return false; if (message.senderId == fromUserId) { return true; diff --git a/lib/src/services/api/client2client/pushkeys.c2c.dart b/lib/src/services/api/client2client/pushkeys.c2c.dart index 31b6984..9f2a707 100644 --- a/lib/src/services/api/client2client/pushkeys.c2c.dart +++ b/lib/src/services/api/client2client/pushkeys.c2c.dart @@ -14,8 +14,9 @@ Future handlePushKey( switch (pushKeys.type) { case EncryptedContent_PushKeys_Type.REQUEST: Log.info('Got a pushkey request from $contactId'); - if (lastPushKeyRequest - .isBefore(clock.now().subtract(const Duration(seconds: 60)))) { + if (lastPushKeyRequest.isBefore( + clock.now().subtract(const Duration(seconds: 60)), + )) { lastPushKeyRequest = clock.now(); unawaited(setupNotificationWithUsers(forceContact: contactId)); } diff --git a/lib/src/services/api/utils.dart b/lib/src/services/api/utils.dart index 6468445..81b084b 100644 --- a/lib/src/services/api/utils.dart +++ b/lib/src/services/api/utils.dart @@ -65,8 +65,9 @@ Future handleMediaError(MediaFile media) async { downloadState: Value(DownloadState.reuploadRequested), ), ); - final messages = - await twonlyDB.messagesDao.getMessagesByMediaId(media.mediaId); + final messages = await twonlyDB.messagesDao.getMessagesByMediaId( + media.mediaId, + ); if (messages.length != 1) return; final message = messages.first; if (message.senderId == null) return; diff --git a/lib/src/services/backup/common.backup.dart b/lib/src/services/backup/common.backup.dart index b636aab..9617954 100644 --- a/lib/src/services/backup/common.backup.dart +++ b/lib/src/services/backup/common.backup.dart @@ -11,8 +11,10 @@ import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/storage.dart'; Future enableTwonlySafe(String password) async { - final (backupId, encryptionKey) = - await getMasterKey(password, gUser.username); + final (backupId, encryptionKey) = await getMasterKey( + password, + gUser.username, + ); await updateUserdata((user) { user.twonlySafeBackup = TwonlySafeBackup( diff --git a/lib/src/services/group.services.dart b/lib/src/services/group.services.dart index e975778..7a64777 100644 --- a/lib/src/services/group.services.dart +++ b/lib/src/services/group.services.dart @@ -45,8 +45,9 @@ Future createNewGroup(String groupName, List members) async { memberIds: [Int64(gUser.userId)] + memberIds, adminIds: [Int64(gUser.userId)], groupName: groupName, - deleteMessagesAfterMilliseconds: - Int64(defaultDeleteMessagesAfterMilliseconds), + deleteMessagesAfterMilliseconds: Int64( + defaultDeleteMessagesAfterMilliseconds, + ), padding: List.generate(Random().nextInt(80), (_) => 0), ); @@ -158,8 +159,9 @@ Future fetchMissingGroupPublicKey() async { for (final member in members) { if (member.lastMessage == null) continue; // only request if the users has send a message in the last two days. - if (member.lastMessage! - .isAfter(clock.now().subtract(const Duration(days: 2)))) { + if (member.lastMessage!.isAfter( + clock.now().subtract(const Duration(days: 2)), + )) { await sendCipherText( member.contactId, EncryptedContent( @@ -227,12 +229,15 @@ Future<(int, EncryptedGroupState)?> fetchGroupState(Group group) async { final groupStateServer = GroupState.fromBuffer(response.bodyBytes); - final encryptedStateRaw = - await _decryptEnvelop(group, groupStateServer.encryptedGroupState); + final encryptedStateRaw = await _decryptEnvelop( + group, + groupStateServer.encryptedGroupState, + ); if (encryptedStateRaw == null) return null; - final encryptedGroupState = - EncryptedGroupState.fromBuffer(encryptedStateRaw); + final encryptedGroupState = EncryptedGroupState.fromBuffer( + encryptedStateRaw, + ); if (group.stateVersionId >= groupStateServer.versionId.toInt()) { Log.info( @@ -266,24 +271,28 @@ Future<(int, EncryptedGroupState)?> fetchGroupState(Group group) async { ); if (encryptedStateRaw == null) continue; - final appended = - EncryptedAppendedGroupState.fromBuffer(encryptedStateRaw); + final appended = EncryptedAppendedGroupState.fromBuffer( + encryptedStateRaw, + ); if (appended.type == EncryptedAppendedGroupState_Type.LEFT_GROUP) { - final keyPair = - IdentityKeyPair.fromSerialized(group.myGroupPrivateKey!); + final keyPair = IdentityKeyPair.fromSerialized( + group.myGroupPrivateKey!, + ); final appendedPubKey = appendedState.appendTBS.publicKey; final myPubKey = keyPair.getPublicKey().serialize().toList(); if (listEquals(appendedPubKey, myPubKey)) { adminIds.remove(Int64(gUser.userId)); - memberIds - .remove(Int64(gUser.userId)); // -> Will remove the user later... + memberIds.remove( + Int64(gUser.userId), + ); // -> Will remove the user later... } else { Log.info('A non admin left the group!!!'); - final member = await twonlyDB.groupsDao - .getGroupMemberByPublicKey(Uint8List.fromList(appendedPubKey)); + final member = await twonlyDB.groupsDao.getGroupMemberByPublicKey( + Uint8List.fromList(appendedPubKey), + ); if (member == null) { Log.error('Member is already not in this group...'); continue; @@ -353,8 +362,9 @@ Future<(int, EncryptedGroupState)?> fetchGroupState(Group group) async { ), ); - var currentGroupMembers = - await twonlyDB.groupsDao.getGroupNonLeftMembers(group.groupId); + var currentGroupMembers = await twonlyDB.groupsDao.getGroupNonLeftMembers( + group.groupId, + ); // First find and insert NEW members for (final memberId in memberIds) { @@ -391,8 +401,9 @@ Future<(int, EncryptedGroupState)?> fetchGroupState(Group group) async { // Send the new user my public group key if (group.myGroupPrivateKey != null) { - final keyPair = - IdentityKeyPair.fromSerialized(group.myGroupPrivateKey!); + final keyPair = IdentityKeyPair.fromSerialized( + group.myGroupPrivateKey!, + ); await sendCipherText( memberId.toInt(), EncryptedContent( @@ -407,8 +418,9 @@ Future<(int, EncryptedGroupState)?> fetchGroupState(Group group) async { // check if there is a member which is not in the server list... // update the current members list - currentGroupMembers = - await twonlyDB.groupsDao.getGroupNonLeftMembers(group.groupId); + currentGroupMembers = await twonlyDB.groupsDao.getGroupNonLeftMembers( + group.groupId, + ); for (final member in currentGroupMembers) { // Member is not any more in the members list @@ -468,8 +480,9 @@ Future addNewHiddenContact(int contactId) async { ContactsCompanion( username: Value(utf8.decode(userData.username)), userId: Value(contactId), - deletedByUser: - const Value(true), // this will hide the contact in the contact list + deletedByUser: const Value( + true, + ), // this will hide the contact in the contact list ), ); await processSignalUserData(userData); @@ -594,8 +607,9 @@ Future manageAdminState( return false; } - final groupActionType = - remove ? GroupActionType.demoteToMember : GroupActionType.promoteToAdmin; + final groupActionType = remove + ? GroupActionType.demoteToMember + : GroupActionType.promoteToAdmin; await sendCipherTextToGroup( group.groupId, @@ -664,8 +678,9 @@ Future updateChatDeletionTime( if (currentState == null) return false; final (versionId, state) = currentState; - state.deleteMessagesAfterMilliseconds = - Int64(deleteMessagesAfterMilliseconds); + state.deleteMessagesAfterMilliseconds = Int64( + deleteMessagesAfterMilliseconds, + ); // send new state to the server if (!await _updateGroupState(group, state)) { @@ -688,8 +703,9 @@ Future updateChatDeletionTime( GroupHistoriesCompanion( groupId: Value(group.groupId), type: const Value(GroupActionType.changeDisplayMaxTime), - newDeleteMessagesAfterMilliseconds: - Value(deleteMessagesAfterMilliseconds), + newDeleteMessagesAfterMilliseconds: Value( + deleteMessagesAfterMilliseconds, + ), ), ); diff --git a/lib/src/services/notifications/setup.notifications.dart b/lib/src/services/notifications/setup.notifications.dart index 7cf9e45..0c6159d 100644 --- a/lib/src/services/notifications/setup.notifications.dart +++ b/lib/src/services/notifications/setup.notifications.dart @@ -9,9 +9,11 @@ final StreamController selectNotificationStream = @pragma('vm:entry-point') void notificationTapBackground(NotificationResponse notificationResponse) { // ignore: avoid_print - print('notification(${notificationResponse.id}) action tapped: ' - '${notificationResponse.actionId} with' - ' payload: ${notificationResponse.payload}'); + print( + 'notification(${notificationResponse.id}) action tapped: ' + '${notificationResponse.actionId} with' + ' payload: ${notificationResponse.payload}', + ); if (notificationResponse.input?.isNotEmpty ?? false) { // ignore: avoid_print print( @@ -26,8 +28,9 @@ final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = int id = 0; Future setupPushNotification() async { - const initializationSettingsAndroid = - AndroidInitializationSettings('ic_launcher_foreground'); + const initializationSettingsAndroid = AndroidInitializationSettings( + 'ic_launcher_foreground', + ); final darwinNotificationCategories = []; diff --git a/lib/src/services/signal/encryption.signal.dart b/lib/src/services/signal/encryption.signal.dart index a565f32..7a6efcd 100644 --- a/lib/src/services/signal/encryption.signal.dart +++ b/lib/src/services/signal/encryption.signal.dart @@ -33,7 +33,7 @@ Future signalEncryptMessage( bool alreadyPerformedAnResync = false; Future<(EncryptedContent?, PlaintextContent_DecryptionErrorMessage_Type?)> - signalDecryptMessage( +signalDecryptMessage( int fromUserId, Uint8List encryptedContentRaw, int type, diff --git a/lib/src/services/signal/identity.signal.dart b/lib/src/services/signal/identity.signal.dart index aecd4a7..a9692da 100644 --- a/lib/src/services/signal/identity.signal.dart +++ b/lib/src/services/signal/identity.signal.dart @@ -22,8 +22,9 @@ Future getSignalIdentityKeyPair() async { Future signalHandleNewServerConnection() async { if (gUser.signalLastSignedPreKeyUpdated != null) { final fortyEightHoursAgo = clock.now().subtract(const Duration(hours: 48)); - final isYoungerThan48Hours = - (gUser.signalLastSignedPreKeyUpdated!).isAfter(fortyEightHoursAgo); + final isYoungerThan48Hours = (gUser.signalLastSignedPreKeyUpdated!).isAfter( + fortyEightHoursAgo, + ); if (isYoungerThan48Hours) { // The key does live for 48 hours then it expires and a new key is generated. return; @@ -76,8 +77,9 @@ Future> signalGetPreKeys() async { Future getSignalIdentity() async { try { const storage = FlutterSecureStorage(); - var signalIdentityJson = - await storage.read(key: SecureStorageKeys.signalIdentity); + var signalIdentityJson = await storage.read( + key: SecureStorageKeys.signalIdentity, + ); if (signalIdentityJson == null) { return null; } @@ -104,13 +106,17 @@ Future createIfNotExistsSignalIdentity() async { final identityKeyPair = generateIdentityKeyPair(); final registrationId = generateRegistrationId(true); - final signalStore = - ConnectSignalProtocolStore(identityKeyPair, registrationId); + final signalStore = ConnectSignalProtocolStore( + identityKeyPair, + registrationId, + ); final signedPreKey = generateSignedPreKey(identityKeyPair, defaultDeviceId); - await signalStore.signedPreKeyStore - .storeSignedPreKey(signedPreKey.id, signedPreKey); + await signalStore.signedPreKeyStore.storeSignedPreKey( + signedPreKey.id, + signedPreKey, + ); final storedSignalIdentity = SignalIdentity( identityKeyPairU8List: identityKeyPair.serialize(), diff --git a/lib/src/services/signal/session.signal.dart b/lib/src/services/signal/session.signal.dart index e00520e..d5d07a0 100644 --- a/lib/src/services/signal/session.signal.dart +++ b/lib/src/services/signal/session.signal.dart @@ -46,8 +46,9 @@ Future processSignalUserData(Response_UserData userData) async { final tempIdentityKey = IdentityKey( Curve.decodePoint( - DjbECPublicKey(Uint8List.fromList(userData.publicIdentityKey)) - .serialize(), + DjbECPublicKey( + Uint8List.fromList(userData.publicIdentityKey), + ).serialize(), 1, ), ); diff --git a/lib/src/services/signal/utils.signal.dart b/lib/src/services/signal/utils.signal.dart index dc8ef36..566fd8e 100644 --- a/lib/src/services/signal/utils.signal.dart +++ b/lib/src/services/signal/utils.signal.dart @@ -11,8 +11,9 @@ Future getSignalStore() async { Future getSignalStoreFromIdentity( SignalIdentity signalIdentity, ) async { - final identityKeyPair = - IdentityKeyPair.fromSerialized(signalIdentity.identityKeyPairU8List); + final identityKeyPair = IdentityKeyPair.fromSerialized( + signalIdentity.identityKeyPairU8List, + ); return ConnectSignalProtocolStore( identityKeyPair, diff --git a/lib/src/utils/misc.dart b/lib/src/utils/misc.dart index 401689a..fb35ec5 100644 --- a/lib/src/utils/misc.dart +++ b/lib/src/utils/misc.dart @@ -85,11 +85,11 @@ const _chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890'; Random _rnd = Random(); String getRandomString(int length) => String.fromCharCodes( - Iterable.generate( - length, - (_) => _chars.codeUnitAt(_rnd.nextInt(_chars.length)), - ), - ); + Iterable.generate( + length, + (_) => _chars.codeUnitAt(_rnd.nextInt(_chars.length)), + ), +); String errorCodeToText(BuildContext context, ErrorCode code) { // ignore: exhaustive_cases @@ -224,13 +224,17 @@ InputDecoration inputTextMessageDeco(BuildContext context) { contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), border: OutlineInputBorder( borderRadius: BorderRadius.circular(20), - borderSide: - BorderSide(color: Theme.of(context).colorScheme.primary, width: 2), + borderSide: BorderSide( + color: Theme.of(context).colorScheme.primary, + width: 2, + ), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(20), - borderSide: - BorderSide(color: Theme.of(context).colorScheme.primary, width: 2), + borderSide: BorderSide( + color: Theme.of(context).colorScheme.primary, + width: 2, + ), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(20), @@ -253,11 +257,13 @@ String formatDateTime(BuildContext context, DateTime? dateTime) { final now = clock.now(); final difference = now.difference(dateTime); - final date = DateFormat.yMd(Localizations.localeOf(context).toLanguageTag()) - .format(dateTime); + final date = DateFormat.yMd( + Localizations.localeOf(context).toLanguageTag(), + ).format(dateTime); - final time = DateFormat.Hm(Localizations.localeOf(context).toLanguageTag()) - .format(dateTime); + final time = DateFormat.Hm( + Localizations.localeOf(context).toLanguageTag(), + ).format(dateTime); if (difference.inDays == 0) { return time; @@ -289,11 +295,11 @@ String uint8ListToHex(List bytes) { } Uint8List hexToUint8List(String hex) => Uint8List.fromList( - List.generate( - hex.length ~/ 2, - (i) => int.parse(hex.substring(i * 2, i * 2 + 2), radix: 16), - ), - ); + List.generate( + hex.length ~/ 2, + (i) => int.parse(hex.substring(i * 2, i * 2 + 2), radix: 16), + ), +); Color getMessageColorFromType( Message message, @@ -359,18 +365,21 @@ String friendlyDateTime( Locale? locale, }) { // Build date part - final datePart = - DateFormat.yMd(Localizations.localeOf(context).toString()).format(dt); + final datePart = DateFormat.yMd( + Localizations.localeOf(context).toString(), + ).format(dt); final use24Hour = MediaQuery.of(context).alwaysUse24HourFormat; var timePart = ''; if (use24Hour) { - timePart = - DateFormat.jm(Localizations.localeOf(context).toString()).format(dt); + timePart = DateFormat.jm( + Localizations.localeOf(context).toString(), + ).format(dt); } else { - timePart = - DateFormat.Hm(Localizations.localeOf(context).toString()).format(dt); + timePart = DateFormat.Hm( + Localizations.localeOf(context).toString(), + ).format(dt); } return '$timePart $datePart'; diff --git a/lib/src/utils/qr.dart b/lib/src/utils/qr.dart index 075a878..87b98ef 100644 --- a/lib/src/utils/qr.dart +++ b/lib/src/utils/qr.dart @@ -19,8 +19,9 @@ Future getProfileQrCodeData() async { final publicProfile = PublicProfile( userId: Int64(gUser.userId), username: gUser.username, - publicIdentityKey: - (await signalStore.getIdentityKeyPair()).getPublicKey().serialize(), + publicIdentityKey: (await signalStore.getIdentityKeyPair()) + .getPublicKey() + .serialize(), registrationId: Int64(signalIdentity.registrationId), signedPrekey: signedPreKey.getKeyPair().publicKey.serialize(), signedPrekeySignature: signedPreKey.signature, diff --git a/lib/src/utils/storage.dart b/lib/src/utils/storage.dart index 19f354c..6d89be2 100644 --- a/lib/src/utils/storage.dart +++ b/lib/src/utils/storage.dart @@ -22,8 +22,9 @@ Future isUserCreated() async { Future getUser() async { try { - final userJson = await const FlutterSecureStorage() - .read(key: SecureStorageKeys.userData); + final userJson = await const FlutterSecureStorage().read( + key: SecureStorageKeys.userData, + ); if (userJson == null) { return null; } @@ -64,8 +65,10 @@ Future updateUserdata( user.defaultShowTime = null; } final updated = updateUser(user); - await const FlutterSecureStorage() - .write(key: SecureStorageKeys.userData, value: jsonEncode(updated)); + await const FlutterSecureStorage().write( + key: SecureStorageKeys.userData, + value: jsonEncode(updated), + ); gUser = updated; return updated; }); diff --git a/lib/src/views/camera/camera_preview_components/camera_preview.dart b/lib/src/views/camera/camera_preview_components/camera_preview.dart index 1f196ea..648e34b 100644 --- a/lib/src/views/camera/camera_preview_components/camera_preview.dart +++ b/lib/src/views/camera/camera_preview_components/camera_preview.dart @@ -34,9 +34,15 @@ class MainCameraPreview extends StatelessWidget { fit: BoxFit.cover, child: SizedBox( width: mainCameraController - .cameraController!.value.previewSize!.height, + .cameraController! + .value + .previewSize! + .height, height: mainCameraController - .cameraController!.value.previewSize!.width, + .cameraController! + .value + .previewSize! + .width, child: CameraPreview( key: mainCameraController.cameraPreviewKey, mainCameraController.cameraController!, @@ -67,9 +73,15 @@ class MainCameraPreview extends StatelessWidget { fit: BoxFit.cover, child: SizedBox( width: mainCameraController - .cameraController!.value.previewSize!.height, + .cameraController! + .value + .previewSize! + .height, height: mainCameraController - .cameraController!.value.previewSize!.width, + .cameraController! + .value + .previewSize! + .width, child: Stack( children: [ Positioned( diff --git a/lib/src/views/camera/camera_preview_components/face_filters.dart b/lib/src/views/camera/camera_preview_components/face_filters.dart index 179f679..11c5e19 100644 --- a/lib/src/views/camera/camera_preview_components/face_filters.dart +++ b/lib/src/views/camera/camera_preview_components/face_filters.dart @@ -15,7 +15,8 @@ extension FaceFilterTypeExtension on FaceFilterType { } FaceFilterType goLeft() { - final prevIndex = (index - 1 + FaceFilterType.values.length) % + final prevIndex = + (index - 1 + FaceFilterType.values.length) % FaceFilterType.values.length; return FaceFilterType.values[prevIndex]; } diff --git a/lib/src/views/camera/camera_preview_components/painters/face_filters/beard_filter_painter.dart b/lib/src/views/camera/camera_preview_components/painters/face_filters/beard_filter_painter.dart index 635f3a2..2380273 100644 --- a/lib/src/views/camera/camera_preview_components/painters/face_filters/beard_filter_painter.dart +++ b/lib/src/views/camera/camera_preview_components/painters/face_filters/beard_filter_painter.dart @@ -159,8 +159,12 @@ class BeardFilterPainter extends FaceFilterPainter { ..rotate(rotation) ..scale(scaleX, Platform.isAndroid ? -1 : 1); - final srcRect = - Rect.fromLTWH(0, 0, image.width.toDouble(), image.height.toDouble()); + final srcRect = Rect.fromLTWH( + 0, + 0, + image.width.toDouble(), + image.height.toDouble(), + ); final aspectRatio = image.width / image.height; final dstWidth = width; diff --git a/lib/src/views/camera/camera_preview_components/painters/face_filters/dog_filter_painter.dart b/lib/src/views/camera/camera_preview_components/painters/face_filters/dog_filter_painter.dart index 3643c33..5a6492f 100644 --- a/lib/src/views/camera/camera_preview_components/painters/face_filters/dog_filter_painter.dart +++ b/lib/src/views/camera/camera_preview_components/painters/face_filters/dog_filter_painter.dart @@ -56,8 +56,9 @@ class DogFilterPainter extends FaceFilterPainter { final points = faceContour.points; if (points.isEmpty) continue; - final upperPoints = - points.where((p) => p.y < noseBase.position.y).toList(); + final upperPoints = points + .where((p) => p.y < noseBase.position.y) + .toList(); if (upperPoints.isEmpty) continue; @@ -186,8 +187,12 @@ class DogFilterPainter extends FaceFilterPainter { canvas.scale(scaleX, Platform.isAndroid ? -1 : 1); } - final srcRect = - Rect.fromLTWH(0, 0, image.width.toDouble(), image.height.toDouble()); + final srcRect = Rect.fromLTWH( + 0, + 0, + image.width.toDouble(), + image.height.toDouble(), + ); final aspectRatio = image.width / image.height; final dstWidth = size; final dstHeight = size / aspectRatio; diff --git a/lib/src/views/camera/camera_preview_components/video_recording_time.dart b/lib/src/views/camera/camera_preview_components/video_recording_time.dart index d8b5211..5177ecb 100644 --- a/lib/src/views/camera/camera_preview_components/video_recording_time.dart +++ b/lib/src/views/camera/camera_preview_components/video_recording_time.dart @@ -26,7 +26,8 @@ class VideoRecordingTimer extends StatelessWidget { children: [ Center( child: CircularProgressIndicator( - value: currentTime + value: + currentTime .difference(videoRecordingStarted!) .inMilliseconds / (maxVideoRecordingTime * 1000), diff --git a/lib/src/views/camera/camera_preview_components/zoom_selector.dart b/lib/src/views/camera/camera_preview_components/zoom_selector.dart index 6397b29..ab7a5eb 100644 --- a/lib/src/views/camera/camera_preview_components/zoom_selector.dart +++ b/lib/src/views/camera/camera_preview_components/zoom_selector.dart @@ -51,8 +51,9 @@ class _CameraZoomButtonsState extends State { Future initAsync() async { showWideAngleZoom = (await widget.controller.getMinZoomLevel()) < 1; - var index = - gCameras.indexWhere((t) => t.lensType == CameraLensType.ultraWide); + var index = gCameras.indexWhere( + (t) => t.lensType == CameraLensType.ultraWide, + ); if (index == -1) { index = gCameras.indexWhere( (t) => t.lensType == CameraLensType.wide, @@ -62,7 +63,8 @@ class _CameraZoomButtonsState extends State { _wideCameraIndex = index; } - final isFront = widget.controller.description.lensDirection == + final isFront = + widget.controller.description.lensDirection == CameraLensDirection.front; if (!showWideAngleZoom && @@ -94,10 +96,12 @@ class _CameraZoomButtonsState extends State { ); const zoomTextStyle = TextStyle(fontSize: 13); - final isSmallerFocused = widget.scaleFactor < 1 || + final isSmallerFocused = + widget.scaleFactor < 1 || (showWideAngleZoomIOS && widget.selectedCameraDetails.cameraId == _wideCameraIndex); - final isMiddleFocused = widget.scaleFactor >= 1 && + final isMiddleFocused = + widget.scaleFactor >= 1 && widget.scaleFactor < 2 && !(showWideAngleZoomIOS && widget.selectedCameraDetails.cameraId == _wideCameraIndex); @@ -107,8 +111,9 @@ class _CameraZoomButtonsState extends State { widget.scaleFactor, ); - final minLevel = - beautifulZoomScale(widget.selectedCameraDetails.minAvailableZoom); + final minLevel = beautifulZoomScale( + widget.selectedCameraDetails.minAvailableZoom, + ); final currentLevel = beautifulZoomScale(widget.scaleFactor); return Center( child: ClipRRect( @@ -173,9 +178,10 @@ class _CameraZoomButtonsState extends State { ), ), onPressed: () async { - final level = - min(await widget.controller.getMaxZoomLevel(), 2) - .toDouble(); + final level = min( + await widget.controller.getMaxZoomLevel(), + 2, + ).toDouble(); if (showWideAngleZoomIOS && widget.selectedCameraDetails.cameraId == diff --git a/lib/src/views/camera/share_image_contact_selection.view.dart b/lib/src/views/camera/share_image_contact_selection.view.dart index e912200..b4505f0 100644 --- a/lib/src/views/camera/share_image_contact_selection.view.dart +++ b/lib/src/views/camera/share_image_contact_selection.view.dart @@ -55,8 +55,9 @@ class _ShareImageView extends State { void initState() { super.initState(); - allGroupSub = - twonlyDB.groupsDao.watchGroupsForShareImage().listen((allGroups) async { + allGroupSub = twonlyDB.groupsDao.watchGroupsForShareImage().listen(( + allGroups, + ) async { setState(() { contacts = allGroups; }); @@ -86,8 +87,9 @@ class _ShareImageView extends State { groups.sort((a, b) { // First, compare by flameCounter - final flameComparison = - getFlameCounterFromGroup(b).compareTo(getFlameCounterFromGroup(a)); + final flameComparison = getFlameCounterFromGroup( + b, + ).compareTo(getFlameCounterFromGroup(a)); if (flameComparison != 0) { return flameComparison; // Sort by flameCounter in descending order } @@ -156,8 +158,12 @@ class _ShareImageView extends State { ), body: SafeArea( child: Padding( - padding: - const EdgeInsets.only(bottom: 40, left: 10, top: 20, right: 10), + padding: const EdgeInsets.only( + bottom: 40, + left: 10, + top: 20, + right: 10, + ), child: Column( children: [ Padding( @@ -211,8 +217,9 @@ class _ShareImageView extends State { return const BorderSide(width: 0); } return BorderSide( - color: - Theme.of(context).colorScheme.outline, + color: Theme.of( + context, + ).colorScheme.outline, ); }, ), @@ -254,8 +261,10 @@ class _ShareImageView extends State { child: Container( clipBehavior: Clip.hardEdge, decoration: BoxDecoration( - border: - Border.all(color: context.color.primary, width: 2), + border: Border.all( + color: context.color.primary, + width: 2, + ), color: context.color.primary, borderRadius: BorderRadius.circular(12), ), @@ -336,8 +345,9 @@ class UserList extends StatelessWidget { @override Widget build(BuildContext context) { // Step 1: Sort the users alphabetically - groups - .sort((a, b) => b.lastMessageExchange.compareTo(a.lastMessageExchange)); + groups.sort( + (a, b) => b.lastMessageExchange.compareTo(a.lastMessageExchange), + ); return ListView.builder( restorationId: 'new_message_users_list', diff --git a/lib/src/views/camera/share_image_contact_selection/best_friends_selector.dart b/lib/src/views/camera/share_image_contact_selection/best_friends_selector.dart index a989497..a15b23e 100644 --- a/lib/src/views/camera/share_image_contact_selection/best_friends_selector.dart +++ b/lib/src/views/camera/share_image_contact_selection/best_friends_selector.dart @@ -42,8 +42,10 @@ class BestFriendsSelector extends StatelessWidget { } }, child: Container( - padding: - const EdgeInsets.symmetric(horizontal: 7, vertical: 4), + padding: const EdgeInsets.symmetric( + horizontal: 7, + vertical: 4, + ), decoration: BoxDecoration( color: Theme.of(context).colorScheme.outline.withAlpha(50), boxShadow: const [ @@ -75,8 +77,9 @@ class BestFriendsSelector extends StatelessWidget { Expanded( child: UserCheckbox( key: ValueKey(groups[firstUserIndex]), - isChecked: selectedGroupIds - .contains(groups[firstUserIndex].groupId), + isChecked: selectedGroupIds.contains( + groups[firstUserIndex].groupId, + ), group: groups[firstUserIndex], onChanged: updateSelectedGroupIds, ), @@ -85,8 +88,9 @@ class BestFriendsSelector extends StatelessWidget { Expanded( child: UserCheckbox( key: ValueKey(groups[secondUserIndex]), - isChecked: selectedGroupIds - .contains(groups[secondUserIndex].groupId), + isChecked: selectedGroupIds.contains( + groups[secondUserIndex].groupId, + ), group: groups[secondUserIndex], onChanged: updateSelectedGroupIds, ), diff --git a/lib/src/views/camera/share_image_editor/layers/emoji.layer.dart b/lib/src/views/camera/share_image_editor/layers/emoji.layer.dart index ac6020f..d01afc2 100755 --- a/lib/src/views/camera/share_image_editor/layers/emoji.layer.dart +++ b/lib/src/views/camera/share_image_editor/layers/emoji.layer.dart @@ -91,8 +91,10 @@ class _EmojiLayerState extends State { initialScale = widget.layerData.size; initialRotation = widget.layerData.rotation; initialOffset = widget.layerData.offset; - initialFocalPoint = - Offset(details.focalPoint.dx, details.focalPoint.dy); + initialFocalPoint = Offset( + details.focalPoint.dx, + details.focalPoint.dy, + ); setState(() {}); }, @@ -100,22 +102,23 @@ class _EmojiLayerState extends State { if (twoPointerWhereDown && details.pointerCount != 2) { return; } - final outlineBox = outlineKey.currentContext! - .findRenderObject()! as RenderBox; + final outlineBox = + outlineKey.currentContext!.findRenderObject()! + as RenderBox; final emojiBox = emojiKey.currentContext!.findRenderObject()! as RenderBox; final isAtTheBottom = (widget.layerData.offset.dy + emojiBox.size.height / 2) > - outlineBox.size.height - 80; + outlineBox.size.height - 80; final isInTheCenter = MediaQuery.of(context).size.width / 2 - 30 < - (widget.layerData.offset.dx + - emojiBox.size.width / 2) && - MediaQuery.of(context).size.width / 2 + 20 > - (widget.layerData.offset.dx + - emojiBox.size.width / 2); + (widget.layerData.offset.dx + + emojiBox.size.width / 2) && + MediaQuery.of(context).size.width / 2 + 20 > + (widget.layerData.offset.dx + + emojiBox.size.width / 2); if (isAtTheBottom && isInTheCenter) { if (!deleteLayer) { @@ -133,9 +136,11 @@ class _EmojiLayerState extends State { initialRotation + details.rotation; // Update the position based on the translation - final dx = (initialOffset.dx) + + final dx = + (initialOffset.dx) + (details.focalPoint.dx - initialFocalPoint.dx); - final dy = (initialOffset.dy) + + final dy = + (initialOffset.dy) + (details.focalPoint.dy - initialFocalPoint.dy); widget.layerData.offset = Offset(dx, dy); }); @@ -203,8 +208,9 @@ class _ScreenshotEmojiState extends State { Future _captureEmoji() async { try { - final boundary = _boundaryKey.currentContext?.findRenderObject() - as RenderRepaintBoundary?; + final boundary = + _boundaryKey.currentContext?.findRenderObject() + as RenderRepaintBoundary?; if (boundary == null) return; final image = await boundary.toImage(pixelRatio: 4); diff --git a/lib/src/views/camera/share_image_editor/layers/filters/location_filter.dart b/lib/src/views/camera/share_image_editor/layers/filters/location_filter.dart index d2d36fa..e397932 100644 --- a/lib/src/views/camera/share_image_editor/layers/filters/location_filter.dart +++ b/lib/src/views/camera/share_image_editor/layers/filters/location_filter.dart @@ -145,8 +145,9 @@ Future> getStickerIndex() async { } } try { - final response = await http - .get(Uri.parse('https://twonly.eu/api/sticker/stickers.json')); + final response = await http.get( + Uri.parse('https://twonly.eu/api/sticker/stickers.json'), + ); if (response.statusCode == 200) { await indexFile.writeAsString(response.body); final jsonList = json.decode(response.body) as List; diff --git a/lib/src/views/camera/share_image_editor/layers/link_preview/parser/mastodon.parser.dart b/lib/src/views/camera/share_image_editor/layers/link_preview/parser/mastodon.parser.dart index a2f466b..a3f8728 100644 --- a/lib/src/views/camera/share_image_editor/layers/link_preview/parser/mastodon.parser.dart +++ b/lib/src/views/camera/share_image_editor/layers/link_preview/parser/mastodon.parser.dart @@ -6,8 +6,10 @@ class MastodonParser with BaseMetaInfo { final Document? _document; @override - Vendor? get vendor => ((_document?.head?.innerHtml - .contains('"repository":"mastodon/mastodon"') ?? + Vendor? get vendor => + ((_document?.head?.innerHtml.contains( + '"repository":"mastodon/mastodon"', + ) ?? false) && (_document?.head?.innerHtml.contains('SocialMediaPosting') ?? false)) ? Vendor.mastodonSocialMediaPosting diff --git a/lib/src/views/camera/share_image_editor/layers/link_preview/parser/twitter.parser.dart b/lib/src/views/camera/share_image_editor/layers/link_preview/parser/twitter.parser.dart index 39cba4a..3e8ee04 100644 --- a/lib/src/views/camera/share_image_editor/layers/link_preview/parser/twitter.parser.dart +++ b/lib/src/views/camera/share_image_editor/layers/link_preview/parser/twitter.parser.dart @@ -30,6 +30,6 @@ class TwitterParser with BaseMetaInfo { @override Vendor? get vendor => _url.startsWith('https://x.com/') && _url.contains('/status/') - ? Vendor.twitterPosting - : null; + ? Vendor.twitterPosting + : null; } diff --git a/lib/src/views/camera/share_image_editor/layers/text.layer.dart b/lib/src/views/camera/share_image_editor/layers/text.layer.dart index 7294b00..b8ac662 100755 --- a/lib/src/views/camera/share_image_editor/layers/text.layer.dart +++ b/lib/src/views/camera/share_image_editor/layers/text.layer.dart @@ -43,7 +43,8 @@ class _TextViewState extends State { if (parentBox != null) { final parentTopGlobal = parentBox.localToGlobal(Offset.zero).dy; final screenHeight = mq.size.height; - localBottom = (screenHeight - globalDesiredBottom) - + localBottom = + (screenHeight - globalDesiredBottom) - parentTopGlobal - (parentBox.size.height); } @@ -87,7 +88,8 @@ class _TextViewState extends State { Widget build(BuildContext context) { if (widget.layerData.isDeleted) return Container(); - final bottom = MediaQuery.of(context).viewInsets.bottom + + final bottom = + MediaQuery.of(context).viewInsets.bottom + MediaQuery.of(context).viewPadding.bottom; // On Android it is possible to close the keyboard without `onEditingComplete` is triggered. @@ -181,7 +183,8 @@ class _TextViewState extends State { } setState(() {}); }, - onTap: (context + onTap: + (context .watch() .someTextViewIsAlreadyEditing) ? null diff --git a/lib/src/views/chats/chat_list_components/last_message_time.dart b/lib/src/views/chats/chat_list_components/last_message_time.dart index 0615cb3..6f01aea 100644 --- a/lib/src/views/chats/chat_list_components/last_message_time.dart +++ b/lib/src/views/chats/chat_list_components/last_message_time.dart @@ -24,18 +24,22 @@ class _LastMessageTimeState extends State { void initState() { super.initState(); // Change the color every 200 milliseconds - updateTime = - Timer.periodic(const Duration(milliseconds: 500), (timer) async { + updateTime = Timer.periodic(const Duration(milliseconds: 500), ( + timer, + ) async { if (widget.message != null) { - final lastAction = await twonlyDB.messagesDao - .getLastMessageAction(widget.message!.messageId); + final lastAction = await twonlyDB.messagesDao.getLastMessageAction( + widget.message!.messageId, + ); lastMessageInSeconds = clock .now() .difference(lastAction?.actionAt ?? widget.message!.createdAt) .inSeconds; } else if (widget.dateTime != null) { - lastMessageInSeconds = - clock.now().difference(widget.dateTime!).inSeconds; + lastMessageInSeconds = clock + .now() + .difference(widget.dateTime!) + .inSeconds; } if (mounted) { setState(() { diff --git a/lib/src/views/chats/chat_messages_components/blink.component.dart b/lib/src/views/chats/chat_messages_components/blink.component.dart index b114b4b..e9e261d 100644 --- a/lib/src/views/chats/chat_messages_components/blink.component.dart +++ b/lib/src/views/chats/chat_messages_components/blink.component.dart @@ -48,7 +48,8 @@ class _BlinkWidgetState extends State void _onTick(Duration elapsed) { var visible = true; if (elapsed.inMilliseconds < widget.blinkDuration.inMilliseconds) { - visible = elapsed.inMilliseconds % (widget.interval.inMilliseconds * 2) < + visible = + elapsed.inMilliseconds % (widget.interval.inMilliseconds * 2) < widget.interval.inMilliseconds; } else { _ticker.stop(); diff --git a/lib/src/views/chats/chat_messages_components/bottom_sheets/all_reactions.bottom_sheet.dart b/lib/src/views/chats/chat_messages_components/bottom_sheets/all_reactions.bottom_sheet.dart index 3c45815..c2cce58 100644 --- a/lib/src/views/chats/chat_messages_components/bottom_sheets/all_reactions.bottom_sheet.dart +++ b/lib/src/views/chats/chat_messages_components/bottom_sheets/all_reactions.bottom_sheet.dart @@ -37,8 +37,9 @@ class _AllReactionsViewState extends State { } Future initAsync() async { - final stream = twonlyDB.reactionsDao - .watchReactionWithContacts(widget.message.messageId); + final stream = twonlyDB.reactionsDao.watchReactionWithContacts( + widget.message.messageId, + ); reactionsSub = stream.listen((update) { setState(() { @@ -139,8 +140,9 @@ class _AllReactionsViewState extends State { ], ), ), - if (EmojiAnimation.animatedIcons - .containsKey(entry.$1.emoji)) + if (EmojiAnimation.animatedIcons.containsKey( + entry.$1.emoji, + )) SizedBox( height: 25, child: EmojiAnimation(emoji: entry.$1.emoji), diff --git a/lib/src/views/chats/chat_messages_components/chat_group_action.dart b/lib/src/views/chats/chat_messages_components/chat_group_action.dart index e711ded..c5d6427 100644 --- a/lib/src/views/chats/chat_messages_components/chat_group_action.dart +++ b/lib/src/views/chats/chat_messages_components/chat_group_action.dart @@ -30,13 +30,15 @@ class _ChatGroupActionState extends State { Future initAsync() async { if (widget.action.contactId != null) { - contact = - await twonlyDB.contactsDao.getContactById(widget.action.contactId!); + contact = await twonlyDB.contactsDao.getContactById( + widget.action.contactId!, + ); } if (widget.action.affectedContactId != null) { - affectedContact = await twonlyDB.contactsDao - .getContactById(widget.action.affectedContactId!); + affectedContact = await twonlyDB.contactsDao.getContactById( + widget.action.affectedContactId!, + ); } if (mounted) setState(() {}); @@ -50,8 +52,9 @@ class _ChatGroupActionState extends State { final affected = (affectedContact == null) ? context.lang.groupActionYou : getContactDisplayName(affectedContact!); - final affectedR = - (affectedContact == null) ? context.lang.groupActionYour : affected; + final affectedR = (affectedContact == null) + ? context.lang.groupActionYour + : affected; final maker = (contact == null) ? '' : getContactDisplayName(contact!); switch (widget.action.type) { @@ -67,8 +70,10 @@ class _ChatGroupActionState extends State { case GroupActionType.updatedGroupName: text = (contact == null) ? context.lang.youChangedGroupName(widget.action.newGroupName!) - : context.lang - .makerChangedGroupName(maker, widget.action.newGroupName!); + : context.lang.makerChangedGroupName( + maker, + widget.action.newGroupName!, + ); icon = FontAwesomeIcons.pencil; case GroupActionType.createdGroup: icon = FontAwesomeIcons.penToSquare; diff --git a/lib/src/views/chats/chat_messages_components/chat_reaction_row.dart b/lib/src/views/chats/chat_messages_components/chat_reaction_row.dart index 32c25b9..983aff8 100644 --- a/lib/src/views/chats/chat_messages_components/chat_reaction_row.dart +++ b/lib/src/views/chats/chat_messages_components/chat_reaction_row.dart @@ -57,8 +57,10 @@ class ReactionRow extends StatelessWidget { ); } if (emojis.containsKey(reaction.emoji)) { - emojis[reaction.emoji] = - (emojis[reaction.emoji]!.$1, emojis[reaction.emoji]!.$2 + 1); + emojis[reaction.emoji] = ( + emojis[reaction.emoji]!.$1, + emojis[reaction.emoji]!.$2 + 1, + ); } else { emojis[reaction.emoji] = (child, 1); } @@ -80,7 +82,7 @@ class ReactionRow extends StatelessWidget { child: const FaIcon(FontAwesomeIcons.ellipsis), ), ), - 1 + 1, ), ); } @@ -117,8 +119,9 @@ class ReactionRow extends StatelessWidget { textAlign: TextAlign.center, style: TextStyle( fontSize: 13, - color: - isDarkMode(context) ? Colors.white : Colors.black, + color: isDarkMode(context) + ? Colors.white + : Colors.black, decoration: TextDecoration.none, fontWeight: FontWeight.normal, ), diff --git a/lib/src/views/chats/chat_messages_components/entries/chat_audio_entry.dart b/lib/src/views/chats/chat_messages_components/entries/chat_audio_entry.dart index 759ac7b..7760355 100644 --- a/lib/src/views/chats/chat_messages_components/entries/chat_audio_entry.dart +++ b/lib/src/views/chats/chat_messages_components/entries/chat_audio_entry.dart @@ -135,8 +135,9 @@ class _InChatAudioPlayerState extends State { _playerController.onPlayerStateChanged.listen((a) async { if (a == PlayerState.initialized) { - _displayDuration = - await _playerController.getDuration(DurationType.max); + _displayDuration = await _playerController.getDuration( + DurationType.max, + ); _maxDuration = _displayDuration; setState(() {}); } diff --git a/lib/src/views/chats/chat_messages_components/entries/chat_contacts.entry.dart b/lib/src/views/chats/chat_messages_components/entries/chat_contacts.entry.dart index debc8f0..fa34699 100644 --- a/lib/src/views/chats/chat_messages_components/entries/chat_contacts.entry.dart +++ b/lib/src/views/chats/chat_messages_components/entries/chat_contacts.entry.dart @@ -115,14 +115,16 @@ class _ContactRowState extends State<_ContactRow> { }); try { - final userdata = - await apiService.getUserById(widget.contact.userId.toInt()); + final userdata = await apiService.getUserById( + widget.contact.userId.toInt(), + ); if (userdata == null) return; var verified = false; if (userdata.publicIdentityKey == widget.contact.publicIdentityKey) { - final sender = - await twonlyDB.contactsDao.getContactById(widget.message.senderId!); + final sender = await twonlyDB.contactsDao.getContactById( + widget.message.senderId!, + ); // in case the sender is verified and the public keys are the same, this trust can be transferred verified = sender != null && sender.verified; } @@ -158,7 +160,8 @@ class _ContactRowState extends State<_ContactRow> { stream: twonlyDB.contactsDao.watchContact(widget.contact.userId.toInt()), builder: (context, snapshot) { final contactInDb = snapshot.data; - final isAdded = contactInDb != null || + final isAdded = + contactInDb != null || widget.contact.userId.toInt() == gUser.userId; return GestureDetector( @@ -191,8 +194,9 @@ class _ContactRowState extends State<_ContactRow> { height: 16, child: CircularProgressIndicator( strokeWidth: 2, - valueColor: - AlwaysStoppedAnimation(Colors.white), + valueColor: AlwaysStoppedAnimation( + Colors.white, + ), ), ) else diff --git a/lib/src/views/chats/chat_messages_components/entries/chat_flame_restored.entry.dart b/lib/src/views/chats/chat_messages_components/entries/chat_flame_restored.entry.dart index 27234c8..6191b4e 100644 --- a/lib/src/views/chats/chat_messages_components/entries/chat_flame_restored.entry.dart +++ b/lib/src/views/chats/chat_messages_components/entries/chat_flame_restored.entry.dart @@ -40,8 +40,9 @@ class ChatFlameRestoredEntry extends StatelessWidget { borderRadius: BorderRadius.circular(12), ), child: BetterText( - text: context.lang - .chatEntryFlameRestored(data.restoredFlameCounter.toInt()), + text: context.lang.chatEntryFlameRestored( + data.restoredFlameCounter.toInt(), + ), textColor: isDarkMode(context) ? Colors.black : Colors.black, ), ); diff --git a/lib/src/views/chats/media_viewer_components/additional_message_content.dart b/lib/src/views/chats/media_viewer_components/additional_message_content.dart index 5371b7f..fc686d7 100644 --- a/lib/src/views/chats/media_viewer_components/additional_message_content.dart +++ b/lib/src/views/chats/media_viewer_components/additional_message_content.dart @@ -14,8 +14,9 @@ class AdditionalMessageContent extends StatelessWidget { Widget build(BuildContext context) { if (message.additionalMessageData == null) return Container(); try { - final data = - AdditionalMessageData.fromBuffer(message.additionalMessageData!); + final data = AdditionalMessageData.fromBuffer( + message.additionalMessageData!, + ); switch (data.type) { case AdditionalMessageData_Type.LINK: diff --git a/lib/src/views/chats/media_viewer_components/reaction_buttons.component.dart b/lib/src/views/chats/media_viewer_components/reaction_buttons.component.dart index d63dfd8..342fbf1 100644 --- a/lib/src/views/chats/media_viewer_components/reaction_buttons.component.dart +++ b/lib/src/views/chats/media_viewer_components/reaction_buttons.component.dart @@ -39,8 +39,9 @@ class _ReactionButtonsState extends State { int selectedShortReaction = -1; final GlobalKey _keyEmojiPicker = GlobalKey(); - List selectedEmojis = - EmojiAnimation.animatedIcons.keys.toList().sublist(0, 6); + List selectedEmojis = EmojiAnimation.animatedIcons.keys + .toList() + .sublist(0, 6); @override void initState() { @@ -58,15 +59,16 @@ class _ReactionButtonsState extends State { @override Widget build(BuildContext context) { final firstRowEmojis = selectedEmojis.take(6).toList(); - final secondRowEmojis = - selectedEmojis.length > 6 ? selectedEmojis.skip(6).toList() : []; + final secondRowEmojis = selectedEmojis.length > 6 + ? selectedEmojis.skip(6).toList() + : []; return AnimatedPositioned( duration: const Duration(milliseconds: 200), // Animation duration bottom: widget.show ? (widget.textInputFocused - ? 50 - : widget.mediaViewerDistanceFromBottom) + ? 50 + : widget.mediaViewerDistanceFromBottom) : widget.mediaViewerDistanceFromBottom - 20, left: widget.show ? 0 : MediaQuery.sizeOf(context).width / 2, right: widget.show ? 0 : MediaQuery.sizeOf(context).width / 2, @@ -76,8 +78,9 @@ class _ReactionButtonsState extends State { duration: const Duration(milliseconds: 150), child: Container( color: widget.show ? Colors.black.withAlpha(0) : Colors.transparent, - padding: - widget.show ? const EdgeInsets.symmetric(vertical: 32) : null, + padding: widget.show + ? const EdgeInsets.symmetric(vertical: 32) + : null, child: Column( children: [ if (secondRowEmojis.isNotEmpty) @@ -115,14 +118,16 @@ class _ReactionButtonsState extends State { GestureDetector( key: _keyEmojiPicker, onTap: () async { - // ignore: inference_failure_on_function_invocation - final layer = await showModalBottomSheet( - context: context, - backgroundColor: context.color.surface, - builder: (context) { - return const EmojiPickerBottom(); - }, - ) as EmojiLayerData?; + final layer = + // ignore: inference_failure_on_function_invocation + await showModalBottomSheet( + context: context, + backgroundColor: context.color.surface, + builder: (context) { + return const EmojiPickerBottom(); + }, + ) + as EmojiLayerData?; if (layer == null) return; await sendReaction( widget.groupId, diff --git a/lib/src/views/chats/message_info.view.dart b/lib/src/views/chats/message_info.view.dart index 7670c48..612198e 100644 --- a/lib/src/views/chats/message_info.view.dart +++ b/lib/src/views/chats/message_info.view.dart @@ -53,24 +53,27 @@ class _MessageInfoViewState extends State { } Future initAsync() async { - final streamActions = - twonlyDB.messagesDao.watchMessageActions(widget.message.messageId); + final streamActions = twonlyDB.messagesDao.watchMessageActions( + widget.message.messageId, + ); actionsStream = streamActions.listen((update) { setState(() { messageActions = update; }); }); - final streamGroup = - twonlyDB.messagesDao.watchMembersByGroupId(widget.message.groupId); + final streamGroup = twonlyDB.messagesDao.watchMembersByGroupId( + widget.message.groupId, + ); groupMemberStream = streamGroup.listen((update) { setState(() { groupMembers = update; }); }); - final streamHistory = - twonlyDB.messagesDao.watchMessageHistory(widget.message.messageId); + final streamHistory = twonlyDB.messagesDao.watchMessageHistory( + widget.message.messageId, + ); historyStream = streamHistory.listen((update) { setState(() { messageHistory = update; diff --git a/lib/src/views/components/animate_icon.dart b/lib/src/views/components/animate_icon.dart index 2be2dc0..055f00c 100644 --- a/lib/src/views/components/animate_icon.dart +++ b/lib/src/views/components/animate_icon.dart @@ -5,8 +5,8 @@ import 'package:lottie/lottie.dart'; // from https://github.com/eitanliu/emoji_regex/tree/master RegExp emojiRegex() => RegExp( - r'[#*0-9]\uFE0F?\u20E3|[\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23ED-\u23EF\u23F1\u23F2\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB\u25FC\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692\u2694-\u2697\u2699\u269B\u269C\u26A0\u26A7\u26AA\u26B0\u26B1\u26BD\u26BE\u26C4\u26C8\u26CF\u26D1\u26D3\u26E9\u26F0-\u26F5\u26F7\u26F8\u26FA\u2702\u2708\u2709\u270F\u2712\u2714\u2716\u271D\u2721\u2733\u2734\u2744\u2747\u2757\u2763\u27A1\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B55\u3030\u303D\u3297\u3299]\uFE0F?|[\u261D\u270C\u270D](?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?|[\u270A\u270B](?:\uD83C[\uDFFB-\uDFFF])?|[\u23E9-\u23EC\u23F0\u23F3\u25FD\u2693\u26A1\u26AB\u26C5\u26CE\u26D4\u26EA\u26FD\u2705\u2728\u274C\u274E\u2753-\u2755\u2795-\u2797\u27B0\u27BF\u2B50]|\u26F9(?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|\u2764\uFE0F?(?:\u200D(?:\uD83D\uDD25|\uD83E\uDE79))?|\uD83C(?:[\uDC04\uDD70\uDD71\uDD7E\uDD7F\uDE02\uDE37\uDF21\uDF24-\uDF2C\uDF36\uDF7D\uDF96\uDF97\uDF99-\uDF9B\uDF9E\uDF9F\uDFCD\uDFCE\uDFD4-\uDFDF\uDFF5\uDFF7]\uFE0F?|[\uDF85\uDFC2\uDFC7](?:\uD83C[\uDFFB-\uDFFF])?|[\uDFC3\uDFC4\uDFCA](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDFCB\uDFCC](?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDCCF\uDD8E\uDD91-\uDD9A\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF84\uDF86-\uDF93\uDFA0-\uDFC1\uDFC5\uDFC6\uDFC8\uDFC9\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF8-\uDFFF]|\uDDE6\uD83C[\uDDE8-\uDDEC\uDDEE\uDDF1\uDDF2\uDDF4\uDDF6-\uDDFA\uDDFC\uDDFD\uDDFF]|\uDDE7\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEF\uDDF1-\uDDF4\uDDF6-\uDDF9\uDDFB\uDDFC\uDDFE\uDDFF]|\uDDE8\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDEE\uDDF0-\uDDF5\uDDF7\uDDFA-\uDDFF]|\uDDE9\uD83C[\uDDEA\uDDEC\uDDEF\uDDF0\uDDF2\uDDF4\uDDFF]|\uDDEA\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDED\uDDF7-\uDDFA]|\uDDEB\uD83C[\uDDEE-\uDDF0\uDDF2\uDDF4\uDDF7]|\uDDEC\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEE\uDDF1-\uDDF3\uDDF5-\uDDFA\uDDFC\uDDFE]|\uDDED\uD83C[\uDDF0\uDDF2\uDDF3\uDDF7\uDDF9\uDDFA]|\uDDEE\uD83C[\uDDE8-\uDDEA\uDDF1-\uDDF4\uDDF6-\uDDF9]|\uDDEF\uD83C[\uDDEA\uDDF2\uDDF4\uDDF5]|\uDDF0\uD83C[\uDDEA\uDDEC-\uDDEE\uDDF2\uDDF3\uDDF5\uDDF7\uDDFC\uDDFE\uDDFF]|\uDDF1\uD83C[\uDDE6-\uDDE8\uDDEE\uDDF0\uDDF7-\uDDFB\uDDFE]|\uDDF2\uD83C[\uDDE6\uDDE8-\uDDED\uDDF0-\uDDFF]|\uDDF3\uD83C[\uDDE6\uDDE8\uDDEA-\uDDEC\uDDEE\uDDF1\uDDF4\uDDF5\uDDF7\uDDFA\uDDFF]|\uDDF4\uD83C\uDDF2|\uDDF5\uD83C[\uDDE6\uDDEA-\uDDED\uDDF0-\uDDF3\uDDF7-\uDDF9\uDDFC\uDDFE]|\uDDF6\uD83C\uDDE6|\uDDF7\uD83C[\uDDEA\uDDF4\uDDF8\uDDFA\uDDFC]|\uDDF8\uD83C[\uDDE6-\uDDEA\uDDEC-\uDDF4\uDDF7-\uDDF9\uDDFB\uDDFD-\uDDFF]|\uDDF9\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDED\uDDEF-\uDDF4\uDDF7\uDDF9\uDDFB\uDDFC\uDDFF]|\uDDFA\uD83C[\uDDE6\uDDEC\uDDF2\uDDF3\uDDF8\uDDFE\uDDFF]|\uDDFB\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDEE\uDDF3\uDDFA]|\uDDFC\uD83C[\uDDEB\uDDF8]|\uDDFD\uD83C\uDDF0|\uDDFE\uD83C[\uDDEA\uDDF9]|\uDDFF\uD83C[\uDDE6\uDDF2\uDDFC]|\uDFF3\uFE0F?(?:\u200D(?:\u26A7\uFE0F?|\uD83C\uDF08))?|\uDFF4(?:\u200D\u2620\uFE0F?|\uDB40\uDC67\uDB40\uDC62\uDB40(?:\uDC65\uDB40\uDC6E\uDB40\uDC67|\uDC73\uDB40\uDC63\uDB40\uDC74|\uDC77\uDB40\uDC6C\uDB40\uDC73)\uDB40\uDC7F)?)|\uD83D(?:[\uDC08\uDC26](?:\u200D\u2B1B)?|[\uDC3F\uDCFD\uDD49\uDD4A\uDD6F\uDD70\uDD73\uDD76-\uDD79\uDD87\uDD8A-\uDD8D\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA\uDECB\uDECD-\uDECF\uDEE0-\uDEE5\uDEE9\uDEF0\uDEF3]\uFE0F?|[\uDC42\uDC43\uDC46-\uDC50\uDC66\uDC67\uDC6B-\uDC6D\uDC72\uDC74-\uDC76\uDC78\uDC7C\uDC83\uDC85\uDC8F\uDC91\uDCAA\uDD7A\uDD95\uDD96\uDE4C\uDE4F\uDEC0\uDECC](?:\uD83C[\uDFFB-\uDFFF])?|[\uDC6E\uDC70\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDD74\uDD90](?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?|[\uDC00-\uDC07\uDC09-\uDC14\uDC16-\uDC25\uDC27-\uDC3A\uDC3C-\uDC3E\uDC40\uDC44\uDC45\uDC51-\uDC65\uDC6A\uDC79-\uDC7B\uDC7D-\uDC80\uDC84\uDC88-\uDC8E\uDC90\uDC92-\uDCA9\uDCAB-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDDA4\uDDFB-\uDE2D\uDE2F-\uDE34\uDE37-\uDE44\uDE48-\uDE4A\uDE80-\uDEA2\uDEA4-\uDEB3\uDEB7-\uDEBF\uDEC1-\uDEC5\uDED0-\uDED2\uDED5-\uDED7\uDEDC-\uDEDF\uDEEB\uDEEC\uDEF4-\uDEFC\uDFE0-\uDFEB\uDFF0]|\uDC15(?:\u200D\uD83E\uDDBA)?|\uDC3B(?:\u200D\u2744\uFE0F?)?|\uDC41\uFE0F?(?:\u200D\uD83D\uDDE8\uFE0F?)?|\uDC68(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDC68\uDC69]\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFC-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFD-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFD\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFE])))?))?|\uDC69(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?[\uDC68\uDC69]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?|\uDC69\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?))|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFC-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB\uDFFD-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB-\uDFFD\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB-\uDFFE])))?))?|\uDC6F(?:\u200D[\u2640\u2642]\uFE0F?)?|\uDD75(?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|\uDE2E(?:\u200D\uD83D\uDCA8)?|\uDE35(?:\u200D\uD83D\uDCAB)?|\uDE36(?:\u200D\uD83C\uDF2B\uFE0F?)?)|\uD83E(?:[\uDD0C\uDD0F\uDD18-\uDD1F\uDD30-\uDD34\uDD36\uDD77\uDDB5\uDDB6\uDDBB\uDDD2\uDDD3\uDDD5\uDEC3-\uDEC5\uDEF0\uDEF2-\uDEF8](?:\uD83C[\uDFFB-\uDFFF])?|[\uDD26\uDD35\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD-\uDDCF\uDDD4\uDDD6-\uDDDD](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDDDE\uDDDF](?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDD0D\uDD0E\uDD10-\uDD17\uDD20-\uDD25\uDD27-\uDD2F\uDD3A\uDD3F-\uDD45\uDD47-\uDD76\uDD78-\uDDB4\uDDB7\uDDBA\uDDBC-\uDDCC\uDDD0\uDDE0-\uDDFF\uDE70-\uDE7C\uDE80-\uDE88\uDE90-\uDEBD\uDEBF-\uDEC2\uDECE-\uDEDB\uDEE0-\uDEE8]|\uDD3C(?:\u200D[\u2640\u2642]\uFE0F?|\uD83C[\uDFFB-\uDFFF])?|\uDDD1(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83E\uDDD1))|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFC-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB\uDFFD-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB-\uDFFD\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB-\uDFFE]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?))?|\uDEF1(?:\uD83C(?:\uDFFB(?:\u200D\uD83E\uDEF2\uD83C[\uDFFC-\uDFFF])?|\uDFFC(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB\uDFFD-\uDFFF])?|\uDFFD(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])?|\uDFFE(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB-\uDFFD\uDFFF])?|\uDFFF(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB-\uDFFE])?))?)', - ); + r'[#*0-9]\uFE0F?\u20E3|[\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23ED-\u23EF\u23F1\u23F2\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB\u25FC\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692\u2694-\u2697\u2699\u269B\u269C\u26A0\u26A7\u26AA\u26B0\u26B1\u26BD\u26BE\u26C4\u26C8\u26CF\u26D1\u26D3\u26E9\u26F0-\u26F5\u26F7\u26F8\u26FA\u2702\u2708\u2709\u270F\u2712\u2714\u2716\u271D\u2721\u2733\u2734\u2744\u2747\u2757\u2763\u27A1\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B55\u3030\u303D\u3297\u3299]\uFE0F?|[\u261D\u270C\u270D](?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?|[\u270A\u270B](?:\uD83C[\uDFFB-\uDFFF])?|[\u23E9-\u23EC\u23F0\u23F3\u25FD\u2693\u26A1\u26AB\u26C5\u26CE\u26D4\u26EA\u26FD\u2705\u2728\u274C\u274E\u2753-\u2755\u2795-\u2797\u27B0\u27BF\u2B50]|\u26F9(?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|\u2764\uFE0F?(?:\u200D(?:\uD83D\uDD25|\uD83E\uDE79))?|\uD83C(?:[\uDC04\uDD70\uDD71\uDD7E\uDD7F\uDE02\uDE37\uDF21\uDF24-\uDF2C\uDF36\uDF7D\uDF96\uDF97\uDF99-\uDF9B\uDF9E\uDF9F\uDFCD\uDFCE\uDFD4-\uDFDF\uDFF5\uDFF7]\uFE0F?|[\uDF85\uDFC2\uDFC7](?:\uD83C[\uDFFB-\uDFFF])?|[\uDFC3\uDFC4\uDFCA](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDFCB\uDFCC](?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDCCF\uDD8E\uDD91-\uDD9A\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF84\uDF86-\uDF93\uDFA0-\uDFC1\uDFC5\uDFC6\uDFC8\uDFC9\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF8-\uDFFF]|\uDDE6\uD83C[\uDDE8-\uDDEC\uDDEE\uDDF1\uDDF2\uDDF4\uDDF6-\uDDFA\uDDFC\uDDFD\uDDFF]|\uDDE7\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEF\uDDF1-\uDDF4\uDDF6-\uDDF9\uDDFB\uDDFC\uDDFE\uDDFF]|\uDDE8\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDEE\uDDF0-\uDDF5\uDDF7\uDDFA-\uDDFF]|\uDDE9\uD83C[\uDDEA\uDDEC\uDDEF\uDDF0\uDDF2\uDDF4\uDDFF]|\uDDEA\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDED\uDDF7-\uDDFA]|\uDDEB\uD83C[\uDDEE-\uDDF0\uDDF2\uDDF4\uDDF7]|\uDDEC\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEE\uDDF1-\uDDF3\uDDF5-\uDDFA\uDDFC\uDDFE]|\uDDED\uD83C[\uDDF0\uDDF2\uDDF3\uDDF7\uDDF9\uDDFA]|\uDDEE\uD83C[\uDDE8-\uDDEA\uDDF1-\uDDF4\uDDF6-\uDDF9]|\uDDEF\uD83C[\uDDEA\uDDF2\uDDF4\uDDF5]|\uDDF0\uD83C[\uDDEA\uDDEC-\uDDEE\uDDF2\uDDF3\uDDF5\uDDF7\uDDFC\uDDFE\uDDFF]|\uDDF1\uD83C[\uDDE6-\uDDE8\uDDEE\uDDF0\uDDF7-\uDDFB\uDDFE]|\uDDF2\uD83C[\uDDE6\uDDE8-\uDDED\uDDF0-\uDDFF]|\uDDF3\uD83C[\uDDE6\uDDE8\uDDEA-\uDDEC\uDDEE\uDDF1\uDDF4\uDDF5\uDDF7\uDDFA\uDDFF]|\uDDF4\uD83C\uDDF2|\uDDF5\uD83C[\uDDE6\uDDEA-\uDDED\uDDF0-\uDDF3\uDDF7-\uDDF9\uDDFC\uDDFE]|\uDDF6\uD83C\uDDE6|\uDDF7\uD83C[\uDDEA\uDDF4\uDDF8\uDDFA\uDDFC]|\uDDF8\uD83C[\uDDE6-\uDDEA\uDDEC-\uDDF4\uDDF7-\uDDF9\uDDFB\uDDFD-\uDDFF]|\uDDF9\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDED\uDDEF-\uDDF4\uDDF7\uDDF9\uDDFB\uDDFC\uDDFF]|\uDDFA\uD83C[\uDDE6\uDDEC\uDDF2\uDDF3\uDDF8\uDDFE\uDDFF]|\uDDFB\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDEE\uDDF3\uDDFA]|\uDDFC\uD83C[\uDDEB\uDDF8]|\uDDFD\uD83C\uDDF0|\uDDFE\uD83C[\uDDEA\uDDF9]|\uDDFF\uD83C[\uDDE6\uDDF2\uDDFC]|\uDFF3\uFE0F?(?:\u200D(?:\u26A7\uFE0F?|\uD83C\uDF08))?|\uDFF4(?:\u200D\u2620\uFE0F?|\uDB40\uDC67\uDB40\uDC62\uDB40(?:\uDC65\uDB40\uDC6E\uDB40\uDC67|\uDC73\uDB40\uDC63\uDB40\uDC74|\uDC77\uDB40\uDC6C\uDB40\uDC73)\uDB40\uDC7F)?)|\uD83D(?:[\uDC08\uDC26](?:\u200D\u2B1B)?|[\uDC3F\uDCFD\uDD49\uDD4A\uDD6F\uDD70\uDD73\uDD76-\uDD79\uDD87\uDD8A-\uDD8D\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA\uDECB\uDECD-\uDECF\uDEE0-\uDEE5\uDEE9\uDEF0\uDEF3]\uFE0F?|[\uDC42\uDC43\uDC46-\uDC50\uDC66\uDC67\uDC6B-\uDC6D\uDC72\uDC74-\uDC76\uDC78\uDC7C\uDC83\uDC85\uDC8F\uDC91\uDCAA\uDD7A\uDD95\uDD96\uDE4C\uDE4F\uDEC0\uDECC](?:\uD83C[\uDFFB-\uDFFF])?|[\uDC6E\uDC70\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDD74\uDD90](?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?|[\uDC00-\uDC07\uDC09-\uDC14\uDC16-\uDC25\uDC27-\uDC3A\uDC3C-\uDC3E\uDC40\uDC44\uDC45\uDC51-\uDC65\uDC6A\uDC79-\uDC7B\uDC7D-\uDC80\uDC84\uDC88-\uDC8E\uDC90\uDC92-\uDCA9\uDCAB-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDDA4\uDDFB-\uDE2D\uDE2F-\uDE34\uDE37-\uDE44\uDE48-\uDE4A\uDE80-\uDEA2\uDEA4-\uDEB3\uDEB7-\uDEBF\uDEC1-\uDEC5\uDED0-\uDED2\uDED5-\uDED7\uDEDC-\uDEDF\uDEEB\uDEEC\uDEF4-\uDEFC\uDFE0-\uDFEB\uDFF0]|\uDC15(?:\u200D\uD83E\uDDBA)?|\uDC3B(?:\u200D\u2744\uFE0F?)?|\uDC41\uFE0F?(?:\u200D\uD83D\uDDE8\uFE0F?)?|\uDC68(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDC68\uDC69]\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFC-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFD-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFD\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFE])))?))?|\uDC69(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?[\uDC68\uDC69]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?|\uDC69\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?))|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFC-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB\uDFFD-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB-\uDFFD\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB-\uDFFE])))?))?|\uDC6F(?:\u200D[\u2640\u2642]\uFE0F?)?|\uDD75(?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|\uDE2E(?:\u200D\uD83D\uDCA8)?|\uDE35(?:\u200D\uD83D\uDCAB)?|\uDE36(?:\u200D\uD83C\uDF2B\uFE0F?)?)|\uD83E(?:[\uDD0C\uDD0F\uDD18-\uDD1F\uDD30-\uDD34\uDD36\uDD77\uDDB5\uDDB6\uDDBB\uDDD2\uDDD3\uDDD5\uDEC3-\uDEC5\uDEF0\uDEF2-\uDEF8](?:\uD83C[\uDFFB-\uDFFF])?|[\uDD26\uDD35\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD-\uDDCF\uDDD4\uDDD6-\uDDDD](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDDDE\uDDDF](?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDD0D\uDD0E\uDD10-\uDD17\uDD20-\uDD25\uDD27-\uDD2F\uDD3A\uDD3F-\uDD45\uDD47-\uDD76\uDD78-\uDDB4\uDDB7\uDDBA\uDDBC-\uDDCC\uDDD0\uDDE0-\uDDFF\uDE70-\uDE7C\uDE80-\uDE88\uDE90-\uDEBD\uDEBF-\uDEC2\uDECE-\uDEDB\uDEE0-\uDEE8]|\uDD3C(?:\u200D[\u2640\u2642]\uFE0F?|\uD83C[\uDFFB-\uDFFF])?|\uDDD1(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83E\uDDD1))|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFC-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB\uDFFD-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB-\uDFFD\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB-\uDFFE]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?))?|\uDEF1(?:\uD83C(?:\uDFFB(?:\u200D\uD83E\uDEF2\uD83C[\uDFFC-\uDFFF])?|\uDFFC(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB\uDFFD-\uDFFF])?|\uDFFD(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])?|\uDFFE(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB-\uDFFD\uDFFF])?|\uDFFF(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB-\uDFFE])?))?)', +); bool isOneEmoji(String character) { final matches = emojiRegex().allMatches(character); diff --git a/lib/src/views/components/app_outdated.dart b/lib/src/views/components/app_outdated.dart index 952cf98..ac50807 100644 --- a/lib/src/views/components/app_outdated.dart +++ b/lib/src/views/components/app_outdated.dart @@ -62,10 +62,10 @@ class _AppOutdatedState extends State { context.lang.newDeviceRegistered, textAlign: TextAlign.center, softWrap: true, - style: Theme.of(context) - .textTheme - .bodyMedium - ?.copyWith(color: Colors.white, fontSize: 16), + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Colors.white, + fontSize: 16, + ), ), ], ), @@ -92,10 +92,10 @@ class _AppOutdatedState extends State { context.lang.appOutdated, textAlign: TextAlign.center, softWrap: true, - style: Theme.of(context) - .textTheme - .bodyMedium - ?.copyWith(color: Colors.white, fontSize: 16), + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Colors.white, + fontSize: 16, + ), ), if (Platform.isAndroid) const SizedBox(height: 5), if (Platform.isAndroid) @@ -114,10 +114,10 @@ class _AppOutdatedState extends State { ), child: Text( context.lang.appOutdatedBtn, - style: Theme.of(context) - .textTheme - .bodyMedium - ?.copyWith(color: Colors.white, fontSize: 16), + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Colors.white, + fontSize: 16, + ), ), ), ], diff --git a/lib/src/views/components/better_text.dart b/lib/src/views/components/better_text.dart index 7b4608f..b2f3709 100644 --- a/lib/src/views/components/better_text.dart +++ b/lib/src/views/components/better_text.dart @@ -37,8 +37,9 @@ class BetterText extends StatelessWidget { ), recognizer: TapGestureRecognizer() ..onTap = () async { - final lUrl = - Uri.parse(url!.startsWith('http') ? url : 'http://$url'); + final lUrl = Uri.parse( + url!.startsWith('http') ? url : 'http://$url', + ); try { await launchUrl(lUrl); } catch (e) { diff --git a/lib/src/views/components/emoji_picker.bottom.dart b/lib/src/views/components/emoji_picker.bottom.dart index 2dff473..ba2e660 100755 --- a/lib/src/views/components/emoji_picker.bottom.dart +++ b/lib/src/views/components/emoji_picker.bottom.dart @@ -53,8 +53,9 @@ class EmojiPickerBottom extends StatelessWidget { config: Config( height: 400, locale: Localizations.localeOf(context), - emojiTextStyle: - TextStyle(fontSize: 24 * (Platform.isIOS ? 1.2 : 1)), + emojiTextStyle: TextStyle( + fontSize: 24 * (Platform.isIOS ? 1.2 : 1), + ), emojiViewConfig: EmojiViewConfig( backgroundColor: context.color.surfaceContainer, recentsLimit: 40, diff --git a/lib/src/views/components/fingerprint_text.dart b/lib/src/views/components/fingerprint_text.dart index e7cf081..e470e46 100644 --- a/lib/src/views/components/fingerprint_text.dart +++ b/lib/src/views/components/fingerprint_text.dart @@ -9,8 +9,10 @@ class FingerprintText extends StatelessWidget { var blockCount = 0; for (var i = 0; i < input.length; i += 4) { - final block = - input.substring(i, i + 4 > input.length ? input.length : i + 4); + final block = input.substring( + i, + i + 4 > input.length ? input.length : i + 4, + ); formattedString.write(block); blockCount++; diff --git a/lib/src/views/components/media_view_sizing.dart b/lib/src/views/components/media_view_sizing.dart index 01bf89a..f40491d 100644 --- a/lib/src/views/components/media_view_sizing.dart +++ b/lib/src/views/components/media_view_sizing.dart @@ -30,7 +30,8 @@ class _MediaViewSizingState extends State { // Calculate the available width and height final availableWidth = screenSize.width; - availableHeight = screenSize.height - + availableHeight = + screenSize.height - safeAreaPadding.top - safeAreaPadding.bottom - (widget.additionalPadding ?? 0); diff --git a/lib/src/views/components/select_chat_deletion_time.comp.dart b/lib/src/views/components/select_chat_deletion_time.comp.dart index b756ebb..58602c6 100644 --- a/lib/src/views/components/select_chat_deletion_time.comp.dart +++ b/lib/src/views/components/select_chat_deletion_time.comp.dart @@ -65,8 +65,9 @@ class _SelectChatDeletionTimeListTitleState height: 216, padding: const EdgeInsets.only(top: 6), // The Bottom margin is provided to align the popup above the system navigation bar. - margin: - EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), + margin: EdgeInsets.only( + bottom: MediaQuery.of(context).viewInsets.bottom, + ), // Provide a background color for the popup. color: CupertinoColors.systemBackground.resolveFrom(context), // Use a SafeArea widget to avoid system overlaps. @@ -128,29 +129,28 @@ class _SelectChatDeletionTimeListTitleState onTap: widget.disabled ? null : () => _showDialog( - CupertinoPicker( - magnification: 1.22, - squeeze: 1.2, - useMagnifier: true, - itemExtent: 32, - // This sets the initial item. - scrollController: FixedExtentScrollController( - initialItem: _selectedDeletionTime, - ), - // This is called when selected item is changed. - onSelectedItemChanged: (selectedItem) { - setState(() { - _selectedDeletionTime = selectedItem; - }); - }, - children: - List.generate(_getOptions().length, (index) { - return Center( - child: Text(_getOptions()[index].$2), - ); - }), + CupertinoPicker( + magnification: 1.22, + squeeze: 1.2, + useMagnifier: true, + itemExtent: 32, + // This sets the initial item. + scrollController: FixedExtentScrollController( + initialItem: _selectedDeletionTime, ), + // This is called when selected item is changed. + onSelectedItemChanged: (selectedItem) { + setState(() { + _selectedDeletionTime = selectedItem; + }); + }, + children: List.generate(_getOptions().length, (index) { + return Center( + child: Text(_getOptions()[index].$2), + ); + }), ), + ), ); } } diff --git a/lib/src/views/components/svg_icon.dart b/lib/src/views/components/svg_icon.dart index 8691f59..fd12414 100644 --- a/lib/src/views/components/svg_icon.dart +++ b/lib/src/views/components/svg_icon.dart @@ -23,8 +23,9 @@ class SvgIcon extends StatelessWidget { assetPath, width: size, height: size, - colorFilter: - color != null ? ColorFilter.mode(color!, BlendMode.srcIn) : null, + colorFilter: color != null + ? ColorFilter.mode(color!, BlendMode.srcIn) + : null, ); } } diff --git a/lib/src/views/groups/group.view.dart b/lib/src/views/groups/group.view.dart index 88220c4..abee84b 100644 --- a/lib/src/views/groups/group.view.dart +++ b/lib/src/views/groups/group.view.dart @@ -85,14 +85,16 @@ class _GroupViewState extends State { } Future _addNewGroupMembers() async { - final selectedUserIds = await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => GroupCreateSelectMembersView( - groupId: _group?.groupId, - ), - ), - ) as List?; + final selectedUserIds = + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => GroupCreateSelectMembersView( + groupId: _group?.groupId, + ), + ), + ) + as List?; if (selectedUserIds == null) return; if (!await addNewGroupMembers(_group!, selectedUserIds)) { if (mounted) { @@ -134,8 +136,9 @@ class _GroupViewState extends State { if (_group!.isGroupAdmin) { // Current user is a admin, to the state can be updated by the user him self. - final keyPair = - IdentityKeyPair.fromSerialized(_group!.myGroupPrivateKey!); + final keyPair = IdentityKeyPair.fromSerialized( + _group!.myGroupPrivateKey!, + ); success = await removeMemberFromGroup( _group!, keyPair.getPublicKey().serialize(), diff --git a/lib/src/views/groups/group_create_select_group_name.view.dart b/lib/src/views/groups/group_create_select_group_name.view.dart index 4bcc094..2339726 100644 --- a/lib/src/views/groups/group_create_select_group_name.view.dart +++ b/lib/src/views/groups/group_create_select_group_name.view.dart @@ -32,8 +32,10 @@ class _GroupCreateSelectGroupNameViewState _isLoading = true; }); - final wasSuccess = - await createNewGroup(textFieldGroupName.text, widget.selectedUsers); + final wasSuccess = await createNewGroup( + textFieldGroupName.text, + widget.selectedUsers, + ); if (wasSuccess) { // POP if (mounted) { @@ -72,8 +74,12 @@ class _GroupCreateSelectGroupNameViewState ), body: SafeArea( child: Padding( - padding: - const EdgeInsets.only(bottom: 40, left: 10, top: 20, right: 10), + padding: const EdgeInsets.only( + bottom: 40, + left: 10, + top: 20, + right: 10, + ), child: Column( children: [ Padding( diff --git a/lib/src/views/groups/group_create_select_members.view.dart b/lib/src/views/groups/group_create_select_members.view.dart index b4d1912..f2092ef 100644 --- a/lib/src/views/groups/group_create_select_members.view.dart +++ b/lib/src/views/groups/group_create_select_members.view.dart @@ -72,9 +72,9 @@ class _StartNewChatView extends State { } final usersFiltered = allContacts .where( - (user) => getContactDisplayName(user) - .toLowerCase() - .contains(searchUserName.value.text.toLowerCase()), + (user) => getContactDisplayName( + user, + ).toLowerCase().contains(searchUserName.value.text.toLowerCase()), ) .toList(); setState(() { @@ -142,8 +142,12 @@ class _StartNewChatView extends State { ), body: SafeArea( child: Padding( - padding: - const EdgeInsets.only(bottom: 40, left: 10, top: 20, right: 10), + padding: const EdgeInsets.only( + bottom: 40, + left: 10, + top: 20, + right: 10, + ), child: Column( children: [ Padding( @@ -182,8 +186,9 @@ class _StartNewChatView extends State { spacing: 8, children: selected.map((w) { return _Chip( - contact: allContacts - .firstWhere((t) => t.userId == w), + contact: allContacts.firstWhere( + (t) => t.userId == w, + ), onTap: toggleSelectedUser, ); }).toList(), @@ -220,7 +225,8 @@ class _StartNewChatView extends State { fontSize: 13, ), trailing: Checkbox( - value: selectedUsers.contains(user.userId) | + value: + selectedUsers.contains(user.userId) | alreadyInGroup.contains(user.userId), side: WidgetStateBorderSide.resolveWith( (states) { diff --git a/lib/src/views/onboarding/onboarding.view.dart b/lib/src/views/onboarding/onboarding.view.dart index c5998b1..e6990c1 100644 --- a/lib/src/views/onboarding/onboarding.view.dart +++ b/lib/src/views/onboarding/onboarding.view.dart @@ -76,8 +76,11 @@ class OnboardingView extends StatelessWidget { style: const TextStyle(fontSize: 18), ), Padding( - padding: - const EdgeInsets.only(left: 50, right: 50, top: 20), + padding: const EdgeInsets.only( + left: 50, + right: 50, + top: 20, + ), child: FilledButton( onPressed: callbackOnSuccess, child: Text(context.lang.registerSubmitButton), @@ -116,8 +119,9 @@ class OnboardingView extends StatelessWidget { activeColor: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.secondary, spacing: const EdgeInsets.symmetric(horizontal: 3), - activeShape: - RoundedRectangleBorder(borderRadius: BorderRadius.circular(25)), + activeShape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(25), + ), ), ), ), diff --git a/lib/src/views/onboarding/recover.view.dart b/lib/src/views/onboarding/recover.view.dart index 89c2c89..0d47932 100644 --- a/lib/src/views/onboarding/recover.view.dart +++ b/lib/src/views/onboarding/recover.view.dart @@ -78,8 +78,10 @@ class _BackupRecoveryViewState extends State { ], ), body: Padding( - padding: - const EdgeInsetsGeometry.symmetric(vertical: 40, horizontal: 40), + padding: const EdgeInsetsGeometry.symmetric( + vertical: 40, + horizontal: 40, + ), child: ListView( children: [ Text( @@ -137,8 +139,9 @@ class _BackupRecoveryViewState extends State { Center( child: OutlinedButton( onPressed: () async { - backupServer = - await context.push(Routes.settingsBackupServer); + backupServer = await context.push( + Routes.settingsBackupServer, + ); setState(() {}); }, child: Text(context.lang.backupExpertSettings), diff --git a/lib/src/views/onboarding/register.view.dart b/lib/src/views/onboarding/register.view.dart index 7da4a58..86f6dcd 100644 --- a/lib/src/views/onboarding/register.view.dart +++ b/lib/src/views/onboarding/register.view.dart @@ -137,8 +137,10 @@ class _RegisterViewState extends State { subscriptionPlan: 'Preview', )..appVersion = 62; - await const FlutterSecureStorage() - .write(key: SecureStorageKeys.userData, value: jsonEncode(userData)); + await const FlutterSecureStorage().write( + key: SecureStorageKeys.userData, + value: jsonEncode(userData), + ); gUser = userData; @@ -258,8 +260,9 @@ class _RegisterViewState extends State { Text( context.lang.registerProofOfWorkFailed, style: TextStyle( - color: - _showProofOfWorkError ? Colors.red : Colors.transparent, + color: _showProofOfWorkError + ? Colors.red + : Colors.transparent, fontSize: 12, ), textAlign: TextAlign.center, diff --git a/lib/src/views/settings/account.view.dart b/lib/src/views/settings/account.view.dart index 25e14c8..969db76 100644 --- a/lib/src/views/settings/account.view.dart +++ b/lib/src/views/settings/account.view.dart @@ -93,26 +93,28 @@ class _AccountViewState extends State { subtitle: (formattedBallance == null) ? Text(context.lang.settingsAccountDeleteAccountNoInternet) : hasRemainingBallance - ? Text( - context.lang.settingsAccountDeleteAccountWithBallance( - formattedBallance!, - ), - ) - : Text(context.lang.settingsAccountDeleteAccountNoBallance), + ? Text( + context.lang.settingsAccountDeleteAccountWithBallance( + formattedBallance!, + ), + ) + : Text(context.lang.settingsAccountDeleteAccountNoBallance), onTap: (formattedBallance == null) ? null : () async { if (hasRemainingBallance) { - final canGoNext = await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return RefundCreditsView( - formattedBalance: formattedBallance!, - ); - }, - ), - ) as bool?; + final canGoNext = + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return RefundCreditsView( + formattedBalance: formattedBallance!, + ); + }, + ), + ) + as bool?; unawaited(initAsync()); if (canGoNext == null || !canGoNext) return; } diff --git a/lib/src/views/settings/appearance.view.dart b/lib/src/views/settings/appearance.view.dart index fa42ac7..a23be18 100644 --- a/lib/src/views/settings/appearance.view.dart +++ b/lib/src/views/settings/appearance.view.dart @@ -66,9 +66,9 @@ class _AppearanceViewState extends State { }, ); if (selectedValue != null && context.mounted) { - await context - .read() - .updateThemeMode(selectedValue); + await context.read().updateThemeMode( + selectedValue, + ); } } diff --git a/lib/src/views/settings/backup/backup.view.dart b/lib/src/views/settings/backup/backup.view.dart index a55ce3f..95e4f31 100644 --- a/lib/src/views/settings/backup/backup.view.dart +++ b/lib/src/views/settings/backup/backup.view.dart @@ -102,35 +102,37 @@ class _BackupViewState extends State { context.lang.backupServer, (backupServer.serverUrl.contains('@')) ? backupServer.serverUrl.split('@')[1] - : backupServer.serverUrl - .replaceAll('https://', '') + : backupServer.serverUrl.replaceAll( + 'https://', + '', + ), ), ( context.lang.backupMaxBackupSize, - formatBytes(backupServer.maxBackupBytes) + formatBytes(backupServer.maxBackupBytes), ), ( context.lang.backupStorageRetention, - '${backupServer.retentionDays} Days' + '${backupServer.retentionDays} Days', ), ( context.lang.backupLastBackupDate, formatDateTime( context, gUser.twonlySafeBackup!.lastBackupDone, - ) + ), ), ( context.lang.backupLastBackupSize, formatBytes( gUser.twonlySafeBackup!.lastBackupSize, - ) + ), ), ( context.lang.backupLastBackupResult, backupStatus( gUser.twonlySafeBackup!.backupUploadState, - ) + ), ), ].map((pair) { return TableRow( @@ -185,8 +187,9 @@ class _BackupViewState extends State { unselectedIconTheme: IconThemeData( color: Theme.of(context).colorScheme.inverseSurface.withAlpha(150), ), - selectedIconTheme: - IconThemeData(color: Theme.of(context).colorScheme.inverseSurface), + selectedIconTheme: IconThemeData( + color: Theme.of(context).colorScheme.inverseSurface, + ), items: [ const BottomNavigationBarItem( icon: FaIcon(FontAwesomeIcons.vault, size: 17), diff --git a/lib/src/views/settings/backup/setup_backup.view.dart b/lib/src/views/settings/backup/setup_backup.view.dart index 077b542..95091d3 100644 --- a/lib/src/views/settings/backup/setup_backup.view.dart +++ b/lib/src/views/settings/backup/setup_backup.view.dart @@ -94,8 +94,10 @@ class _SetupBackupViewState extends State { ], ), body: Padding( - padding: - const EdgeInsetsGeometry.symmetric(vertical: 40, horizontal: 40), + padding: const EdgeInsetsGeometry.symmetric( + vertical: 40, + horizontal: 40, + ), child: ListView( children: [ Text( @@ -143,7 +145,8 @@ class _SetupBackupViewState extends State { context.lang.backupPasswordRequirement, style: TextStyle( fontSize: 13, - color: (passwordCtrl.text.length < 8 && + color: + (passwordCtrl.text.length < 8 && passwordCtrl.text.isNotEmpty) ? Colors.red : Colors.transparent, @@ -169,7 +172,8 @@ class _SetupBackupViewState extends State { context.lang.passwordRepeatedNotEqual, style: TextStyle( fontSize: 13, - color: (passwordCtrl.text != repeatedPasswordCtrl.text && + color: + (passwordCtrl.text != repeatedPasswordCtrl.text && repeatedPasswordCtrl.text.isNotEmpty) ? Colors.red : Colors.transparent, @@ -192,7 +196,8 @@ class _SetupBackupViewState extends State { const SizedBox(height: 10), Center( child: FilledButton.icon( - onPressed: (!isLoading && + onPressed: + (!isLoading && (passwordCtrl.text == repeatedPasswordCtrl.text && passwordCtrl.text.length >= 8 || !kReleaseMode)) @@ -236,8 +241,9 @@ class _SetupBackupViewState extends State { } Future isSecurePassword(String password) async { - final badPasswordsStr = - await rootBundle.loadString('assets/passwords/bad_passwords.txt'); + final badPasswordsStr = await rootBundle.loadString( + 'assets/passwords/bad_passwords.txt', + ); final badPasswords = badPasswordsStr.split('\n'); if (badPasswords.contains(password)) { return false; diff --git a/lib/src/views/settings/chat/chat_reactions.view.dart b/lib/src/views/settings/chat/chat_reactions.view.dart index 086d44a..6adae92 100644 --- a/lib/src/views/settings/chat/chat_reactions.view.dart +++ b/lib/src/views/settings/chat/chat_reactions.view.dart @@ -94,8 +94,10 @@ class _ChatReactionSelectionView extends State { child: FloatingActionButton( foregroundColor: Colors.white, onPressed: () async { - selectedEmojis = - EmojiAnimation.animatedIcons.keys.toList().sublist(0, 6); + selectedEmojis = EmojiAnimation.animatedIcons.keys.toList().sublist( + 0, + 6, + ); setState(() {}); await updateUserdata((user) { user.preSelectedEmojies = selectedEmojis; diff --git a/lib/src/views/settings/data_and_storage.view.dart b/lib/src/views/settings/data_and_storage.view.dart index 9e2d568..7043785 100644 --- a/lib/src/views/settings/data_and_storage.view.dart +++ b/lib/src/views/settings/data_and_storage.view.dart @@ -170,16 +170,18 @@ class _AutoDownloadOptionsDialogState extends State { children: [ CheckboxListTile( title: const Text('Image'), - value: autoDownloadOptions[widget.connectionMode.name]! - .contains(DownloadMediaTypes.image.name), + value: autoDownloadOptions[widget.connectionMode.name]!.contains( + DownloadMediaTypes.image.name, + ), onChanged: (value) async { await _updateAutoDownloadSetting(DownloadMediaTypes.image, value); }, ), CheckboxListTile( title: const Text('Video'), - value: autoDownloadOptions[widget.connectionMode.name]! - .contains(DownloadMediaTypes.video.name), + value: autoDownloadOptions[widget.connectionMode.name]!.contains( + DownloadMediaTypes.video.name, + ), onChanged: (value) async { await _updateAutoDownloadSetting(DownloadMediaTypes.video, value); }, @@ -204,8 +206,9 @@ class _AutoDownloadOptionsDialogState extends State { if (value == null) return; // Update the autoDownloadOptions based on the checkbox state - autoDownloadOptions[widget.connectionMode.name]! - .removeWhere((element) => element == type.name); + autoDownloadOptions[widget.connectionMode.name]!.removeWhere( + (element) => element == type.name, + ); if (value) { autoDownloadOptions[widget.connectionMode.name]!.add(type.name); } diff --git a/lib/src/views/settings/data_and_storage/export_media.view.dart b/lib/src/views/settings/data_and_storage/export_media.view.dart index 5abace4..9c3426a 100644 --- a/lib/src/views/settings/data_and_storage/export_media.view.dart +++ b/lib/src/views/settings/data_and_storage/export_media.view.dart @@ -64,8 +64,10 @@ class _ExportMediaViewState extends State { try { final folder = _mediaFolder(); - final allFiles = - folder.listSync(recursive: true).whereType().toList(); + final allFiles = folder + .listSync(recursive: true) + .whereType() + .toList(); final mediaFiles = allFiles.where((f) { final name = p.basename(f.path).toLowerCase(); diff --git a/lib/src/views/settings/data_and_storage/import_media.view.dart b/lib/src/views/settings/data_and_storage/import_media.view.dart index e7b771b..a844643 100644 --- a/lib/src/views/settings/data_and_storage/import_media.view.dart +++ b/lib/src/views/settings/data_and_storage/import_media.view.dart @@ -165,8 +165,9 @@ class _ImportMediaViewState extends State { const SizedBox(height: 24), if (_isProcessing || _zipFile != null) LinearProgressIndicator( - value: - _isProcessing ? _progress : (_zipFile != null ? 1.0 : 0.0), + value: _isProcessing + ? _progress + : (_zipFile != null ? 1.0 : 0.0), ), const SizedBox(height: 8), if (_status != null) diff --git a/lib/src/views/settings/developer/automated_testing.view.dart b/lib/src/views/settings/developer/automated_testing.view.dart index aafa579..deb527a 100644 --- a/lib/src/views/settings/developer/automated_testing.view.dart +++ b/lib/src/views/settings/developer/automated_testing.view.dart @@ -40,8 +40,9 @@ class _AutomatedTestingViewState extends State { onTap: () async { final username = await showUserNameDialog(context); if (username == null) return; - final contacts = await twonlyDB.contactsDao - .getContactsByUsername(username.toLowerCase()); + final contacts = await twonlyDB.contactsDao.getContactsByUsername( + username.toLowerCase(), + ); if (contacts.length != 1) { Log.error('No single user fund'); return; @@ -57,8 +58,9 @@ class _AutomatedTestingViewState extends State { final sessionStore = await getSignalStore(); // 1. Store a valid session - final originalSession = - await sessionStore!.loadSession(getSignalAddress(userId)); + final originalSession = await sessionStore!.loadSession( + getSignalAddress(userId), + ); final serializedSession = originalSession.serialize(); for (var i = 0; i < 10; i++) { @@ -69,8 +71,9 @@ class _AutomatedTestingViewState extends State { ); } - final corruptedSession = - SessionRecord.fromSerialized(serializedSession); + final corruptedSession = SessionRecord.fromSerialized( + serializedSession, + ); await sessionStore.storeSession( getSignalAddress(userId), corruptedSession, @@ -93,13 +96,15 @@ class _AutomatedTestingViewState extends State { if (username == null) return; Log.info('Requested to send to $username'); - final contacts = await twonlyDB.contactsDao - .getContactsByUsername(username.toLowerCase()); + final contacts = await twonlyDB.contactsDao.getContactsByUsername( + username.toLowerCase(), + ); for (final contact in contacts) { Log.info('Sending to ${contact.username}'); - final group = - await twonlyDB.groupsDao.getDirectChat(contact.userId); + final group = await twonlyDB.groupsDao.getDirectChat( + contact.userId, + ); for (var i = 0; i < 200; i++) { setState(() { lotsOfMessagesStatus = @@ -144,8 +149,9 @@ Future showUserNameDialog( TextButton( child: Text(context.lang.ok), onPressed: () { - Navigator.of(context) - .pop(controller.text); // Return the input text + Navigator.of( + context, + ).pop(controller.text); // Return the input text }, ), ], diff --git a/lib/src/views/settings/help/contact_us.view.dart b/lib/src/views/settings/help/contact_us.view.dart index 362f63c..c90710f 100644 --- a/lib/src/views/settings/help/contact_us.view.dart +++ b/lib/src/views/settings/help/contact_us.view.dart @@ -47,8 +47,9 @@ class _ContactUsState extends State { final uploadRequestBytes = uploadRequest.writeToBuffer(); - final apiAuthTokenRaw = await const FlutterSecureStorage() - .read(key: SecureStorageKeys.apiAuthToken); + final apiAuthTokenRaw = await const FlutterSecureStorage().read( + key: SecureStorageKeys.apiAuthToken, + ); if (apiAuthTokenRaw == null) { Log.error('api auth token not defined.'); return null; diff --git a/lib/src/views/settings/help/contact_us/submit_message.view.dart b/lib/src/views/settings/help/contact_us/submit_message.view.dart index 708d356..91281ba 100644 --- a/lib/src/views/settings/help/contact_us/submit_message.view.dart +++ b/lib/src/views/settings/help/contact_us/submit_message.view.dart @@ -89,8 +89,10 @@ class _ContactUsState extends State { maxLines: 20, ), Padding( - padding: - const EdgeInsets.symmetric(vertical: 40, horizontal: 40), + padding: const EdgeInsets.symmetric( + vertical: 40, + horizontal: 40, + ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ diff --git a/lib/src/views/settings/help/faq.view.dart b/lib/src/views/settings/help/faq.view.dart index 01b114a..c6e10da 100644 --- a/lib/src/views/settings/help/faq.view.dart +++ b/lib/src/views/settings/help/faq.view.dart @@ -34,8 +34,9 @@ class _FaqViewState extends State { if (response.statusCode == 200) { setState(() { - _faqData = json.decode(utf8.decode(response.bodyBytes)) - as Map?; + _faqData = + json.decode(utf8.decode(response.bodyBytes)) + as Map?; noInternet = false; }); } else { @@ -87,12 +88,14 @@ class _FaqViewState extends State { child: ExpansionTile( title: Text(categoryData['meta']['title'] as String), subtitle: Text(categoryData['meta']['desc'] as String), - children: categoryData['questions'].map((question) { - return ListTile( - title: Text(question['title'] as String), - onTap: () => _launchURL(question['path'] as String), - ); - }).toList() as List, + children: + categoryData['questions'].map((question) { + return ListTile( + title: Text(question['title'] as String), + onTap: () => _launchURL(question['path'] as String), + ); + }).toList() + as List, ), ); }, diff --git a/lib/src/views/settings/help/help.view.dart b/lib/src/views/settings/help/help.view.dart index 00d6c97..19b671e 100644 --- a/lib/src/views/settings/help/help.view.dart +++ b/lib/src/views/settings/help/help.view.dart @@ -95,21 +95,27 @@ class _HelpViewState extends State { onTap: () => launchUrl( Uri.parse('https://github.com/twonlyapp/twonly-app'), ), - trailing: - const FaIcon(FontAwesomeIcons.arrowUpRightFromSquare, size: 15), + trailing: const FaIcon( + FontAwesomeIcons.arrowUpRightFromSquare, + size: 15, + ), ), ListTile( title: Text(context.lang.settingsHelpImprint), onTap: () => launchUrl(Uri.parse('https://twonly.eu/de/legal/')), - trailing: - const FaIcon(FontAwesomeIcons.arrowUpRightFromSquare, size: 15), + trailing: const FaIcon( + FontAwesomeIcons.arrowUpRightFromSquare, + size: 15, + ), ), ListTile( title: Text(context.lang.settingsHelpTerms), onTap: () => launchUrl(Uri.parse('https://twonly.eu/de/legal/agb.html')), - trailing: - const FaIcon(FontAwesomeIcons.arrowUpRightFromSquare, size: 15), + trailing: const FaIcon( + FontAwesomeIcons.arrowUpRightFromSquare, + size: 15, + ), ), ListTile( onLongPress: () async { diff --git a/lib/src/views/settings/privacy_view_block.view.dart b/lib/src/views/settings/privacy_view_block.view.dart index f3af120..d9b835d 100644 --- a/lib/src/views/settings/privacy_view_block.view.dart +++ b/lib/src/views/settings/privacy_view_block.view.dart @@ -32,8 +32,12 @@ class _PrivacyViewBlockUsers extends State { title: Text(context.lang.settingsPrivacyBlockUsers), ), body: Padding( - padding: - const EdgeInsets.only(bottom: 20, left: 10, top: 20, right: 10), + padding: const EdgeInsets.only( + bottom: 20, + left: 10, + top: 20, + right: 10, + ), child: Column( children: [ Padding( @@ -63,9 +67,9 @@ class _PrivacyViewBlockUsers extends State { } final filteredContacts = snapshot.data!.where((contact) { - return getContactDisplayName(contact) - .toLowerCase() - .contains(filter.toLowerCase()); + return getContactDisplayName( + contact, + ).toLowerCase().contains(filter.toLowerCase()); }).toList(); return UserList( diff --git a/lib/src/views/settings/profile/modify_avatar.view.dart b/lib/src/views/settings/profile/modify_avatar.view.dart index 314b40c..4fb0f94 100644 --- a/lib/src/views/settings/profile/modify_avatar.view.dart +++ b/lib/src/views/settings/profile/modify_avatar.view.dart @@ -50,8 +50,9 @@ class _ModifyAvatarViewState extends State { unselectedIconColor: Colors.grey, primaryBgColor: Colors.black, // Dark mode background secondaryBgColor: Colors.grey[850], // Dark mode secondary background - labelTextStyle: - const TextStyle(color: Colors.white), // Light text for dark mode + labelTextStyle: const TextStyle( + color: Colors.white, + ), // Light text for dark mode ); } else { return AvatarMakerThemeData( @@ -70,8 +71,9 @@ class _ModifyAvatarViewState extends State { unselectedIconColor: Colors.grey, primaryBgColor: Colors.white, // Light mode background secondaryBgColor: Colors.grey[200], // Light mode secondary background - labelTextStyle: - const TextStyle(color: Colors.black), // Dark text for light mode + labelTextStyle: const TextStyle( + color: Colors.black, + ), // Dark text for light mode ); } } @@ -161,8 +163,7 @@ class _ModifyAvatarViewState extends State { IconButton( icon: const Icon(FontAwesomeIcons.rotateLeft), onLongPress: () async { - await PersistentAvatarMakerController - .clearAvatarMaker(); + await PersistentAvatarMakerController.clearAvatarMaker(); await _avatarMakerController.restoreState(); }, onPressed: _avatarMakerController.restoreState, @@ -171,11 +172,15 @@ class _ModifyAvatarViewState extends State { ), ), Padding( - padding: - const EdgeInsets.symmetric(horizontal: 8, vertical: 30), + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 30, + ), child: AvatarMakerCustomizer( - scaffoldWidth: - min(600, MediaQuery.of(context).size.width * 0.85), + scaffoldWidth: min( + 600, + MediaQuery.of(context).size.width * 0.85, + ), theme: getAvatarMakerTheme(context), controller: _avatarMakerController, ), diff --git a/lib/src/views/settings/profile/profile.view.dart b/lib/src/views/settings/profile/profile.view.dart index a6f89d4..89ea84f 100644 --- a/lib/src/views/settings/profile/profile.view.dart +++ b/lib/src/views/settings/profile/profile.view.dart @@ -30,8 +30,9 @@ class _ProfileViewState extends State { @override void initState() { - twonlyScoreSub = - twonlyDB.groupsDao.watchSumTotalMediaCounter().listen((update) { + twonlyScoreSub = twonlyDB.groupsDao.watchSumTotalMediaCounter().listen(( + update, + ) { setState(() { twonlyScore = update; }); diff --git a/lib/src/views/settings/share_with_friends.view.dart b/lib/src/views/settings/share_with_friends.view.dart index c9b9f65..412e6cd 100644 --- a/lib/src/views/settings/share_with_friends.view.dart +++ b/lib/src/views/settings/share_with_friends.view.dart @@ -17,8 +17,9 @@ class _ShareWithFriendsView extends State { void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { - _controller.text = - context.lang.inviteFriendsShareText('https://twonly.eu/install'); + _controller.text = context.lang.inviteFriendsShareText( + 'https://twonly.eu/install', + ); }); } diff --git a/lib/src/views/settings/subscription/additional_users.view.dart b/lib/src/views/settings/subscription/additional_users.view.dart index 67fbcad..dd6c190 100644 --- a/lib/src/views/settings/subscription/additional_users.view.dart +++ b/lib/src/views/settings/subscription/additional_users.view.dart @@ -56,24 +56,28 @@ class _AdditionalUsersViewState extends State { } Future addAdditionalUser() async { - final selectedUserIds = await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => SelectAdditionalUsers( - limit: _planLimit, - alreadySelected: ballance?.additionalAccounts - .map((e) => e.userId.toInt()) - .toList() ?? - [], - ), - ), - ) as List?; + final selectedUserIds = + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => SelectAdditionalUsers( + limit: _planLimit, + alreadySelected: + ballance?.additionalAccounts + .map((e) => e.userId.toInt()) + .toList() ?? + [], + ), + ), + ) + as List?; if (selectedUserIds == null) return; for (final selectedUserId in selectedUserIds) { final res = await apiService.addAdditionalUser(Int64(selectedUserId)); if (res.isError && mounted) { - final contact = - await twonlyDB.contactsDao.getContactById(selectedUserId); + final contact = await twonlyDB.contactsDao.getContactById( + selectedUserId, + ); if (contact != null && mounted) { if (res.error == ErrorCode.UserIsNotInFreePlan) { ScaffoldMessenger.of(context).showSnackBar( @@ -89,8 +93,9 @@ class _AdditionalUsersViewState extends State { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( - context.lang - .additionalUserAddError(getContactDisplayName(contact)), + context.lang.additionalUserAddError( + getContactDisplayName(contact), + ), ), ), ); @@ -219,8 +224,9 @@ class _AdditionalAccountState extends State { context.lang.additionalUserRemoveDesc, ); if (remove) { - final res = await apiService - .removeAdditionalUser(widget.account.userId); + final res = await apiService.removeAdditionalUser( + widget.account.userId, + ); if (!context.mounted) return; if (res.isSuccess) { widget.refresh(); diff --git a/lib/src/views/settings/subscription/select_additional_users.view.dart b/lib/src/views/settings/subscription/select_additional_users.view.dart index f949513..bb85ef7 100644 --- a/lib/src/views/settings/subscription/select_additional_users.view.dart +++ b/lib/src/views/settings/subscription/select_additional_users.view.dart @@ -67,9 +67,9 @@ class _SelectAdditionalUsers extends State { } final usersFiltered = allContacts .where( - (user) => getContactDisplayName(user) - .toLowerCase() - .contains(searchUserName.value.text.toLowerCase()), + (user) => getContactDisplayName( + user, + ).toLowerCase().contains(searchUserName.value.text.toLowerCase()), ) .toList(); setState(() { @@ -111,8 +111,12 @@ class _SelectAdditionalUsers extends State { ), body: SafeArea( child: Padding( - padding: - const EdgeInsets.only(bottom: 40, left: 10, top: 20, right: 10), + padding: const EdgeInsets.only( + bottom: 40, + left: 10, + top: 20, + right: 10, + ), child: Column( children: [ Padding( @@ -150,8 +154,9 @@ class _SelectAdditionalUsers extends State { spacing: 8, children: selected.map((w) { return _Chip( - contact: allContacts - .firstWhere((t) => t.userId == w), + contact: allContacts.firstWhere( + (t) => t.userId == w, + ), onTap: toggleSelectedUser, ); }).toList(), @@ -188,7 +193,8 @@ class _SelectAdditionalUsers extends State { fontSize: 13, ), trailing: Checkbox( - value: selectedUsers.contains(user.userId) | + value: + selectedUsers.contains(user.userId) | _alreadySelected.contains(user.userId), side: WidgetStateBorderSide.resolveWith( (states) { diff --git a/lib/src/views/settings/subscription/subscription.view.dart b/lib/src/views/settings/subscription/subscription.view.dart index de8b192..1283992 100644 --- a/lib/src/views/settings/subscription/subscription.view.dart +++ b/lib/src/views/settings/subscription/subscription.view.dart @@ -118,8 +118,10 @@ class _SubscriptionViewState extends State { BetterListTile( icon: FontAwesomeIcons.fileContract, text: context.lang.termsOfService, - trailing: - const FaIcon(FontAwesomeIcons.arrowUpRightFromSquare, size: 15), + trailing: const FaIcon( + FontAwesomeIcons.arrowUpRightFromSquare, + size: 15, + ), onTap: () async { await launchUrl(Uri.parse('https://twonly.eu/de/legal/agb.html')); }, @@ -130,8 +132,10 @@ class _SubscriptionViewState extends State { size: 15, ), text: context.lang.privacyPolicy, - trailing: - const FaIcon(FontAwesomeIcons.arrowUpRightFromSquare, size: 15), + trailing: const FaIcon( + FontAwesomeIcons.arrowUpRightFromSquare, + size: 15, + ), onTap: () async { await launchUrl( Uri.parse('https://twonly.eu/de/legal/privacy.html'), diff --git a/lib/src/views/settings/subscription_custom/checkout.view.dart b/lib/src/views/settings/subscription_custom/checkout.view.dart index 1709640..916c6d8 100644 --- a/lib/src/views/settings/subscription_custom/checkout.view.dart +++ b/lib/src/views/settings/subscription_custom/checkout.view.dart @@ -101,17 +101,19 @@ class _CheckoutViewState extends State { padding: const EdgeInsets.symmetric(horizontal: 16), child: FilledButton( onPressed: () async { - final success = await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return SelectPaymentView( - plan: widget.plan, - payMonthly: paidMonthly, - ); - }, - ), - ) as bool?; + final success = + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return SelectPaymentView( + plan: widget.plan, + payMonthly: paidMonthly, + ); + }, + ), + ) + as bool?; if (success != null && success && context.mounted) { Navigator.pop(context); } diff --git a/lib/src/views/settings/subscription_custom/select_payment.view.dart b/lib/src/views/settings/subscription_custom/select_payment.view.dart index 9d9c830..658eda6 100644 --- a/lib/src/views/settings/subscription_custom/select_payment.view.dart +++ b/lib/src/views/settings/subscription_custom/select_payment.view.dart @@ -52,8 +52,9 @@ class _SelectPaymentViewState extends State { if (balance == null) { balanceInCents = 0; } else { - balanceInCents = - balance.transactions.map((a) => a.depositCents.toInt()).sum; + balanceInCents = balance.transactions + .map((a) => a.depositCents.toInt()) + .sum; } setState(() {}); } @@ -62,8 +63,10 @@ class _SelectPaymentViewState extends State { if (widget.valueInCents != null && widget.valueInCents! > 0) { checkoutInCents = widget.valueInCents!; } else if (widget.plan != null) { - checkoutInCents = - getPlanPrice(widget.plan!, paidMonthly: widget.payMonthly!); + checkoutInCents = getPlanPrice( + widget.plan!, + paidMonthly: widget.payMonthly!, + ); } else { /// Nothing to checkout for... Navigator.pop(context); @@ -79,7 +82,8 @@ class _SelectPaymentViewState extends State { final totalPrice = (widget.plan != null && widget.payMonthly != null) ? '${localePrizing(context, checkoutInCents)}/${(widget.payMonthly!) ? context.lang.month : context.lang.year}' : localePrizing(context, checkoutInCents); - final canPay = paymentMethods == PaymentMethods.twonlyCredit && + final canPay = + paymentMethods == PaymentMethods.twonlyCredit && (balanceInCents == null || balanceInCents! >= checkoutInCents); return Scaffold( appBar: AppBar( diff --git a/lib/src/views/settings/subscription_custom/subscription.view.dart b/lib/src/views/settings/subscription_custom/subscription.view.dart index 2a9eeb9..4867d27 100644 --- a/lib/src/views/settings/subscription_custom/subscription.view.dart +++ b/lib/src/views/settings/subscription_custom/subscription.view.dart @@ -112,11 +112,13 @@ class _SubscriptionCustomViewState extends State { ballance!.lastPaymentDoneUnixTimestamp.toInt() * 1000, ); if (isPayingUser(currentPlan)) { - nextPayment = lastPaymentDateTime - .add(Duration(days: ballance!.paymentPeriodDays.toInt())); + nextPayment = lastPaymentDateTime.add( + Duration(days: ballance!.paymentPeriodDays.toInt()), + ); } - final ballanceInCents = - ballance!.transactions.map((a) => a.depositCents.toInt()).sum; + final ballanceInCents = ballance!.transactions + .map((a) => a.depositCents.toInt()) + .sum; formattedBalance = NumberFormat.currency( locale: myLocale.toString(), symbol: '€', @@ -217,8 +219,8 @@ class _SubscriptionCustomViewState extends State { plan: SubscriptionPlan.Family, disableMonthlyOption: currentPlan == SubscriptionPlan.Pro && - ballance!.paymentPeriodDays.toInt() == - YEARLY_PAYMENT_DAYS, + ballance!.paymentPeriodDays.toInt() == + YEARLY_PAYMENT_DAYS, ); }, ), @@ -444,8 +446,9 @@ class PlanCard extends StatelessWidget { Padding( padding: const EdgeInsets.only(top: 7), child: Text( - context.lang - .subscriptionRefund(localePrizing(context, refund!)), + context.lang.subscriptionRefund( + localePrizing(context, refund!), + ), textAlign: TextAlign.center, style: TextStyle( color: context.color.primary, diff --git a/lib/src/views/shared/select_contacts.view.dart b/lib/src/views/shared/select_contacts.view.dart index c013a6c..22fc4a9 100644 --- a/lib/src/views/shared/select_contacts.view.dart +++ b/lib/src/views/shared/select_contacts.view.dart @@ -80,9 +80,9 @@ class _SelectAdditionalUsers extends State { } final usersFiltered = allContacts .where( - (user) => getContactDisplayName(user) - .toLowerCase() - .contains(searchUserName.value.text.toLowerCase()), + (user) => getContactDisplayName( + user, + ).toLowerCase().contains(searchUserName.value.text.toLowerCase()), ) .toList(); setState(() { @@ -124,8 +124,12 @@ class _SelectAdditionalUsers extends State { ), body: SafeArea( child: Padding( - padding: - const EdgeInsets.only(bottom: 40, left: 10, top: 20, right: 10), + padding: const EdgeInsets.only( + bottom: 40, + left: 10, + top: 20, + right: 10, + ), child: Column( children: [ Padding( @@ -163,8 +167,9 @@ class _SelectAdditionalUsers extends State { spacing: 8, children: selected.map((w) { return _Chip( - contact: allContacts - .firstWhere((t) => t.userId == w), + contact: allContacts.firstWhere( + (t) => t.userId == w, + ), onTap: toggleSelectedUser, ); }).toList(), @@ -201,7 +206,8 @@ class _SelectAdditionalUsers extends State { fontSize: 13, ), trailing: Checkbox( - value: selectedUsers.contains(user.userId) | + value: + selectedUsers.contains(user.userId) | _alreadySelected.contains(user.userId), side: WidgetStateBorderSide.resolveWith( (states) { diff --git a/lib/src/views/user_study/user_study_questionnaire.view.dart b/lib/src/views/user_study/user_study_questionnaire.view.dart index 6ae7b2c..96cbc9d 100644 --- a/lib/src/views/user_study/user_study_questionnaire.view.dart +++ b/lib/src/views/user_study/user_study_questionnaire.view.dart @@ -194,8 +194,10 @@ class _UserStudyQuestionnaireViewState onPressed: _submitData, child: const Padding( padding: EdgeInsets.symmetric(horizontal: 40, vertical: 15), - child: - Text('Jetzt teilnehmen', style: TextStyle(fontSize: 18)), + child: Text( + 'Jetzt teilnehmen', + style: TextStyle(fontSize: 18), + ), ), ), ), @@ -208,23 +210,23 @@ class _UserStudyQuestionnaireViewState // Hilfsmethoden für das UI Widget _sectionTitle(String title) => Padding( - padding: const EdgeInsets.symmetric(vertical: 10), - child: Text( - title, - style: const TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - ), - ), - ); + padding: const EdgeInsets.symmetric(vertical: 10), + child: Text( + title, + style: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + ); Widget _questionText(String text) => Padding( - padding: const EdgeInsets.only(top: 20, bottom: 5), - child: Text( - text, - style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600), - ), - ); + padding: const EdgeInsets.only(top: 20, bottom: 5), + child: Text( + text, + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600), + ), + ); Widget _buildRadioList(List options, String key) { return Padding( @@ -253,8 +255,10 @@ class _UserStudyQuestionnaireViewState Widget _buildTextField(String hint, void Function(String) onChanged) { return TextField( - decoration: - InputDecoration(hintText: hint, border: const OutlineInputBorder()), + decoration: InputDecoration( + hintText: hint, + border: const OutlineInputBorder(), + ), onChanged: onChanged, ); } diff --git a/lib/src/views/user_study/user_study_welcome.view.dart b/lib/src/views/user_study/user_study_welcome.view.dart index c11e12c..4598d01 100644 --- a/lib/src/views/user_study/user_study_welcome.view.dart +++ b/lib/src/views/user_study/user_study_welcome.view.dart @@ -55,8 +55,9 @@ class _UserStudyWelcomeViewState extends State { const SizedBox(height: 40), Center( child: FilledButton( - onPressed: () => context - .pushReplacement(Routes.settingsHelpUserStudyQuestionnaire), + onPressed: () => context.pushReplacement( + Routes.settingsHelpUserStudyQuestionnaire, + ), child: const Padding( padding: EdgeInsets.symmetric(horizontal: 30, vertical: 15), child: Text( From cfc6e945dada42fe2b04909ffa62162348609058 Mon Sep 17 00:00:00 2001 From: otsmr Date: Sun, 12 Apr 2026 02:01:31 +0200 Subject: [PATCH 3/5] fix: reupload of media files --- CHANGELOG.md | 6 + lib/src/database/daos/mediafiles.dao.dart | 6 +- lib/src/database/daos/messages.dao.dart | 2 +- lib/src/database/daos/receipts.dao.dart | 47 ++++- lib/src/services/api.service.dart | 6 +- .../services/api/client2client/media.c2c.dart | 52 +++--- .../api/mediafiles/upload.service.dart | 160 +++++++++++++++++- lib/src/services/api/messages.dart | 37 +++- .../save_to_gallery.dart | 15 +- lib/src/views/chats/chat_messages.view.dart | 2 +- .../typing_indicator.dart | 2 +- .../data_and_storage/import_media.view.dart | 2 +- 12 files changed, 287 insertions(+), 50 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2814eef..8b582b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.1.5 + +- Fix: Reupload of media files was not working properly +- Fix: Chats where ordered wrongly +- Fix: Typing indicator was not shown always + ## 0.1.4 - New: Typing and chat open indicator diff --git a/lib/src/database/daos/mediafiles.dao.dart b/lib/src/database/daos/mediafiles.dao.dart index 7f4ff8c..01752ed 100644 --- a/lib/src/database/daos/mediafiles.dao.dart +++ b/lib/src/database/daos/mediafiles.dao.dart @@ -14,7 +14,7 @@ class MediaFilesDao extends DatabaseAccessor // ignore: matching_super_parameters MediaFilesDao(super.db); - Future insertMedia(MediaFilesCompanion mediaFile) async { + Future insertOrUpdateMedia(MediaFilesCompanion mediaFile) async { try { var insertMediaFile = mediaFile; @@ -24,7 +24,9 @@ class MediaFilesDao extends DatabaseAccessor ); } - final rowId = await into(mediaFiles).insert(insertMediaFile); + final rowId = await into( + mediaFiles, + ).insertOnConflictUpdate(insertMediaFile); return await (select( mediaFiles, diff --git a/lib/src/database/daos/messages.dao.dart b/lib/src/database/daos/messages.dao.dart index 2b58186..7a1c36a 100644 --- a/lib/src/database/daos/messages.dao.dart +++ b/lib/src/database/daos/messages.dao.dart @@ -318,7 +318,7 @@ class MessagesDao extends DatabaseAccessor with _$MessagesDaoMixin { ); } - final rowId = await into(messages).insert(insertMessage); + final rowId = await into(messages).insertOnConflictUpdate(insertMessage); await twonlyDB.groupsDao.updateGroup( message.groupId.value, diff --git a/lib/src/database/daos/receipts.dao.dart b/lib/src/database/daos/receipts.dao.dart index d3e4b78..34b9a7a 100644 --- a/lib/src/database/daos/receipts.dao.dart +++ b/lib/src/database/daos/receipts.dao.dart @@ -31,7 +31,7 @@ class ReceiptsDao extends DatabaseAccessor with _$ReceiptsDaoMixin { if (receipt == null) return; if (receipt.messageId != null) { - await into(messageActions).insert( + await into(messageActions).insertOnConflictUpdate( MessageActionsCompanion( messageId: Value(receipt.messageId!), contactId: Value(fromUserId), @@ -113,6 +113,16 @@ class ReceiptsDao extends DatabaseAccessor with _$ReceiptsDaoMixin { } } + Future> getReceiptsByContactAndMessageId( + int contactId, + String messageId, + ) async { + return (select(receipts)..where( + (t) => t.contactId.equals(contactId) & t.messageId.equals(messageId), + )) + .get(); + } + Future> getReceiptsForRetransmission() async { final markedRetriesTime = clock.now().subtract( const Duration( @@ -132,6 +142,24 @@ class ReceiptsDao extends DatabaseAccessor with _$ReceiptsDaoMixin { .get(); } + Future> getReceiptsForMediaRetransmissions() async { + final markedRetriesTime = clock.now().subtract( + const Duration( + // give the server time to transmit all messages to the client + seconds: 20, + ), + ); + return (select(receipts)..where( + (t) => + (t.markForRetry.isSmallerThanValue(markedRetriesTime) | + t.markForRetryAfterAccepted.isSmallerThanValue( + markedRetriesTime, + )) & + t.willBeRetriedByMediaUpload.equals(true), + )) + .get(); + } + Stream> watchAll() { return select(receipts).watch(); } @@ -155,6 +183,19 @@ class ReceiptsDao extends DatabaseAccessor with _$ReceiptsDaoMixin { )..where((c) => c.receiptId.equals(receiptId))).write(updates); } + Future updateReceiptByContactAndMessageId( + int contactId, + String messageId, + ReceiptsCompanion updates, + ) async { + await (update( + receipts, + )..where( + (c) => c.contactId.equals(contactId) & c.messageId.equals(messageId), + )) + .write(updates); + } + Future updateReceiptWidthUserId( int fromUserId, String receiptId, @@ -168,9 +209,7 @@ class ReceiptsDao extends DatabaseAccessor with _$ReceiptsDaoMixin { Future markMessagesForRetry(int contactId) async { await (update(receipts)..where( - (c) => - c.contactId.equals(contactId) & - c.willBeRetriedByMediaUpload.equals(false), + (c) => c.contactId.equals(contactId) & c.markForRetry.isNull(), )) .write( ReceiptsCompanion( diff --git a/lib/src/services/api.service.dart b/lib/src/services/api.service.dart index 4b65f32..432b16b 100644 --- a/lib/src/services/api.service.dart +++ b/lib/src/services/api.service.dart @@ -92,12 +92,14 @@ class ApiService { if (globalIsInBackgroundTask) { await retransmitRawBytes(); - await tryTransmitMessages(); + await retransmitAllMessages(); + await reuploadMediaFiles(); await tryDownloadAllMediaFiles(); } else if (!globalIsAppInBackground) { unawaited(retransmitRawBytes()); - unawaited(tryTransmitMessages()); + unawaited(retransmitAllMessages()); unawaited(tryDownloadAllMediaFiles()); + unawaited(reuploadMediaFiles()); twonlyDB.markUpdated(); unawaited(syncFlameCounters()); unawaited(setupNotificationWithUsers()); diff --git a/lib/src/services/api/client2client/media.c2c.dart b/lib/src/services/api/client2client/media.c2c.dart index 0369eb9..4f08d91 100644 --- a/lib/src/services/api/client2client/media.c2c.dart +++ b/lib/src/services/api/client2client/media.c2c.dart @@ -73,12 +73,38 @@ Future handleMedia( mediaType = MediaType.audio; } + var mediaIdValue = const Value.absent(); + final messageTmp = await twonlyDB.messagesDao .getMessageById(media.senderMessageId) .getSingleOrNull(); if (messageTmp != null) { - Log.warn('This message already exit. Message is dropped.'); - return; + if (messageTmp.senderId != fromUserId) { + Log.warn( + '$fromUserId tried to modify the message from ${messageTmp.senderId}.', + ); + return; + } + if (messageTmp.mediaId == null) { + Log.warn( + 'This message already exit without a mediaId. Message is dropped.', + ); + return; + } + final mediaFile = await twonlyDB.mediaFilesDao.getMediaFileById( + messageTmp.mediaId!, + ); + if (mediaFile?.downloadState != DownloadState.reuploadRequested) { + Log.warn( + 'This message and media file already exit and was not requested again. Dropping it.', + ); + return; + } + + if (mediaFile != null) { + // media file is reuploaded use the same mediaId + mediaIdValue = Value(mediaFile.mediaId); + } } int? displayLimitInMilliseconds; @@ -95,8 +121,9 @@ Future handleMedia( late Message? message; await twonlyDB.transaction(() async { - mediaFile = await twonlyDB.mediaFilesDao.insertMedia( + mediaFile = await twonlyDB.mediaFilesDao.insertOrUpdateMedia( MediaFilesCompanion( + mediaId: mediaIdValue, downloadState: const Value(DownloadState.pending), type: Value(mediaType), requiresAuthentication: Value(media.requiresAuthentication), @@ -205,23 +232,6 @@ Future handleMediaUpdate( case EncryptedContent_MediaUpdate_Type.DECRYPTION_ERROR: Log.info('Got media file decryption error ${mediaFile.mediaId}'); - final reuploadRequestedBy = mediaFile.reuploadRequestedBy ?? []; - reuploadRequestedBy.add(fromUserId); - await twonlyDB.mediaFilesDao.updateMedia( - mediaFile.mediaId, - MediaFilesCompanion( - uploadState: const Value(UploadState.preprocessing), - reuploadRequestedBy: Value(reuploadRequestedBy), - ), - ); - final mediaFileUpdated = await MediaFileService.fromMediaId( - mediaFile.mediaId, - ); - if (mediaFileUpdated != null) { - if (mediaFileUpdated.uploadRequestPath.existsSync()) { - mediaFileUpdated.uploadRequestPath.deleteSync(); - } - unawaited(startBackgroundMediaUpload(mediaFileUpdated)); - } + await reuploadMediaFile(fromUserId, mediaFile, message.messageId); } } diff --git a/lib/src/services/api/mediafiles/upload.service.dart b/lib/src/services/api/mediafiles/upload.service.dart index 16f745e..806bce5 100644 --- a/lib/src/services/api/mediafiles/upload.service.dart +++ b/lib/src/services/api/mediafiles/upload.service.dart @@ -26,6 +26,148 @@ import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:workmanager/workmanager.dart' hide TaskStatus; +final lockRetransmission = Mutex(); + +Future reuploadMediaFiles() async { + return lockRetransmission.protect(() async { + final receipts = await twonlyDB.receiptsDao + .getReceiptsForMediaRetransmissions(); + + if (receipts.isEmpty) return; + + Log.info('Reuploading ${receipts.length} media files to the server.'); + + final contacts = {}; + + for (final receipt in receipts) { + if (receipt.retryCount > 1 && receipt.lastRetry != null) { + final twentyFourHoursAgo = DateTime.now().subtract( + const Duration(hours: 24), + ); + if (receipt.lastRetry!.isAfter(twentyFourHoursAgo)) { + Log.info( + 'Ignoring ${receipt.receiptId} as it was retried in the last 24h', + ); + continue; + } + } + var messageId = receipt.messageId; + if (receipt.messageId == null) { + Log.info('Message not in receipt. Loading it from the content.'); + try { + final content = EncryptedContent.fromBuffer(receipt.message); + if (content.hasMedia()) { + messageId = content.media.senderMessageId; + await twonlyDB.receiptsDao.updateReceipt( + receipt.receiptId, + ReceiptsCompanion( + messageId: Value(messageId), + ), + ); + } + } catch (e) { + Log.error(e); + } + } + if (messageId == null) { + Log.error('MessageId is empty for media file receipts'); + continue; + } + if (receipt.markForRetryAfterAccepted != null) { + if (!contacts.containsKey(receipt.contactId)) { + final contact = await twonlyDB.contactsDao + .getContactByUserId(receipt.contactId) + .getSingleOrNull(); + if (contact == null) { + Log.error( + 'Contact does not exists, but has a record in receipts, this should not be possible, because of the DELETE CASCADE relation.', + ); + continue; + } + contacts[receipt.contactId] = contact; + } + if (!(contacts[receipt.contactId]?.accepted ?? true)) { + Log.warn( + 'Could not send message as contact has still not yet accepted.', + ); + continue; + } + } + + if (receipt.ackByServerAt == null) { + // media file must be reuploaded again in case the media files + // was deleted by the server, the receiver will request a new media reupload + + final message = await twonlyDB.messagesDao + .getMessageById(messageId) + .getSingleOrNull(); + if (message == null || message.mediaId == null) { + Log.error( + 'Message not found for reupload of the receipt (${message == null} - ${message?.mediaId}).', + ); + continue; + } + + final mediaFile = await twonlyDB.mediaFilesDao.getMediaFileById( + message.mediaId!, + ); + if (mediaFile == null) { + Log.error( + 'Mediafile not found for reupload of the receipt (${message.messageId} - ${message.mediaId}).', + ); + continue; + } + await reuploadMediaFile( + receipt.contactId, + mediaFile, + message.messageId, + ); + } else { + Log.info('Reuploading media file $messageId'); + // the media file should be still on the server, so it should be enough + // to just resend the message containing the download token. + await tryToSendCompleteMessage(receipt: receipt); + } + } + }); +} + +Future reuploadMediaFile( + int contactId, + MediaFile mediaFile, + String messageId, +) async { + Log.info('Reuploading media file: ${mediaFile.mediaId}'); + + await twonlyDB.receiptsDao.updateReceiptByContactAndMessageId( + contactId, + messageId, + const ReceiptsCompanion( + markForRetry: Value(null), + markForRetryAfterAccepted: Value(null), + ), + ); + + final reuploadRequestedBy = (mediaFile.reuploadRequestedBy ?? []) + ..add(contactId); + await twonlyDB.mediaFilesDao.updateMedia( + mediaFile.mediaId, + MediaFilesCompanion( + uploadState: const Value(UploadState.preprocessing), + reuploadRequestedBy: Value(reuploadRequestedBy), + ), + ); + final mediaFileUpdated = await MediaFileService.fromMediaId( + mediaFile.mediaId, + ); + if (mediaFileUpdated != null) { + if (mediaFileUpdated.uploadRequestPath.existsSync()) { + mediaFileUpdated.uploadRequestPath.deleteSync(); + } + unawaited(startBackgroundMediaUpload(mediaFileUpdated)); + } +} + Future finishStartedPreprocessing() async { final mediaFiles = await twonlyDB.mediaFilesDao .getAllMediaFilesPendingUpload(); @@ -62,7 +204,7 @@ Future finishStartedPreprocessing() async { /// It can happen, that a media files is uploaded but not yet marked for been uploaded. /// For example because the background_downloader plugin has not yet reported the finished upload. -/// In case the the message receipts or a reaction was received, mark the media file as been uploaded. +/// In case the message receipts or a reaction was received, mark the media file as been uploaded. Future handleMediaRelatedResponseFromReceiver(String messageId) async { final message = await twonlyDB.messagesDao .getMessageById(messageId) @@ -100,6 +242,16 @@ Future markUploadAsSuccessful(MediaFile media) async { message.messageId, clock.now(), ); + await twonlyDB.receiptsDao.updateReceiptByContactAndMessageId( + contact.contactId, + message.messageId, + ReceiptsCompanion( + ackByServerAt: Value(clock.now()), + retryCount: const Value(1), + lastRetry: Value(clock.now()), + markForRetry: const Value(null), + ), + ); } } } @@ -122,7 +274,7 @@ Future initializeMediaUpload( const MediaFilesCompanion(isDraftMedia: Value(false)), ); - final mediaFile = await twonlyDB.mediaFilesDao.insertMedia( + final mediaFile = await twonlyDB.mediaFilesDao.insertOrUpdateMedia( MediaFilesCompanion( uploadState: const Value(UploadState.initialized), displayLimitInMilliseconds: Value(displayLimitInMilliseconds), @@ -313,7 +465,8 @@ Future _createUploadRequest(MediaFileService media) async { } if (media.mediaFile.reuploadRequestedBy != null) { - type = EncryptedContent_Media_Type.REUPLOAD; + // not used any more... Receiver detects automatically if it is an reupload... + // type = EncryptedContent_Media_Type.REUPLOAD; } final notEncryptedContent = EncryptedContent( @@ -340,6 +493,7 @@ Future _createUploadRequest(MediaFileService media) async { final cipherText = await sendCipherText( groupMember.contactId, notEncryptedContent, + messageId: message.messageId, onlyReturnEncryptedData: true, ); diff --git a/lib/src/services/api/messages.dart b/lib/src/services/api/messages.dart index e4fe6d9..07865ec 100644 --- a/lib/src/services/api/messages.dart +++ b/lib/src/services/api/messages.dart @@ -23,7 +23,7 @@ import 'package:twonly/src/utils/misc.dart'; final lockRetransmission = Mutex(); -Future tryTransmitMessages() async { +Future retransmitAllMessages() async { return lockRetransmission.protect(() async { final receipts = await twonlyDB.receiptsDao.getReceiptsForRetransmission(); @@ -304,7 +304,11 @@ Future sendCipherTextToGroup( }) async { final groupMembers = await twonlyDB.groupsDao.getGroupNonLeftMembers(groupId); - if (!onlySendIfNoReceiptsAreOpen) { + if (messageId != null || + encryptedContent.hasReaction() || + encryptedContent.hasMedia() || + encryptedContent.hasTextMessage()) { + // only update the counter in case this is a actual message await twonlyDB.groupsDao.increaseLastMessageExchange(groupId, clock.now()); } @@ -330,11 +334,11 @@ Future<(Uint8List, Uint8List?)?> sendCipherText( bool onlySendIfNoReceiptsAreOpen = false, }) async { if (onlySendIfNoReceiptsAreOpen) { - if (await twonlyDB.receiptsDao.getReceiptCountForContact( - contactId, - ) > - 0) { - // this prevents that this message is send in case the receiver is not online + final openReceipts = await twonlyDB.receiptsDao.getReceiptCountForContact( + contactId, + ); + if (openReceipts > 2) { + // this prevents that these types of messages are send in case the receiver is offline return null; } } @@ -344,12 +348,31 @@ Future<(Uint8List, Uint8List?)?> sendCipherText( ..type = pb.Message_Type.CIPHERTEXT ..encryptedContent = encryptedContent.writeToBuffer(); + var retryCounter = 0; + DateTime? lastRetry; + + if (messageId != null) { + final receipts = await twonlyDB.receiptsDao + .getReceiptsByContactAndMessageId(contactId, messageId); + + for (final receipt in receipts) { + if (receipt.lastRetry != null) { + lastRetry = receipt.lastRetry; + } + retryCounter += 1; + Log.info('Removing duplicated receipt for message $messageId'); + await twonlyDB.receiptsDao.deleteReceipt(receipt.receiptId); + } + } + final receipt = await twonlyDB.receiptsDao.insertReceipt( ReceiptsCompanion( contactId: Value(contactId), message: Value(response.writeToBuffer()), messageId: Value(messageId), willBeRetriedByMediaUpload: Value(onlyReturnEncryptedData), + retryCount: Value(retryCounter), + lastRetry: Value(lastRetry), ), ); diff --git a/lib/src/views/camera/camera_preview_components/save_to_gallery.dart b/lib/src/views/camera/camera_preview_components/save_to_gallery.dart index 282ccc5..efb57d4 100644 --- a/lib/src/views/camera/camera_preview_components/save_to_gallery.dart +++ b/lib/src/views/camera/camera_preview_components/save_to_gallery.dart @@ -52,13 +52,14 @@ class SaveToGalleryButtonState extends State { await widget.storeImageAsOriginal!(); } - final newMediaFile = await twonlyDB.mediaFilesDao.insertMedia( - MediaFilesCompanion( - type: Value(widget.mediaService.mediaFile.type), - createdAt: Value(clock.now()), - stored: const Value(true), - ), - ); + final newMediaFile = await twonlyDB.mediaFilesDao + .insertOrUpdateMedia( + MediaFilesCompanion( + type: Value(widget.mediaService.mediaFile.type), + createdAt: Value(clock.now()), + stored: const Value(true), + ), + ); if (newMediaFile != null) { final newService = MediaFileService(newMediaFile); diff --git a/lib/src/views/chats/chat_messages.view.dart b/lib/src/views/chats/chat_messages.view.dart index c3c3ac1..dcb1c2b 100644 --- a/lib/src/views/chats/chat_messages.view.dart +++ b/lib/src/views/chats/chat_messages.view.dart @@ -124,7 +124,7 @@ class _ChatMessagesViewState extends State { if (gUser.typingIndicators) { unawaited(sendTypingIndication(widget.groupId, false)); - _nextTypingIndicator = Timer.periodic(const Duration(seconds: 5), ( + _nextTypingIndicator = Timer.periodic(const Duration(seconds: 4), ( _, ) async { await sendTypingIndication(widget.groupId, false); diff --git a/lib/src/views/chats/chat_messages_components/typing_indicator.dart b/lib/src/views/chats/chat_messages_components/typing_indicator.dart index 695b897..da1f725 100644 --- a/lib/src/views/chats/chat_messages_components/typing_indicator.dart +++ b/lib/src/views/chats/chat_messages_components/typing_indicator.dart @@ -112,7 +112,7 @@ class _TypingIndicatorState extends State member.lastChatOpened!, ) .inSeconds <= - 8; + 6; } @override diff --git a/lib/src/views/settings/data_and_storage/import_media.view.dart b/lib/src/views/settings/data_and_storage/import_media.view.dart index a844643..bd8e8a5 100644 --- a/lib/src/views/settings/data_and_storage/import_media.view.dart +++ b/lib/src/views/settings/data_and_storage/import_media.view.dart @@ -111,7 +111,7 @@ class _ImportMediaViewState extends State { continue; } - final mediaFile = await twonlyDB.mediaFilesDao.insertMedia( + final mediaFile = await twonlyDB.mediaFilesDao.insertOrUpdateMedia( MediaFilesCompanion( type: Value(type), createdAt: Value(file.lastModDateTime), From c42ff41eb8adca24ee119189e3b14c1c16a77a6f Mon Sep 17 00:00:00 2001 From: otsmr Date: Sun, 12 Apr 2026 02:22:30 +0200 Subject: [PATCH 4/5] fix notification tap initial page view --- CHANGELOG.md | 5 +++-- lib/src/views/home.view.dart | 12 +++--------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b582b8..544acb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,9 @@ ## 0.1.5 - Fix: Reupload of media files was not working properly -- Fix: Chats where ordered wrongly -- Fix: Typing indicator was not shown always +- Fix: Chats were sometimes ordered wrongly +- Fix: Typing indicator was not always shown +- Fix: Multiple smaller issues ## 0.1.4 diff --git a/lib/src/views/home.view.dart b/lib/src/views/home.view.dart index a4234a0..83a52c6 100644 --- a/lib/src/views/home.view.dart +++ b/lib/src/views/home.view.dart @@ -101,6 +101,7 @@ class HomeViewState extends State { if (mounted) setState(() {}); }; activePageIdx = widget.initialPage; + globalUpdateOfHomeViewPageIndex = (index) { homeViewPageController.jumpToPage(index); setState(() { @@ -111,9 +112,8 @@ class HomeViewState extends State { if (response.payload != null && response.payload!.startsWith(Routes.chats)) { await routerProvider.push(response.payload!); - } else { - globalUpdateOfHomeViewPageIndex(0); } + globalUpdateOfHomeViewPageIndex(0); }); unawaited(_mainCameraController.selectCamera(0, true)); unawaited(initAsync()); @@ -153,20 +153,14 @@ class HomeViewState extends State { if (widget.initialPage == 0 || (notificationAppLaunchDetails != null && notificationAppLaunchDetails.didNotificationLaunchApp)) { - var pushed = false; - if (notificationAppLaunchDetails?.didNotificationLaunchApp ?? false) { final payload = notificationAppLaunchDetails?.notificationResponse?.payload; if (payload != null && payload.startsWith(Routes.chats)) { await routerProvider.push(payload); - pushed = true; + globalUpdateOfHomeViewPageIndex(0); } } - - if (!pushed) { - globalUpdateOfHomeViewPageIndex(0); - } } final draftMedia = await twonlyDB.mediaFilesDao.getDraftMediaFile(); From 2271453d54d29c9ab66d48c919616d4f82df12ac Mon Sep 17 00:00:00 2001 From: otsmr Date: Sun, 12 Apr 2026 02:30:33 +0200 Subject: [PATCH 5/5] bump version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 98816bc..d576d53 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: "twonly, a privacy-friendly way to connect with friends through sec publish_to: 'none' -version: 0.1.4+104 +version: 0.1.5+105 environment: sdk: ^3.11.0