diff --git a/CHANGELOG.md b/CHANGELOG.md
index 811b901..a9d3887 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
# Changelog
+## 0.0.86
+
+- Allows to reopen send images (if send without time limit or enabled auth)
+- Added support for front camera zoom
+- Several bug fixes
+
## 0.0.83
- Improved view of the diagnostic log
diff --git a/README.md b/README.md
index 76c6b52..e66c36d 100644
--- a/README.md
+++ b/README.md
@@ -2,31 +2,26 @@
-This repository contains the complete source code of the [twonly](https://twonly.eu) apps.
+This repository contains the complete source code of the [twonly](https://twonly.eu) app. twonly is a replacement for Snapchat, but its purpose is not to replace instant messaging apps, as there are already [many fantastic alternatives](https://www.messenger-matrix.de/messenger-matrix-en.html) out there. It was started because I liked the basic features of Snapchat, such as opening with the camera, the easy-to-use image editor, and the focus on sending fun pictures to friends. But I was annoyed by Snapchat's forced AI chat, receiving random messages to follow strangers, and not knowing how my sent images/text messages were encrypted, if at all. I am also very critical of the direction in which the US is currently moving and therefore try to avoid US providers wherever possible.
-
-
-
+
+If you decide to give twonly a try, please keep in mind that it is still in its early stages and is currently being developed by a single student. So if you are not satisfied at the moment, please come back later, as it is constantly being improved, and I may one day be able to develop it full-time.
## Features
@@ -42,6 +37,8 @@ This repository contains the complete source code of the [twonly](https://twonly
- For Android: Optional support for [UnifiedPush](https://unifiedpush.org/)
- For Android: Reproducible Builds
- Implementing [Sealed Sender](https://signal.org/blog/sealed-sender/) to minimize metadata
+- Switch from the Signal-Protocol to [MLS](https://github.com/openmls/openmls) for Post-Quantum-Crypto support
+- And, of course, many more features such as dog filters, E2EE cloud backup, and more.
## Security Issues
@@ -49,6 +46,10 @@ If you discover a security issue in twonly, please adhere to the coordinated vul
us your report to security@twonly.eu. We also offer for critical security issues a small bug bounties, but we can not
guarantee a bounty currently :/
+## Contribution
+
+If you have any questions or feature requests, feel free to start a new discussion. Issues are limited to bugs, and for maintainers only.
+
## Development
diff --git a/lib/src/database/daos/groups.dao.dart b/lib/src/database/daos/groups.dao.dart
index b76063c..6821bb8 100644
--- a/lib/src/database/daos/groups.dao.dart
+++ b/lib/src/database/daos/groups.dao.dart
@@ -194,6 +194,14 @@ class GroupsDao extends DatabaseAccessor with _$GroupsDaoMixin {
.watch();
}
+ Stream> watchContactGroupMember(int contactId) {
+ return (select(groupMembers)
+ ..where(
+ (g) => g.contactId.equals(contactId),
+ ))
+ .watch();
+ }
+
Stream watchGroup(String groupId) {
return (select(groups)..where((t) => t.groupId.equals(groupId)))
.watchSingleOrNull();
diff --git a/lib/src/database/daos/receipts.dao.dart b/lib/src/database/daos/receipts.dao.dart
index 397f5d6..26a72e0 100644
--- a/lib/src/database/daos/receipts.dao.dart
+++ b/lib/src/database/daos/receipts.dao.dart
@@ -92,7 +92,9 @@ class ReceiptsDao extends DatabaseAccessor with _$ReceiptsDaoMixin {
..where(
(t) =>
t.ackByServerAt.isNull() |
- t.markForRetry.isSmallerThanValue(markedRetriesTime),
+ t.markForRetry.isSmallerThanValue(markedRetriesTime) |
+ t.markForRetryAfterAccepted
+ .isSmallerThanValue(markedRetriesTime),
))
.get();
}
@@ -109,6 +111,19 @@ class ReceiptsDao extends DatabaseAccessor with _$ReceiptsDaoMixin {
.write(updates);
}
+ Future updateReceiptWidthUserId(
+ int fromUserId,
+ String receiptId,
+ ReceiptsCompanion updates,
+ ) async {
+ await (update(receipts)
+ ..where(
+ (c) =>
+ c.receiptId.equals(receiptId) & c.contactId.equals(fromUserId),
+ ))
+ .write(updates);
+ }
+
Future markMessagesForRetry(int contactId) async {
await (update(receipts)..where((c) => c.contactId.equals(contactId))).write(
ReceiptsCompanion(
diff --git a/lib/src/database/schemas/twonly_db/drift_schema_v6.json b/lib/src/database/schemas/twonly_db/drift_schema_v6.json
new file mode 100644
index 0000000..7d504ad
--- /dev/null
+++ b/lib/src/database/schemas/twonly_db/drift_schema_v6.json
@@ -0,0 +1 @@
+{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.2.0"},"options":{"store_date_time_values_as_text":false},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"contacts","was_declared_in_moor":false,"columns":[{"name":"user_id","getter_name":"userId","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"username","getter_name":"username","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"display_name","getter_name":"displayName","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"nick_name","getter_name":"nickName","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"avatar_svg_compressed","getter_name":"avatarSvgCompressed","moor_type":"blob","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"sender_profile_counter","getter_name":"senderProfileCounter","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"accepted","getter_name":"accepted","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"accepted\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"accepted\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"deleted_by_user","getter_name":"deletedByUser","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"deleted_by_user\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"deleted_by_user\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"requested","getter_name":"requested","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"requested\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"requested\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"blocked","getter_name":"blocked","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"blocked\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"blocked\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"verified","getter_name":"verified","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"verified\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"verified\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"account_deleted","getter_name":"accountDeleted","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"account_deleted\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"account_deleted\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CAST(strftime(\\'%s\\', CURRENT_TIMESTAMP) AS INTEGER)')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[],"explicit_pk":["user_id"]}},{"id":1,"references":[],"type":"table","data":{"name":"groups","was_declared_in_moor":false,"columns":[{"name":"group_id","getter_name":"groupId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_group_admin","getter_name":"isGroupAdmin","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_group_admin\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_group_admin\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"is_direct_chat","getter_name":"isDirectChat","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_direct_chat\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_direct_chat\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"pinned","getter_name":"pinned","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"pinned\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"pinned\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"archived","getter_name":"archived","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"archived\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"archived\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"joined_group","getter_name":"joinedGroup","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"joined_group\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"joined_group\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"left_group","getter_name":"leftGroup","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"left_group\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"left_group\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"deleted_content","getter_name":"deletedContent","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"deleted_content\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"deleted_content\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"state_version_id","getter_name":"stateVersionId","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"state_encryption_key","getter_name":"stateEncryptionKey","moor_type":"blob","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"my_group_private_key","getter_name":"myGroupPrivateKey","moor_type":"blob","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"group_name","getter_name":"groupName","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"draft_message","getter_name":"draftMessage","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"total_media_counter","getter_name":"totalMediaCounter","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"also_best_friend","getter_name":"alsoBestFriend","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"also_best_friend\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"also_best_friend\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"delete_messages_after_milliseconds","getter_name":"deleteMessagesAfterMilliseconds","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('86400000')","default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CAST(strftime(\\'%s\\', CURRENT_TIMESTAMP) AS INTEGER)')","default_client_dart":null,"dsl_features":[]},{"name":"last_message_send","getter_name":"lastMessageSend","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"last_message_received","getter_name":"lastMessageReceived","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"last_flame_counter_change","getter_name":"lastFlameCounterChange","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"last_flame_sync","getter_name":"lastFlameSync","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"flame_counter","getter_name":"flameCounter","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"max_flame_counter","getter_name":"maxFlameCounter","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"max_flame_counter_from","getter_name":"maxFlameCounterFrom","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"last_message_exchange","getter_name":"lastMessageExchange","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CAST(strftime(\\'%s\\', CURRENT_TIMESTAMP) AS INTEGER)')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[],"explicit_pk":["group_id"]}},{"id":2,"references":[],"type":"table","data":{"name":"media_files","was_declared_in_moor":false,"columns":[{"name":"media_id","getter_name":"mediaId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"type","getter_name":"type","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(MediaType.values)","dart_type_name":"MediaType"}},{"name":"upload_state","getter_name":"uploadState","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(UploadState.values)","dart_type_name":"UploadState"}},{"name":"download_state","getter_name":"downloadState","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(DownloadState.values)","dart_type_name":"DownloadState"}},{"name":"requires_authentication","getter_name":"requiresAuthentication","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"requires_authentication\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"requires_authentication\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"stored","getter_name":"stored","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"stored\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"stored\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"is_draft_media","getter_name":"isDraftMedia","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_draft_media\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_draft_media\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"reupload_requested_by","getter_name":"reuploadRequestedBy","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"IntListTypeConverter()","dart_type_name":"List"}},{"name":"display_limit_in_milliseconds","getter_name":"displayLimitInMilliseconds","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"remove_audio","getter_name":"removeAudio","moor_type":"bool","nullable":true,"customConstraints":null,"defaultConstraints":"CHECK (\"remove_audio\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"remove_audio\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"download_token","getter_name":"downloadToken","moor_type":"blob","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"encryption_key","getter_name":"encryptionKey","moor_type":"blob","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"encryption_mac","getter_name":"encryptionMac","moor_type":"blob","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"encryption_nonce","getter_name":"encryptionNonce","moor_type":"blob","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"stored_file_hash","getter_name":"storedFileHash","moor_type":"blob","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CAST(strftime(\\'%s\\', CURRENT_TIMESTAMP) AS INTEGER)')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[],"explicit_pk":["media_id"]}},{"id":3,"references":[1,0,2],"type":"table","data":{"name":"messages","was_declared_in_moor":false,"columns":[{"name":"group_id","getter_name":"groupId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES \"groups\" (group_id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES \"groups\" (group_id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":[{"foreign_key":{"to":{"table":"groups","column":"group_id"},"initially_deferred":false,"on_update":null,"on_delete":"cascade"}}]},{"name":"message_id","getter_name":"messageId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"sender_id","getter_name":"senderId","moor_type":"int","nullable":true,"customConstraints":null,"defaultConstraints":"REFERENCES contacts (user_id)","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES contacts (user_id)"},"default_dart":null,"default_client_dart":null,"dsl_features":[{"foreign_key":{"to":{"table":"contacts","column":"user_id"},"initially_deferred":false,"on_update":null,"on_delete":null}}]},{"name":"type","getter_name":"type","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(MessageType.values)","dart_type_name":"MessageType"}},{"name":"content","getter_name":"content","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"media_id","getter_name":"mediaId","moor_type":"string","nullable":true,"customConstraints":null,"defaultConstraints":"REFERENCES media_files (media_id) ON DELETE SET NULL","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES media_files (media_id) ON DELETE SET NULL"},"default_dart":null,"default_client_dart":null,"dsl_features":[{"foreign_key":{"to":{"table":"media_files","column":"media_id"},"initially_deferred":false,"on_update":null,"on_delete":"setNull"}}]},{"name":"media_stored","getter_name":"mediaStored","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"media_stored\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"media_stored\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"media_reopened","getter_name":"mediaReopened","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"media_reopened\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"media_reopened\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"download_token","getter_name":"downloadToken","moor_type":"blob","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"quotes_message_id","getter_name":"quotesMessageId","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_deleted_from_sender","getter_name":"isDeletedFromSender","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_deleted_from_sender\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"is_deleted_from_sender\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"opened_at","getter_name":"openedAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"opened_by_all","getter_name":"openedByAll","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CAST(strftime(\\'%s\\', CURRENT_TIMESTAMP) AS INTEGER)')","default_client_dart":null,"dsl_features":[]},{"name":"modified_at","getter_name":"modifiedAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"ack_by_user","getter_name":"ackByUser","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"ack_by_server","getter_name":"ackByServer","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[],"explicit_pk":["message_id"]}},{"id":4,"references":[3],"type":"table","data":{"name":"message_histories","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"message_id","getter_name":"messageId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES messages (message_id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES messages (message_id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":[{"foreign_key":{"to":{"table":"messages","column":"message_id"},"initially_deferred":false,"on_update":null,"on_delete":"cascade"}}]},{"name":"contact_id","getter_name":"contactId","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"content","getter_name":"content","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CAST(strftime(\\'%s\\', CURRENT_TIMESTAMP) AS INTEGER)')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[],"explicit_pk":["id"]}},{"id":5,"references":[3,0],"type":"table","data":{"name":"reactions","was_declared_in_moor":false,"columns":[{"name":"message_id","getter_name":"messageId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES messages (message_id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES messages (message_id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":[{"foreign_key":{"to":{"table":"messages","column":"message_id"},"initially_deferred":false,"on_update":null,"on_delete":"cascade"}}]},{"name":"emoji","getter_name":"emoji","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"sender_id","getter_name":"senderId","moor_type":"int","nullable":true,"customConstraints":null,"defaultConstraints":"REFERENCES contacts (user_id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES contacts (user_id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":[{"foreign_key":{"to":{"table":"contacts","column":"user_id"},"initially_deferred":false,"on_update":null,"on_delete":"cascade"}}]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CAST(strftime(\\'%s\\', CURRENT_TIMESTAMP) AS INTEGER)')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[],"explicit_pk":["message_id","sender_id","emoji"]}},{"id":6,"references":[1,0],"type":"table","data":{"name":"group_members","was_declared_in_moor":false,"columns":[{"name":"group_id","getter_name":"groupId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES \"groups\" (group_id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES \"groups\" (group_id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":[{"foreign_key":{"to":{"table":"groups","column":"group_id"},"initially_deferred":false,"on_update":null,"on_delete":"cascade"}}]},{"name":"contact_id","getter_name":"contactId","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES contacts (user_id)","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES contacts (user_id)"},"default_dart":null,"default_client_dart":null,"dsl_features":[{"foreign_key":{"to":{"table":"contacts","column":"user_id"},"initially_deferred":false,"on_update":null,"on_delete":null}}]},{"name":"member_state","getter_name":"memberState","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(MemberState.values)","dart_type_name":"MemberState"}},{"name":"group_public_key","getter_name":"groupPublicKey","moor_type":"blob","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"last_message","getter_name":"lastMessage","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CAST(strftime(\\'%s\\', CURRENT_TIMESTAMP) AS INTEGER)')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[],"explicit_pk":["group_id","contact_id"]}},{"id":7,"references":[0,3],"type":"table","data":{"name":"receipts","was_declared_in_moor":false,"columns":[{"name":"receipt_id","getter_name":"receiptId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"contact_id","getter_name":"contactId","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES contacts (user_id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES contacts (user_id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":[{"foreign_key":{"to":{"table":"contacts","column":"user_id"},"initially_deferred":false,"on_update":null,"on_delete":"cascade"}}]},{"name":"message_id","getter_name":"messageId","moor_type":"string","nullable":true,"customConstraints":null,"defaultConstraints":"REFERENCES messages (message_id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES messages (message_id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":[{"foreign_key":{"to":{"table":"messages","column":"message_id"},"initially_deferred":false,"on_update":null,"on_delete":"cascade"}}]},{"name":"message","getter_name":"message","moor_type":"blob","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"contact_will_sends_receipt","getter_name":"contactWillSendsReceipt","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"contact_will_sends_receipt\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"contact_will_sends_receipt\" IN (0, 1))"},"default_dart":"const CustomExpression('1')","default_client_dart":null,"dsl_features":[]},{"name":"mark_for_retry","getter_name":"markForRetry","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"mark_for_retry_after_accepted","getter_name":"markForRetryAfterAccepted","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"ack_by_server_at","getter_name":"ackByServerAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"retry_count","getter_name":"retryCount","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"last_retry","getter_name":"lastRetry","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CAST(strftime(\\'%s\\', CURRENT_TIMESTAMP) AS INTEGER)')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[],"explicit_pk":["receipt_id"]}},{"id":8,"references":[],"type":"table","data":{"name":"received_receipts","was_declared_in_moor":false,"columns":[{"name":"receipt_id","getter_name":"receiptId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CAST(strftime(\\'%s\\', CURRENT_TIMESTAMP) AS INTEGER)')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[],"explicit_pk":["receipt_id"]}},{"id":9,"references":[],"type":"table","data":{"name":"signal_identity_key_stores","was_declared_in_moor":false,"columns":[{"name":"device_id","getter_name":"deviceId","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"identity_key","getter_name":"identityKey","moor_type":"blob","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CAST(strftime(\\'%s\\', CURRENT_TIMESTAMP) AS INTEGER)')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[],"explicit_pk":["device_id","name"]}},{"id":10,"references":[],"type":"table","data":{"name":"signal_pre_key_stores","was_declared_in_moor":false,"columns":[{"name":"pre_key_id","getter_name":"preKeyId","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"pre_key","getter_name":"preKey","moor_type":"blob","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CAST(strftime(\\'%s\\', CURRENT_TIMESTAMP) AS INTEGER)')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[],"explicit_pk":["pre_key_id"]}},{"id":11,"references":[],"type":"table","data":{"name":"signal_sender_key_stores","was_declared_in_moor":false,"columns":[{"name":"sender_key_name","getter_name":"senderKeyName","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"sender_key","getter_name":"senderKey","moor_type":"blob","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[],"explicit_pk":["sender_key_name"]}},{"id":12,"references":[],"type":"table","data":{"name":"signal_session_stores","was_declared_in_moor":false,"columns":[{"name":"device_id","getter_name":"deviceId","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"session_record","getter_name":"sessionRecord","moor_type":"blob","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CAST(strftime(\\'%s\\', CURRENT_TIMESTAMP) AS INTEGER)')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[],"explicit_pk":["device_id","name"]}},{"id":13,"references":[0],"type":"table","data":{"name":"signal_contact_pre_keys","was_declared_in_moor":false,"columns":[{"name":"contact_id","getter_name":"contactId","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES contacts (user_id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES contacts (user_id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":[{"foreign_key":{"to":{"table":"contacts","column":"user_id"},"initially_deferred":false,"on_update":null,"on_delete":"cascade"}}]},{"name":"pre_key_id","getter_name":"preKeyId","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"pre_key","getter_name":"preKey","moor_type":"blob","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CAST(strftime(\\'%s\\', CURRENT_TIMESTAMP) AS INTEGER)')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[],"explicit_pk":["contact_id","pre_key_id"]}},{"id":14,"references":[0],"type":"table","data":{"name":"signal_contact_signed_pre_keys","was_declared_in_moor":false,"columns":[{"name":"contact_id","getter_name":"contactId","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES contacts (user_id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES contacts (user_id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":[{"foreign_key":{"to":{"table":"contacts","column":"user_id"},"initially_deferred":false,"on_update":null,"on_delete":"cascade"}}]},{"name":"signed_pre_key_id","getter_name":"signedPreKeyId","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"signed_pre_key","getter_name":"signedPreKey","moor_type":"blob","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"signed_pre_key_signature","getter_name":"signedPreKeySignature","moor_type":"blob","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CAST(strftime(\\'%s\\', CURRENT_TIMESTAMP) AS INTEGER)')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[],"explicit_pk":["contact_id"]}},{"id":15,"references":[3],"type":"table","data":{"name":"message_actions","was_declared_in_moor":false,"columns":[{"name":"message_id","getter_name":"messageId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES messages (message_id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES messages (message_id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":[{"foreign_key":{"to":{"table":"messages","column":"message_id"},"initially_deferred":false,"on_update":null,"on_delete":"cascade"}}]},{"name":"contact_id","getter_name":"contactId","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"type","getter_name":"type","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(MessageActionType.values)","dart_type_name":"MessageActionType"}},{"name":"action_at","getter_name":"actionAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CAST(strftime(\\'%s\\', CURRENT_TIMESTAMP) AS INTEGER)')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[],"explicit_pk":["message_id","contact_id","type"]}},{"id":16,"references":[1,0],"type":"table","data":{"name":"group_histories","was_declared_in_moor":false,"columns":[{"name":"group_history_id","getter_name":"groupHistoryId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"group_id","getter_name":"groupId","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES \"groups\" (group_id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES \"groups\" (group_id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":[{"foreign_key":{"to":{"table":"groups","column":"group_id"},"initially_deferred":false,"on_update":null,"on_delete":"cascade"}}]},{"name":"contact_id","getter_name":"contactId","moor_type":"int","nullable":true,"customConstraints":null,"defaultConstraints":"REFERENCES contacts (user_id)","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES contacts (user_id)"},"default_dart":null,"default_client_dart":null,"dsl_features":[{"foreign_key":{"to":{"table":"contacts","column":"user_id"},"initially_deferred":false,"on_update":null,"on_delete":null}}]},{"name":"affected_contact_id","getter_name":"affectedContactId","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"old_group_name","getter_name":"oldGroupName","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"new_group_name","getter_name":"newGroupName","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"new_delete_messages_after_milliseconds","getter_name":"newDeleteMessagesAfterMilliseconds","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"type","getter_name":"type","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(GroupActionType.values)","dart_type_name":"GroupActionType"}},{"name":"action_at","getter_name":"actionAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CAST(strftime(\\'%s\\', CURRENT_TIMESTAMP) AS INTEGER)')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[],"explicit_pk":["group_history_id"]}}]}
\ No newline at end of file
diff --git a/lib/src/database/tables/receipts.table.dart b/lib/src/database/tables/receipts.table.dart
index be8b970..944db15 100644
--- a/lib/src/database/tables/receipts.table.dart
+++ b/lib/src/database/tables/receipts.table.dart
@@ -21,6 +21,7 @@ class Receipts extends Table {
boolean().withDefault(const Constant(true))();
DateTimeColumn get markForRetry => dateTime().nullable()();
+ DateTimeColumn get markForRetryAfterAccepted => dateTime().nullable()();
DateTimeColumn get ackByServerAt => dateTime().nullable()();
diff --git a/lib/src/database/twonly.db.dart b/lib/src/database/twonly.db.dart
index 4cfb944..3cace23 100644
--- a/lib/src/database/twonly.db.dart
+++ b/lib/src/database/twonly.db.dart
@@ -68,7 +68,7 @@ class TwonlyDB extends _$TwonlyDB {
TwonlyDB.forTesting(DatabaseConnection super.connection);
@override
- int get schemaVersion => 5;
+ int get schemaVersion => 6;
static QueryExecutor _openConnection() {
return driftDatabase(
@@ -111,6 +111,12 @@ class TwonlyDB extends _$TwonlyDB {
schema.mediaFiles.storedFileHash,
);
},
+ from5To6: (m, schema) async {
+ await m.addColumn(
+ schema.receipts,
+ schema.receipts.markForRetryAfterAccepted,
+ );
+ },
),
);
}
diff --git a/lib/src/database/twonly.db.g.dart b/lib/src/database/twonly.db.g.dart
index a9eea8c..0ae2daf 100644
--- a/lib/src/database/twonly.db.g.dart
+++ b/lib/src/database/twonly.db.g.dart
@@ -4598,6 +4598,13 @@ class $ReceiptsTable extends Receipts with TableInfo<$ReceiptsTable, Receipt> {
late final GeneratedColumn markForRetry = GeneratedColumn(
'mark_for_retry', aliasedName, true,
type: DriftSqlType.dateTime, requiredDuringInsert: false);
+ static const VerificationMeta _markForRetryAfterAcceptedMeta =
+ const VerificationMeta('markForRetryAfterAccepted');
+ @override
+ late final GeneratedColumn markForRetryAfterAccepted =
+ GeneratedColumn(
+ 'mark_for_retry_after_accepted', aliasedName, true,
+ type: DriftSqlType.dateTime, requiredDuringInsert: false);
static const VerificationMeta _ackByServerAtMeta =
const VerificationMeta('ackByServerAt');
@override
@@ -4634,6 +4641,7 @@ class $ReceiptsTable extends Receipts with TableInfo<$ReceiptsTable, Receipt> {
message,
contactWillSendsReceipt,
markForRetry,
+ markForRetryAfterAccepted,
ackByServerAt,
retryCount,
lastRetry,
@@ -4684,6 +4692,13 @@ class $ReceiptsTable extends Receipts with TableInfo<$ReceiptsTable, Receipt> {
markForRetry.isAcceptableOrUnknown(
data['mark_for_retry']!, _markForRetryMeta));
}
+ if (data.containsKey('mark_for_retry_after_accepted')) {
+ context.handle(
+ _markForRetryAfterAcceptedMeta,
+ markForRetryAfterAccepted.isAcceptableOrUnknown(
+ data['mark_for_retry_after_accepted']!,
+ _markForRetryAfterAcceptedMeta));
+ }
if (data.containsKey('ack_by_server_at')) {
context.handle(
_ackByServerAtMeta,
@@ -4726,6 +4741,9 @@ class $ReceiptsTable extends Receipts with TableInfo<$ReceiptsTable, Receipt> {
data['${effectivePrefix}contact_will_sends_receipt'])!,
markForRetry: attachedDatabase.typeMapping.read(
DriftSqlType.dateTime, data['${effectivePrefix}mark_for_retry']),
+ markForRetryAfterAccepted: attachedDatabase.typeMapping.read(
+ DriftSqlType.dateTime,
+ data['${effectivePrefix}mark_for_retry_after_accepted']),
ackByServerAt: attachedDatabase.typeMapping.read(
DriftSqlType.dateTime, data['${effectivePrefix}ack_by_server_at']),
retryCount: attachedDatabase.typeMapping
@@ -4752,6 +4770,7 @@ class Receipt extends DataClass implements Insertable {
final Uint8List message;
final bool contactWillSendsReceipt;
final DateTime? markForRetry;
+ final DateTime? markForRetryAfterAccepted;
final DateTime? ackByServerAt;
final int retryCount;
final DateTime? lastRetry;
@@ -4763,6 +4782,7 @@ class Receipt extends DataClass implements Insertable {
required this.message,
required this.contactWillSendsReceipt,
this.markForRetry,
+ this.markForRetryAfterAccepted,
this.ackByServerAt,
required this.retryCount,
this.lastRetry,
@@ -4780,6 +4800,10 @@ class Receipt extends DataClass implements Insertable {
if (!nullToAbsent || markForRetry != null) {
map['mark_for_retry'] = Variable(markForRetry);
}
+ if (!nullToAbsent || markForRetryAfterAccepted != null) {
+ map['mark_for_retry_after_accepted'] =
+ Variable(markForRetryAfterAccepted);
+ }
if (!nullToAbsent || ackByServerAt != null) {
map['ack_by_server_at'] = Variable(ackByServerAt);
}
@@ -4803,6 +4827,10 @@ class Receipt extends DataClass implements Insertable {
markForRetry: markForRetry == null && nullToAbsent
? const Value.absent()
: Value(markForRetry),
+ markForRetryAfterAccepted:
+ markForRetryAfterAccepted == null && nullToAbsent
+ ? const Value.absent()
+ : Value(markForRetryAfterAccepted),
ackByServerAt: ackByServerAt == null && nullToAbsent
? const Value.absent()
: Value(ackByServerAt),
@@ -4825,6 +4853,8 @@ class Receipt extends DataClass implements Insertable {
contactWillSendsReceipt:
serializer.fromJson(json['contactWillSendsReceipt']),
markForRetry: serializer.fromJson(json['markForRetry']),
+ markForRetryAfterAccepted:
+ serializer.fromJson(json['markForRetryAfterAccepted']),
ackByServerAt: serializer.fromJson(json['ackByServerAt']),
retryCount: serializer.fromJson(json['retryCount']),
lastRetry: serializer.fromJson(json['lastRetry']),
@@ -4842,6 +4872,8 @@ class Receipt extends DataClass implements Insertable {
'contactWillSendsReceipt':
serializer.toJson(contactWillSendsReceipt),
'markForRetry': serializer.toJson(markForRetry),
+ 'markForRetryAfterAccepted':
+ serializer.toJson(markForRetryAfterAccepted),
'ackByServerAt': serializer.toJson(ackByServerAt),
'retryCount': serializer.toJson(retryCount),
'lastRetry': serializer.toJson(lastRetry),
@@ -4856,6 +4888,7 @@ class Receipt extends DataClass implements Insertable {
Uint8List? message,
bool? contactWillSendsReceipt,
Value markForRetry = const Value.absent(),
+ Value markForRetryAfterAccepted = const Value.absent(),
Value ackByServerAt = const Value.absent(),
int? retryCount,
Value lastRetry = const Value.absent(),
@@ -4869,6 +4902,9 @@ class Receipt extends DataClass implements Insertable {
contactWillSendsReceipt ?? this.contactWillSendsReceipt,
markForRetry:
markForRetry.present ? markForRetry.value : this.markForRetry,
+ markForRetryAfterAccepted: markForRetryAfterAccepted.present
+ ? markForRetryAfterAccepted.value
+ : this.markForRetryAfterAccepted,
ackByServerAt:
ackByServerAt.present ? ackByServerAt.value : this.ackByServerAt,
retryCount: retryCount ?? this.retryCount,
@@ -4887,6 +4923,9 @@ class Receipt extends DataClass implements Insertable {
markForRetry: data.markForRetry.present
? data.markForRetry.value
: this.markForRetry,
+ markForRetryAfterAccepted: data.markForRetryAfterAccepted.present
+ ? data.markForRetryAfterAccepted.value
+ : this.markForRetryAfterAccepted,
ackByServerAt: data.ackByServerAt.present
? data.ackByServerAt.value
: this.ackByServerAt,
@@ -4906,6 +4945,7 @@ class Receipt extends DataClass implements Insertable {
..write('message: $message, ')
..write('contactWillSendsReceipt: $contactWillSendsReceipt, ')
..write('markForRetry: $markForRetry, ')
+ ..write('markForRetryAfterAccepted: $markForRetryAfterAccepted, ')
..write('ackByServerAt: $ackByServerAt, ')
..write('retryCount: $retryCount, ')
..write('lastRetry: $lastRetry, ')
@@ -4922,6 +4962,7 @@ class Receipt extends DataClass implements Insertable {
$driftBlobEquality.hash(message),
contactWillSendsReceipt,
markForRetry,
+ markForRetryAfterAccepted,
ackByServerAt,
retryCount,
lastRetry,
@@ -4936,6 +4977,7 @@ class Receipt extends DataClass implements Insertable {
$driftBlobEquality.equals(other.message, this.message) &&
other.contactWillSendsReceipt == this.contactWillSendsReceipt &&
other.markForRetry == this.markForRetry &&
+ other.markForRetryAfterAccepted == this.markForRetryAfterAccepted &&
other.ackByServerAt == this.ackByServerAt &&
other.retryCount == this.retryCount &&
other.lastRetry == this.lastRetry &&
@@ -4949,6 +4991,7 @@ class ReceiptsCompanion extends UpdateCompanion {
final Value message;
final Value contactWillSendsReceipt;
final Value markForRetry;
+ final Value markForRetryAfterAccepted;
final Value ackByServerAt;
final Value retryCount;
final Value lastRetry;
@@ -4961,6 +5004,7 @@ class ReceiptsCompanion extends UpdateCompanion {
this.message = const Value.absent(),
this.contactWillSendsReceipt = const Value.absent(),
this.markForRetry = const Value.absent(),
+ this.markForRetryAfterAccepted = const Value.absent(),
this.ackByServerAt = const Value.absent(),
this.retryCount = const Value.absent(),
this.lastRetry = const Value.absent(),
@@ -4974,6 +5018,7 @@ class ReceiptsCompanion extends UpdateCompanion {
required Uint8List message,
this.contactWillSendsReceipt = const Value.absent(),
this.markForRetry = const Value.absent(),
+ this.markForRetryAfterAccepted = const Value.absent(),
this.ackByServerAt = const Value.absent(),
this.retryCount = const Value.absent(),
this.lastRetry = const Value.absent(),
@@ -4989,6 +5034,7 @@ class ReceiptsCompanion extends UpdateCompanion {
Expression? message,
Expression? contactWillSendsReceipt,
Expression? markForRetry,
+ Expression? markForRetryAfterAccepted,
Expression? ackByServerAt,
Expression? retryCount,
Expression? lastRetry,
@@ -5003,6 +5049,8 @@ class ReceiptsCompanion extends UpdateCompanion {
if (contactWillSendsReceipt != null)
'contact_will_sends_receipt': contactWillSendsReceipt,
if (markForRetry != null) 'mark_for_retry': markForRetry,
+ if (markForRetryAfterAccepted != null)
+ 'mark_for_retry_after_accepted': markForRetryAfterAccepted,
if (ackByServerAt != null) 'ack_by_server_at': ackByServerAt,
if (retryCount != null) 'retry_count': retryCount,
if (lastRetry != null) 'last_retry': lastRetry,
@@ -5018,6 +5066,7 @@ class ReceiptsCompanion extends UpdateCompanion {
Value? message,
Value? contactWillSendsReceipt,
Value? markForRetry,
+ Value? markForRetryAfterAccepted,
Value? ackByServerAt,
Value? retryCount,
Value? lastRetry,
@@ -5031,6 +5080,8 @@ class ReceiptsCompanion extends UpdateCompanion {
contactWillSendsReceipt:
contactWillSendsReceipt ?? this.contactWillSendsReceipt,
markForRetry: markForRetry ?? this.markForRetry,
+ markForRetryAfterAccepted:
+ markForRetryAfterAccepted ?? this.markForRetryAfterAccepted,
ackByServerAt: ackByServerAt ?? this.ackByServerAt,
retryCount: retryCount ?? this.retryCount,
lastRetry: lastRetry ?? this.lastRetry,
@@ -5061,6 +5112,10 @@ class ReceiptsCompanion extends UpdateCompanion {
if (markForRetry.present) {
map['mark_for_retry'] = Variable(markForRetry.value);
}
+ if (markForRetryAfterAccepted.present) {
+ map['mark_for_retry_after_accepted'] =
+ Variable(markForRetryAfterAccepted.value);
+ }
if (ackByServerAt.present) {
map['ack_by_server_at'] = Variable(ackByServerAt.value);
}
@@ -5088,6 +5143,7 @@ class ReceiptsCompanion extends UpdateCompanion {
..write('message: $message, ')
..write('contactWillSendsReceipt: $contactWillSendsReceipt, ')
..write('markForRetry: $markForRetry, ')
+ ..write('markForRetryAfterAccepted: $markForRetryAfterAccepted, ')
..write('ackByServerAt: $ackByServerAt, ')
..write('retryCount: $retryCount, ')
..write('lastRetry: $lastRetry, ')
@@ -11771,6 +11827,7 @@ typedef $$ReceiptsTableCreateCompanionBuilder = ReceiptsCompanion Function({
required Uint8List message,
Value contactWillSendsReceipt,
Value markForRetry,
+ Value markForRetryAfterAccepted,
Value ackByServerAt,
Value retryCount,
Value lastRetry,
@@ -11784,6 +11841,7 @@ typedef $$ReceiptsTableUpdateCompanionBuilder = ReceiptsCompanion Function({
Value message,
Value contactWillSendsReceipt,
Value markForRetry,
+ Value markForRetryAfterAccepted,
Value ackByServerAt,
Value retryCount,
Value lastRetry,
@@ -11848,6 +11906,10 @@ class $$ReceiptsTableFilterComposer
ColumnFilters get markForRetry => $composableBuilder(
column: $table.markForRetry, builder: (column) => ColumnFilters(column));
+ ColumnFilters get markForRetryAfterAccepted => $composableBuilder(
+ column: $table.markForRetryAfterAccepted,
+ builder: (column) => ColumnFilters(column));
+
ColumnFilters get ackByServerAt => $composableBuilder(
column: $table.ackByServerAt, builder: (column) => ColumnFilters(column));
@@ -11924,6 +11986,10 @@ class $$ReceiptsTableOrderingComposer
column: $table.markForRetry,
builder: (column) => ColumnOrderings(column));
+ ColumnOrderings get markForRetryAfterAccepted => $composableBuilder(
+ column: $table.markForRetryAfterAccepted,
+ builder: (column) => ColumnOrderings(column));
+
ColumnOrderings get ackByServerAt => $composableBuilder(
column: $table.ackByServerAt,
builder: (column) => ColumnOrderings(column));
@@ -11999,6 +12065,9 @@ class $$ReceiptsTableAnnotationComposer
GeneratedColumn get markForRetry => $composableBuilder(
column: $table.markForRetry, builder: (column) => column);
+ GeneratedColumn get markForRetryAfterAccepted => $composableBuilder(
+ column: $table.markForRetryAfterAccepted, builder: (column) => column);
+
GeneratedColumn get ackByServerAt => $composableBuilder(
column: $table.ackByServerAt, builder: (column) => column);
@@ -12081,6 +12150,7 @@ class $$ReceiptsTableTableManager extends RootTableManager<
Value message = const Value.absent(),
Value contactWillSendsReceipt = const Value.absent(),
Value markForRetry = const Value.absent(),
+ Value markForRetryAfterAccepted = const Value.absent(),
Value ackByServerAt = const Value.absent(),
Value retryCount = const Value.absent(),
Value lastRetry = const Value.absent(),
@@ -12094,6 +12164,7 @@ class $$ReceiptsTableTableManager extends RootTableManager<
message: message,
contactWillSendsReceipt: contactWillSendsReceipt,
markForRetry: markForRetry,
+ markForRetryAfterAccepted: markForRetryAfterAccepted,
ackByServerAt: ackByServerAt,
retryCount: retryCount,
lastRetry: lastRetry,
@@ -12107,6 +12178,7 @@ class $$ReceiptsTableTableManager extends RootTableManager<
required Uint8List message,
Value contactWillSendsReceipt = const Value.absent(),
Value markForRetry = const Value.absent(),
+ Value markForRetryAfterAccepted = const Value.absent(),
Value ackByServerAt = const Value.absent(),
Value retryCount = const Value.absent(),
Value lastRetry = const Value.absent(),
@@ -12120,6 +12192,7 @@ class $$ReceiptsTableTableManager extends RootTableManager<
message: message,
contactWillSendsReceipt: contactWillSendsReceipt,
markForRetry: markForRetry,
+ markForRetryAfterAccepted: markForRetryAfterAccepted,
ackByServerAt: ackByServerAt,
retryCount: retryCount,
lastRetry: lastRetry,
diff --git a/lib/src/database/twonly.db.steps.dart b/lib/src/database/twonly.db.steps.dart
index ed8ea29..24ecbc9 100644
--- a/lib/src/database/twonly.db.steps.dart
+++ b/lib/src/database/twonly.db.steps.dart
@@ -2393,11 +2393,423 @@ class Shape19 extends i0.VersionedTable {
i1.GeneratedColumn _column_103(String aliasedName) =>
i1.GeneratedColumn('mark_for_retry', aliasedName, true,
type: i1.DriftSqlType.dateTime);
+
+final class Schema6 extends i0.VersionedSchema {
+ Schema6({required super.database}) : super(version: 6);
+ @override
+ late final List entities = [
+ contacts,
+ groups,
+ mediaFiles,
+ messages,
+ messageHistories,
+ reactions,
+ groupMembers,
+ receipts,
+ receivedReceipts,
+ signalIdentityKeyStores,
+ signalPreKeyStores,
+ signalSenderKeyStores,
+ signalSessionStores,
+ signalContactPreKeys,
+ signalContactSignedPreKeys,
+ messageActions,
+ groupHistories,
+ ];
+ late final Shape0 contacts = Shape0(
+ source: i0.VersionedTable(
+ entityName: 'contacts',
+ withoutRowId: false,
+ isStrict: false,
+ tableConstraints: [
+ 'PRIMARY KEY(user_id)',
+ ],
+ columns: [
+ _column_0,
+ _column_1,
+ _column_2,
+ _column_3,
+ _column_4,
+ _column_5,
+ _column_6,
+ _column_7,
+ _column_8,
+ _column_9,
+ _column_10,
+ _column_11,
+ _column_12,
+ ],
+ attachedDatabase: database,
+ ),
+ alias: null);
+ late final Shape17 groups = Shape17(
+ source: i0.VersionedTable(
+ entityName: 'groups',
+ withoutRowId: false,
+ isStrict: false,
+ tableConstraints: [
+ 'PRIMARY KEY(group_id)',
+ ],
+ columns: [
+ _column_13,
+ _column_14,
+ _column_15,
+ _column_16,
+ _column_17,
+ _column_18,
+ _column_19,
+ _column_20,
+ _column_21,
+ _column_22,
+ _column_23,
+ _column_24,
+ _column_100,
+ _column_25,
+ _column_26,
+ _column_27,
+ _column_12,
+ _column_28,
+ _column_29,
+ _column_30,
+ _column_31,
+ _column_32,
+ _column_33,
+ _column_34,
+ _column_35,
+ ],
+ attachedDatabase: database,
+ ),
+ alias: null);
+ late final Shape18 mediaFiles = Shape18(
+ source: i0.VersionedTable(
+ entityName: 'media_files',
+ withoutRowId: false,
+ isStrict: false,
+ tableConstraints: [
+ 'PRIMARY KEY(media_id)',
+ ],
+ columns: [
+ _column_36,
+ _column_37,
+ _column_38,
+ _column_39,
+ _column_40,
+ _column_41,
+ _column_42,
+ _column_43,
+ _column_44,
+ _column_45,
+ _column_46,
+ _column_47,
+ _column_48,
+ _column_49,
+ _column_102,
+ _column_12,
+ ],
+ attachedDatabase: database,
+ ),
+ alias: null);
+ late final Shape3 messages = Shape3(
+ source: i0.VersionedTable(
+ entityName: 'messages',
+ withoutRowId: false,
+ isStrict: false,
+ tableConstraints: [
+ 'PRIMARY KEY(message_id)',
+ ],
+ columns: [
+ _column_50,
+ _column_51,
+ _column_52,
+ _column_37,
+ _column_53,
+ _column_54,
+ _column_55,
+ _column_56,
+ _column_46,
+ _column_57,
+ _column_58,
+ _column_59,
+ _column_60,
+ _column_12,
+ _column_61,
+ _column_62,
+ _column_63,
+ ],
+ attachedDatabase: database,
+ ),
+ alias: null);
+ late final Shape4 messageHistories = Shape4(
+ source: i0.VersionedTable(
+ entityName: 'message_histories',
+ withoutRowId: false,
+ isStrict: false,
+ tableConstraints: [
+ 'PRIMARY KEY(id)',
+ ],
+ columns: [
+ _column_64,
+ _column_65,
+ _column_66,
+ _column_53,
+ _column_12,
+ ],
+ attachedDatabase: database,
+ ),
+ alias: null);
+ late final Shape5 reactions = Shape5(
+ source: i0.VersionedTable(
+ entityName: 'reactions',
+ withoutRowId: false,
+ isStrict: false,
+ tableConstraints: [
+ 'PRIMARY KEY(message_id, sender_id, emoji)',
+ ],
+ columns: [
+ _column_65,
+ _column_67,
+ _column_68,
+ _column_12,
+ ],
+ attachedDatabase: database,
+ ),
+ alias: null);
+ late final Shape6 groupMembers = Shape6(
+ source: i0.VersionedTable(
+ entityName: 'group_members',
+ withoutRowId: false,
+ isStrict: false,
+ tableConstraints: [
+ 'PRIMARY KEY(group_id, contact_id)',
+ ],
+ columns: [
+ _column_50,
+ _column_69,
+ _column_70,
+ _column_71,
+ _column_72,
+ _column_12,
+ ],
+ attachedDatabase: database,
+ ),
+ alias: null);
+ late final Shape20 receipts = Shape20(
+ source: i0.VersionedTable(
+ entityName: 'receipts',
+ withoutRowId: false,
+ isStrict: false,
+ tableConstraints: [
+ 'PRIMARY KEY(receipt_id)',
+ ],
+ columns: [
+ _column_73,
+ _column_74,
+ _column_75,
+ _column_76,
+ _column_77,
+ _column_103,
+ _column_104,
+ _column_78,
+ _column_79,
+ _column_80,
+ _column_12,
+ ],
+ attachedDatabase: database,
+ ),
+ alias: null);
+ late final Shape8 receivedReceipts = Shape8(
+ source: i0.VersionedTable(
+ entityName: 'received_receipts',
+ withoutRowId: false,
+ isStrict: false,
+ tableConstraints: [
+ 'PRIMARY KEY(receipt_id)',
+ ],
+ columns: [
+ _column_73,
+ _column_12,
+ ],
+ attachedDatabase: database,
+ ),
+ alias: null);
+ late final Shape9 signalIdentityKeyStores = Shape9(
+ source: i0.VersionedTable(
+ entityName: 'signal_identity_key_stores',
+ withoutRowId: false,
+ isStrict: false,
+ tableConstraints: [
+ 'PRIMARY KEY(device_id, name)',
+ ],
+ columns: [
+ _column_81,
+ _column_82,
+ _column_83,
+ _column_12,
+ ],
+ attachedDatabase: database,
+ ),
+ alias: null);
+ late final Shape10 signalPreKeyStores = Shape10(
+ source: i0.VersionedTable(
+ entityName: 'signal_pre_key_stores',
+ withoutRowId: false,
+ isStrict: false,
+ tableConstraints: [
+ 'PRIMARY KEY(pre_key_id)',
+ ],
+ columns: [
+ _column_84,
+ _column_85,
+ _column_12,
+ ],
+ attachedDatabase: database,
+ ),
+ alias: null);
+ late final Shape11 signalSenderKeyStores = Shape11(
+ source: i0.VersionedTable(
+ entityName: 'signal_sender_key_stores',
+ withoutRowId: false,
+ isStrict: false,
+ tableConstraints: [
+ 'PRIMARY KEY(sender_key_name)',
+ ],
+ columns: [
+ _column_86,
+ _column_87,
+ ],
+ attachedDatabase: database,
+ ),
+ alias: null);
+ late final Shape12 signalSessionStores = Shape12(
+ source: i0.VersionedTable(
+ entityName: 'signal_session_stores',
+ withoutRowId: false,
+ isStrict: false,
+ tableConstraints: [
+ 'PRIMARY KEY(device_id, name)',
+ ],
+ columns: [
+ _column_81,
+ _column_82,
+ _column_88,
+ _column_12,
+ ],
+ attachedDatabase: database,
+ ),
+ alias: null);
+ late final Shape13 signalContactPreKeys = Shape13(
+ source: i0.VersionedTable(
+ entityName: 'signal_contact_pre_keys',
+ withoutRowId: false,
+ isStrict: false,
+ tableConstraints: [
+ 'PRIMARY KEY(contact_id, pre_key_id)',
+ ],
+ columns: [
+ _column_74,
+ _column_84,
+ _column_85,
+ _column_12,
+ ],
+ attachedDatabase: database,
+ ),
+ alias: null);
+ late final Shape14 signalContactSignedPreKeys = Shape14(
+ source: i0.VersionedTable(
+ entityName: 'signal_contact_signed_pre_keys',
+ withoutRowId: false,
+ isStrict: false,
+ tableConstraints: [
+ 'PRIMARY KEY(contact_id)',
+ ],
+ columns: [
+ _column_74,
+ _column_89,
+ _column_90,
+ _column_91,
+ _column_12,
+ ],
+ attachedDatabase: database,
+ ),
+ alias: null);
+ late final Shape15 messageActions = Shape15(
+ source: i0.VersionedTable(
+ entityName: 'message_actions',
+ withoutRowId: false,
+ isStrict: false,
+ tableConstraints: [
+ 'PRIMARY KEY(message_id, contact_id, type)',
+ ],
+ columns: [
+ _column_65,
+ _column_92,
+ _column_37,
+ _column_93,
+ ],
+ attachedDatabase: database,
+ ),
+ alias: null);
+ late final Shape16 groupHistories = Shape16(
+ source: i0.VersionedTable(
+ entityName: 'group_histories',
+ withoutRowId: false,
+ isStrict: false,
+ tableConstraints: [
+ 'PRIMARY KEY(group_history_id)',
+ ],
+ columns: [
+ _column_94,
+ _column_50,
+ _column_95,
+ _column_101,
+ _column_97,
+ _column_98,
+ _column_99,
+ _column_37,
+ _column_93,
+ ],
+ attachedDatabase: database,
+ ),
+ alias: null);
+}
+
+class Shape20 extends i0.VersionedTable {
+ Shape20({required super.source, required super.alias}) : super.aliased();
+ i1.GeneratedColumn get receiptId =>
+ columnsByName['receipt_id']! as i1.GeneratedColumn;
+ i1.GeneratedColumn get contactId =>
+ columnsByName['contact_id']! as i1.GeneratedColumn;
+ i1.GeneratedColumn get messageId =>
+ columnsByName['message_id']! as i1.GeneratedColumn;
+ i1.GeneratedColumn get message =>
+ columnsByName['message']! as i1.GeneratedColumn;
+ i1.GeneratedColumn get contactWillSendsReceipt =>
+ columnsByName['contact_will_sends_receipt']! as i1.GeneratedColumn;
+ i1.GeneratedColumn get markForRetry =>
+ columnsByName['mark_for_retry']! as i1.GeneratedColumn;
+ i1.GeneratedColumn get markForRetryAfterAccepted =>
+ columnsByName['mark_for_retry_after_accepted']!
+ as i1.GeneratedColumn;
+ i1.GeneratedColumn get ackByServerAt =>
+ columnsByName['ack_by_server_at']! as i1.GeneratedColumn;
+ i1.GeneratedColumn get retryCount =>
+ columnsByName['retry_count']! as i1.GeneratedColumn;
+ i1.GeneratedColumn get lastRetry =>
+ columnsByName['last_retry']! as i1.GeneratedColumn;
+ i1.GeneratedColumn get createdAt =>
+ columnsByName['created_at']! as i1.GeneratedColumn;
+}
+
+i1.GeneratedColumn _column_104(String aliasedName) =>
+ i1.GeneratedColumn(
+ 'mark_for_retry_after_accepted', aliasedName, true,
+ type: i1.DriftSqlType.dateTime);
i0.MigrationStepWithVersion migrationSteps({
required Future Function(i1.Migrator m, Schema2 schema) from1To2,
required Future Function(i1.Migrator m, Schema3 schema) from2To3,
required Future Function(i1.Migrator m, Schema4 schema) from3To4,
required Future Function(i1.Migrator m, Schema5 schema) from4To5,
+ required Future Function(i1.Migrator m, Schema6 schema) from5To6,
}) {
return (currentVersion, database) async {
switch (currentVersion) {
@@ -2421,6 +2833,11 @@ i0.MigrationStepWithVersion migrationSteps({
final migrator = i1.Migrator(database, schema);
await from4To5(migrator, schema);
return 5;
+ case 5:
+ final schema = Schema6(database: database);
+ final migrator = i1.Migrator(database, schema);
+ await from5To6(migrator, schema);
+ return 6;
default:
throw ArgumentError.value('Unknown migration from $currentVersion');
}
@@ -2432,6 +2849,7 @@ i1.OnUpgrade stepByStep({
required Future Function(i1.Migrator m, Schema3 schema) from2To3,
required Future Function(i1.Migrator m, Schema4 schema) from3To4,
required Future Function(i1.Migrator m, Schema5 schema) from4To5,
+ required Future Function(i1.Migrator m, Schema6 schema) from5To6,
}) =>
i0.VersionedSchema.stepByStepHelper(
step: migrationSteps(
@@ -2439,4 +2857,5 @@ i1.OnUpgrade stepByStep({
from2To3: from2To3,
from3To4: from3To4,
from4To5: from4To5,
+ from5To6: from5To6,
));
diff --git a/lib/src/localization/generated/app_localizations.dart b/lib/src/localization/generated/app_localizations.dart
index 3577707..2a6f815 100644
--- a/lib/src/localization/generated/app_localizations.dart
+++ b/lib/src/localization/generated/app_localizations.dart
@@ -508,6 +508,12 @@ abstract class AppLocalizations {
/// **'Unpin'**
String get contextMenuUnpin;
+ /// No description provided for @contextMenuViewAgain.
+ ///
+ /// In en, this message translates to:
+ /// **'View again'**
+ String get contextMenuViewAgain;
+
/// No description provided for @mediaViewerAuthReason.
///
/// In en, this message translates to:
@@ -1021,7 +1027,7 @@ abstract class AppLocalizations {
/// No description provided for @contactRemoveBody.
///
/// In en, this message translates to:
- /// **'Remove the user and permanently delete the chat and all associated media files. This will also delete YOUR ACCOUNT FROM YOUR CONTACT\'S PHONE.'**
+ /// **'Permanently remove the user. If the user tries to send you a new message, you will have to accept the user again first.'**
String get contactRemoveBody;
/// No description provided for @undo.
@@ -2887,8 +2893,14 @@ abstract class AppLocalizations {
/// No description provided for @additionalUserAddError.
///
/// In en, this message translates to:
- /// **'Could not add additional user. Try again later.'**
- String get additionalUserAddError;
+ /// **'{username} could not be added, please try again later.'**
+ String additionalUserAddError(Object username);
+
+ /// No description provided for @additionalUserAddErrorNotInFreePlan.
+ ///
+ /// In en, this message translates to:
+ /// **'{username} is already on a paid plan and therefore could not be added.'**
+ String additionalUserAddErrorNotInFreePlan(Object username);
/// No description provided for @additionalUserAddButton.
///
@@ -2925,6 +2937,24 @@ abstract class AppLocalizations {
/// In en, this message translates to:
/// **'Store as default'**
String get storeAsDefault;
+
+ /// No description provided for @deleteUserErrorMessage.
+ ///
+ /// In en, this message translates to:
+ /// **'You can only delete the contact once the direct chat has been deleted and the contact is no longer a member of a group.'**
+ String get deleteUserErrorMessage;
+
+ /// No description provided for @groupSizeLimitError.
+ ///
+ /// In en, this message translates to:
+ /// **'Currently, group size is limited to {size} people!'**
+ String groupSizeLimitError(Object size);
+
+ /// No description provided for @authRequestReopenImage.
+ ///
+ /// In en, this message translates to:
+ /// **'You must authenticate to reopen the image.'**
+ String get authRequestReopenImage;
}
class _AppLocalizationsDelegate
diff --git a/lib/src/localization/generated/app_localizations_de.dart b/lib/src/localization/generated/app_localizations_de.dart
index 1dc85d2..c328e26 100644
--- a/lib/src/localization/generated/app_localizations_de.dart
+++ b/lib/src/localization/generated/app_localizations_de.dart
@@ -234,6 +234,9 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get contextMenuUnpin => 'Lösen';
+ @override
+ String get contextMenuViewAgain => 'Nochmal anschauen';
+
@override
String get mediaViewerAuthReason =>
'Bitte authentifiziere dich, um diesen twonly zu sehen!';
@@ -517,7 +520,7 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get contactRemoveBody =>
- 'Entferne den Benutzer und lösche den Chat sowie alle zugehörigen Mediendateien dauerhaft. Dadurch wird auch DEIN KONTO VON DEM TELEFON DEINES KONTAKTS gelöscht.';
+ 'Den Benutzer dauerhaft entfernen. Wenn der Benutzer versucht, dir eine neue Nachricht zu senden, musst du den Benutzer erst wieder akzeptieren.';
@override
String get undo => 'Rückgängig';
@@ -1601,8 +1604,14 @@ class AppLocalizationsDe extends AppLocalizations {
String get privacyPolicy => 'Datenschutzerklärung';
@override
- String get additionalUserAddError =>
- 'Es konnte kein zusätzlicher Nutzer hinzugefügt werden. Versuche es später noch einmal.';
+ String additionalUserAddError(Object username) {
+ return '$username konnte nicht hinzugefügt werden, bitte versuche es später noch einmal.';
+ }
+
+ @override
+ String additionalUserAddErrorNotInFreePlan(Object username) {
+ return '$username hat bereits einen bezahlten Tarif und konnte daher nicht hinzugefügt werden.';
+ }
@override
String additionalUserAddButton(Object limit, Object used) {
@@ -1627,4 +1636,17 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get storeAsDefault => 'Als Standard speichern';
+
+ @override
+ String get deleteUserErrorMessage =>
+ 'Du kannst den Kontakt erst löschen, wenn der direkte Chat gelöscht wurde und der Kontakt nicht mehr Mitglied einer Gruppe ist.';
+
+ @override
+ String groupSizeLimitError(Object size) {
+ return 'Derzeit ist die Gruppengröße auf $size Personen begrenzt!';
+ }
+
+ @override
+ String get authRequestReopenImage =>
+ 'Um das Bild erneut zu öffnen, musst du dich authentifizieren.';
}
diff --git a/lib/src/localization/generated/app_localizations_en.dart b/lib/src/localization/generated/app_localizations_en.dart
index a981ffc..13f17c7 100644
--- a/lib/src/localization/generated/app_localizations_en.dart
+++ b/lib/src/localization/generated/app_localizations_en.dart
@@ -232,6 +232,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get contextMenuUnpin => 'Unpin';
+ @override
+ String get contextMenuViewAgain => 'View again';
+
@override
String get mediaViewerAuthReason => 'Please authenticate to see this twonly!';
@@ -512,7 +515,7 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get contactRemoveBody =>
- 'Remove the user and permanently delete the chat and all associated media files. This will also delete YOUR ACCOUNT FROM YOUR CONTACT\'S PHONE.';
+ 'Permanently remove the user. If the user tries to send you a new message, you will have to accept the user again first.';
@override
String get undo => 'Undo';
@@ -1590,8 +1593,14 @@ class AppLocalizationsEn extends AppLocalizations {
String get privacyPolicy => 'Privacy policy';
@override
- String get additionalUserAddError =>
- 'Could not add additional user. Try again later.';
+ String additionalUserAddError(Object username) {
+ return '$username could not be added, please try again later.';
+ }
+
+ @override
+ String additionalUserAddErrorNotInFreePlan(Object username) {
+ return '$username is already on a paid plan and therefore could not be added.';
+ }
@override
String additionalUserAddButton(Object limit, Object used) {
@@ -1615,4 +1624,17 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get storeAsDefault => 'Store as default';
+
+ @override
+ String get deleteUserErrorMessage =>
+ 'You can only delete the contact once the direct chat has been deleted and the contact is no longer a member of a group.';
+
+ @override
+ String groupSizeLimitError(Object size) {
+ return 'Currently, group size is limited to $size people!';
+ }
+
+ @override
+ String get authRequestReopenImage =>
+ 'You must authenticate to reopen the image.';
}
diff --git a/lib/src/localization/generated/app_localizations_sv.dart b/lib/src/localization/generated/app_localizations_sv.dart
index 96c0a60..7b15afc 100644
--- a/lib/src/localization/generated/app_localizations_sv.dart
+++ b/lib/src/localization/generated/app_localizations_sv.dart
@@ -232,6 +232,9 @@ class AppLocalizationsSv extends AppLocalizations {
@override
String get contextMenuUnpin => 'Unpin';
+ @override
+ String get contextMenuViewAgain => 'View again';
+
@override
String get mediaViewerAuthReason => 'Please authenticate to see this twonly!';
@@ -512,7 +515,7 @@ class AppLocalizationsSv extends AppLocalizations {
@override
String get contactRemoveBody =>
- 'Remove the user and permanently delete the chat and all associated media files. This will also delete YOUR ACCOUNT FROM YOUR CONTACT\'S PHONE.';
+ 'Permanently remove the user. If the user tries to send you a new message, you will have to accept the user again first.';
@override
String get undo => 'Undo';
@@ -1590,8 +1593,14 @@ class AppLocalizationsSv extends AppLocalizations {
String get privacyPolicy => 'Privacy policy';
@override
- String get additionalUserAddError =>
- 'Could not add additional user. Try again later.';
+ String additionalUserAddError(Object username) {
+ return '$username could not be added, please try again later.';
+ }
+
+ @override
+ String additionalUserAddErrorNotInFreePlan(Object username) {
+ return '$username is already on a paid plan and therefore could not be added.';
+ }
@override
String additionalUserAddButton(Object limit, Object used) {
@@ -1615,4 +1624,17 @@ class AppLocalizationsSv extends AppLocalizations {
@override
String get storeAsDefault => 'Store as default';
+
+ @override
+ String get deleteUserErrorMessage =>
+ 'You can only delete the contact once the direct chat has been deleted and the contact is no longer a member of a group.';
+
+ @override
+ String groupSizeLimitError(Object size) {
+ return 'Currently, group size is limited to $size people!';
+ }
+
+ @override
+ String get authRequestReopenImage =>
+ 'You must authenticate to reopen the image.';
}
diff --git a/lib/src/localization/translations b/lib/src/localization/translations
index 775c0ff..20f3c2f 160000
--- a/lib/src/localization/translations
+++ b/lib/src/localization/translations
@@ -1 +1 @@
-Subproject commit 775c0ffd9523177478681ecff4e8c4613bf57ee3
+Subproject commit 20f3c2f0a49e4c9be452ecbc84d98054c92974e1
diff --git a/lib/src/model/protobuf/client/generated/messages.pb.dart b/lib/src/model/protobuf/client/generated/messages.pb.dart
index 662a9e9..a02260f 100644
--- a/lib/src/model/protobuf/client/generated/messages.pb.dart
+++ b/lib/src/model/protobuf/client/generated/messages.pb.dart
@@ -311,6 +311,82 @@ class PlaintextContent extends $pb.GeneratedMessage {
PlaintextContent_RetryErrorMessage ensureRetryControlError() => $_ensure(1);
}
+class EncryptedContent_ErrorMessages extends $pb.GeneratedMessage {
+ factory EncryptedContent_ErrorMessages({
+ EncryptedContent_ErrorMessages_Type? type,
+ $core.String? relatedReceiptId,
+ }) {
+ final result = create();
+ if (type != null) result.type = type;
+ if (relatedReceiptId != null) result.relatedReceiptId = relatedReceiptId;
+ return result;
+ }
+
+ EncryptedContent_ErrorMessages._();
+
+ factory EncryptedContent_ErrorMessages.fromBuffer($core.List<$core.int> data,
+ [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
+ create()..mergeFromBuffer(data, registry);
+ factory EncryptedContent_ErrorMessages.fromJson($core.String json,
+ [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
+ create()..mergeFromJson(json, registry);
+
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo(
+ _omitMessageNames ? '' : 'EncryptedContent.ErrorMessages',
+ createEmptyInstance: create)
+ ..e(
+ 1, _omitFieldNames ? '' : 'type', $pb.PbFieldType.OE,
+ defaultOrMaker: EncryptedContent_ErrorMessages_Type
+ .ERROR_PROCESSING_MESSAGE_CREATED_ACCOUNT_REQUEST_INSTEAD,
+ valueOf: EncryptedContent_ErrorMessages_Type.valueOf,
+ enumValues: EncryptedContent_ErrorMessages_Type.values)
+ ..aOS(2, _omitFieldNames ? '' : 'relatedReceiptId')
+ ..hasRequiredFields = false;
+
+ @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
+ EncryptedContent_ErrorMessages clone() =>
+ EncryptedContent_ErrorMessages()..mergeFromMessage(this);
+ @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
+ EncryptedContent_ErrorMessages copyWith(
+ void Function(EncryptedContent_ErrorMessages) updates) =>
+ super.copyWith(
+ (message) => updates(message as EncryptedContent_ErrorMessages))
+ as EncryptedContent_ErrorMessages;
+
+ @$core.override
+ $pb.BuilderInfo get info_ => _i;
+
+ @$core.pragma('dart2js:noInline')
+ static EncryptedContent_ErrorMessages create() =>
+ EncryptedContent_ErrorMessages._();
+ @$core.override
+ EncryptedContent_ErrorMessages createEmptyInstance() => create();
+ static $pb.PbList createRepeated() =>
+ $pb.PbList();
+ @$core.pragma('dart2js:noInline')
+ static EncryptedContent_ErrorMessages getDefault() => _defaultInstance ??=
+ $pb.GeneratedMessage.$_defaultFor(create);
+ static EncryptedContent_ErrorMessages? _defaultInstance;
+
+ @$pb.TagNumber(1)
+ EncryptedContent_ErrorMessages_Type get type => $_getN(0);
+ @$pb.TagNumber(1)
+ set type(EncryptedContent_ErrorMessages_Type value) => $_setField(1, value);
+ @$pb.TagNumber(1)
+ $core.bool hasType() => $_has(0);
+ @$pb.TagNumber(1)
+ void clearType() => $_clearField(1);
+
+ @$pb.TagNumber(2)
+ $core.String get relatedReceiptId => $_getSZ(1);
+ @$pb.TagNumber(2)
+ set relatedReceiptId($core.String value) => $_setString(1, value);
+ @$pb.TagNumber(2)
+ $core.bool hasRelatedReceiptId() => $_has(1);
+ @$pb.TagNumber(2)
+ void clearRelatedReceiptId() => $_clearField(2);
+}
+
class EncryptedContent_GroupCreate extends $pb.GeneratedMessage {
factory EncryptedContent_GroupCreate({
$core.List<$core.int>? stateKey,
@@ -1519,6 +1595,7 @@ class EncryptedContent extends $pb.GeneratedMessage {
EncryptedContent_GroupJoin? groupJoin,
EncryptedContent_GroupUpdate? groupUpdate,
EncryptedContent_ResendGroupPublicKey? resendGroupPublicKey,
+ EncryptedContent_ErrorMessages? errorMessages,
}) {
final result = create();
if (groupId != null) result.groupId = groupId;
@@ -1539,6 +1616,7 @@ class EncryptedContent extends $pb.GeneratedMessage {
if (groupUpdate != null) result.groupUpdate = groupUpdate;
if (resendGroupPublicKey != null)
result.resendGroupPublicKey = resendGroupPublicKey;
+ if (errorMessages != null) result.errorMessages = errorMessages;
return result;
}
@@ -1599,6 +1677,9 @@ class EncryptedContent extends $pb.GeneratedMessage {
17, _omitFieldNames ? '' : 'resendGroupPublicKey',
protoName: 'resendGroupPublicKey',
subBuilder: EncryptedContent_ResendGroupPublicKey.create)
+ ..aOM(
+ 18, _omitFieldNames ? '' : 'errorMessages',
+ subBuilder: EncryptedContent_ErrorMessages.create)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
@@ -1797,6 +1878,18 @@ class EncryptedContent extends $pb.GeneratedMessage {
@$pb.TagNumber(17)
EncryptedContent_ResendGroupPublicKey ensureResendGroupPublicKey() =>
$_ensure(15);
+
+ @$pb.TagNumber(18)
+ EncryptedContent_ErrorMessages get errorMessages => $_getN(16);
+ @$pb.TagNumber(18)
+ set errorMessages(EncryptedContent_ErrorMessages value) =>
+ $_setField(18, value);
+ @$pb.TagNumber(18)
+ $core.bool hasErrorMessages() => $_has(16);
+ @$pb.TagNumber(18)
+ void clearErrorMessages() => $_clearField(18);
+ @$pb.TagNumber(18)
+ EncryptedContent_ErrorMessages ensureErrorMessages() => $_ensure(16);
}
const $core.bool _omitFieldNames =
diff --git a/lib/src/model/protobuf/client/generated/messages.pbenum.dart b/lib/src/model/protobuf/client/generated/messages.pbenum.dart
index c2fe7b1..8277780 100644
--- a/lib/src/model/protobuf/client/generated/messages.pbenum.dart
+++ b/lib/src/model/protobuf/client/generated/messages.pbenum.dart
@@ -65,6 +65,32 @@ class PlaintextContent_DecryptionErrorMessage_Type extends $pb.ProtobufEnum {
const PlaintextContent_DecryptionErrorMessage_Type._(super.value, super.name);
}
+class EncryptedContent_ErrorMessages_Type extends $pb.ProtobufEnum {
+ static const EncryptedContent_ErrorMessages_Type
+ ERROR_PROCESSING_MESSAGE_CREATED_ACCOUNT_REQUEST_INSTEAD =
+ EncryptedContent_ErrorMessages_Type._(
+ 0,
+ _omitEnumNames
+ ? ''
+ : 'ERROR_PROCESSING_MESSAGE_CREATED_ACCOUNT_REQUEST_INSTEAD');
+ static const EncryptedContent_ErrorMessages_Type UNKNOWN_MESSAGE_TYPE =
+ EncryptedContent_ErrorMessages_Type._(
+ 2, _omitEnumNames ? '' : 'UNKNOWN_MESSAGE_TYPE');
+
+ static const $core.List values =
+ [
+ ERROR_PROCESSING_MESSAGE_CREATED_ACCOUNT_REQUEST_INSTEAD,
+ UNKNOWN_MESSAGE_TYPE,
+ ];
+
+ static final $core.Map<$core.int, EncryptedContent_ErrorMessages_Type>
+ _byValue = $pb.ProtobufEnum.initByValue(values);
+ static EncryptedContent_ErrorMessages_Type? valueOf($core.int value) =>
+ _byValue[value];
+
+ const EncryptedContent_ErrorMessages_Type._(super.value, super.name);
+}
+
class EncryptedContent_MessageUpdate_Type extends $pb.ProtobufEnum {
static const EncryptedContent_MessageUpdate_Type DELETE =
EncryptedContent_MessageUpdate_Type._(0, _omitEnumNames ? '' : 'DELETE');
diff --git a/lib/src/model/protobuf/client/generated/messages.pbjson.dart b/lib/src/model/protobuf/client/generated/messages.pbjson.dart
index f1403a9..0331672 100644
--- a/lib/src/model/protobuf/client/generated/messages.pbjson.dart
+++ b/lib/src/model/protobuf/client/generated/messages.pbjson.dart
@@ -306,8 +306,19 @@ const EncryptedContent$json = {
'10': 'resendGroupPublicKey',
'17': true
},
+ {
+ '1': 'error_messages',
+ '3': 18,
+ '4': 1,
+ '5': 11,
+ '6': '.EncryptedContent.ErrorMessages',
+ '9': 16,
+ '10': 'errorMessages',
+ '17': true
+ },
],
'3': [
+ EncryptedContent_ErrorMessages$json,
EncryptedContent_GroupCreate$json,
EncryptedContent_GroupJoin$json,
EncryptedContent_ResendGroupPublicKey$json,
@@ -339,6 +350,39 @@ const EncryptedContent$json = {
{'1': '_groupJoin'},
{'1': '_groupUpdate'},
{'1': '_resendGroupPublicKey'},
+ {'1': '_error_messages'},
+ ],
+};
+
+@$core.Deprecated('Use encryptedContentDescriptor instead')
+const EncryptedContent_ErrorMessages$json = {
+ '1': 'ErrorMessages',
+ '2': [
+ {
+ '1': 'type',
+ '3': 1,
+ '4': 1,
+ '5': 14,
+ '6': '.EncryptedContent.ErrorMessages.Type',
+ '10': 'type'
+ },
+ {
+ '1': 'related_receipt_id',
+ '3': 2,
+ '4': 1,
+ '5': 9,
+ '10': 'relatedReceiptId'
+ },
+ ],
+ '4': [EncryptedContent_ErrorMessages_Type$json],
+};
+
+@$core.Deprecated('Use encryptedContentDescriptor instead')
+const EncryptedContent_ErrorMessages_Type$json = {
+ '1': 'Type',
+ '2': [
+ {'1': 'ERROR_PROCESSING_MESSAGE_CREATED_ACCOUNT_REQUEST_INSTEAD', '2': 0},
+ {'1': 'UNKNOWN_MESSAGE_TYPE', '2': 2},
],
};
@@ -772,57 +816,62 @@ final $typed_data.Uint8List encryptedContentDescriptor = $convert.base64Decode(
'AQESRAoLZ3JvdXBVcGRhdGUYECABKAsyHS5FbmNyeXB0ZWRDb250ZW50Lkdyb3VwVXBkYXRlSA'
'5SC2dyb3VwVXBkYXRliAEBEl8KFHJlc2VuZEdyb3VwUHVibGljS2V5GBEgASgLMiYuRW5jcnlw'
'dGVkQ29udGVudC5SZXNlbmRHcm91cFB1YmxpY0tleUgPUhRyZXNlbmRHcm91cFB1YmxpY0tleY'
- 'gBARpRCgtHcm91cENyZWF0ZRIaCghzdGF0ZUtleRgDIAEoDFIIc3RhdGVLZXkSJgoOZ3JvdXBQ'
- 'dWJsaWNLZXkYBCABKAxSDmdyb3VwUHVibGljS2V5GjMKCUdyb3VwSm9pbhImCg5ncm91cFB1Ym'
- 'xpY0tleRgBIAEoDFIOZ3JvdXBQdWJsaWNLZXkaFgoUUmVzZW5kR3JvdXBQdWJsaWNLZXkatgIK'
- 'C0dyb3VwVXBkYXRlEigKD2dyb3VwQWN0aW9uVHlwZRgBIAEoCVIPZ3JvdXBBY3Rpb25UeXBlEj'
- 'EKEWFmZmVjdGVkQ29udGFjdElkGAIgASgDSABSEWFmZmVjdGVkQ29udGFjdElkiAEBEicKDG5l'
- 'd0dyb3VwTmFtZRgDIAEoCUgBUgxuZXdHcm91cE5hbWWIAQESUwoibmV3RGVsZXRlTWVzc2FnZX'
- 'NBZnRlck1pbGxpc2Vjb25kcxgEIAEoA0gCUiJuZXdEZWxldGVNZXNzYWdlc0FmdGVyTWlsbGlz'
- 'ZWNvbmRziAEBQhQKEl9hZmZlY3RlZENvbnRhY3RJZEIPCg1fbmV3R3JvdXBOYW1lQiUKI19uZX'
- 'dEZWxldGVNZXNzYWdlc0FmdGVyTWlsbGlzZWNvbmRzGqkBCgtUZXh0TWVzc2FnZRIoCg9zZW5k'
- 'ZXJNZXNzYWdlSWQYASABKAlSD3NlbmRlck1lc3NhZ2VJZBISCgR0ZXh0GAIgASgJUgR0ZXh0Eh'
- 'wKCXRpbWVzdGFtcBgDIAEoA1IJdGltZXN0YW1wEisKDnF1b3RlTWVzc2FnZUlkGAQgASgJSABS'
- 'DnF1b3RlTWVzc2FnZUlkiAEBQhEKD19xdW90ZU1lc3NhZ2VJZBpiCghSZWFjdGlvbhIoCg90YX'
- 'JnZXRNZXNzYWdlSWQYASABKAlSD3RhcmdldE1lc3NhZ2VJZBIUCgVlbW9qaRgCIAEoCVIFZW1v'
- 'amkSFgoGcmVtb3ZlGAMgASgIUgZyZW1vdmUatwIKDU1lc3NhZ2VVcGRhdGUSOAoEdHlwZRgBIA'
- 'EoDjIkLkVuY3J5cHRlZENvbnRlbnQuTWVzc2FnZVVwZGF0ZS5UeXBlUgR0eXBlEi0KD3NlbmRl'
- 'ck1lc3NhZ2VJZBgCIAEoCUgAUg9zZW5kZXJNZXNzYWdlSWSIAQESOgoYbXVsdGlwbGVUYXJnZX'
- 'RNZXNzYWdlSWRzGAMgAygJUhhtdWx0aXBsZVRhcmdldE1lc3NhZ2VJZHMSFwoEdGV4dBgEIAEo'
- 'CUgBUgR0ZXh0iAEBEhwKCXRpbWVzdGFtcBgFIAEoA1IJdGltZXN0YW1wIi0KBFR5cGUSCgoGRE'
- 'VMRVRFEAASDQoJRURJVF9URVhUEAESCgoGT1BFTkVEEAJCEgoQX3NlbmRlck1lc3NhZ2VJZEIH'
- 'CgVfdGV4dBqXBQoFTWVkaWESKAoPc2VuZGVyTWVzc2FnZUlkGAEgASgJUg9zZW5kZXJNZXNzYW'
- 'dlSWQSMAoEdHlwZRgCIAEoDjIcLkVuY3J5cHRlZENvbnRlbnQuTWVkaWEuVHlwZVIEdHlwZRJD'
- 'ChpkaXNwbGF5TGltaXRJbk1pbGxpc2Vjb25kcxgDIAEoA0gAUhpkaXNwbGF5TGltaXRJbk1pbG'
- 'xpc2Vjb25kc4gBARI2ChZyZXF1aXJlc0F1dGhlbnRpY2F0aW9uGAQgASgIUhZyZXF1aXJlc0F1'
- 'dGhlbnRpY2F0aW9uEhwKCXRpbWVzdGFtcBgFIAEoA1IJdGltZXN0YW1wEisKDnF1b3RlTWVzc2'
- 'FnZUlkGAYgASgJSAFSDnF1b3RlTWVzc2FnZUlkiAEBEikKDWRvd25sb2FkVG9rZW4YByABKAxI'
- 'AlINZG93bmxvYWRUb2tlbogBARIpCg1lbmNyeXB0aW9uS2V5GAggASgMSANSDWVuY3J5cHRpb2'
- '5LZXmIAQESKQoNZW5jcnlwdGlvbk1hYxgJIAEoDEgEUg1lbmNyeXB0aW9uTWFjiAEBEi0KD2Vu'
- 'Y3J5cHRpb25Ob25jZRgKIAEoDEgFUg9lbmNyeXB0aW9uTm9uY2WIAQEiPgoEVHlwZRIMCghSRV'
- 'VQTE9BRBAAEgkKBUlNQUdFEAESCQoFVklERU8QAhIHCgNHSUYQAxIJCgVBVURJTxAEQh0KG19k'
- 'aXNwbGF5TGltaXRJbk1pbGxpc2Vjb25kc0IRCg9fcXVvdGVNZXNzYWdlSWRCEAoOX2Rvd25sb2'
- 'FkVG9rZW5CEAoOX2VuY3J5cHRpb25LZXlCEAoOX2VuY3J5cHRpb25NYWNCEgoQX2VuY3J5cHRp'
- 'b25Ob25jZRqnAQoLTWVkaWFVcGRhdGUSNgoEdHlwZRgBIAEoDjIiLkVuY3J5cHRlZENvbnRlbn'
- 'QuTWVkaWFVcGRhdGUuVHlwZVIEdHlwZRIoCg90YXJnZXRNZXNzYWdlSWQYAiABKAlSD3Rhcmdl'
- 'dE1lc3NhZ2VJZCI2CgRUeXBlEgwKCFJFT1BFTkVEEAASCgoGU1RPUkVEEAESFAoQREVDUllQVE'
- 'lPTl9FUlJPUhACGngKDkNvbnRhY3RSZXF1ZXN0EjkKBHR5cGUYASABKA4yJS5FbmNyeXB0ZWRD'
- 'b250ZW50LkNvbnRhY3RSZXF1ZXN0LlR5cGVSBHR5cGUiKwoEVHlwZRILCgdSRVFVRVNUEAASCg'
- 'oGUkVKRUNUEAESCgoGQUNDRVBUEAIangIKDUNvbnRhY3RVcGRhdGUSOAoEdHlwZRgBIAEoDjIk'
- 'LkVuY3J5cHRlZENvbnRlbnQuQ29udGFjdFVwZGF0ZS5UeXBlUgR0eXBlEjUKE2F2YXRhclN2Z0'
- 'NvbXByZXNzZWQYAiABKAxIAFITYXZhdGFyU3ZnQ29tcHJlc3NlZIgBARIfCgh1c2VybmFtZRgD'
- 'IAEoCUgBUgh1c2VybmFtZYgBARIlCgtkaXNwbGF5TmFtZRgEIAEoCUgCUgtkaXNwbGF5TmFtZY'
- 'gBASIfCgRUeXBlEgsKB1JFUVVFU1QQABIKCgZVUERBVEUQAUIWChRfYXZhdGFyU3ZnQ29tcHJl'
- 'c3NlZEILCglfdXNlcm5hbWVCDgoMX2Rpc3BsYXlOYW1lGtUBCghQdXNoS2V5cxIzCgR0eXBlGA'
- 'EgASgOMh8uRW5jcnlwdGVkQ29udGVudC5QdXNoS2V5cy5UeXBlUgR0eXBlEhkKBWtleUlkGAIg'
- 'ASgDSABSBWtleUlkiAEBEhUKA2tleRgDIAEoDEgBUgNrZXmIAQESIQoJY3JlYXRlZEF0GAQgAS'
- 'gDSAJSCWNyZWF0ZWRBdIgBASIfCgRUeXBlEgsKB1JFUVVFU1QQABIKCgZVUERBVEUQAUIICgZf'
- 'a2V5SWRCBgoEX2tleUIMCgpfY3JlYXRlZEF0GqkBCglGbGFtZVN5bmMSIgoMZmxhbWVDb3VudG'
- 'VyGAEgASgDUgxmbGFtZUNvdW50ZXISNgoWbGFzdEZsYW1lQ291bnRlckNoYW5nZRgCIAEoA1IW'
- 'bGFzdEZsYW1lQ291bnRlckNoYW5nZRIeCgpiZXN0RnJpZW5kGAMgASgIUgpiZXN0RnJpZW5kEi'
- 'AKC2ZvcmNlVXBkYXRlGAQgASgIUgtmb3JjZVVwZGF0ZUIKCghfZ3JvdXBJZEIPCg1faXNEaXJl'
- 'Y3RDaGF0QhcKFV9zZW5kZXJQcm9maWxlQ291bnRlckIQCg5fbWVzc2FnZVVwZGF0ZUIICgZfbW'
- 'VkaWFCDgoMX21lZGlhVXBkYXRlQhAKDl9jb250YWN0VXBkYXRlQhEKD19jb250YWN0UmVxdWVz'
- 'dEIMCgpfZmxhbWVTeW5jQgsKCV9wdXNoS2V5c0ILCglfcmVhY3Rpb25CDgoMX3RleHRNZXNzYW'
- 'dlQg4KDF9ncm91cENyZWF0ZUIMCgpfZ3JvdXBKb2luQg4KDF9ncm91cFVwZGF0ZUIXChVfcmVz'
- 'ZW5kR3JvdXBQdWJsaWNLZXk=');
+ 'gBARJLCg5lcnJvcl9tZXNzYWdlcxgSIAEoCzIfLkVuY3J5cHRlZENvbnRlbnQuRXJyb3JNZXNz'
+ 'YWdlc0gQUg1lcnJvck1lc3NhZ2VziAEBGtcBCg1FcnJvck1lc3NhZ2VzEjgKBHR5cGUYASABKA'
+ '4yJC5FbmNyeXB0ZWRDb250ZW50LkVycm9yTWVzc2FnZXMuVHlwZVIEdHlwZRIsChJyZWxhdGVk'
+ 'X3JlY2VpcHRfaWQYAiABKAlSEHJlbGF0ZWRSZWNlaXB0SWQiXgoEVHlwZRI8CjhFUlJPUl9QUk'
+ '9DRVNTSU5HX01FU1NBR0VfQ1JFQVRFRF9BQ0NPVU5UX1JFUVVFU1RfSU5TVEVBRBAAEhgKFFVO'
+ 'S05PV05fTUVTU0FHRV9UWVBFEAIaUQoLR3JvdXBDcmVhdGUSGgoIc3RhdGVLZXkYAyABKAxSCH'
+ 'N0YXRlS2V5EiYKDmdyb3VwUHVibGljS2V5GAQgASgMUg5ncm91cFB1YmxpY0tleRozCglHcm91'
+ 'cEpvaW4SJgoOZ3JvdXBQdWJsaWNLZXkYASABKAxSDmdyb3VwUHVibGljS2V5GhYKFFJlc2VuZE'
+ 'dyb3VwUHVibGljS2V5GrYCCgtHcm91cFVwZGF0ZRIoCg9ncm91cEFjdGlvblR5cGUYASABKAlS'
+ 'D2dyb3VwQWN0aW9uVHlwZRIxChFhZmZlY3RlZENvbnRhY3RJZBgCIAEoA0gAUhFhZmZlY3RlZE'
+ 'NvbnRhY3RJZIgBARInCgxuZXdHcm91cE5hbWUYAyABKAlIAVIMbmV3R3JvdXBOYW1liAEBElMK'
+ 'Im5ld0RlbGV0ZU1lc3NhZ2VzQWZ0ZXJNaWxsaXNlY29uZHMYBCABKANIAlIibmV3RGVsZXRlTW'
+ 'Vzc2FnZXNBZnRlck1pbGxpc2Vjb25kc4gBAUIUChJfYWZmZWN0ZWRDb250YWN0SWRCDwoNX25l'
+ 'd0dyb3VwTmFtZUIlCiNfbmV3RGVsZXRlTWVzc2FnZXNBZnRlck1pbGxpc2Vjb25kcxqpAQoLVG'
+ 'V4dE1lc3NhZ2USKAoPc2VuZGVyTWVzc2FnZUlkGAEgASgJUg9zZW5kZXJNZXNzYWdlSWQSEgoE'
+ 'dGV4dBgCIAEoCVIEdGV4dBIcCgl0aW1lc3RhbXAYAyABKANSCXRpbWVzdGFtcBIrCg5xdW90ZU'
+ '1lc3NhZ2VJZBgEIAEoCUgAUg5xdW90ZU1lc3NhZ2VJZIgBAUIRCg9fcXVvdGVNZXNzYWdlSWQa'
+ 'YgoIUmVhY3Rpb24SKAoPdGFyZ2V0TWVzc2FnZUlkGAEgASgJUg90YXJnZXRNZXNzYWdlSWQSFA'
+ 'oFZW1vamkYAiABKAlSBWVtb2ppEhYKBnJlbW92ZRgDIAEoCFIGcmVtb3ZlGrcCCg1NZXNzYWdl'
+ 'VXBkYXRlEjgKBHR5cGUYASABKA4yJC5FbmNyeXB0ZWRDb250ZW50Lk1lc3NhZ2VVcGRhdGUuVH'
+ 'lwZVIEdHlwZRItCg9zZW5kZXJNZXNzYWdlSWQYAiABKAlIAFIPc2VuZGVyTWVzc2FnZUlkiAEB'
+ 'EjoKGG11bHRpcGxlVGFyZ2V0TWVzc2FnZUlkcxgDIAMoCVIYbXVsdGlwbGVUYXJnZXRNZXNzYW'
+ 'dlSWRzEhcKBHRleHQYBCABKAlIAVIEdGV4dIgBARIcCgl0aW1lc3RhbXAYBSABKANSCXRpbWVz'
+ 'dGFtcCItCgRUeXBlEgoKBkRFTEVURRAAEg0KCUVESVRfVEVYVBABEgoKBk9QRU5FRBACQhIKEF'
+ '9zZW5kZXJNZXNzYWdlSWRCBwoFX3RleHQalwUKBU1lZGlhEigKD3NlbmRlck1lc3NhZ2VJZBgB'
+ 'IAEoCVIPc2VuZGVyTWVzc2FnZUlkEjAKBHR5cGUYAiABKA4yHC5FbmNyeXB0ZWRDb250ZW50Lk'
+ '1lZGlhLlR5cGVSBHR5cGUSQwoaZGlzcGxheUxpbWl0SW5NaWxsaXNlY29uZHMYAyABKANIAFIa'
+ 'ZGlzcGxheUxpbWl0SW5NaWxsaXNlY29uZHOIAQESNgoWcmVxdWlyZXNBdXRoZW50aWNhdGlvbh'
+ 'gEIAEoCFIWcmVxdWlyZXNBdXRoZW50aWNhdGlvbhIcCgl0aW1lc3RhbXAYBSABKANSCXRpbWVz'
+ 'dGFtcBIrCg5xdW90ZU1lc3NhZ2VJZBgGIAEoCUgBUg5xdW90ZU1lc3NhZ2VJZIgBARIpCg1kb3'
+ 'dubG9hZFRva2VuGAcgASgMSAJSDWRvd25sb2FkVG9rZW6IAQESKQoNZW5jcnlwdGlvbktleRgI'
+ 'IAEoDEgDUg1lbmNyeXB0aW9uS2V5iAEBEikKDWVuY3J5cHRpb25NYWMYCSABKAxIBFINZW5jcn'
+ 'lwdGlvbk1hY4gBARItCg9lbmNyeXB0aW9uTm9uY2UYCiABKAxIBVIPZW5jcnlwdGlvbk5vbmNl'
+ 'iAEBIj4KBFR5cGUSDAoIUkVVUExPQUQQABIJCgVJTUFHRRABEgkKBVZJREVPEAISBwoDR0lGEA'
+ 'MSCQoFQVVESU8QBEIdChtfZGlzcGxheUxpbWl0SW5NaWxsaXNlY29uZHNCEQoPX3F1b3RlTWVz'
+ 'c2FnZUlkQhAKDl9kb3dubG9hZFRva2VuQhAKDl9lbmNyeXB0aW9uS2V5QhAKDl9lbmNyeXB0aW'
+ '9uTWFjQhIKEF9lbmNyeXB0aW9uTm9uY2UapwEKC01lZGlhVXBkYXRlEjYKBHR5cGUYASABKA4y'
+ 'Ii5FbmNyeXB0ZWRDb250ZW50Lk1lZGlhVXBkYXRlLlR5cGVSBHR5cGUSKAoPdGFyZ2V0TWVzc2'
+ 'FnZUlkGAIgASgJUg90YXJnZXRNZXNzYWdlSWQiNgoEVHlwZRIMCghSRU9QRU5FRBAAEgoKBlNU'
+ 'T1JFRBABEhQKEERFQ1JZUFRJT05fRVJST1IQAhp4Cg5Db250YWN0UmVxdWVzdBI5CgR0eXBlGA'
+ 'EgASgOMiUuRW5jcnlwdGVkQ29udGVudC5Db250YWN0UmVxdWVzdC5UeXBlUgR0eXBlIisKBFR5'
+ 'cGUSCwoHUkVRVUVTVBAAEgoKBlJFSkVDVBABEgoKBkFDQ0VQVBACGp4CCg1Db250YWN0VXBkYX'
+ 'RlEjgKBHR5cGUYASABKA4yJC5FbmNyeXB0ZWRDb250ZW50LkNvbnRhY3RVcGRhdGUuVHlwZVIE'
+ 'dHlwZRI1ChNhdmF0YXJTdmdDb21wcmVzc2VkGAIgASgMSABSE2F2YXRhclN2Z0NvbXByZXNzZW'
+ 'SIAQESHwoIdXNlcm5hbWUYAyABKAlIAVIIdXNlcm5hbWWIAQESJQoLZGlzcGxheU5hbWUYBCAB'
+ 'KAlIAlILZGlzcGxheU5hbWWIAQEiHwoEVHlwZRILCgdSRVFVRVNUEAASCgoGVVBEQVRFEAFCFg'
+ 'oUX2F2YXRhclN2Z0NvbXByZXNzZWRCCwoJX3VzZXJuYW1lQg4KDF9kaXNwbGF5TmFtZRrVAQoI'
+ 'UHVzaEtleXMSMwoEdHlwZRgBIAEoDjIfLkVuY3J5cHRlZENvbnRlbnQuUHVzaEtleXMuVHlwZV'
+ 'IEdHlwZRIZCgVrZXlJZBgCIAEoA0gAUgVrZXlJZIgBARIVCgNrZXkYAyABKAxIAVIDa2V5iAEB'
+ 'EiEKCWNyZWF0ZWRBdBgEIAEoA0gCUgljcmVhdGVkQXSIAQEiHwoEVHlwZRILCgdSRVFVRVNUEA'
+ 'ASCgoGVVBEQVRFEAFCCAoGX2tleUlkQgYKBF9rZXlCDAoKX2NyZWF0ZWRBdBqpAQoJRmxhbWVT'
+ 'eW5jEiIKDGZsYW1lQ291bnRlchgBIAEoA1IMZmxhbWVDb3VudGVyEjYKFmxhc3RGbGFtZUNvdW'
+ '50ZXJDaGFuZ2UYAiABKANSFmxhc3RGbGFtZUNvdW50ZXJDaGFuZ2USHgoKYmVzdEZyaWVuZBgD'
+ 'IAEoCFIKYmVzdEZyaWVuZBIgCgtmb3JjZVVwZGF0ZRgEIAEoCFILZm9yY2VVcGRhdGVCCgoIX2'
+ 'dyb3VwSWRCDwoNX2lzRGlyZWN0Q2hhdEIXChVfc2VuZGVyUHJvZmlsZUNvdW50ZXJCEAoOX21l'
+ 'c3NhZ2VVcGRhdGVCCAoGX21lZGlhQg4KDF9tZWRpYVVwZGF0ZUIQCg5fY29udGFjdFVwZGF0ZU'
+ 'IRCg9fY29udGFjdFJlcXVlc3RCDAoKX2ZsYW1lU3luY0ILCglfcHVzaEtleXNCCwoJX3JlYWN0'
+ 'aW9uQg4KDF90ZXh0TWVzc2FnZUIOCgxfZ3JvdXBDcmVhdGVCDAoKX2dyb3VwSm9pbkIOCgxfZ3'
+ 'JvdXBVcGRhdGVCFwoVX3Jlc2VuZEdyb3VwUHVibGljS2V5QhEKD19lcnJvcl9tZXNzYWdlcw==');
diff --git a/lib/src/model/protobuf/client/messages.proto b/lib/src/model/protobuf/client/messages.proto
index 61f6364..1e71cbc 100644
--- a/lib/src/model/protobuf/client/messages.proto
+++ b/lib/src/model/protobuf/client/messages.proto
@@ -51,6 +51,17 @@ message EncryptedContent {
optional GroupJoin groupJoin = 15;
optional GroupUpdate groupUpdate = 16;
optional ResendGroupPublicKey resendGroupPublicKey = 17;
+ optional ErrorMessages error_messages = 18;
+
+
+ message ErrorMessages {
+ enum Type {
+ ERROR_PROCESSING_MESSAGE_CREATED_ACCOUNT_REQUEST_INSTEAD = 0;
+ UNKNOWN_MESSAGE_TYPE = 2;
+ }
+ Type type = 1;
+ string related_receipt_id = 2;
+ }
message GroupCreate {
diff --git a/lib/src/services/api/client2client/contact.c2c.dart b/lib/src/services/api/client2client/contact.c2c.dart
index ed09723..64d4450 100644
--- a/lib/src/services/api/client2client/contact.c2c.dart
+++ b/lib/src/services/api/client2client/contact.c2c.dart
@@ -13,6 +13,43 @@ import 'package:twonly/src/utils/avatars.dart';
import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.dart';
+Future handleNewContactRequest(int fromUserId) async {
+ final contact = await twonlyDB.contactsDao
+ .getContactByUserId(fromUserId)
+ .getSingleOrNull();
+ if (contact != null) {
+ if (contact.accepted) {
+ // contact was already accepted, so just accept the request in the background.
+ await sendCipherText(
+ contact.userId,
+ EncryptedContent(
+ contactRequest: EncryptedContent_ContactRequest(
+ type: EncryptedContent_ContactRequest_Type.ACCEPT,
+ ),
+ ),
+ );
+ return true;
+ }
+ }
+ // Request the username by the server so an attacker can not
+ // forge the displayed username in the contact request
+ final user = await apiService.getUserById(fromUserId);
+ if (user == null) {
+ return false;
+ }
+ await twonlyDB.contactsDao.insertOnConflictUpdate(
+ ContactsCompanion(
+ username: Value(utf8.decode(user.username)),
+ userId: Value(fromUserId),
+ requested: const Value(true),
+ deletedByUser: const Value(false),
+ ),
+ );
+ await setupNotificationWithUsers();
+
+ return true;
+}
+
Future handleContactRequest(
int fromUserId,
EncryptedContent_ContactRequest contactRequest,
@@ -20,38 +57,7 @@ Future handleContactRequest(
switch (contactRequest.type) {
case EncryptedContent_ContactRequest_Type.REQUEST:
Log.info('Got a contact request from $fromUserId');
- final contact = await twonlyDB.contactsDao
- .getContactByUserId(fromUserId)
- .getSingleOrNull();
- if (contact != null) {
- if (contact.accepted) {
- // contact was already accepted, so just accept the request in the background.
- await sendCipherText(
- contact.userId,
- EncryptedContent(
- contactRequest: EncryptedContent_ContactRequest(
- type: EncryptedContent_ContactRequest_Type.ACCEPT,
- ),
- ),
- );
- return true;
- }
- }
- // Request the username by the server so an attacker can not
- // forge the displayed username in the contact request
- final user = await apiService.getUserById(fromUserId);
- if (user == null) {
- return false;
- }
- await twonlyDB.contactsDao.insertOnConflictUpdate(
- ContactsCompanion(
- username: Value(utf8.decode(user.username)),
- userId: Value(fromUserId),
- requested: const Value(true),
- deletedByUser: const Value(false),
- ),
- );
- await setupNotificationWithUsers();
+ return handleNewContactRequest(fromUserId);
case EncryptedContent_ContactRequest_Type.ACCEPT:
Log.info('Got a contact accept from $fromUserId');
await twonlyDB.contactsDao.updateContact(
diff --git a/lib/src/services/api/client2client/errors.c2c.dart b/lib/src/services/api/client2client/errors.c2c.dart
new file mode 100644
index 0000000..d5259f0
--- /dev/null
+++ b/lib/src/services/api/client2client/errors.c2c.dart
@@ -0,0 +1,29 @@
+import 'package:clock/clock.dart';
+import 'package:drift/drift.dart' show Value;
+import 'package:twonly/globals.dart';
+import 'package:twonly/src/database/twonly.db.dart';
+import 'package:twonly/src/model/protobuf/client/generated/messages.pbserver.dart';
+
+Future handleErrorMessage(
+ int fromUserId,
+ EncryptedContent_ErrorMessages error,
+) async {
+ switch (error.type) {
+ case EncryptedContent_ErrorMessages_Type.UNKNOWN_MESSAGE_TYPE:
+ break;
+ case EncryptedContent_ErrorMessages_Type
+ .ERROR_PROCESSING_MESSAGE_CREATED_ACCOUNT_REQUEST_INSTEAD:
+ await twonlyDB.receiptsDao.updateReceiptWidthUserId(
+ fromUserId,
+ error.relatedReceiptId,
+ ReceiptsCompanion(markForRetryAfterAccepted: Value(clock.now())),
+ );
+ await twonlyDB.contactsDao.updateContact(
+ fromUserId,
+ const ContactsCompanion(
+ accepted: Value(false),
+ requested: Value(true),
+ ),
+ );
+ }
+}
diff --git a/lib/src/services/api/messages.dart b/lib/src/services/api/messages.dart
index 9212316..49ee512 100644
--- a/lib/src/services/api/messages.dart
+++ b/lib/src/services/api/messages.dart
@@ -28,7 +28,29 @@ Future tryTransmitMessages() async {
Log.info('Reuploading ${receipts.length} messages to the server.');
+ final contacts = {};
+
for (final receipt in receipts) {
+ 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;
+ }
+ }
await tryToSendCompleteMessage(receipt: receipt);
}
});
diff --git a/lib/src/services/api/server_messages.dart b/lib/src/services/api/server_messages.dart
index c3600f1..bd2e54f 100644
--- a/lib/src/services/api/server_messages.dart
+++ b/lib/src/services/api/server_messages.dart
@@ -1,5 +1,4 @@
import 'dart:async';
-import 'dart:convert';
import 'package:clock/clock.dart';
import 'package:drift/drift.dart';
import 'package:hashlib/random.dart';
@@ -15,6 +14,7 @@ import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pbserver.dart';
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
import 'package:twonly/src/services/api/client2client/contact.c2c.dart';
+import 'package:twonly/src/services/api/client2client/errors.c2c.dart';
import 'package:twonly/src/services/api/client2client/groups.c2c.dart';
import 'package:twonly/src/services/api/client2client/media.c2c.dart';
import 'package:twonly/src/services/api/client2client/messages.c2c.dart';
@@ -23,6 +23,7 @@ import 'package:twonly/src/services/api/client2client/pushkeys.c2c.dart';
import 'package:twonly/src/services/api/client2client/reaction.c2c.dart';
import 'package:twonly/src/services/api/client2client/text_message.c2c.dart';
import 'package:twonly/src/services/api/messages.dart';
+import 'package:twonly/src/services/group.services.dart';
import 'package:twonly/src/services/signal/encryption.signal.dart';
import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.dart';
@@ -117,47 +118,60 @@ Future handleClient2ClientMessage(NewMessage newMessage) async {
case Message_Type.CIPHERTEXT:
case Message_Type.PREKEY_BUNDLE:
if (message.hasEncryptedContent()) {
+ Value? receiptIdDB;
+
final encryptedContentRaw =
Uint8List.fromList(message.encryptedContent);
- if (await twonlyDB.contactsDao
- .getContactByUserId(fromUserId)
- .getSingleOrNull() ==
- null) {
- final user = await apiService.getUserById(fromUserId);
+ Message? response;
- /// In case the user does not exists, just create a dummy user which was deleted by the user, so the message
- /// can be inserted into the receipts database
- await twonlyDB.contactsDao.insertContact(
- ContactsCompanion(
- userId: Value(fromUserId),
- deletedByUser: const Value(true),
- username: Value(
- user == null ? '[Unknown]' : utf8.decode(user.username),
+ final user = await twonlyDB.contactsDao
+ .getContactByUserId(fromUserId)
+ .getSingleOrNull();
+
+ if (user == null) {
+ if (!await addNewHiddenContact(fromUserId)) {
+ // in case the user could not be added, send a retry error message as this error should only happen in case
+ // it was not possible to load the user from the server
+ response = Message(
+ receiptId: receiptId,
+ type: Message_Type.PLAINTEXT_CONTENT,
+ plaintextContent: PlaintextContent(
+ retryControlError: PlaintextContent_RetryErrorMessage(),
),
- ),
- );
+ );
+ }
}
- final responsePlaintextContent = await handleEncryptedMessage(
- fromUserId,
- encryptedContentRaw,
- message.type,
- );
- Message response;
- if (responsePlaintextContent != null) {
- response = Message()
- ..receiptId = receiptId
- ..type = Message_Type.PLAINTEXT_CONTENT
- ..plaintextContent = responsePlaintextContent;
- } else {
- response = Message()..type = Message_Type.SENDER_DELIVERY_RECEIPT;
+ if (response == null) {
+ final (encryptedContent, plainTextContent) =
+ await handleEncryptedMessage(
+ fromUserId,
+ encryptedContentRaw,
+ message.type,
+ receiptId,
+ );
+ if (plainTextContent != null) {
+ response = Message(
+ receiptId: receiptId,
+ type: Message_Type.PLAINTEXT_CONTENT,
+ plaintextContent: plainTextContent,
+ );
+ } else if (encryptedContent != null) {
+ response = Message(
+ type: Message_Type.CIPHERTEXT,
+ encryptedContent: encryptedContent.writeToBuffer(),
+ );
+ receiptIdDB = const Value.absent();
+ }
}
+ response ??= Message(type: Message_Type.SENDER_DELIVERY_RECEIPT);
+
try {
await twonlyDB.receiptsDao.insertReceipt(
ReceiptsCompanion(
- receiptId: Value(receiptId),
+ receiptId: receiptIdDB ?? Value(receiptId),
contactId: Value(fromUserId),
message: Value(response.writeToBuffer()),
contactWillSendsReceipt: const Value(false),
@@ -173,10 +187,11 @@ Future handleClient2ClientMessage(NewMessage newMessage) async {
}
}
-Future handleEncryptedMessage(
+Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage(
int fromUserId,
Uint8List encryptedContentRaw,
Message_Type messageType,
+ String receiptId,
) async {
final (content, decryptionErrorType) = await signalDecryptMessage(
fromUserId,
@@ -185,9 +200,12 @@ Future handleEncryptedMessage(
);
if (content == null) {
- return PlaintextContent()
- ..decryptionErrorMessage = (PlaintextContent_DecryptionErrorMessage()
- ..type = decryptionErrorType!);
+ return (
+ null,
+ PlaintextContent()
+ ..decryptionErrorMessage = (PlaintextContent_DecryptionErrorMessage()
+ ..type = decryptionErrorType!)
+ );
}
// We got a valid message fromUserId, so mark all messages which where
@@ -203,10 +221,21 @@ Future handleEncryptedMessage(
if (content.hasContactRequest()) {
if (!await handleContactRequest(fromUserId, content.contactRequest)) {
- return PlaintextContent()
- ..retryControlError = PlaintextContent_RetryErrorMessage();
+ return (
+ null,
+ PlaintextContent()
+ ..retryControlError = PlaintextContent_RetryErrorMessage()
+ );
}
- return null;
+ return (null, null);
+ }
+
+ if (content.hasErrorMessages()) {
+ await handleErrorMessage(
+ fromUserId,
+ content.errorMessages,
+ );
+ return (null, null);
}
if (content.hasContactUpdate()) {
@@ -215,17 +244,17 @@ Future handleEncryptedMessage(
content.contactUpdate,
senderProfileCounter,
);
- return null;
+ return (null, null);
}
if (content.hasFlameSync()) {
await handleFlameSync(fromUserId, content.flameSync);
- return null;
+ return (null, null);
}
if (content.hasPushKeys()) {
await handlePushKey(fromUserId, content.pushKeys);
- return null;
+ return (null, null);
}
if (content.hasMessageUpdate()) {
@@ -233,7 +262,7 @@ Future handleEncryptedMessage(
fromUserId,
content.messageUpdate,
);
- return null;
+ return (null, null);
}
if (content.hasMediaUpdate()) {
@@ -241,12 +270,12 @@ Future handleEncryptedMessage(
fromUserId,
content.mediaUpdate,
);
- return null;
+ return (null, null);
}
if (!content.hasGroupId()) {
Log.error('Messages should have a groupId $fromUserId.');
- return null;
+ return (null, null);
}
if (content.hasGroupCreate()) {
@@ -255,7 +284,7 @@ Future handleEncryptedMessage(
content.groupId,
content.groupCreate,
);
- return null;
+ return (null, null);
}
/// Verify that the user is (still) in that group...
@@ -265,10 +294,20 @@ Future handleEncryptedMessage(
.getContactByUserId(fromUserId)
.getSingleOrNull();
if (contact == null || contact.deletedByUser) {
+ await handleNewContactRequest(fromUserId);
Log.error(
'User tries to send message to direct chat while the user does not exists !',
);
- return null;
+ return (
+ EncryptedContent(
+ errorMessages: EncryptedContent_ErrorMessages(
+ type: EncryptedContent_ErrorMessages_Type
+ .ERROR_PROCESSING_MESSAGE_CREATED_ACCOUNT_REQUEST_INSTEAD,
+ relatedReceiptId: receiptId,
+ ),
+ ),
+ null
+ );
}
Log.info(
'Creating new DirectChat between two users',
@@ -285,12 +324,15 @@ Future handleEncryptedMessage(
'Got group join message, but group does not exists yet, retry later. As probably the GroupCreate was not yet received.',
);
// In case the group join was received before the GroupCreate the sender should send it later again.
- return PlaintextContent()
- ..retryControlError = PlaintextContent_RetryErrorMessage();
+ return (
+ null,
+ PlaintextContent()
+ ..retryControlError = PlaintextContent_RetryErrorMessage()
+ );
}
Log.error('User $fromUserId tried to access group ${content.groupId}.');
- return null;
+ return (null, null);
}
}
@@ -300,7 +342,7 @@ Future handleEncryptedMessage(
content.groupId,
content.groupUpdate,
);
- return null;
+ return (null, null);
}
if (content.hasGroupJoin()) {
@@ -309,10 +351,13 @@ Future handleEncryptedMessage(
content.groupId,
content.groupJoin,
)) {
- return PlaintextContent()
- ..retryControlError = PlaintextContent_RetryErrorMessage();
+ return (
+ null,
+ PlaintextContent()
+ ..retryControlError = PlaintextContent_RetryErrorMessage()
+ );
}
- return null;
+ return (null, null);
}
if (content.hasResendGroupPublicKey()) {
@@ -321,7 +366,7 @@ Future handleEncryptedMessage(
content.groupId,
content.groupJoin,
);
- return null;
+ return (null, null);
}
if (content.hasTextMessage()) {
@@ -330,7 +375,7 @@ Future handleEncryptedMessage(
content.groupId,
content.textMessage,
);
- return null;
+ return (null, null);
}
if (content.hasReaction()) {
@@ -339,7 +384,7 @@ Future handleEncryptedMessage(
content.groupId,
content.reaction,
);
- return null;
+ return (null, null);
}
if (content.hasMedia()) {
@@ -348,8 +393,8 @@ Future handleEncryptedMessage(
content.groupId,
content.media,
);
- return null;
+ return (null, null);
}
- return null;
+ return (null, null);
}
diff --git a/lib/src/services/mediafiles/mediafile.service.dart b/lib/src/services/mediafiles/mediafile.service.dart
index 22952cb..2901006 100644
--- a/lib/src/services/mediafiles/mediafile.service.dart
+++ b/lib/src/services/mediafiles/mediafile.service.dart
@@ -59,6 +59,7 @@ class MediaFileService {
} else if (service.mediaFile.requiresAuthentication ||
service.mediaFile.displayLimitInMilliseconds != null) {
// Message was opened by all persons, and they can not reopen the image.
+ // This branch will prevent to reach the next if condition, with would otherwise store the image for two days
// delete = true; // do not overwrite a previous delete = false
// this is just to make it easier to understand :)
} else if (message.openedAt!
diff --git a/lib/src/views/camera/camera_preview_components/camera_preview_controller_view.dart b/lib/src/views/camera/camera_preview_components/camera_preview_controller_view.dart
index 9600cd5..dadb1fd 100644
--- a/lib/src/views/camera/camera_preview_components/camera_preview_controller_view.dart
+++ b/lib/src/views/camera/camera_preview_components/camera_preview_controller_view.dart
@@ -436,7 +436,7 @@ class _CameraPreviewViewState extends State {
CameraLensDirection.front;
Future onPanUpdate(dynamic details) async {
- if (isFront || details == null) {
+ if (details == null) {
return;
}
if (mc.cameraController == null ||
@@ -603,9 +603,6 @@ class _CameraPreviewViewState extends State {
bottomNavigation: Container(),
child: GestureDetector(
onPanStart: (details) async {
- if (isFront) {
- return;
- }
setState(() {
_basePanY = details.localPosition.dy;
_baseScaleFactor = mc.selectedCameraDetails.scaleFactor;
@@ -721,12 +718,11 @@ class _CameraPreviewViewState extends State {
children: [
if (mc.cameraController!.value.isInitialized &&
mc.selectedCameraDetails.isZoomAble &&
- !isFront &&
!_isVideoRecording)
SizedBox(
width: 120,
child: CameraZoomButtons(
- key: widget.key,
+ key: mc.zoomButtonKey,
scaleFactor: mc.selectedCameraDetails.scaleFactor,
updateScaleFactor: updateScaleFactor,
selectCamera: mc.selectCamera,
diff --git a/lib/src/views/camera/camera_preview_components/main_camera_controller.dart b/lib/src/views/camera/camera_preview_components/main_camera_controller.dart
index c6543a7..a67d2fa 100644
--- a/lib/src/views/camera/camera_preview_components/main_camera_controller.dart
+++ b/lib/src/views/camera/camera_preview_components/main_camera_controller.dart
@@ -44,6 +44,7 @@ class MainCameraController {
Map contactsVerified = {};
Map scannedNewProfiles = {};
String? scannedUrl;
+ GlobalKey zoomButtonKey = GlobalKey();
Future closeCamera() async {
contactsVerified = {};
@@ -76,6 +77,7 @@ class MainCameraController {
CameraLensDirection.back) {
await cameraController?.startImageStream(_processCameraImage);
}
+ zoomButtonKey = GlobalKey();
setState();
return cameraController;
}
@@ -89,10 +91,11 @@ class MainCameraController {
try {
await cameraController!.stopImageStream();
} catch (e) {
- Log.warn(e);
+ // Log.warn(e);
}
- await cameraController!.dispose();
+ final tmp = cameraController;
cameraController = null;
+ await tmp!.dispose();
await selectCamera((selectedCameraDetails.cameraId + 1) % 2, false);
}
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 2b20fae..85c57d4 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
@@ -1,19 +1,23 @@
import 'dart:async';
import 'dart:typed_data';
+import 'package:clock/clock.dart';
+import 'package:drift/drift.dart' show Value;
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
+import 'package:twonly/globals.dart';
+import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
import 'package:twonly/src/utils/misc.dart';
class SaveToGalleryButton extends StatefulWidget {
const SaveToGalleryButton({
- required this.storeImageAsOriginal,
required this.isLoading,
required this.displayButtonLabel,
required this.mediaService,
+ this.storeImageAsOriginal,
super.key,
});
- final Future Function() storeImageAsOriginal;
+ final Future Function()? storeImageAsOriginal;
final bool displayButtonLabel;
final MediaFileService mediaService;
final bool isLoading;
@@ -44,8 +48,32 @@ class SaveToGalleryButtonState extends State {
_imageSaving = true;
});
- await widget.storeImageAsOriginal();
- await widget.mediaService.storeMediaFile();
+ if (widget.storeImageAsOriginal != null) {
+ await widget.storeImageAsOriginal!();
+ }
+
+ final newMediaFile = await twonlyDB.mediaFilesDao.insertMedia(
+ MediaFilesCompanion(
+ type: Value(widget.mediaService.mediaFile.type),
+ createdAt: Value(clock.now()),
+ stored: const Value(true),
+ ),
+ );
+
+ if (newMediaFile != null) {
+ final newService = MediaFileService(newMediaFile);
+
+ if (widget.mediaService.tempPath.existsSync()) {
+ widget.mediaService.tempPath.copySync(
+ newService.tempPath.path,
+ );
+ } else if (widget.mediaService.originalPath.existsSync()) {
+ widget.mediaService.originalPath.copySync(
+ newService.originalPath.path,
+ );
+ }
+ await newService.storeMediaFile();
+ }
setState(() {
_imageSaved = true;
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 058618e..e3f7032 100644
--- a/lib/src/views/camera/camera_preview_components/zoom_selector.dart
+++ b/lib/src/views/camera/camera_preview_components/zoom_selector.dart
@@ -62,8 +62,16 @@ class _CameraZoomButtonsState extends State {
_wideCameraIndex = index;
}
- if (!showWideAngleZoom && Platform.isIOS && _wideCameraIndex != null) {
+ final isFront = widget.controller.description.lensDirection ==
+ CameraLensDirection.front;
+
+ if (!showWideAngleZoom &&
+ Platform.isIOS &&
+ _wideCameraIndex != null &&
+ !isFront) {
showWideAngleZoomIOS = true;
+ } else {
+ showWideAngleZoomIOS = false;
}
if (_isDisposed) return;
setState(() {});
diff --git a/lib/src/views/camera/share_image_editor_view.dart b/lib/src/views/camera/share_image_editor_view.dart
index 1eed51c..28e3cb8 100644
--- a/lib/src/views/camera/share_image_editor_view.dart
+++ b/lib/src/views/camera/share_image_editor_view.dart
@@ -2,7 +2,6 @@
import 'dart:async';
import 'dart:collection';
-import 'package:clock/clock.dart';
import 'package:drift/drift.dart' show Value;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -487,20 +486,6 @@ class _ShareImageEditorView extends State {
}
}
- // In case the image was already stored, then rename the stored image.
- if (mediaService.storedPath.existsSync()) {
- final mediaFile = await twonlyDB.mediaFilesDao.insertMedia(
- MediaFilesCompanion(
- type: Value(mediaService.mediaFile.type),
- createdAt: Value(clock.now()),
- stored: const Value(true),
- ),
- );
- if (mediaFile != null) {
- mediaService.storedPath
- .renameSync(MediaFileService(mediaFile).storedPath.path);
- }
- }
return bytes;
}
diff --git a/lib/src/views/chats/chat_messages_components/chat_list_entry.dart b/lib/src/views/chats/chat_messages_components/chat_list_entry.dart
index 5f146e9..44e51e3 100644
--- a/lib/src/views/chats/chat_messages_components/chat_list_entry.dart
+++ b/lib/src/views/chats/chat_messages_components/chat_list_entry.dart
@@ -185,6 +185,7 @@ class _ChatListEntryState extends State {
group: widget.group,
onResponseTriggered: widget.onResponseTriggered!,
galleryItems: widget.galleryItems,
+ mediaFileService: mediaService,
child: Container(
child: child,
),
diff --git a/lib/src/views/chats/chat_messages_components/message_context_menu.dart b/lib/src/views/chats/chat_messages_components/message_context_menu.dart
index 5226345..62a9456 100644
--- a/lib/src/views/chats/chat_messages_components/message_context_menu.dart
+++ b/lib/src/views/chats/chat_messages_components/message_context_menu.dart
@@ -12,12 +12,14 @@ import 'package:twonly/src/model/memory_item.model.dart';
import 'package:twonly/src/model/protobuf/client/generated/messages.pbserver.dart'
as pb;
import 'package:twonly/src/services/api/messages.dart';
+import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/camera/image_editor/data/layer.dart';
import 'package:twonly/src/views/camera/image_editor/modules/all_emojis.dart';
import 'package:twonly/src/views/chats/message_info.view.dart';
import 'package:twonly/src/views/components/alert_dialog.dart';
import 'package:twonly/src/views/components/context_menu.component.dart';
+import 'package:twonly/src/views/memories/memories_photo_slider.view.dart';
class MessageContextMenu extends StatelessWidget {
const MessageContextMenu({
@@ -26,16 +28,55 @@ class MessageContextMenu extends StatelessWidget {
required this.child,
required this.onResponseTriggered,
required this.galleryItems,
+ required this.mediaFileService,
super.key,
});
final Group group;
final Widget child;
final Message message;
final List galleryItems;
+ final MediaFileService? mediaFileService;
final VoidCallback onResponseTriggered;
+ Future reopenMediaFile(BuildContext context) async {
+ final isAuth = await authenticateUser(
+ context.lang.authRequestReopenImage,
+ force: false,
+ );
+
+ if (isAuth && context.mounted && mediaFileService != null) {
+ final galleryItems = [
+ MemoryItem(mediaService: mediaFileService!, messages: []),
+ ];
+
+ await Navigator.push(
+ context,
+ PageRouteBuilder(
+ opaque: false,
+ pageBuilder: (context, a1, a2) => MemoriesPhotoSliderView(
+ galleryItems: galleryItems,
+ ),
+ ),
+ );
+ }
+ }
+
@override
Widget build(BuildContext context) {
+ var canBeOpenedAgain = false;
+ // in case this is a media send from this user...
+ if (mediaFileService != null && message.senderId == null) {
+ // and the media was send with unlimited display limit time and without auth required...
+ if (!mediaFileService!.mediaFile.requiresAuthentication &&
+ mediaFileService!.mediaFile.displayLimitInMilliseconds == null) {
+ // and the temp media file still exists
+ if (mediaFileService!.tempPath.existsSync()) {
+ // the media file can be opened again...
+ canBeOpenedAgain = true;
+ }
+ }
+ }
+
return ContextMenu(
items: [
if (!message.isDeletedFromSender)
@@ -70,6 +111,12 @@ class MessageContextMenu extends StatelessWidget {
},
icon: FontAwesomeIcons.faceLaugh,
),
+ if (canBeOpenedAgain)
+ ContextMenuItem(
+ title: context.lang.contextMenuViewAgain,
+ onTap: () => reopenMediaFile(context),
+ icon: FontAwesomeIcons.clockRotateLeft,
+ ),
if (!message.isDeletedFromSender)
ContextMenuItem(
title: context.lang.reply,
diff --git a/lib/src/views/components/group_context_menu.component.dart b/lib/src/views/components/group_context_menu.component.dart
index 28808a1..438a77d 100644
--- a/lib/src/views/components/group_context_menu.component.dart
+++ b/lib/src/views/components/group_context_menu.component.dart
@@ -83,13 +83,16 @@ class GroupContextMenu extends StatelessWidget {
);
if (ok) {
await twonlyDB.messagesDao.deleteMessagesByGroupId(group.groupId);
- // await twonlyDB.groupsDao.deleteGroup(group.groupId);
- await twonlyDB.groupsDao.updateGroup(
- group.groupId,
- const GroupsCompanion(
- deletedContent: Value(true),
- ),
- );
+ if (group.isDirectChat) {
+ await twonlyDB.groupsDao.deleteGroup(group.groupId);
+ } else {
+ await twonlyDB.groupsDao.updateGroup(
+ group.groupId,
+ const GroupsCompanion(
+ deletedContent: Value(true),
+ ),
+ );
+ }
}
},
),
diff --git a/lib/src/views/contact/contact.view.dart b/lib/src/views/contact/contact.view.dart
index aeec7ce..161fa25 100644
--- a/lib/src/views/contact/contact.view.dart
+++ b/lib/src/views/contact/contact.view.dart
@@ -26,7 +26,48 @@ class ContactView extends StatefulWidget {
}
class _ContactViewState extends State {
+ Contact? _contact;
+ bool _contactIsStillAGroupMember = true;
+
+ late StreamSubscription _contactSub;
+ late StreamSubscription> _groupMemberSub;
+
+ @override
+ void initState() {
+ _contactSub =
+ twonlyDB.contactsDao.watchContact(widget.userId).listen((update) {
+ setState(() {
+ _contact = update;
+ });
+ });
+ _groupMemberSub = twonlyDB.groupsDao
+ .watchContactGroupMember(widget.userId)
+ .listen((update) {
+ setState(() {
+ _contactIsStillAGroupMember = update.isNotEmpty;
+ });
+ });
+ super.initState();
+ }
+
+ @override
+ void dispose() {
+ _contactSub.cancel();
+ _groupMemberSub.cancel();
+ super.dispose();
+ }
+
Future handleUserRemoveRequest(Contact contact) async {
+ if (_contactIsStillAGroupMember) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(
+ content: Text(context.lang.deleteUserErrorMessage),
+ duration: const Duration(seconds: 8),
+ ),
+ );
+ return;
+ }
+
final remove = await showAlertDialog(
context,
context.lang
@@ -84,128 +125,117 @@ class _ContactViewState extends State {
@override
Widget build(BuildContext context) {
- final contact = twonlyDB.contactsDao
- .getContactByUserId(widget.userId)
- .watchSingleOrNull();
+ if (_contact == null) return Container();
+ final contact = _contact!;
return Scaffold(
appBar: AppBar(
title: const Text(''),
),
- body: StreamBuilder(
- stream: contact,
- builder: (context, snapshot) {
- if (!snapshot.hasData || snapshot.data == null) {
- return Container();
- }
- final contact = snapshot.data!;
- return ListView(
- key: ValueKey(contact.userId),
+ body: ListView(
+ key: ValueKey(contact.userId),
+ children: [
+ Padding(
+ padding: const EdgeInsets.all(10),
+ child: AvatarIcon(contactId: contact.userId, fontSize: 30),
+ ),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
- padding: const EdgeInsets.all(10),
- child: AvatarIcon(contactId: contact.userId, fontSize: 30),
+ padding: const EdgeInsets.only(right: 10),
+ child: VerifiedShield(
+ key: GlobalKey(),
+ contact: contact,
+ ),
),
- Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Padding(
- padding: const EdgeInsets.only(right: 10),
- child: VerifiedShield(
- key: GlobalKey(),
- contact: contact,
- ),
- ),
- Text(
- getContactDisplayName(contact, maxLength: 20),
- style: const TextStyle(fontSize: 20),
- ),
- FlameCounterWidget(
- contactId: contact.userId,
- prefix: true,
- ),
- ],
+ Text(
+ getContactDisplayName(contact, maxLength: 20),
+ style: const TextStyle(fontSize: 20),
),
- if (getContactDisplayName(contact) != contact.username)
- Center(child: Text('(${contact.username})')),
- const SizedBox(height: 50),
- BetterListTile(
- icon: FontAwesomeIcons.pencil,
- text: context.lang.contactNickname,
- onTap: () async {
- final nickName =
- await showNicknameChangeDialog(context, contact);
-
- if (context.mounted && nickName != null && nickName != '') {
- final update = ContactsCompanion(nickName: Value(nickName));
- await twonlyDB.contactsDao
- .updateContact(contact.userId, update);
- }
- },
+ FlameCounterWidget(
+ contactId: contact.userId,
+ prefix: true,
),
- const Divider(),
- SelectChatDeletionTimeListTitle(
- groupId: getUUIDforDirectChat(widget.userId, gUser.userId),
- ),
- const Divider(),
- MaxFlameListTitle(
- contactId: widget.userId,
- ),
- BetterListTile(
- icon: FontAwesomeIcons.shieldHeart,
- text: context.lang.contactVerifyNumberTitle,
- onTap: () async {
- await Navigator.push(
- context,
- MaterialPageRoute(
- builder: (context) {
- return const PublicProfileView();
- },
- ),
- );
- setState(() {});
- },
- ),
- // BetterListTile(
- // icon: FontAwesomeIcons.eraser,
- // iconSize: 16,
- // text: context.lang.deleteAllContactMessages,
- // onTap: () async {
- // final block = await showAlertDialog(
- // context,
- // context.lang.deleteAllContactMessages,
- // context.lang.deleteAllContactMessagesBody(
- // getContactDisplayName(contact),
- // ),
- // );
- // if (block) {
- // if (context.mounted) {
- // await twonlyDB.messagesDao
- // .deleteMessagesByContactId(contact.userId);
- // }
- // }
- // },
- // ),
- BetterListTile(
- icon: FontAwesomeIcons.flag,
- text: context.lang.reportUser,
- onTap: () => handleReportUser(contact),
- ),
- BetterListTile(
- icon: FontAwesomeIcons.ban,
- text: context.lang.contactBlock,
- onTap: () => handleUserBlockRequest(contact),
- ),
- // BetterListTile(
- // icon: FontAwesomeIcons.userMinus,
- // iconSize: 16,
- // color: Colors.red,
- // text: context.lang.contactRemove,
- // onTap: () => handleUserRemoveRequest(contact),
- // ),
],
- );
- },
+ ),
+ if (getContactDisplayName(contact) != contact.username)
+ Center(child: Text('(${contact.username})')),
+ const SizedBox(height: 50),
+ BetterListTile(
+ icon: FontAwesomeIcons.pencil,
+ text: context.lang.contactNickname,
+ onTap: () async {
+ final nickName = await showNicknameChangeDialog(context, contact);
+
+ if (context.mounted && nickName != null && nickName != '') {
+ final update = ContactsCompanion(nickName: Value(nickName));
+ await twonlyDB.contactsDao
+ .updateContact(contact.userId, update);
+ }
+ },
+ ),
+ const Divider(),
+ SelectChatDeletionTimeListTitle(
+ groupId: getUUIDforDirectChat(widget.userId, gUser.userId),
+ ),
+ const Divider(),
+ MaxFlameListTitle(
+ contactId: widget.userId,
+ ),
+ BetterListTile(
+ icon: FontAwesomeIcons.shieldHeart,
+ text: context.lang.contactVerifyNumberTitle,
+ onTap: () async {
+ await Navigator.push(
+ context,
+ MaterialPageRoute(
+ builder: (context) {
+ return const PublicProfileView();
+ },
+ ),
+ );
+ setState(() {});
+ },
+ ),
+ // BetterListTile(
+ // icon: FontAwesomeIcons.eraser,
+ // iconSize: 16,
+ // text: context.lang.deleteAllContactMessages,
+ // onTap: () async {
+ // final block = await showAlertDialog(
+ // context,
+ // context.lang.deleteAllContactMessages,
+ // context.lang.deleteAllContactMessagesBody(
+ // getContactDisplayName(contact),
+ // ),
+ // );
+ // if (block) {
+ // if (context.mounted) {
+ // await twonlyDB.messagesDao
+ // .deleteMessagesByContactId(contact.userId);
+ // }
+ // }
+ // },
+ // ),
+ BetterListTile(
+ icon: FontAwesomeIcons.flag,
+ text: context.lang.reportUser,
+ onTap: () => handleReportUser(contact),
+ ),
+ BetterListTile(
+ icon: FontAwesomeIcons.ban,
+ text: context.lang.contactBlock,
+ onTap: () => handleUserBlockRequest(contact),
+ ),
+ BetterListTile(
+ icon: FontAwesomeIcons.userMinus,
+ iconSize: 16,
+ color: Colors.red,
+ text: context.lang.contactRemove,
+ onTap: () => handleUserRemoveRequest(contact),
+ ),
+ ],
),
);
}
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 dedf974..4306c86 100644
--- a/lib/src/views/groups/group_create_select_members.view.dart
+++ b/lib/src/views/groups/group_create_select_members.view.dart
@@ -84,6 +84,15 @@ class _StartNewChatView extends State {
void toggleSelectedUser(int userId) {
if (alreadyInGroup.contains(userId)) return;
if (!selectedUsers.contains(userId)) {
+ if (selectedUsers.length + alreadyInGroup.length > 256) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(
+ content: Text(context.lang.groupSizeLimitError(256)),
+ duration: const Duration(seconds: 3),
+ ),
+ );
+ return;
+ }
selectedUsers.add(userId);
} else {
selectedUsers.remove(userId);
diff --git a/lib/src/views/memories/memories_photo_slider.view.dart b/lib/src/views/memories/memories_photo_slider.view.dart
index 13cea18..b0cfaf5 100644
--- a/lib/src/views/memories/memories_photo_slider.view.dart
+++ b/lib/src/views/memories/memories_photo_slider.view.dart
@@ -8,6 +8,7 @@ import 'package:twonly/src/model/memory_item.model.dart';
import 'package:twonly/src/services/api/mediafiles/upload.service.dart';
import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.dart';
+import 'package:twonly/src/views/camera/camera_preview_components/save_to_gallery.dart';
import 'package:twonly/src/views/camera/share_image_editor_view.dart';
import 'package:twonly/src/views/components/alert_dialog.dart';
import 'package:twonly/src/views/components/media_view_sizing.dart';
@@ -92,8 +93,36 @@ class _MemoriesPhotoSliderViewState extends State {
}
}
+ Future shareMediaFile() async {
+ final orgMediaService = widget.galleryItems[currentIndex].mediaService;
+
+ final newMediaService = await initializeMediaUpload(
+ orgMediaService.mediaFile.type,
+ gUser.defaultShowTime,
+ );
+ if (newMediaService == null) {
+ Log.error('Could not create new mediaFIle');
+ return;
+ }
+
+ orgMediaService.storedPath.copySync(newMediaService.originalPath.path);
+
+ if (!mounted) return;
+
+ await Navigator.push(
+ context,
+ MaterialPageRoute(
+ builder: (context) => ShareImageEditorView(
+ mediaFileService: newMediaService,
+ sharedFromGallery: true,
+ ),
+ ),
+ );
+ }
+
@override
Widget build(BuildContext context) {
+ final orgMediaService = widget.galleryItems[currentIndex].mediaService;
return Dismissible(
key: key,
direction: DismissDirection.vertical,
@@ -117,36 +146,18 @@ class _MemoriesPhotoSliderViewState extends State {
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
+ if (!orgMediaService.storedPath.existsSync())
+ Padding(
+ padding: const EdgeInsets.only(right: 12),
+ child: SaveToGalleryButton(
+ isLoading: false,
+ displayButtonLabel: true,
+ mediaService: orgMediaService,
+ ),
+ ),
FilledButton.icon(
icon: const FaIcon(FontAwesomeIcons.solidPaperPlane),
- onPressed: () async {
- final orgMediaService =
- widget.galleryItems[currentIndex].mediaService;
-
- final newMediaService = await initializeMediaUpload(
- orgMediaService.mediaFile.type,
- gUser.defaultShowTime,
- );
- if (newMediaService == null) {
- Log.error('Could not create new mediaFIle');
- return;
- }
-
- orgMediaService.storedPath
- .copySync(newMediaService.originalPath.path);
-
- if (!context.mounted) return;
-
- await Navigator.push(
- context,
- MaterialPageRoute(
- builder: (context) => ShareImageEditorView(
- mediaFileService: newMediaService,
- sharedFromGallery: true,
- ),
- ),
- );
- },
+ onPressed: shareMediaFile,
style: ButtonStyle(
padding: WidgetStateProperty.all(
const EdgeInsets.symmetric(
@@ -217,10 +228,16 @@ class _MemoriesPhotoSliderViewState extends State {
PhotoViewGalleryPageOptions _buildItem(BuildContext context, int index) {
final item = widget.galleryItems[index];
+
+ var filePath = item.mediaService.storedPath;
+ if (!filePath.existsSync()) {
+ filePath = item.mediaService.tempPath;
+ }
+
return item.mediaService.mediaFile.type == MediaType.video
? PhotoViewGalleryPageOptions.customChild(
child: VideoPlayerWrapper(
- videoPath: item.mediaService.storedPath,
+ videoPath: filePath,
),
// childSize: const Size(300, 300),
initialScale: PhotoViewComputedScale.contained,
@@ -231,7 +248,7 @@ class _MemoriesPhotoSliderViewState extends State {
),
)
: PhotoViewGalleryPageOptions(
- imageProvider: FileImage(item.mediaService.storedPath),
+ imageProvider: FileImage(filePath),
initialScale: PhotoViewComputedScale.contained,
minScale: PhotoViewComputedScale.contained,
maxScale: PhotoViewComputedScale.covered * 4.1,
diff --git a/lib/src/views/settings/help/contact_us.view.dart b/lib/src/views/settings/help/contact_us.view.dart
index 52d644a..4aad469 100644
--- a/lib/src/views/settings/help/contact_us.view.dart
+++ b/lib/src/views/settings/help/contact_us.view.dart
@@ -3,6 +3,7 @@ import 'package:device_info_plus/device_info_plus.dart';
import 'package:fixnum/fixnum.dart';
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
+import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:http/http.dart' as http;
import 'package:package_info_plus/package_info_plus.dart';
import 'package:twonly/globals.dart';
@@ -81,10 +82,7 @@ class _ContactUsState extends State {
return null;
}
- Future _getFeedbackText() async {
- setState(() {
- isLoading = true;
- });
+ Future _getFeedbackText() async {
var osVersion = '';
final locale = context.lang.localeName;
final deviceInfo = DeviceInfoPlugin();
@@ -95,7 +93,11 @@ class _ContactUsState extends State {
final feedback = _controller.text;
var debugLogToken = '';
- if (!mounted) return '';
+ if (!mounted) return null;
+
+ setState(() {
+ isLoading = true;
+ });
// Get device information
if (Theme.of(context).platform == TargetPlatform.android) {
@@ -109,18 +111,23 @@ class _ContactUsState extends State {
}
if (includeDebugLog) {
+ String? token;
try {
- final token = await uploadDebugLog();
- if (token != null) {
- debugLogToken =
- 'Debug Log: https://api.twonly.eu/api/download/$token';
- }
+ token = await uploadDebugLog();
} catch (e) {
- if (!mounted) return '';
+ Log.error(e);
+ }
+ if (token == null) {
+ if (!mounted) return null;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Could not upload the debug log!')),
);
+ setState(() {
+ isLoading = false;
+ });
+ return null;
}
+ debugLogToken = 'Debug Log: https://api.twonly.eu/api/download/$token';
}
setState(() {
@@ -238,12 +245,22 @@ $debugLogToken
),
),
),
- ElevatedButton(
+ ElevatedButton.icon(
+ icon: isLoading
+ ? SizedBox(
+ height: 12,
+ width: 12,
+ child: CircularProgressIndicator(
+ strokeWidth: 2,
+ color: Theme.of(context).colorScheme.inversePrimary,
+ ),
+ )
+ : const FaIcon(FontAwesomeIcons.angleRight),
onPressed: isLoading
? null
: () async {
final fullMessage = await _getFeedbackText();
- if (!context.mounted) return;
+ if (!context.mounted || fullMessage == null) return;
final feedbackSend = await Navigator.push(
context,
@@ -260,7 +277,7 @@ $debugLogToken
Navigator.pop(context);
}
},
- child: Text(context.lang.next),
+ label: Text(context.lang.next),
),
],
),
diff --git a/lib/src/views/settings/subscription/additional_users.view.dart b/lib/src/views/settings/subscription/additional_users.view.dart
index 6fd5374..67fbcad 100644
--- a/lib/src/views/settings/subscription/additional_users.view.dart
+++ b/lib/src/views/settings/subscription/additional_users.view.dart
@@ -72,9 +72,30 @@ class _AdditionalUsersViewState extends State {
for (final selectedUserId in selectedUserIds) {
final res = await apiService.addAdditionalUser(Int64(selectedUserId));
if (res.isError && mounted) {
- ScaffoldMessenger.of(context).showSnackBar(
- SnackBar(content: Text(context.lang.additionalUserAddError)),
- );
+ final contact =
+ await twonlyDB.contactsDao.getContactById(selectedUserId);
+ if (contact != null && mounted) {
+ if (res.error == ErrorCode.UserIsNotInFreePlan) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(
+ content: Text(
+ context.lang.additionalUserAddErrorNotInFreePlan(
+ getContactDisplayName(contact),
+ ),
+ ),
+ ),
+ );
+ } else {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(
+ content: Text(
+ context.lang
+ .additionalUserAddError(getContactDisplayName(contact)),
+ ),
+ ),
+ );
+ }
+ }
}
}
await initAsync(force: true);
diff --git a/test/drift/twonly_db/generated/schema.dart b/test/drift/twonly_db/generated/schema.dart
index c42542a..d59002b 100644
--- a/test/drift/twonly_db/generated/schema.dart
+++ b/test/drift/twonly_db/generated/schema.dart
@@ -8,6 +8,7 @@ import 'schema_v2.dart' as v2;
import 'schema_v3.dart' as v3;
import 'schema_v4.dart' as v4;
import 'schema_v5.dart' as v5;
+import 'schema_v6.dart' as v6;
class GeneratedHelper implements SchemaInstantiationHelper {
@override
@@ -23,10 +24,12 @@ class GeneratedHelper implements SchemaInstantiationHelper {
return v4.DatabaseAtV4(db);
case 5:
return v5.DatabaseAtV5(db);
+ case 6:
+ return v6.DatabaseAtV6(db);
default:
throw MissingSchemaException(version, versions);
}
}
- static const versions = const [1, 2, 3, 4, 5];
+ static const versions = const [1, 2, 3, 4, 5, 6];
}
diff --git a/test/drift/twonly_db/generated/schema_v6.dart b/test/drift/twonly_db/generated/schema_v6.dart
new file mode 100644
index 0000000..c4a5734
--- /dev/null
+++ b/test/drift/twonly_db/generated/schema_v6.dart
@@ -0,0 +1,6537 @@
+// dart format width=80
+import 'dart:typed_data' as i2;
+// GENERATED CODE, DO NOT EDIT BY HAND.
+// ignore_for_file: type=lint
+import 'package:drift/drift.dart';
+
+class Contacts extends Table with TableInfo {
+ @override
+ final GeneratedDatabase attachedDatabase;
+ final String? _alias;
+ Contacts(this.attachedDatabase, [this._alias]);
+ late final GeneratedColumn userId = GeneratedColumn(
+ 'user_id', aliasedName, false,
+ type: DriftSqlType.int, requiredDuringInsert: false);
+ late final GeneratedColumn username = GeneratedColumn(
+ 'username', aliasedName, false,
+ type: DriftSqlType.string, requiredDuringInsert: true);
+ late final GeneratedColumn displayName = GeneratedColumn(
+ 'display_name', aliasedName, true,
+ type: DriftSqlType.string, requiredDuringInsert: false);
+ late final GeneratedColumn nickName = GeneratedColumn(
+ 'nick_name', aliasedName, true,
+ type: DriftSqlType.string, requiredDuringInsert: false);
+ late final GeneratedColumn avatarSvgCompressed =
+ GeneratedColumn('avatar_svg_compressed', aliasedName, true,
+ type: DriftSqlType.blob, requiredDuringInsert: false);
+ late final GeneratedColumn senderProfileCounter = GeneratedColumn(
+ 'sender_profile_counter', aliasedName, false,
+ type: DriftSqlType.int,
+ requiredDuringInsert: false,
+ defaultValue: const CustomExpression('0'));
+ late final GeneratedColumn accepted = GeneratedColumn(
+ 'accepted', aliasedName, false,
+ type: DriftSqlType.bool,
+ requiredDuringInsert: false,
+ defaultConstraints:
+ GeneratedColumn.constraintIsAlways('CHECK ("accepted" IN (0, 1))'),
+ defaultValue: const CustomExpression('0'));
+ late final GeneratedColumn deletedByUser = GeneratedColumn(
+ 'deleted_by_user', aliasedName, false,
+ type: DriftSqlType.bool,
+ requiredDuringInsert: false,
+ defaultConstraints: GeneratedColumn.constraintIsAlways(
+ 'CHECK ("deleted_by_user" IN (0, 1))'),
+ defaultValue: const CustomExpression('0'));
+ late final GeneratedColumn requested = GeneratedColumn(
+ 'requested', aliasedName, false,
+ type: DriftSqlType.bool,
+ requiredDuringInsert: false,
+ defaultConstraints:
+ GeneratedColumn.constraintIsAlways('CHECK ("requested" IN (0, 1))'),
+ defaultValue: const CustomExpression('0'));
+ late final GeneratedColumn blocked = GeneratedColumn(
+ 'blocked', aliasedName, false,
+ type: DriftSqlType.bool,
+ requiredDuringInsert: false,
+ defaultConstraints:
+ GeneratedColumn.constraintIsAlways('CHECK ("blocked" IN (0, 1))'),
+ defaultValue: const CustomExpression('0'));
+ late final GeneratedColumn verified = GeneratedColumn(
+ 'verified', aliasedName, false,
+ type: DriftSqlType.bool,
+ requiredDuringInsert: false,
+ defaultConstraints:
+ GeneratedColumn.constraintIsAlways('CHECK ("verified" IN (0, 1))'),
+ defaultValue: const CustomExpression('0'));
+ late final GeneratedColumn accountDeleted = GeneratedColumn(
+ 'account_deleted', aliasedName, false,
+ type: DriftSqlType.bool,
+ requiredDuringInsert: false,
+ defaultConstraints: GeneratedColumn.constraintIsAlways(
+ 'CHECK ("account_deleted" IN (0, 1))'),
+ defaultValue: const CustomExpression('0'));
+ late final GeneratedColumn createdAt = GeneratedColumn(
+ 'created_at', aliasedName, false,
+ type: DriftSqlType.dateTime,
+ requiredDuringInsert: false,
+ defaultValue: const CustomExpression(
+ 'CAST(strftime(\'%s\', CURRENT_TIMESTAMP) AS INTEGER)'));
+ @override
+ List get $columns => [
+ userId,
+ username,
+ displayName,
+ nickName,
+ avatarSvgCompressed,
+ senderProfileCounter,
+ accepted,
+ deletedByUser,
+ requested,
+ blocked,
+ verified,
+ accountDeleted,
+ createdAt
+ ];
+ @override
+ String get aliasedName => _alias ?? actualTableName;
+ @override
+ String get actualTableName => $name;
+ static const String $name = 'contacts';
+ @override
+ Set get $primaryKey => {userId};
+ @override
+ ContactsData map(Map data, {String? tablePrefix}) {
+ final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
+ return ContactsData(
+ userId: attachedDatabase.typeMapping
+ .read(DriftSqlType.int, data['${effectivePrefix}user_id'])!,
+ username: attachedDatabase.typeMapping
+ .read(DriftSqlType.string, data['${effectivePrefix}username'])!,
+ displayName: attachedDatabase.typeMapping
+ .read(DriftSqlType.string, data['${effectivePrefix}display_name']),
+ nickName: attachedDatabase.typeMapping
+ .read(DriftSqlType.string, data['${effectivePrefix}nick_name']),
+ avatarSvgCompressed: attachedDatabase.typeMapping.read(
+ DriftSqlType.blob, data['${effectivePrefix}avatar_svg_compressed']),
+ senderProfileCounter: attachedDatabase.typeMapping.read(
+ DriftSqlType.int, data['${effectivePrefix}sender_profile_counter'])!,
+ accepted: attachedDatabase.typeMapping
+ .read(DriftSqlType.bool, data['${effectivePrefix}accepted'])!,
+ deletedByUser: attachedDatabase.typeMapping
+ .read(DriftSqlType.bool, data['${effectivePrefix}deleted_by_user'])!,
+ requested: attachedDatabase.typeMapping
+ .read(DriftSqlType.bool, data['${effectivePrefix}requested'])!,
+ blocked: attachedDatabase.typeMapping
+ .read(DriftSqlType.bool, data['${effectivePrefix}blocked'])!,
+ verified: attachedDatabase.typeMapping
+ .read(DriftSqlType.bool, data['${effectivePrefix}verified'])!,
+ accountDeleted: attachedDatabase.typeMapping
+ .read(DriftSqlType.bool, data['${effectivePrefix}account_deleted'])!,
+ createdAt: attachedDatabase.typeMapping
+ .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!,
+ );
+ }
+
+ @override
+ Contacts createAlias(String alias) {
+ return Contacts(attachedDatabase, alias);
+ }
+}
+
+class ContactsData extends DataClass implements Insertable {
+ final int userId;
+ final String username;
+ final String? displayName;
+ final String? nickName;
+ final i2.Uint8List? avatarSvgCompressed;
+ final int senderProfileCounter;
+ final bool accepted;
+ final bool deletedByUser;
+ final bool requested;
+ final bool blocked;
+ final bool verified;
+ final bool accountDeleted;
+ final DateTime createdAt;
+ const ContactsData(
+ {required this.userId,
+ required this.username,
+ this.displayName,
+ this.nickName,
+ this.avatarSvgCompressed,
+ required this.senderProfileCounter,
+ required this.accepted,
+ required this.deletedByUser,
+ required this.requested,
+ required this.blocked,
+ required this.verified,
+ required this.accountDeleted,
+ required this.createdAt});
+ @override
+ Map toColumns(bool nullToAbsent) {
+ final map = {};
+ map['user_id'] = Variable(userId);
+ map['username'] = Variable