diff --git a/CHANGELOG.md b/CHANGELOG.md index a9d3887..5aae546 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## 0.0.89 + +- Adds link preview to images +- Adds option to manual focus in the camera +- Adds support to switch between front and back cameras during video recording +- Adds basic face filters +- Improves image editor, like emojis or text under a drawing can be moved +- Improves speed after taking a picture +- Fixes issue with emojis disappearing in the image editor + ## 0.0.86 - Allows to reopen send images (if send without time limit or enabled auth) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index bc0b3a7..deed6bf 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -33,6 +33,21 @@ + + + + + + + + + + + + + + + diff --git a/assets/filters/beard_upper_lip.webp b/assets/filters/beard_upper_lip.webp new file mode 100644 index 0000000..058b2ed Binary files /dev/null and b/assets/filters/beard_upper_lip.webp differ diff --git a/assets/filters/dog_brown_ear.webp b/assets/filters/dog_brown_ear.webp new file mode 100644 index 0000000..0dfec38 Binary files /dev/null and b/assets/filters/dog_brown_ear.webp differ diff --git a/assets/filters/dog_brown_nose.webp b/assets/filters/dog_brown_nose.webp new file mode 100644 index 0000000..7716995 Binary files /dev/null and b/assets/filters/dog_brown_nose.webp differ diff --git a/ios/NotificationService/NotificationService.swift b/ios/NotificationService/NotificationService.swift index ab236bd..5ec1185 100644 --- a/ios/NotificationService/NotificationService.swift +++ b/ios/NotificationService/NotificationService.swift @@ -109,10 +109,10 @@ func getPushNotificationData(pushData: String) -> ( } else if pushUser != nil { return ( pushUser!.displayName, - getPushNotificationText(pushNotification: pushNotification).0, pushUser!.userID + getPushNotificationText(pushNotification: pushNotification, userKnown: true).0, pushUser!.userID ) } else { - let content = getPushNotificationText(pushNotification: pushNotification) + let content = getPushNotificationText(pushNotification: pushNotification, userKnown: false) return ( content.1, content.0, 1 ) @@ -205,57 +205,77 @@ func readFromKeychain(key: String) -> String? { return nil } -func getPushNotificationText(pushNotification: PushNotification) -> (String, String) { +func getPushNotificationText(pushNotification: PushNotification, userKnown: Bool) -> (String, String) { let systemLanguage = Locale.current.language.languageCode?.identifier ?? "en" // Get the current system language var pushNotificationText: [PushKind: String] = [:] - var title = "[Unknown]" + var title = "You" + var noTranslationFoundTitle = "You have a new message." + var noTranslationFoundBody = "Open twonly to learn more." // Define the messages based on the system language if systemLanguage.contains("de") { // German - title = "[Unbekannt]" - pushNotificationText = [ - .text: "hat eine Nachricht{inGroup} gesendet.", - .twonly: "hat ein twonly{inGroup} gesendet.", - .video: "hat ein Video{inGroup} gesendet.", - .image: "hat ein Bild{inGroup} gesendet.", - .audio: "hat eine Sprachnachricht{inGroup} gesendet.", - .contactRequest: "möchte sich mit dir vernetzen.", - .acceptRequest: "ist jetzt mit dir vernetzt.", - .storedMediaFile: "hat dein Bild gespeichert.", - .reaction: "hat auf dein Bild reagiert.", - .testNotification: "Das ist eine Testbenachrichtigung.", - .reopenedMedia: "hat dein Bild erneut geöffnet.", - .reactionToVideo: "hat mit {{content}} auf dein Video reagiert.", - .reactionToText: "hat mit {{content}} auf deinen Text reagiert.", - .reactionToImage: "hat mit {{content}} auf dein Bild reagiert.", - .reactionToAudio: "hat mit {{content}} auf deine Sprachnachricht reagiert.", - .response: "hat dir{inGroup} geantwortet.", - .addedToGroup: "hat dich zu \"{{content}}\" hinzugefügt.", - ] - } else { // Default to English - pushNotificationText = [ - .text: "sent a message{inGroup}.", - .twonly: "sent a twonly{inGroup}.", - .video: "sent a video{inGroup}.", - .image: "sent an image{inGroup}.", - .audio: "sent a voice message{inGroup}.", - .contactRequest: "wants to connect with you.", - .acceptRequest: "is now connected with you.", - .storedMediaFile: "has stored your image.", - .reaction: "has reacted to your image.", - .testNotification: "This is a test notification.", - .reopenedMedia: "has reopened your image.", - .reactionToVideo: "has reacted with {{content}} to your video.", - .reactionToText: "has reacted with {{content}} to your text.", - .reactionToImage: "has reacted with {{content}} to your image.", - .reactionToAudio: "has reacted with {{content}} to your voice message.", - .response: "has responded{inGroup}.", - .addedToGroup: "has added you to \"{{content}}\"", - ] + title = "Du" + noTranslationFoundTitle = "Du hast eine neue Nachricht." + noTranslationFoundBody = "Öffne twonly um mehr zu erfahren." + if (userKnown) { + pushNotificationText = [ + .text: "hat eine Nachricht{inGroup} gesendet.", + .twonly: "hat ein twonly{inGroup} gesendet.", + .video: "hat ein Video{inGroup} gesendet.", + .image: "hat ein Bild{inGroup} gesendet.", + .audio: "hat eine Sprachnachricht{inGroup} gesendet.", + .contactRequest: "möchte sich mit dir vernetzen.", + .acceptRequest: "ist jetzt mit dir vernetzt.", + .storedMediaFile: "hat dein Bild gespeichert.", + .reaction: "hat auf dein Bild reagiert.", + .testNotification: "Das ist eine Testbenachrichtigung.", + .reopenedMedia: "hat dein Bild erneut geöffnet.", + .reactionToVideo: "hat mit {{content}} auf dein Video reagiert.", + .reactionToText: "hat mit {{content}} auf deinen Text reagiert.", + .reactionToImage: "hat mit {{content}} auf dein Bild reagiert.", + .reactionToAudio: "hat mit {{content}} auf deine Sprachnachricht reagiert.", + .response: "hat dir{inGroup} geantwortet.", + .addedToGroup: "hat dich zu \"{{content}}\" hinzugefügt.", + ] + } else { + pushNotificationText = [ + .contactRequest: "hast eine neue Kontaktanfrage erhalten.", + ] + } + } else { + if (userKnown) { + pushNotificationText = [ + .text: "sent a message{inGroup}.", + .twonly: "sent a twonly{inGroup}.", + .video: "sent a video{inGroup}.", + .image: "sent an image{inGroup}.", + .audio: "sent a voice message{inGroup}.", + .contactRequest: "wants to connect with you.", + .acceptRequest: "is now connected with you.", + .storedMediaFile: "has stored your image.", + .reaction: "has reacted to your image.", + .testNotification: "This is a test notification.", + .reopenedMedia: "has reopened your image.", + .reactionToVideo: "has reacted with {{content}} to your video.", + .reactionToText: "has reacted with {{content}} to your text.", + .reactionToImage: "has reacted with {{content}} to your image.", + .reactionToAudio: "has reacted with {{content}} to your voice message.", + .response: "has responded{inGroup}.", + .addedToGroup: "has added you to \"{{content}}\"", + ] + } else { + pushNotificationText = [ + .contactRequest: "have received a new contact request.", + ] + } } var content = pushNotificationText[pushNotification.kind] ?? "" + if (content == "") { + title = noTranslationFoundTitle + content = noTranslationFoundBody + } if pushNotification.hasAdditionalContent { content.replace("{{content}}", with: pushNotification.additionalContent) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 301b4b9..b0cfa92 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -136,6 +136,10 @@ PODS: - google_mlkit_commons (0.11.0): - Flutter - MLKitVision + - google_mlkit_face_detection (0.13.1): + - Flutter + - google_mlkit_commons + - GoogleMLKit/FaceDetection (~> 7.0.0) - GoogleAdsOnDeviceConversion (3.2.0): - GoogleUtilities/Environment (~> 8.1) - GoogleUtilities/Logger (~> 8.1) @@ -169,6 +173,9 @@ PODS: - GoogleMLKit/BarcodeScanning (7.0.0): - GoogleMLKit/MLKitCore - MLKitBarcodeScanning (~> 6.0.0) + - GoogleMLKit/FaceDetection (7.0.0): + - GoogleMLKit/MLKitCore + - MLKitFaceDetection (~> 6.0.0) - GoogleMLKit/MLKitCore (7.0.0): - MLKitCommon (~> 12.0.0) - GoogleToolboxForMac/Defines (4.2.1) @@ -251,6 +258,9 @@ PODS: - GoogleUtilities/Logger (~> 8.0) - GoogleUtilities/UserDefaults (~> 8.0) - GTMSessionFetcher/Core (< 4.0, >= 3.3.2) + - MLKitFaceDetection (6.0.0): + - MLKitCommon (~> 12.0) + - MLKitVision (~> 8.0) - MLKitVision (8.0.0): - GoogleToolboxForMac/Logger (< 5.0, >= 4.2.1) - "GoogleToolboxForMac/NSData+zlib (< 5.0, >= 4.2.1)" @@ -357,6 +367,7 @@ DEPENDENCIES: - gal (from `.symlinks/plugins/gal/darwin`) - google_mlkit_barcode_scanning (from `.symlinks/plugins/google_mlkit_barcode_scanning/ios`) - google_mlkit_commons (from `.symlinks/plugins/google_mlkit_commons/ios`) + - google_mlkit_face_detection (from `.symlinks/plugins/google_mlkit_face_detection/ios`) - GoogleUtilities - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) - in_app_purchase_storekit (from `.symlinks/plugins/in_app_purchase_storekit/darwin`) @@ -398,6 +409,7 @@ SPEC REPOS: - MLImage - MLKitBarcodeScanning - MLKitCommon + - MLKitFaceDetection - MLKitVision - nanopb - PromisesObjC @@ -454,6 +466,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/google_mlkit_barcode_scanning/ios" google_mlkit_commons: :path: ".symlinks/plugins/google_mlkit_commons/ios" + google_mlkit_face_detection: + :path: ".symlinks/plugins/google_mlkit_face_detection/ios" image_picker_ios: :path: ".symlinks/plugins/image_picker_ios/ios" in_app_purchase_storekit: @@ -518,6 +532,7 @@ SPEC CHECKSUMS: gal: baecd024ebfd13c441269ca7404792a7152fde89 google_mlkit_barcode_scanning: 8f5987f244a43fe1167689c548342a5174108159 google_mlkit_commons: 2abe6a70e1824e431d16a51085cb475b672c8aab + google_mlkit_face_detection: 754da2113a1952f063c7c5dc347ac6ae8934fb77 GoogleAdsOnDeviceConversion: d68c69dd9581a0f5da02617b6f377e5be483970f GoogleAppMeasurement: 3bf40aff49a601af5da1c3345702fcb4991d35ee GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 @@ -533,6 +548,7 @@ SPEC CHECKSUMS: MLImage: 0ad1c5f50edd027672d8b26b0fee78a8b4a0fc56 MLKitBarcodeScanning: 0a3064da0a7f49ac24ceb3cb46a5bc67496facd2 MLKitCommon: 07c2c33ae5640e5380beaaa6e4b9c249a205542d + MLKitFaceDetection: 2a593db4837db503ad3426b565e7aab045cefea5 MLKitVision: 45e79d68845a2de77e2dd4d7f07947f0ed157b0e nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 no_screenshot: 89e778ede9f1e39cc3fb9404d782a42712f2a0b2 diff --git a/ios/ShareExtension/Info.plist b/ios/ShareExtension/Info.plist index 3e8521b..5edbc20 100644 --- a/ios/ShareExtension/Info.plist +++ b/ios/ShareExtension/Info.plist @@ -22,6 +22,8 @@ NSExtensionActivationRule + NSExtensionActivationSupportsWebURLWithMaxCount + 1 NSExtensionActivationSupportsWebURLWithMaxCount 1 NSExtensionActivationSupportsImageWithMaxCount diff --git a/lib/src/database/daos/reactions.dao.dart b/lib/src/database/daos/reactions.dao.dart index 59ef34a..286fc68 100644 --- a/lib/src/database/daos/reactions.dao.dart +++ b/lib/src/database/daos/reactions.dao.dart @@ -22,7 +22,7 @@ class ReactionsDao extends DatabaseAccessor with _$ReactionsDaoMixin { String emoji, bool remove, ) async { - if (!isEmoji(emoji)) { + if (!isOneEmoji(emoji)) { Log.error('Did not update reaction as it is not an emoji!'); return; } @@ -59,7 +59,7 @@ class ReactionsDao extends DatabaseAccessor with _$ReactionsDaoMixin { String emoji, bool remove, ) async { - if (!isEmoji(emoji)) { + if (!isOneEmoji(emoji)) { Log.error('Did not update reaction as it is not an emoji!'); return; } diff --git a/lib/src/database/schemas/twonly_db/drift_schema_v7.json b/lib/src/database/schemas/twonly_db/drift_schema_v7.json new file mode 100644 index 0000000..f77953a --- /dev/null +++ b/lib/src/database/schemas/twonly_db/drift_schema_v7.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":"additional_message_data","getter_name":"additionalMessageData","moor_type":"blob","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"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/messages.table.dart b/lib/src/database/tables/messages.table.dart index 6739432..89e2975 100644 --- a/lib/src/database/tables/messages.table.dart +++ b/lib/src/database/tables/messages.table.dart @@ -22,6 +22,8 @@ class Messages extends Table { .nullable() .references(MediaFiles, #mediaId, onDelete: KeyAction.setNull)(); + BlobColumn get additionalMessageData => blob().nullable()(); + BoolColumn get mediaStored => boolean().withDefault(const Constant(false))(); BoolColumn get mediaReopened => boolean().withDefault(const Constant(false))(); @@ -56,7 +58,7 @@ class MessageActions extends Table { text().references(Messages, #messageId, onDelete: KeyAction.cascade)(); IntColumn get contactId => - integer().references(Contacts, #contactId, onDelete: KeyAction.cascade)(); + integer().references(Contacts, #userId, onDelete: KeyAction.cascade)(); TextColumn get type => textEnum()(); @@ -75,7 +77,7 @@ class MessageHistories extends Table { IntColumn get contactId => integer() .nullable() - .references(Contacts, #contactId, onDelete: KeyAction.cascade)(); + .references(Contacts, #userId, onDelete: KeyAction.cascade)(); TextColumn get content => text().nullable()(); diff --git a/lib/src/database/twonly.db.dart b/lib/src/database/twonly.db.dart index 3cace23..07ec778 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 => 6; + int get schemaVersion => 7; static QueryExecutor _openConnection() { return driftDatabase( @@ -85,39 +85,49 @@ class TwonlyDB extends _$TwonlyDB { beforeOpen: (details) async { await customStatement('PRAGMA foreign_keys = ON'); }, - onUpgrade: stepByStep( - from1To2: (m, schema) async { - await m.addColumn(schema.messages, schema.messages.mediaReopened); - await m.dropColumn(schema.mediaFiles, 'reopen_by_contact'); - }, - from2To3: (m, schema) async { - await m.addColumn(schema.groups, schema.groups.draftMessage); - }, - from3To4: (m, schema) async { - await m.alterTable( - TableMigration( - schema.groupHistories, - columnTransformer: { - schema.groupHistories.affectedContactId: - schema.groupHistories.affectedContactId, - }, - ), - ); - }, - from4To5: (m, schema) async { - await m.addColumn(schema.receipts, schema.receipts.markForRetry); - await m.addColumn( - schema.mediaFiles, - schema.mediaFiles.storedFileHash, - ); - }, - from5To6: (m, schema) async { - await m.addColumn( - schema.receipts, - schema.receipts.markForRetryAfterAccepted, - ); - }, - ), + onUpgrade: (m, from, to) async { + // disable foreign_keys before migrations + await customStatement('PRAGMA foreign_keys = OFF'); + return stepByStep( + from1To2: (m, schema) async { + await m.addColumn(schema.messages, schema.messages.mediaReopened); + await m.dropColumn(schema.mediaFiles, 'reopen_by_contact'); + }, + from2To3: (m, schema) async { + await m.addColumn(schema.groups, schema.groups.draftMessage); + }, + from3To4: (m, schema) async { + await m.alterTable( + TableMigration( + schema.groupHistories, + columnTransformer: { + schema.groupHistories.affectedContactId: + schema.groupHistories.affectedContactId, + }, + ), + ); + }, + from4To5: (m, schema) async { + await m.addColumn(schema.receipts, schema.receipts.markForRetry); + await m.addColumn( + schema.mediaFiles, + schema.mediaFiles.storedFileHash, + ); + }, + from5To6: (m, schema) async { + await m.addColumn( + schema.receipts, + schema.receipts.markForRetryAfterAccepted, + ); + }, + from6To7: (m, schema) async { + await m.addColumn( + schema.messages, + schema.messages.additionalMessageData, + ); + }, + )(m, from, to); + }, ); } diff --git a/lib/src/database/twonly.db.g.dart b/lib/src/database/twonly.db.g.dart index 0ae2daf..bad66a1 100644 --- a/lib/src/database/twonly.db.g.dart +++ b/lib/src/database/twonly.db.g.dart @@ -2796,6 +2796,12 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> { requiredDuringInsert: false, defaultConstraints: GeneratedColumn.constraintIsAlways( 'REFERENCES media_files (media_id) ON DELETE SET NULL')); + static const VerificationMeta _additionalMessageDataMeta = + const VerificationMeta('additionalMessageData'); + @override + late final GeneratedColumn additionalMessageData = + GeneratedColumn('additional_message_data', aliasedName, true, + type: DriftSqlType.blob, requiredDuringInsert: false); static const VerificationMeta _mediaStoredMeta = const VerificationMeta('mediaStored'); @override @@ -2884,6 +2890,7 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> { type, content, mediaId, + additionalMessageData, mediaStored, mediaReopened, downloadToken, @@ -2930,6 +2937,12 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> { context.handle(_mediaIdMeta, mediaId.isAcceptableOrUnknown(data['media_id']!, _mediaIdMeta)); } + if (data.containsKey('additional_message_data')) { + context.handle( + _additionalMessageDataMeta, + additionalMessageData.isAcceptableOrUnknown( + data['additional_message_data']!, _additionalMessageDataMeta)); + } if (data.containsKey('media_stored')) { context.handle( _mediaStoredMeta, @@ -3013,6 +3026,8 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> { .read(DriftSqlType.string, data['${effectivePrefix}content']), mediaId: attachedDatabase.typeMapping .read(DriftSqlType.string, data['${effectivePrefix}media_id']), + additionalMessageData: attachedDatabase.typeMapping.read( + DriftSqlType.blob, data['${effectivePrefix}additional_message_data']), mediaStored: attachedDatabase.typeMapping .read(DriftSqlType.bool, data['${effectivePrefix}media_stored'])!, mediaReopened: attachedDatabase.typeMapping @@ -3054,6 +3069,7 @@ class Message extends DataClass implements Insertable { final MessageType type; final String? content; final String? mediaId; + final Uint8List? additionalMessageData; final bool mediaStored; final bool mediaReopened; final Uint8List? downloadToken; @@ -3072,6 +3088,7 @@ class Message extends DataClass implements Insertable { required this.type, this.content, this.mediaId, + this.additionalMessageData, required this.mediaStored, required this.mediaReopened, this.downloadToken, @@ -3100,6 +3117,10 @@ class Message extends DataClass implements Insertable { if (!nullToAbsent || mediaId != null) { map['media_id'] = Variable(mediaId); } + if (!nullToAbsent || additionalMessageData != null) { + map['additional_message_data'] = + Variable(additionalMessageData); + } map['media_stored'] = Variable(mediaStored); map['media_reopened'] = Variable(mediaReopened); if (!nullToAbsent || downloadToken != null) { @@ -3142,6 +3163,9 @@ class Message extends DataClass implements Insertable { mediaId: mediaId == null && nullToAbsent ? const Value.absent() : Value(mediaId), + additionalMessageData: additionalMessageData == null && nullToAbsent + ? const Value.absent() + : Value(additionalMessageData), mediaStored: Value(mediaStored), mediaReopened: Value(mediaReopened), downloadToken: downloadToken == null && nullToAbsent @@ -3181,6 +3205,8 @@ class Message extends DataClass implements Insertable { .fromJson(serializer.fromJson(json['type'])), content: serializer.fromJson(json['content']), mediaId: serializer.fromJson(json['mediaId']), + additionalMessageData: + serializer.fromJson(json['additionalMessageData']), mediaStored: serializer.fromJson(json['mediaStored']), mediaReopened: serializer.fromJson(json['mediaReopened']), downloadToken: serializer.fromJson(json['downloadToken']), @@ -3206,6 +3232,8 @@ class Message extends DataClass implements Insertable { serializer.toJson($MessagesTable.$convertertype.toJson(type)), 'content': serializer.toJson(content), 'mediaId': serializer.toJson(mediaId), + 'additionalMessageData': + serializer.toJson(additionalMessageData), 'mediaStored': serializer.toJson(mediaStored), 'mediaReopened': serializer.toJson(mediaReopened), 'downloadToken': serializer.toJson(downloadToken), @@ -3227,6 +3255,7 @@ class Message extends DataClass implements Insertable { MessageType? type, Value content = const Value.absent(), Value mediaId = const Value.absent(), + Value additionalMessageData = const Value.absent(), bool? mediaStored, bool? mediaReopened, Value downloadToken = const Value.absent(), @@ -3245,6 +3274,9 @@ class Message extends DataClass implements Insertable { type: type ?? this.type, content: content.present ? content.value : this.content, mediaId: mediaId.present ? mediaId.value : this.mediaId, + additionalMessageData: additionalMessageData.present + ? additionalMessageData.value + : this.additionalMessageData, mediaStored: mediaStored ?? this.mediaStored, mediaReopened: mediaReopened ?? this.mediaReopened, downloadToken: @@ -3268,6 +3300,9 @@ class Message extends DataClass implements Insertable { type: data.type.present ? data.type.value : this.type, content: data.content.present ? data.content.value : this.content, mediaId: data.mediaId.present ? data.mediaId.value : this.mediaId, + additionalMessageData: data.additionalMessageData.present + ? data.additionalMessageData.value + : this.additionalMessageData, mediaStored: data.mediaStored.present ? data.mediaStored.value : this.mediaStored, mediaReopened: data.mediaReopened.present @@ -3303,6 +3338,7 @@ class Message extends DataClass implements Insertable { ..write('type: $type, ') ..write('content: $content, ') ..write('mediaId: $mediaId, ') + ..write('additionalMessageData: $additionalMessageData, ') ..write('mediaStored: $mediaStored, ') ..write('mediaReopened: $mediaReopened, ') ..write('downloadToken: $downloadToken, ') @@ -3326,6 +3362,7 @@ class Message extends DataClass implements Insertable { type, content, mediaId, + $driftBlobEquality.hash(additionalMessageData), mediaStored, mediaReopened, $driftBlobEquality.hash(downloadToken), @@ -3347,6 +3384,8 @@ class Message extends DataClass implements Insertable { other.type == this.type && other.content == this.content && other.mediaId == this.mediaId && + $driftBlobEquality.equals( + other.additionalMessageData, this.additionalMessageData) && other.mediaStored == this.mediaStored && other.mediaReopened == this.mediaReopened && $driftBlobEquality.equals(other.downloadToken, this.downloadToken) && @@ -3367,6 +3406,7 @@ class MessagesCompanion extends UpdateCompanion { final Value type; final Value content; final Value mediaId; + final Value additionalMessageData; final Value mediaStored; final Value mediaReopened; final Value downloadToken; @@ -3386,6 +3426,7 @@ class MessagesCompanion extends UpdateCompanion { this.type = const Value.absent(), this.content = const Value.absent(), this.mediaId = const Value.absent(), + this.additionalMessageData = const Value.absent(), this.mediaStored = const Value.absent(), this.mediaReopened = const Value.absent(), this.downloadToken = const Value.absent(), @@ -3406,6 +3447,7 @@ class MessagesCompanion extends UpdateCompanion { required MessageType type, this.content = const Value.absent(), this.mediaId = const Value.absent(), + this.additionalMessageData = const Value.absent(), this.mediaStored = const Value.absent(), this.mediaReopened = const Value.absent(), this.downloadToken = const Value.absent(), @@ -3428,6 +3470,7 @@ class MessagesCompanion extends UpdateCompanion { Expression? type, Expression? content, Expression? mediaId, + Expression? additionalMessageData, Expression? mediaStored, Expression? mediaReopened, Expression? downloadToken, @@ -3448,6 +3491,8 @@ class MessagesCompanion extends UpdateCompanion { if (type != null) 'type': type, if (content != null) 'content': content, if (mediaId != null) 'media_id': mediaId, + if (additionalMessageData != null) + 'additional_message_data': additionalMessageData, if (mediaStored != null) 'media_stored': mediaStored, if (mediaReopened != null) 'media_reopened': mediaReopened, if (downloadToken != null) 'download_token': downloadToken, @@ -3471,6 +3516,7 @@ class MessagesCompanion extends UpdateCompanion { Value? type, Value? content, Value? mediaId, + Value? additionalMessageData, Value? mediaStored, Value? mediaReopened, Value? downloadToken, @@ -3490,6 +3536,8 @@ class MessagesCompanion extends UpdateCompanion { type: type ?? this.type, content: content ?? this.content, mediaId: mediaId ?? this.mediaId, + additionalMessageData: + additionalMessageData ?? this.additionalMessageData, mediaStored: mediaStored ?? this.mediaStored, mediaReopened: mediaReopened ?? this.mediaReopened, downloadToken: downloadToken ?? this.downloadToken, @@ -3527,6 +3575,10 @@ class MessagesCompanion extends UpdateCompanion { if (mediaId.present) { map['media_id'] = Variable(mediaId.value); } + if (additionalMessageData.present) { + map['additional_message_data'] = + Variable(additionalMessageData.value); + } if (mediaStored.present) { map['media_stored'] = Variable(mediaStored.value); } @@ -3575,6 +3627,7 @@ class MessagesCompanion extends UpdateCompanion { ..write('type: $type, ') ..write('content: $content, ') ..write('mediaId: $mediaId, ') + ..write('additionalMessageData: $additionalMessageData, ') ..write('mediaStored: $mediaStored, ') ..write('mediaReopened: $mediaReopened, ') ..write('downloadToken: $downloadToken, ') @@ -3621,7 +3674,10 @@ class $MessageHistoriesTable extends MessageHistories @override late final GeneratedColumn contactId = GeneratedColumn( 'contact_id', aliasedName, true, - type: DriftSqlType.int, requiredDuringInsert: false); + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES contacts (user_id) ON DELETE CASCADE')); static const VerificationMeta _contentMeta = const VerificationMeta('content'); @override @@ -6964,7 +7020,10 @@ class $MessageActionsTable extends MessageActions @override late final GeneratedColumn contactId = GeneratedColumn( 'contact_id', aliasedName, false, - type: DriftSqlType.int, requiredDuringInsert: true); + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES contacts (user_id) ON DELETE CASCADE')); @override late final GeneratedColumnWithTypeConverter type = GeneratedColumn('type', aliasedName, false, @@ -7837,6 +7896,13 @@ abstract class _$TwonlyDB extends GeneratedDatabase { TableUpdate('message_histories', kind: UpdateKind.delete), ], ), + WritePropagation( + on: TableUpdateQuery.onTableName('contacts', + limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('message_histories', kind: UpdateKind.delete), + ], + ), WritePropagation( on: TableUpdateQuery.onTableName('messages', limitUpdateKind: UpdateKind.delete), @@ -7894,6 +7960,13 @@ abstract class _$TwonlyDB extends GeneratedDatabase { TableUpdate('message_actions', kind: UpdateKind.delete), ], ), + WritePropagation( + on: TableUpdateQuery.onTableName('contacts', + limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('message_actions', kind: UpdateKind.delete), + ], + ), WritePropagation( on: TableUpdateQuery.onTableName('groups', limitUpdateKind: UpdateKind.delete), @@ -7955,6 +8028,23 @@ final class $$ContactsTableReferences manager.$state.copyWith(prefetchedData: cache)); } + static MultiTypedResultKey<$MessageHistoriesTable, List> + _messageHistoriesRefsTable(_$TwonlyDB db) => + MultiTypedResultKey.fromTable(db.messageHistories, + aliasName: $_aliasNameGenerator( + db.contacts.userId, db.messageHistories.contactId)); + + $$MessageHistoriesTableProcessedTableManager get messageHistoriesRefs { + final manager = + $$MessageHistoriesTableTableManager($_db, $_db.messageHistories).filter( + (f) => f.contactId.userId.sqlEquals($_itemColumn('user_id')!)); + + final cache = + $_typedResult.readTableOrNull(_messageHistoriesRefsTable($_db)); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache)); + } + static MultiTypedResultKey<$ReactionsTable, List> _reactionsRefsTable(_$TwonlyDB db) => MultiTypedResultKey.fromTable( db.reactions, @@ -8041,6 +8131,22 @@ final class $$ContactsTableReferences manager.$state.copyWith(prefetchedData: cache)); } + static MultiTypedResultKey<$MessageActionsTable, List> + _messageActionsRefsTable(_$TwonlyDB db) => + MultiTypedResultKey.fromTable(db.messageActions, + aliasName: $_aliasNameGenerator( + db.contacts.userId, db.messageActions.contactId)); + + $$MessageActionsTableProcessedTableManager get messageActionsRefs { + final manager = $$MessageActionsTableTableManager($_db, $_db.messageActions) + .filter( + (f) => f.contactId.userId.sqlEquals($_itemColumn('user_id')!)); + + final cache = $_typedResult.readTableOrNull(_messageActionsRefsTable($_db)); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache)); + } + static MultiTypedResultKey<$GroupHistoriesTable, List> _groupHistoriesRefsTable(_$TwonlyDB db) => MultiTypedResultKey.fromTable(db.groupHistories, @@ -8130,6 +8236,27 @@ class $$ContactsTableFilterComposer return f(composer); } + Expression messageHistoriesRefs( + Expression Function($$MessageHistoriesTableFilterComposer f) f) { + final $$MessageHistoriesTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.userId, + referencedTable: $db.messageHistories, + getReferencedColumn: (t) => t.contactId, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $$MessageHistoriesTableFilterComposer( + $db: $db, + $table: $db.messageHistories, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return f(composer); + } + Expression reactionsRefs( Expression Function($$ReactionsTableFilterComposer f) f) { final $$ReactionsTableFilterComposer composer = $composerBuilder( @@ -8239,6 +8366,27 @@ class $$ContactsTableFilterComposer return f(composer); } + Expression messageActionsRefs( + Expression Function($$MessageActionsTableFilterComposer f) f) { + final $$MessageActionsTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.userId, + referencedTable: $db.messageActions, + getReferencedColumn: (t) => t.contactId, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $$MessageActionsTableFilterComposer( + $db: $db, + $table: $db.messageActions, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return f(composer); + } + Expression groupHistoriesRefs( Expression Function($$GroupHistoriesTableFilterComposer f) f) { final $$GroupHistoriesTableFilterComposer composer = $composerBuilder( @@ -8383,6 +8531,27 @@ class $$ContactsTableAnnotationComposer return f(composer); } + Expression messageHistoriesRefs( + Expression Function($$MessageHistoriesTableAnnotationComposer a) f) { + final $$MessageHistoriesTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.userId, + referencedTable: $db.messageHistories, + getReferencedColumn: (t) => t.contactId, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $$MessageHistoriesTableAnnotationComposer( + $db: $db, + $table: $db.messageHistories, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return f(composer); + } + Expression reactionsRefs( Expression Function($$ReactionsTableAnnotationComposer a) f) { final $$ReactionsTableAnnotationComposer composer = $composerBuilder( @@ -8493,6 +8662,27 @@ class $$ContactsTableAnnotationComposer return f(composer); } + Expression messageActionsRefs( + Expression Function($$MessageActionsTableAnnotationComposer a) f) { + final $$MessageActionsTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.userId, + referencedTable: $db.messageActions, + getReferencedColumn: (t) => t.contactId, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $$MessageActionsTableAnnotationComposer( + $db: $db, + $table: $db.messageActions, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return f(composer); + } + Expression groupHistoriesRefs( Expression Function($$GroupHistoriesTableAnnotationComposer a) f) { final $$GroupHistoriesTableAnnotationComposer composer = $composerBuilder( @@ -8528,11 +8718,13 @@ class $$ContactsTableTableManager extends RootTableManager< Contact, PrefetchHooks Function( {bool messagesRefs, + bool messageHistoriesRefs, bool reactionsRefs, bool groupMembersRefs, bool receiptsRefs, bool signalContactPreKeysRefs, bool signalContactSignedPreKeysRefs, + bool messageActionsRefs, bool groupHistoriesRefs})> { $$ContactsTableTableManager(_$TwonlyDB db, $ContactsTable table) : super(TableManagerState( @@ -8610,22 +8802,26 @@ class $$ContactsTableTableManager extends RootTableManager< .toList(), prefetchHooksCallback: ( {messagesRefs = false, + messageHistoriesRefs = false, reactionsRefs = false, groupMembersRefs = false, receiptsRefs = false, signalContactPreKeysRefs = false, signalContactSignedPreKeysRefs = false, + messageActionsRefs = false, groupHistoriesRefs = false}) { return PrefetchHooks( db: db, explicitlyWatchedTables: [ if (messagesRefs) db.messages, + if (messageHistoriesRefs) db.messageHistories, if (reactionsRefs) db.reactions, if (groupMembersRefs) db.groupMembers, if (receiptsRefs) db.receipts, if (signalContactPreKeysRefs) db.signalContactPreKeys, if (signalContactSignedPreKeysRefs) db.signalContactSignedPreKeys, + if (messageActionsRefs) db.messageActions, if (groupHistoriesRefs) db.groupHistories ], addJoins: null, @@ -8643,6 +8839,19 @@ class $$ContactsTableTableManager extends RootTableManager< (item, referencedItems) => referencedItems .where((e) => e.senderId == item.userId), typedResults: items), + if (messageHistoriesRefs) + await $_getPrefetchedData( + currentTable: table, + referencedTable: $$ContactsTableReferences + ._messageHistoriesRefsTable(db), + managerFromTypedResult: (p0) => + $$ContactsTableReferences(db, table, p0) + .messageHistoriesRefs, + referencedItemsForCurrentItem: + (item, referencedItems) => referencedItems + .where((e) => e.contactId == item.userId), + typedResults: items), if (reactionsRefs) await $_getPrefetchedData( @@ -8707,6 +8916,19 @@ class $$ContactsTableTableManager extends RootTableManager< (item, referencedItems) => referencedItems .where((e) => e.contactId == item.userId), typedResults: items), + if (messageActionsRefs) + await $_getPrefetchedData( + currentTable: table, + referencedTable: $$ContactsTableReferences + ._messageActionsRefsTable(db), + managerFromTypedResult: (p0) => + $$ContactsTableReferences(db, table, p0) + .messageActionsRefs, + referencedItemsForCurrentItem: + (item, referencedItems) => referencedItems + .where((e) => e.contactId == item.userId), + typedResults: items), if (groupHistoriesRefs) await $_getPrefetchedData( @@ -8740,11 +8962,13 @@ typedef $$ContactsTableProcessedTableManager = ProcessedTableManager< Contact, PrefetchHooks Function( {bool messagesRefs, + bool messageHistoriesRefs, bool reactionsRefs, bool groupMembersRefs, bool receiptsRefs, bool signalContactPreKeysRefs, bool signalContactSignedPreKeysRefs, + bool messageActionsRefs, bool groupHistoriesRefs})>; typedef $$GroupsTableCreateCompanionBuilder = GroupsCompanion Function({ required String groupId, @@ -9927,6 +10151,7 @@ typedef $$MessagesTableCreateCompanionBuilder = MessagesCompanion Function({ required MessageType type, Value content, Value mediaId, + Value additionalMessageData, Value mediaStored, Value mediaReopened, Value downloadToken, @@ -9947,6 +10172,7 @@ typedef $$MessagesTableUpdateCompanionBuilder = MessagesCompanion Function({ Value type, Value content, Value mediaId, + Value additionalMessageData, Value mediaStored, Value mediaReopened, Value downloadToken, @@ -10096,6 +10322,10 @@ class $$MessagesTableFilterComposer ColumnFilters get content => $composableBuilder( column: $table.content, builder: (column) => ColumnFilters(column)); + ColumnFilters get additionalMessageData => $composableBuilder( + column: $table.additionalMessageData, + builder: (column) => ColumnFilters(column)); + ColumnFilters get mediaStored => $composableBuilder( column: $table.mediaStored, builder: (column) => ColumnFilters(column)); @@ -10294,6 +10524,10 @@ class $$MessagesTableOrderingComposer ColumnOrderings get content => $composableBuilder( column: $table.content, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get additionalMessageData => $composableBuilder( + column: $table.additionalMessageData, + builder: (column) => ColumnOrderings(column)); + ColumnOrderings get mediaStored => $composableBuilder( column: $table.mediaStored, builder: (column) => ColumnOrderings(column)); @@ -10410,6 +10644,9 @@ class $$MessagesTableAnnotationComposer GeneratedColumn get content => $composableBuilder(column: $table.content, builder: (column) => column); + GeneratedColumn get additionalMessageData => $composableBuilder( + column: $table.additionalMessageData, builder: (column) => column); + GeneratedColumn get mediaStored => $composableBuilder( column: $table.mediaStored, builder: (column) => column); @@ -10624,6 +10861,7 @@ class $$MessagesTableTableManager extends RootTableManager< Value type = const Value.absent(), Value content = const Value.absent(), Value mediaId = const Value.absent(), + Value additionalMessageData = const Value.absent(), Value mediaStored = const Value.absent(), Value mediaReopened = const Value.absent(), Value downloadToken = const Value.absent(), @@ -10644,6 +10882,7 @@ class $$MessagesTableTableManager extends RootTableManager< type: type, content: content, mediaId: mediaId, + additionalMessageData: additionalMessageData, mediaStored: mediaStored, mediaReopened: mediaReopened, downloadToken: downloadToken, @@ -10664,6 +10903,7 @@ class $$MessagesTableTableManager extends RootTableManager< required MessageType type, Value content = const Value.absent(), Value mediaId = const Value.absent(), + Value additionalMessageData = const Value.absent(), Value mediaStored = const Value.absent(), Value mediaReopened = const Value.absent(), Value downloadToken = const Value.absent(), @@ -10684,6 +10924,7 @@ class $$MessagesTableTableManager extends RootTableManager< type: type, content: content, mediaId: mediaId, + additionalMessageData: additionalMessageData, mediaStored: mediaStored, mediaReopened: mediaReopened, downloadToken: downloadToken, @@ -10878,6 +11119,21 @@ final class $$MessageHistoriesTableReferences return ProcessedTableManager( manager.$state.copyWith(prefetchedData: [item])); } + + static $ContactsTable _contactIdTable(_$TwonlyDB db) => + db.contacts.createAlias($_aliasNameGenerator( + db.messageHistories.contactId, db.contacts.userId)); + + $$ContactsTableProcessedTableManager? get contactId { + final $_column = $_itemColumn('contact_id'); + if ($_column == null) return null; + final manager = $$ContactsTableTableManager($_db, $_db.contacts) + .filter((f) => f.userId.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_contactIdTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item])); + } } class $$MessageHistoriesTableFilterComposer @@ -10892,9 +11148,6 @@ class $$MessageHistoriesTableFilterComposer ColumnFilters get id => $composableBuilder( column: $table.id, builder: (column) => ColumnFilters(column)); - ColumnFilters get contactId => $composableBuilder( - column: $table.contactId, builder: (column) => ColumnFilters(column)); - ColumnFilters get content => $composableBuilder( column: $table.content, builder: (column) => ColumnFilters(column)); @@ -10920,6 +11173,26 @@ class $$MessageHistoriesTableFilterComposer )); return composer; } + + $$ContactsTableFilterComposer get contactId { + final $$ContactsTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.contactId, + referencedTable: $db.contacts, + getReferencedColumn: (t) => t.userId, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $$ContactsTableFilterComposer( + $db: $db, + $table: $db.contacts, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } } class $$MessageHistoriesTableOrderingComposer @@ -10934,9 +11207,6 @@ class $$MessageHistoriesTableOrderingComposer ColumnOrderings get id => $composableBuilder( column: $table.id, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get contactId => $composableBuilder( - column: $table.contactId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get content => $composableBuilder( column: $table.content, builder: (column) => ColumnOrderings(column)); @@ -10962,6 +11232,26 @@ class $$MessageHistoriesTableOrderingComposer )); return composer; } + + $$ContactsTableOrderingComposer get contactId { + final $$ContactsTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.contactId, + referencedTable: $db.contacts, + getReferencedColumn: (t) => t.userId, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $$ContactsTableOrderingComposer( + $db: $db, + $table: $db.contacts, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } } class $$MessageHistoriesTableAnnotationComposer @@ -10976,9 +11266,6 @@ class $$MessageHistoriesTableAnnotationComposer GeneratedColumn get id => $composableBuilder(column: $table.id, builder: (column) => column); - GeneratedColumn get contactId => - $composableBuilder(column: $table.contactId, builder: (column) => column); - GeneratedColumn get content => $composableBuilder(column: $table.content, builder: (column) => column); @@ -11004,6 +11291,26 @@ class $$MessageHistoriesTableAnnotationComposer )); return composer; } + + $$ContactsTableAnnotationComposer get contactId { + final $$ContactsTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.contactId, + referencedTable: $db.contacts, + getReferencedColumn: (t) => t.userId, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $$ContactsTableAnnotationComposer( + $db: $db, + $table: $db.contacts, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } } class $$MessageHistoriesTableTableManager extends RootTableManager< @@ -11017,7 +11324,7 @@ class $$MessageHistoriesTableTableManager extends RootTableManager< $$MessageHistoriesTableUpdateCompanionBuilder, (MessageHistory, $$MessageHistoriesTableReferences), MessageHistory, - PrefetchHooks Function({bool messageId})> { + PrefetchHooks Function({bool messageId, bool contactId})> { $$MessageHistoriesTableTableManager( _$TwonlyDB db, $MessageHistoriesTable table) : super(TableManagerState( @@ -11063,7 +11370,7 @@ class $$MessageHistoriesTableTableManager extends RootTableManager< $$MessageHistoriesTableReferences(db, table, e) )) .toList(), - prefetchHooksCallback: ({messageId = false}) { + prefetchHooksCallback: ({messageId = false, contactId = false}) { return PrefetchHooks( db: db, explicitlyWatchedTables: [], @@ -11091,6 +11398,17 @@ class $$MessageHistoriesTableTableManager extends RootTableManager< .messageId, ) as T; } + if (contactId) { + state = state.withJoin( + currentTable: table, + currentColumn: table.contactId, + referencedTable: + $$MessageHistoriesTableReferences._contactIdTable(db), + referencedColumn: $$MessageHistoriesTableReferences + ._contactIdTable(db) + .userId, + ) as T; + } return state; }, @@ -11113,7 +11431,7 @@ typedef $$MessageHistoriesTableProcessedTableManager = ProcessedTableManager< $$MessageHistoriesTableUpdateCompanionBuilder, (MessageHistory, $$MessageHistoriesTableReferences), MessageHistory, - PrefetchHooks Function({bool messageId})>; + PrefetchHooks Function({bool messageId, bool contactId})>; typedef $$ReactionsTableCreateCompanionBuilder = ReactionsCompanion Function({ required String messageId, required String emoji, @@ -13581,6 +13899,21 @@ final class $$MessageActionsTableReferences return ProcessedTableManager( manager.$state.copyWith(prefetchedData: [item])); } + + static $ContactsTable _contactIdTable(_$TwonlyDB db) => + db.contacts.createAlias($_aliasNameGenerator( + db.messageActions.contactId, db.contacts.userId)); + + $$ContactsTableProcessedTableManager get contactId { + final $_column = $_itemColumn('contact_id')!; + + final manager = $$ContactsTableTableManager($_db, $_db.contacts) + .filter((f) => f.userId.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_contactIdTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item])); + } } class $$MessageActionsTableFilterComposer @@ -13592,9 +13925,6 @@ class $$MessageActionsTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnFilters get contactId => $composableBuilder( - column: $table.contactId, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters get type => $composableBuilder( column: $table.type, @@ -13622,6 +13952,26 @@ class $$MessageActionsTableFilterComposer )); return composer; } + + $$ContactsTableFilterComposer get contactId { + final $$ContactsTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.contactId, + referencedTable: $db.contacts, + getReferencedColumn: (t) => t.userId, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $$ContactsTableFilterComposer( + $db: $db, + $table: $db.contacts, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } } class $$MessageActionsTableOrderingComposer @@ -13633,9 +13983,6 @@ class $$MessageActionsTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnOrderings get contactId => $composableBuilder( - column: $table.contactId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get type => $composableBuilder( column: $table.type, builder: (column) => ColumnOrderings(column)); @@ -13661,6 +14008,26 @@ class $$MessageActionsTableOrderingComposer )); return composer; } + + $$ContactsTableOrderingComposer get contactId { + final $$ContactsTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.contactId, + referencedTable: $db.contacts, + getReferencedColumn: (t) => t.userId, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $$ContactsTableOrderingComposer( + $db: $db, + $table: $db.contacts, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } } class $$MessageActionsTableAnnotationComposer @@ -13672,9 +14039,6 @@ class $$MessageActionsTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - GeneratedColumn get contactId => - $composableBuilder(column: $table.contactId, builder: (column) => column); - GeneratedColumnWithTypeConverter get type => $composableBuilder(column: $table.type, builder: (column) => column); @@ -13700,6 +14064,26 @@ class $$MessageActionsTableAnnotationComposer )); return composer; } + + $$ContactsTableAnnotationComposer get contactId { + final $$ContactsTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.contactId, + referencedTable: $db.contacts, + getReferencedColumn: (t) => t.userId, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $$ContactsTableAnnotationComposer( + $db: $db, + $table: $db.contacts, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } } class $$MessageActionsTableTableManager extends RootTableManager< @@ -13713,7 +14097,7 @@ class $$MessageActionsTableTableManager extends RootTableManager< $$MessageActionsTableUpdateCompanionBuilder, (MessageAction, $$MessageActionsTableReferences), MessageAction, - PrefetchHooks Function({bool messageId})> { + PrefetchHooks Function({bool messageId, bool contactId})> { $$MessageActionsTableTableManager(_$TwonlyDB db, $MessageActionsTable table) : super(TableManagerState( db: db, @@ -13758,7 +14142,7 @@ class $$MessageActionsTableTableManager extends RootTableManager< $$MessageActionsTableReferences(db, table, e) )) .toList(), - prefetchHooksCallback: ({messageId = false}) { + prefetchHooksCallback: ({messageId = false, contactId = false}) { return PrefetchHooks( db: db, explicitlyWatchedTables: [], @@ -13786,6 +14170,17 @@ class $$MessageActionsTableTableManager extends RootTableManager< .messageId, ) as T; } + if (contactId) { + state = state.withJoin( + currentTable: table, + currentColumn: table.contactId, + referencedTable: + $$MessageActionsTableReferences._contactIdTable(db), + referencedColumn: $$MessageActionsTableReferences + ._contactIdTable(db) + .userId, + ) as T; + } return state; }, @@ -13808,7 +14203,7 @@ typedef $$MessageActionsTableProcessedTableManager = ProcessedTableManager< $$MessageActionsTableUpdateCompanionBuilder, (MessageAction, $$MessageActionsTableReferences), MessageAction, - PrefetchHooks Function({bool messageId})>; + PrefetchHooks Function({bool messageId, bool contactId})>; typedef $$GroupHistoriesTableCreateCompanionBuilder = GroupHistoriesCompanion Function({ required String groupHistoryId, diff --git a/lib/src/database/twonly.db.steps.dart b/lib/src/database/twonly.db.steps.dart index 24ecbc9..fc68ba1 100644 --- a/lib/src/database/twonly.db.steps.dart +++ b/lib/src/database/twonly.db.steps.dart @@ -2804,12 +2804,439 @@ i1.GeneratedColumn _column_104(String aliasedName) => i1.GeneratedColumn( 'mark_for_retry_after_accepted', aliasedName, true, type: i1.DriftSqlType.dateTime); + +final class Schema7 extends i0.VersionedSchema { + Schema7({required super.database}) : super(version: 7); + @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 Shape21 messages = Shape21( + 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_105, + _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 Shape21 extends i0.VersionedTable { + Shape21({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get groupId => + columnsByName['group_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get messageId => + columnsByName['message_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get senderId => + columnsByName['sender_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get type => + columnsByName['type']! as i1.GeneratedColumn; + i1.GeneratedColumn get content => + columnsByName['content']! as i1.GeneratedColumn; + i1.GeneratedColumn get mediaId => + columnsByName['media_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get additionalMessageData => + columnsByName['additional_message_data']! + as i1.GeneratedColumn; + i1.GeneratedColumn get mediaStored => + columnsByName['media_stored']! as i1.GeneratedColumn; + i1.GeneratedColumn get mediaReopened => + columnsByName['media_reopened']! as i1.GeneratedColumn; + i1.GeneratedColumn get downloadToken => + columnsByName['download_token']! as i1.GeneratedColumn; + i1.GeneratedColumn get quotesMessageId => + columnsByName['quotes_message_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get isDeletedFromSender => + columnsByName['is_deleted_from_sender']! as i1.GeneratedColumn; + i1.GeneratedColumn get openedAt => + columnsByName['opened_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get openedByAll => + columnsByName['opened_by_all']! as i1.GeneratedColumn; + i1.GeneratedColumn get createdAt => + columnsByName['created_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get modifiedAt => + columnsByName['modified_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get ackByUser => + columnsByName['ack_by_user']! as i1.GeneratedColumn; + i1.GeneratedColumn get ackByServer => + columnsByName['ack_by_server']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_105(String aliasedName) => + i1.GeneratedColumn( + 'additional_message_data', aliasedName, true, + type: i1.DriftSqlType.blob); 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, + required Future Function(i1.Migrator m, Schema7 schema) from6To7, }) { return (currentVersion, database) async { switch (currentVersion) { @@ -2838,6 +3265,11 @@ i0.MigrationStepWithVersion migrationSteps({ final migrator = i1.Migrator(database, schema); await from5To6(migrator, schema); return 6; + case 6: + final schema = Schema7(database: database); + final migrator = i1.Migrator(database, schema); + await from6To7(migrator, schema); + return 7; default: throw ArgumentError.value('Unknown migration from $currentVersion'); } @@ -2850,6 +3282,7 @@ i1.OnUpgrade stepByStep({ 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, + required Future Function(i1.Migrator m, Schema7 schema) from6To7, }) => i0.VersionedSchema.stepByStepHelper( step: migrationSteps( @@ -2858,4 +3291,5 @@ i1.OnUpgrade stepByStep({ from3To4: from3To4, from4To5: from4To5, from5To6: from5To6, + from6To7: from6To7, )); diff --git a/lib/src/localization/generated/app_localizations.dart b/lib/src/localization/generated/app_localizations.dart index 2a6f815..fb8ea1b 100644 --- a/lib/src/localization/generated/app_localizations.dart +++ b/lib/src/localization/generated/app_localizations.dart @@ -2518,6 +2518,12 @@ abstract class AppLocalizations { /// **'wants to connect with you.'** String get notificationContactRequest; + /// No description provided for @notificationContactRequestUnknownUser. + /// + /// In en, this message translates to: + /// **'have received a new contact request.'** + String get notificationContactRequestUnknownUser; + /// No description provided for @notificationAcceptRequest. /// /// In en, this message translates to: @@ -2572,11 +2578,17 @@ abstract class AppLocalizations { /// **'has responded{inGroup}.'** String notificationResponse(Object inGroup); - /// No description provided for @notificationTitleUnknownUser. + /// No description provided for @notificationTitleUnknown. /// /// In en, this message translates to: - /// **'[Unknown]'** - String get notificationTitleUnknownUser; + /// **'You have a new message.'** + String get notificationTitleUnknown; + + /// No description provided for @notificationBodyUnknown. + /// + /// In en, this message translates to: + /// **'Open twonly to learn more.'** + String get notificationBodyUnknown; /// No description provided for @notificationCategoryMessageTitle. /// diff --git a/lib/src/localization/generated/app_localizations_de.dart b/lib/src/localization/generated/app_localizations_de.dart index c328e26..3a3c99a 100644 --- a/lib/src/localization/generated/app_localizations_de.dart +++ b/lib/src/localization/generated/app_localizations_de.dart @@ -1380,6 +1380,10 @@ class AppLocalizationsDe extends AppLocalizations { @override String get notificationContactRequest => 'möchte sich mit dir vernetzen.'; + @override + String get notificationContactRequestUnknownUser => + 'hast eine neue Kontaktanfrage erhalten.'; + @override String get notificationAcceptRequest => 'ist jetzt mit dir vernetzt.'; @@ -1418,7 +1422,10 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String get notificationTitleUnknownUser => '[Unbekannt]'; + String get notificationTitleUnknown => 'Du hast eine neue Nachricht.'; + + @override + String get notificationBodyUnknown => 'Öffne twonly um mehr zu erfahren.'; @override String get notificationCategoryMessageTitle => 'Nachrichten'; diff --git a/lib/src/localization/generated/app_localizations_en.dart b/lib/src/localization/generated/app_localizations_en.dart index 13f17c7..fecc2ac 100644 --- a/lib/src/localization/generated/app_localizations_en.dart +++ b/lib/src/localization/generated/app_localizations_en.dart @@ -1372,6 +1372,10 @@ class AppLocalizationsEn extends AppLocalizations { @override String get notificationContactRequest => 'wants to connect with you.'; + @override + String get notificationContactRequestUnknownUser => + 'have received a new contact request.'; + @override String get notificationAcceptRequest => 'is now connected with you.'; @@ -1410,7 +1414,10 @@ class AppLocalizationsEn extends AppLocalizations { } @override - String get notificationTitleUnknownUser => '[Unknown]'; + String get notificationTitleUnknown => 'You have a new message.'; + + @override + String get notificationBodyUnknown => 'Open twonly to learn more.'; @override String get notificationCategoryMessageTitle => 'Messages'; diff --git a/lib/src/localization/generated/app_localizations_sv.dart b/lib/src/localization/generated/app_localizations_sv.dart index 7b15afc..bcd7c52 100644 --- a/lib/src/localization/generated/app_localizations_sv.dart +++ b/lib/src/localization/generated/app_localizations_sv.dart @@ -1372,6 +1372,10 @@ class AppLocalizationsSv extends AppLocalizations { @override String get notificationContactRequest => 'wants to connect with you.'; + @override + String get notificationContactRequestUnknownUser => + 'have received a new contact request.'; + @override String get notificationAcceptRequest => 'is now connected with you.'; @@ -1410,7 +1414,10 @@ class AppLocalizationsSv extends AppLocalizations { } @override - String get notificationTitleUnknownUser => '[Unknown]'; + String get notificationTitleUnknown => 'You have a new message.'; + + @override + String get notificationBodyUnknown => 'Open twonly to learn more.'; @override String get notificationCategoryMessageTitle => 'Messages'; diff --git a/lib/src/localization/translations b/lib/src/localization/translations index 20f3c2f..9d04e9e 160000 --- a/lib/src/localization/translations +++ b/lib/src/localization/translations @@ -1 +1 @@ -Subproject commit 20f3c2f0a49e4c9be452ecbc84d98054c92974e1 +Subproject commit 9d04e9e1d0cdba8f1be4b0cbba341706c3cffac9 diff --git a/lib/src/model/json/userdata.dart b/lib/src/model/json/userdata.dart index eb26ef3..9945264 100644 --- a/lib/src/model/json/userdata.dart +++ b/lib/src/model/json/userdata.dart @@ -108,6 +108,19 @@ class UserData { DateTime? nextTimeToShowBackupNotice; BackupServer? backupServer; TwonlySafeBackup? twonlySafeBackup; + + // For my master thesis I want to create a anonymous user study: + // - users in the "Tester" Plan can, if they want, take part of the user study + + @JsonKey(defaultValue: false) + bool askedForUserStudyPermission = false; + + // So update data can be assigned. If set the user choose to participate. + String? userStudyParticipantsToken; + + // Once a day the anonymous data is collected and send to the server + DateTime? lastUserStudyDataUpload; + Map toJson() => _$UserDataToJson(this); } diff --git a/lib/src/model/json/userdata.g.dart b/lib/src/model/json/userdata.g.dart index a478117..3dc2fbb 100644 --- a/lib/src/model/json/userdata.g.dart +++ b/lib/src/model/json/userdata.g.dart @@ -77,7 +77,14 @@ UserData _$UserDataFromJson(Map json) => UserData( ..twonlySafeBackup = json['twonlySafeBackup'] == null ? null : TwonlySafeBackup.fromJson( - json['twonlySafeBackup'] as Map); + json['twonlySafeBackup'] as Map) + ..askedForUserStudyPermission = + json['askedForUserStudyPermission'] as bool? ?? false + ..userStudyParticipantsToken = + json['userStudyParticipantsToken'] as String? + ..lastUserStudyDataUpload = json['lastUserStudyDataUpload'] == null + ? null + : DateTime.parse(json['lastUserStudyDataUpload'] as String); Map _$UserDataToJson(UserData instance) => { 'userId': instance.userId, @@ -122,6 +129,10 @@ Map _$UserDataToJson(UserData instance) => { instance.nextTimeToShowBackupNotice?.toIso8601String(), 'backupServer': instance.backupServer, 'twonlySafeBackup': instance.twonlySafeBackup, + 'askedForUserStudyPermission': instance.askedForUserStudyPermission, + 'userStudyParticipantsToken': instance.userStudyParticipantsToken, + 'lastUserStudyDataUpload': + instance.lastUserStudyDataUpload?.toIso8601String(), }; const _$ThemeModeEnumMap = { diff --git a/lib/src/model/protobuf/client/data.proto b/lib/src/model/protobuf/client/data.proto new file mode 100644 index 0000000..20a3b48 --- /dev/null +++ b/lib/src/model/protobuf/client/data.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + + +message AdditionalMessageData { + enum Type { + LINK = 0; + } + Type type = 1; + + optional string link = 2; +} \ No newline at end of file diff --git a/lib/src/model/protobuf/client/generated/data.pb.dart b/lib/src/model/protobuf/client/generated/data.pb.dart new file mode 100644 index 0000000..9b547b3 --- /dev/null +++ b/lib/src/model/protobuf/client/generated/data.pb.dart @@ -0,0 +1,99 @@ +// This is a generated file - do not edit. +// +// Generated from data.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names + +import 'dart:core' as $core; + +import 'package:protobuf/protobuf.dart' as $pb; + +import 'data.pbenum.dart'; + +export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions; + +export 'data.pbenum.dart'; + +class AdditionalMessageData extends $pb.GeneratedMessage { + factory AdditionalMessageData({ + AdditionalMessageData_Type? type, + $core.String? link, + }) { + final result = create(); + if (type != null) result.type = type; + if (link != null) result.link = link; + return result; + } + + AdditionalMessageData._(); + + factory AdditionalMessageData.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory AdditionalMessageData.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'AdditionalMessageData', + createEmptyInstance: create) + ..e( + 1, _omitFieldNames ? '' : 'type', $pb.PbFieldType.OE, + defaultOrMaker: AdditionalMessageData_Type.LINK, + valueOf: AdditionalMessageData_Type.valueOf, + enumValues: AdditionalMessageData_Type.values) + ..aOS(2, _omitFieldNames ? '' : 'link') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + AdditionalMessageData clone() => + AdditionalMessageData()..mergeFromMessage(this); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + AdditionalMessageData copyWith( + void Function(AdditionalMessageData) updates) => + super.copyWith((message) => updates(message as AdditionalMessageData)) + as AdditionalMessageData; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static AdditionalMessageData create() => AdditionalMessageData._(); + @$core.override + AdditionalMessageData createEmptyInstance() => create(); + static $pb.PbList createRepeated() => + $pb.PbList(); + @$core.pragma('dart2js:noInline') + static AdditionalMessageData getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static AdditionalMessageData? _defaultInstance; + + @$pb.TagNumber(1) + AdditionalMessageData_Type get type => $_getN(0); + @$pb.TagNumber(1) + set type(AdditionalMessageData_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 link => $_getSZ(1); + @$pb.TagNumber(2) + set link($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasLink() => $_has(1); + @$pb.TagNumber(2) + void clearLink() => $_clearField(2); +} + +const $core.bool _omitFieldNames = + $core.bool.fromEnvironment('protobuf.omit_field_names'); +const $core.bool _omitMessageNames = + $core.bool.fromEnvironment('protobuf.omit_message_names'); diff --git a/lib/src/model/protobuf/client/generated/data.pbenum.dart b/lib/src/model/protobuf/client/generated/data.pbenum.dart new file mode 100644 index 0000000..4f40622 --- /dev/null +++ b/lib/src/model/protobuf/client/generated/data.pbenum.dart @@ -0,0 +1,35 @@ +// This is a generated file - do not edit. +// +// Generated from data.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names + +import 'dart:core' as $core; + +import 'package:protobuf/protobuf.dart' as $pb; + +class AdditionalMessageData_Type extends $pb.ProtobufEnum { + static const AdditionalMessageData_Type LINK = + AdditionalMessageData_Type._(0, _omitEnumNames ? '' : 'LINK'); + + static const $core.List values = + [ + LINK, + ]; + + static final $core.List _byValue = + $pb.ProtobufEnum.$_initByValueList(values, 0); + static AdditionalMessageData_Type? valueOf($core.int value) => + value < 0 || value >= _byValue.length ? null : _byValue[value]; + + const AdditionalMessageData_Type._(super.value, super.name); +} + +const $core.bool _omitEnumNames = + $core.bool.fromEnvironment('protobuf.omit_enum_names'); diff --git a/lib/src/model/protobuf/client/generated/data.pbjson.dart b/lib/src/model/protobuf/client/generated/data.pbjson.dart new file mode 100644 index 0000000..fb0a248 --- /dev/null +++ b/lib/src/model/protobuf/client/generated/data.pbjson.dart @@ -0,0 +1,49 @@ +// This is a generated file - do not edit. +// +// Generated from data.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, unused_import + +import 'dart:convert' as $convert; +import 'dart:core' as $core; +import 'dart:typed_data' as $typed_data; + +@$core.Deprecated('Use additionalMessageDataDescriptor instead') +const AdditionalMessageData$json = { + '1': 'AdditionalMessageData', + '2': [ + { + '1': 'type', + '3': 1, + '4': 1, + '5': 14, + '6': '.AdditionalMessageData.Type', + '10': 'type' + }, + {'1': 'link', '3': 2, '4': 1, '5': 9, '9': 0, '10': 'link', '17': true}, + ], + '4': [AdditionalMessageData_Type$json], + '8': [ + {'1': '_link'}, + ], +}; + +@$core.Deprecated('Use additionalMessageDataDescriptor instead') +const AdditionalMessageData_Type$json = { + '1': 'Type', + '2': [ + {'1': 'LINK', '2': 0}, + ], +}; + +/// Descriptor for `AdditionalMessageData`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List additionalMessageDataDescriptor = $convert.base64Decode( + 'ChVBZGRpdGlvbmFsTWVzc2FnZURhdGESLwoEdHlwZRgBIAEoDjIbLkFkZGl0aW9uYWxNZXNzYW' + 'dlRGF0YS5UeXBlUgR0eXBlEhcKBGxpbmsYAiABKAlIAFIEbGlua4gBASIQCgRUeXBlEggKBExJ' + 'TksQAEIHCgVfbGluaw=='); diff --git a/lib/src/model/protobuf/client/generated/messages.pb.dart b/lib/src/model/protobuf/client/generated/messages.pb.dart index a02260f..b137dde 100644 --- a/lib/src/model/protobuf/client/generated/messages.pb.dart +++ b/lib/src/model/protobuf/client/generated/messages.pb.dart @@ -969,6 +969,7 @@ class EncryptedContent_Media extends $pb.GeneratedMessage { $core.List<$core.int>? encryptionKey, $core.List<$core.int>? encryptionMac, $core.List<$core.int>? encryptionNonce, + $core.List<$core.int>? additionalMessageData, }) { final result = create(); if (senderMessageId != null) result.senderMessageId = senderMessageId; @@ -983,6 +984,8 @@ class EncryptedContent_Media extends $pb.GeneratedMessage { if (encryptionKey != null) result.encryptionKey = encryptionKey; if (encryptionMac != null) result.encryptionMac = encryptionMac; if (encryptionNonce != null) result.encryptionNonce = encryptionNonce; + if (additionalMessageData != null) + result.additionalMessageData = additionalMessageData; return result; } @@ -1024,6 +1027,8 @@ class EncryptedContent_Media extends $pb.GeneratedMessage { ..a<$core.List<$core.int>>( 10, _omitFieldNames ? '' : 'encryptionNonce', $pb.PbFieldType.OY, protoName: 'encryptionNonce') + ..a<$core.List<$core.int>>( + 11, _omitFieldNames ? '' : 'additionalMessageData', $pb.PbFieldType.OY) ..hasRequiredFields = false; @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') @@ -1138,6 +1143,16 @@ class EncryptedContent_Media extends $pb.GeneratedMessage { $core.bool hasEncryptionNonce() => $_has(9); @$pb.TagNumber(10) void clearEncryptionNonce() => $_clearField(10); + + @$pb.TagNumber(11) + $core.List<$core.int> get additionalMessageData => $_getN(10); + @$pb.TagNumber(11) + set additionalMessageData($core.List<$core.int> value) => + $_setBytes(10, value); + @$pb.TagNumber(11) + $core.bool hasAdditionalMessageData() => $_has(10); + @$pb.TagNumber(11) + void clearAdditionalMessageData() => $_clearField(11); } class EncryptedContent_MediaUpdate extends $pb.GeneratedMessage { diff --git a/lib/src/model/protobuf/client/generated/messages.pbjson.dart b/lib/src/model/protobuf/client/generated/messages.pbjson.dart index 0331672..fd49512 100644 --- a/lib/src/model/protobuf/client/generated/messages.pbjson.dart +++ b/lib/src/model/protobuf/client/generated/messages.pbjson.dart @@ -603,6 +603,15 @@ const EncryptedContent_Media$json = { '10': 'encryptionNonce', '17': true }, + { + '1': 'additional_message_data', + '3': 11, + '4': 1, + '5': 12, + '9': 6, + '10': 'additionalMessageData', + '17': true + }, ], '4': [EncryptedContent_Media_Type$json], '8': [ @@ -612,6 +621,7 @@ const EncryptedContent_Media$json = { {'1': '_encryptionKey'}, {'1': '_encryptionMac'}, {'1': '_encryptionNonce'}, + {'1': '_additional_message_data'}, ], }; @@ -840,7 +850,7 @@ final $typed_data.Uint8List encryptedContentDescriptor = $convert.base64Decode( 'EjoKGG11bHRpcGxlVGFyZ2V0TWVzc2FnZUlkcxgDIAMoCVIYbXVsdGlwbGVUYXJnZXRNZXNzYW' 'dlSWRzEhcKBHRleHQYBCABKAlIAVIEdGV4dIgBARIcCgl0aW1lc3RhbXAYBSABKANSCXRpbWVz' 'dGFtcCItCgRUeXBlEgoKBkRFTEVURRAAEg0KCUVESVRfVEVYVBABEgoKBk9QRU5FRBACQhIKEF' - '9zZW5kZXJNZXNzYWdlSWRCBwoFX3RleHQalwUKBU1lZGlhEigKD3NlbmRlck1lc3NhZ2VJZBgB' + '9zZW5kZXJNZXNzYWdlSWRCBwoFX3RleHQa8AUKBU1lZGlhEigKD3NlbmRlck1lc3NhZ2VJZBgB' 'IAEoCVIPc2VuZGVyTWVzc2FnZUlkEjAKBHR5cGUYAiABKA4yHC5FbmNyeXB0ZWRDb250ZW50Lk' '1lZGlhLlR5cGVSBHR5cGUSQwoaZGlzcGxheUxpbWl0SW5NaWxsaXNlY29uZHMYAyABKANIAFIa' 'ZGlzcGxheUxpbWl0SW5NaWxsaXNlY29uZHOIAQESNgoWcmVxdWlyZXNBdXRoZW50aWNhdGlvbh' @@ -849,29 +859,31 @@ final $typed_data.Uint8List encryptedContentDescriptor = $convert.base64Decode( '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=='); + 'iAEBEjsKF2FkZGl0aW9uYWxfbWVzc2FnZV9kYXRhGAsgASgMSAZSFWFkZGl0aW9uYWxNZXNzYW' + 'dlRGF0YYgBASI+CgRUeXBlEgwKCFJFVVBMT0FEEAASCQoFSU1BR0UQARIJCgVWSURFTxACEgcK' + 'A0dJRhADEgkKBUFVRElPEARCHQobX2Rpc3BsYXlMaW1pdEluTWlsbGlzZWNvbmRzQhEKD19xdW' + '90ZU1lc3NhZ2VJZEIQCg5fZG93bmxvYWRUb2tlbkIQCg5fZW5jcnlwdGlvbktleUIQCg5fZW5j' + 'cnlwdGlvbk1hY0ISChBfZW5jcnlwdGlvbk5vbmNlQhoKGF9hZGRpdGlvbmFsX21lc3NhZ2VfZG' + 'F0YRqnAQoLTWVkaWFVcGRhdGUSNgoEdHlwZRgBIAEoDjIiLkVuY3J5cHRlZENvbnRlbnQuTWVk' + 'aWFVcGRhdGUuVHlwZVIEdHlwZRIoCg90YXJnZXRNZXNzYWdlSWQYAiABKAlSD3RhcmdldE1lc3' + 'NhZ2VJZCI2CgRUeXBlEgwKCFJFT1BFTkVEEAASCgoGU1RPUkVEEAESFAoQREVDUllQVElPTl9F' + 'UlJPUhACGngKDkNvbnRhY3RSZXF1ZXN0EjkKBHR5cGUYASABKA4yJS5FbmNyeXB0ZWRDb250ZW' + '50LkNvbnRhY3RSZXF1ZXN0LlR5cGVSBHR5cGUiKwoEVHlwZRILCgdSRVFVRVNUEAASCgoGUkVK' + 'RUNUEAESCgoGQUNDRVBUEAIangIKDUNvbnRhY3RVcGRhdGUSOAoEdHlwZRgBIAEoDjIkLkVuY3' + 'J5cHRlZENvbnRlbnQuQ29udGFjdFVwZGF0ZS5UeXBlUgR0eXBlEjUKE2F2YXRhclN2Z0NvbXBy' + 'ZXNzZWQYAiABKAxIAFITYXZhdGFyU3ZnQ29tcHJlc3NlZIgBARIfCgh1c2VybmFtZRgDIAEoCU' + 'gBUgh1c2VybmFtZYgBARIlCgtkaXNwbGF5TmFtZRgEIAEoCUgCUgtkaXNwbGF5TmFtZYgBASIf' + 'CgRUeXBlEgsKB1JFUVVFU1QQABIKCgZVUERBVEUQAUIWChRfYXZhdGFyU3ZnQ29tcHJlc3NlZE' + 'ILCglfdXNlcm5hbWVCDgoMX2Rpc3BsYXlOYW1lGtUBCghQdXNoS2V5cxIzCgR0eXBlGAEgASgO' + 'Mh8uRW5jcnlwdGVkQ29udGVudC5QdXNoS2V5cy5UeXBlUgR0eXBlEhkKBWtleUlkGAIgASgDSA' + 'BSBWtleUlkiAEBEhUKA2tleRgDIAEoDEgBUgNrZXmIAQESIQoJY3JlYXRlZEF0GAQgASgDSAJS' + 'CWNyZWF0ZWRBdIgBASIfCgRUeXBlEgsKB1JFUVVFU1QQABIKCgZVUERBVEUQAUIICgZfa2V5SW' + 'RCBgoEX2tleUIMCgpfY3JlYXRlZEF0GqkBCglGbGFtZVN5bmMSIgoMZmxhbWVDb3VudGVyGAEg' + 'ASgDUgxmbGFtZUNvdW50ZXISNgoWbGFzdEZsYW1lQ291bnRlckNoYW5nZRgCIAEoA1IWbGFzdE' + 'ZsYW1lQ291bnRlckNoYW5nZRIeCgpiZXN0RnJpZW5kGAMgASgIUgpiZXN0RnJpZW5kEiAKC2Zv' + 'cmNlVXBkYXRlGAQgASgIUgtmb3JjZVVwZGF0ZUIKCghfZ3JvdXBJZEIPCg1faXNEaXJlY3RDaG' + 'F0QhcKFV9zZW5kZXJQcm9maWxlQ291bnRlckIQCg5fbWVzc2FnZVVwZGF0ZUIICgZfbWVkaWFC' + 'DgoMX21lZGlhVXBkYXRlQhAKDl9jb250YWN0VXBkYXRlQhEKD19jb250YWN0UmVxdWVzdEIMCg' + 'pfZmxhbWVTeW5jQgsKCV9wdXNoS2V5c0ILCglfcmVhY3Rpb25CDgoMX3RleHRNZXNzYWdlQg4K' + 'DF9ncm91cENyZWF0ZUIMCgpfZ3JvdXBKb2luQg4KDF9ncm91cFVwZGF0ZUIXChVfcmVzZW5kR3' + 'JvdXBQdWJsaWNLZXlCEQoPX2Vycm9yX21lc3NhZ2Vz'); diff --git a/lib/src/model/protobuf/client/messages.proto b/lib/src/model/protobuf/client/messages.proto index 1e71cbc..38e24ad 100644 --- a/lib/src/model/protobuf/client/messages.proto +++ b/lib/src/model/protobuf/client/messages.proto @@ -132,6 +132,8 @@ message EncryptedContent { optional bytes encryptionKey = 8; optional bytes encryptionMac = 9; optional bytes encryptionNonce = 10; + + optional bytes additional_message_data = 11; } message MediaUpdate { diff --git a/lib/src/services/api.service.dart b/lib/src/services/api.service.dart index 68e7489..7c3c395 100644 --- a/lib/src/services/api.service.dart +++ b/lib/src/services/api.service.dart @@ -26,6 +26,7 @@ import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart as server; import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pbserver.dart'; import 'package:twonly/src/services/api/mediafiles/download.service.dart'; +import 'package:twonly/src/services/api/mediafiles/upload.service.dart'; import 'package:twonly/src/services/api/messages.dart'; import 'package:twonly/src/services/api/server_messages.dart'; import 'package:twonly/src/services/api/utils.dart'; @@ -41,6 +42,7 @@ import 'package:twonly/src/utils/keyvalue.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/storage.dart'; +import 'package:twonly/src/views/user_study/user_study_data_collection.dart'; import 'package:web_socket_channel/io.dart'; final lockConnecting = Mutex(); @@ -100,6 +102,11 @@ class ApiService { unawaited(fetchGroupStatesForUnjoinedGroups()); unawaited(fetchMissingGroupPublicKey()); unawaited(checkForDeletedUsernames()); + + if (gUser.userStudyParticipantsToken != null) { + // In case the user participates in the user study, call the handler after authenticated, to be sure there is a internet connection + unawaited(handleUserStudyUpload()); + } } } @@ -313,6 +320,12 @@ class ApiService { return user; }); globalCallbackUpdatePlan(planFromString(authenticated.plan)); + + // this was triggered by apiService.ipaPurchase, so call the onAuthenticated again + if (isAuthenticated) { + // Trigger the re-upload from images, after Plan change, in case the limit was reached before... + unawaited(finishStartedPreprocessing()); + } } } if (res.isError) { diff --git a/lib/src/services/api/client2client/media.c2c.dart b/lib/src/services/api/client2client/media.c2c.dart index 807706a..d0c8e3a 100644 --- a/lib/src/services/api/client2client/media.c2c.dart +++ b/lib/src/services/api/client2client/media.c2c.dart @@ -105,6 +105,11 @@ Future handleMedia( groupId: Value(groupId), mediaId: Value(mediaFile.mediaId), type: const Value(MessageType.media), + additionalMessageData: Value.absentIfNull( + media.hasAdditionalMessageData() + ? Uint8List.fromList(media.additionalMessageData) + : null, + ), quotesMessageId: Value( media.hasQuoteMessageId() ? media.quoteMessageId : null, ), diff --git a/lib/src/services/api/mediafiles/upload.service.dart b/lib/src/services/api/mediafiles/upload.service.dart index 26f3d7e..6826f20 100644 --- a/lib/src/services/api/mediafiles/upload.service.dart +++ b/lib/src/services/api/mediafiles/upload.service.dart @@ -16,6 +16,7 @@ import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/database/tables/messages.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/model/protobuf/api/http/http_requests.pb.dart'; +import 'package:twonly/src/model/protobuf/client/generated/data.pb.dart'; import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'; import 'package:twonly/src/services/api/mediafiles/media_background.service.dart'; import 'package:twonly/src/services/api/messages.dart'; @@ -88,8 +89,9 @@ Future initializeMediaUpload( Future insertMediaFileInMessagesTable( MediaFileService mediaService, - List groupIds, -) async { + List groupIds, { + AdditionalMessageData? additionalData, +}) async { await twonlyDB.mediaFilesDao.updateAllMediaFiles( const MediaFilesCompanion( isDraftMedia: Value(false), @@ -101,6 +103,8 @@ Future insertMediaFileInMessagesTable( groupId: Value(groupId), mediaId: Value(mediaService.mediaFile.mediaId), type: const Value(MessageType.media), + additionalMessageData: + Value.absentIfNull(additionalData?.writeToBuffer()), ), ); await twonlyDB.groupsDao.increaseLastMessageExchange(groupId, clock.now()); @@ -245,6 +249,7 @@ Future _createUploadRequest(MediaFileService media) async { encryptionKey: media.mediaFile.encryptionKey, encryptionNonce: media.mediaFile.encryptionNonce, encryptionMac: media.mediaFile.encryptionMac, + additionalMessageData: message.additionalMessageData, ), ); diff --git a/lib/src/services/api/messages.dart b/lib/src/services/api/messages.dart index 49ee512..6db4af4 100644 --- a/lib/src/services/api/messages.dart +++ b/lib/src/services/api/messages.dart @@ -61,6 +61,7 @@ Future<(Uint8List, Uint8List?)?> tryToSendCompleteMessage({ String? receiptId, Receipt? receipt, bool onlyReturnEncryptedData = false, + bool blocking = true, }) async { try { if (receiptId == null && receipt == null) return null; @@ -238,12 +239,11 @@ Future sendCipherTextToGroup( encryptedContent.groupId = groupId; for (final groupMember in groupMembers) { - unawaited( - sendCipherText( - groupMember.contactId, - encryptedContent, - messageId: messageId, - ), + await sendCipherText( + groupMember.contactId, + encryptedContent, + messageId: messageId, + blocking: false, ); } } @@ -252,6 +252,7 @@ Future<(Uint8List, Uint8List?)?> sendCipherText( int contactId, pb.EncryptedContent encryptedContent, { bool onlyReturnEncryptedData = false, + bool blocking = true, String? messageId, }) async { encryptedContent.senderProfileCounter = Int64(gUser.avatarCounter); @@ -270,10 +271,15 @@ Future<(Uint8List, Uint8List?)?> sendCipherText( ); if (receipt != null) { - return tryToSendCompleteMessage( + final tmp = tryToSendCompleteMessage( receipt: receipt, onlyReturnEncryptedData: onlyReturnEncryptedData, + blocking: blocking, ); + if (!blocking) { + return null; + } + return tmp; } return null; } @@ -302,6 +308,7 @@ Future notifyContactAboutOpeningMessage( timestamp: Int64(actionAt.millisecondsSinceEpoch), ), ), + blocking: false, ); for (final messageId in messageOtherIds) { await twonlyDB.messagesDao.updateMessageId( diff --git a/lib/src/services/intent/links.intent.dart b/lib/src/services/intent/links.intent.dart index c4a1066..cf4e7fa 100644 --- a/lib/src/services/intent/links.intent.dart +++ b/lib/src/services/intent/links.intent.dart @@ -13,7 +13,7 @@ import 'package:twonly/src/services/api/mediafiles/upload.service.dart'; import 'package:twonly/src/services/signal/session.signal.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/views/camera/share_image_editor_view.dart'; +import 'package:twonly/src/views/camera/share_image_editor.view.dart'; import 'package:twonly/src/views/chats/add_new_user.view.dart'; import 'package:twonly/src/views/components/alert_dialog.dart'; import 'package:twonly/src/views/contact/contact.view.dart'; @@ -151,6 +151,7 @@ Future handleIntentMediaFile( Future handleIntentSharedFile( BuildContext context, List files, + void Function(Uri) onUrlCallBack, ) async { for (final file in files) { if (file.value == null) { @@ -163,7 +164,9 @@ Future handleIntentSharedFile( switch (file.type) { case SharedMediaType.URL: - // await handleIntentUrl(context, Uri.parse(file.value!)); + if (file.value?.startsWith('http') ?? false) { + onUrlCallBack(Uri.parse(file.value!)); + } case SharedMediaType.IMAGE: var type = MediaType.image; if (file.value!.endsWith('.gif')) { diff --git a/lib/src/services/mediafiles/mediafile.service.dart b/lib/src/services/mediafiles/mediafile.service.dart index 2901006..2528ea0 100644 --- a/lib/src/services/mediafiles/mediafile.service.dart +++ b/lib/src/services/mediafiles/mediafile.service.dart @@ -24,66 +24,71 @@ class MediaFileService { } static Future purgeTempFolder() async { - final tempDirectory = MediaFileService.buildDirectoryPath( - 'tmp', - globalApplicationSupportDirectory, - ); + try { + final tempDirectory = MediaFileService.buildDirectoryPath( + 'tmp', + globalApplicationSupportDirectory, + ); - final files = tempDirectory.listSync(); - for (final file in files) { - final mediaId = basename(file.path).split('.').first; + final files = tempDirectory.listSync(); + for (final file in files) { + final mediaId = basename(file.path).split('.').first; - // in case the mediaID is unknown the file will be deleted - var delete = true; + // in case the mediaID is unknown the file will be deleted + var delete = true; - final service = await MediaFileService.fromMediaId(mediaId); + final service = await MediaFileService.fromMediaId(mediaId); - if (service != null) { - if (service.mediaFile.isDraftMedia) { - delete = false; - } - - final messages = - await twonlyDB.messagesDao.getMessagesByMediaId(mediaId); - - // in case messages in empty the file will be deleted, as delete is true by default - - for (final message in messages) { - if (service.mediaFile.type == MediaType.audio) { - delete = false; // do not delete voice messages - } - - if (message.openedAt == null) { - // Message was not yet opened from all persons, so wait... + if (service != null) { + if (service.mediaFile.isDraftMedia) { delete = false; - } 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! - .isAfter(clock.now().subtract(const Duration(days: 2)))) { - // In case the image was opened, but send with unlimited time or no authentication. - if (message.senderId == null) { - delete = false; - } else { - // Check weather the image was send in a group. Then the images is preserved for two days in case another person stores the image. - // This also allows to reopen this image for two days. - final group = await twonlyDB.groupsDao.getGroup(message.groupId); - if (group != null && !group.isDirectChat) { - delete = false; - } + } + + final messages = + await twonlyDB.messagesDao.getMessagesByMediaId(mediaId); + + // in case messages in empty the file will be deleted, as delete is true by default + + for (final message in messages) { + if (service.mediaFile.type == MediaType.audio) { + delete = false; // do not delete voice messages + } + + if (message.openedAt == null) { + // Message was not yet opened from all persons, so wait... + delete = false; + } 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! + .isAfter(clock.now().subtract(const Duration(days: 2)))) { + // In case the image was opened, but send with unlimited time or no authentication. + if (message.senderId == null) { + delete = false; + } else { + // Check weather the image was send in a group. Then the images is preserved for two days in case another person stores the image. + // This also allows to reopen this image for two days. + final group = + await twonlyDB.groupsDao.getGroup(message.groupId); + if (group != null && !group.isDirectChat) { + delete = false; + } + } + // In case the app was send in a direct chat, then it can be deleted. } - // In case the app was send in a direct chat, then it can be deleted. } } - } - if (delete) { - Log.info('Purging media file $mediaId'); - file.deleteSync(); + if (delete) { + Log.info('Purging media file $mediaId'); + file.deleteSync(); + } } + } catch (e) { + Log.error(e); } } diff --git a/lib/src/services/notifications/background.notifications.dart b/lib/src/services/notifications/background.notifications.dart index 4c652d2..f7eaacc 100644 --- a/lib/src/services/notifications/background.notifications.dart +++ b/lib/src/services/notifications/background.notifications.dart @@ -99,9 +99,10 @@ Future handlePushData(String pushDataB64) async { } } catch (e) { Log.error(e); + final lang = getLocalizations(); await customLocalPushNotification( - 'Du hast eine neue Nachricht.', - 'Öffne twonly um mehr zu erfahren.', + lang.notificationTitleUnknown, + lang.notificationBodyUnknown, ); } } @@ -186,16 +187,14 @@ Future showLocalPushNotification( Future showLocalPushNotificationWithoutUserId( PushNotification pushNotification, ) async { - String? body; - - body = getPushNotificationText(pushNotification); - final lang = getLocalizations(); - final title = lang.notificationTitleUnknownUser; + var title = lang.notificationTitleUnknown; + var body = lang.notificationBodyUnknown; - if (body == '') { - Log.error('No push notification type defined!'); + if (pushNotification.kind == PushKind.contactRequest) { + title = lang.you; + body = lang.notificationContactRequestUnknownUser; } final androidNotificationDetails = AndroidNotificationDetails( diff --git a/lib/src/utils/keyvalue.dart b/lib/src/utils/keyvalue.dart index 05e5639..e52274d 100644 --- a/lib/src/utils/keyvalue.dart +++ b/lib/src/utils/keyvalue.dart @@ -4,38 +4,41 @@ import 'package:path_provider/path_provider.dart'; import 'package:twonly/src/utils/log.dart'; class KeyValueStore { - static Future _getFilePath(String key) async { + static Future _getFilePath(String key) async { final directory = await getApplicationSupportDirectory(); - return '${directory.path}/keyvalue/$key.json'; + return File('${directory.path}/keyvalue/$key.json'); + } + + static Future delete(String key) async { + try { + final file = await _getFilePath(key); + if (file.existsSync()) { + file.deleteSync(); + } + } catch (e) { + Log.error('Error deleting file: $e'); + } } static Future?> get(String key) async { try { - final filePath = await _getFilePath(key); - final file = File(filePath); - - // Check if the file exists + final file = await _getFilePath(key); if (file.existsSync()) { final contents = await file.readAsString(); return jsonDecode(contents) as Map; } else { - return null; // File does not exist + return null; } } catch (e) { - Log.error('Error reading file: $e'); + Log.warn('Error reading file: $e'); return null; } } static Future put(String key, Map value) async { try { - final filePath = await _getFilePath(key); - final file = File(filePath); - - // Create the directory if it doesn't exist + final file = await _getFilePath(key); await file.parent.create(recursive: true); - - // Write the JSON data to the file await file.writeAsString(jsonEncode(value)); } catch (e) { Log.error('Error writing file: $e'); diff --git a/lib/src/views/camera/camera_preview_components/camera_preview.dart b/lib/src/views/camera/camera_preview_components/camera_preview.dart index 5d9d4bb..1f196ea 100644 --- a/lib/src/views/camera/camera_preview_components/camera_preview.dart +++ b/lib/src/views/camera/camera_preview_components/camera_preview.dart @@ -23,26 +23,78 @@ class MainCameraPreview extends StatelessWidget { requiredHeight: 0, additionalPadding: 59, bottomNavigation: Container(), - child: Screenshot( - controller: mainCameraController.screenshotController, - child: AspectRatio( - aspectRatio: 9 / 16, - child: ClipRect( - child: FittedBox( - fit: BoxFit.cover, - child: SizedBox( - width: mainCameraController - .cameraController!.value.previewSize!.height, - height: mainCameraController - .cameraController!.value.previewSize!.width, - child: CameraPreview( - mainCameraController.cameraController!, - child: mainCameraController.customPaint, + child: Stack( + children: [ + Screenshot( + controller: mainCameraController.screenshotController, + child: AspectRatio( + aspectRatio: 9 / 16, + child: ClipRect( + child: FittedBox( + fit: BoxFit.cover, + child: SizedBox( + width: mainCameraController + .cameraController!.value.previewSize!.height, + height: mainCameraController + .cameraController!.value.previewSize!.width, + child: CameraPreview( + key: mainCameraController.cameraPreviewKey, + mainCameraController.cameraController!, + child: Stack( + children: [ + if (mainCameraController.customPaint != null) + Positioned.fill( + child: mainCameraController.customPaint!, + ), + if (mainCameraController.facePaint != null) + Positioned.fill( + child: mainCameraController.facePaint!, + ), + ], + ), + ), + ), ), ), ), ), - ), + if (mainCameraController.focusPointOffset != null && + !mainCameraController.isSharePreviewIsShown) + AspectRatio( + aspectRatio: 9 / 16, + child: ClipRect( + child: FittedBox( + fit: BoxFit.cover, + child: SizedBox( + width: mainCameraController + .cameraController!.value.previewSize!.height, + height: mainCameraController + .cameraController!.value.previewSize!.width, + child: Stack( + children: [ + Positioned( + top: mainCameraController.focusPointOffset!.dy - 40, + left: + mainCameraController.focusPointOffset!.dx - 40, + child: Container( + height: 80, + width: 80, + clipBehavior: Clip.antiAliasWithSaveLayer, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: Colors.white.withAlpha(150), + ), + ), + ), + ), + ], + ), + ), + ), + ), + ), + ], ), ), ); 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 dadb1fd..34e1e78 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 @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:io'; + import 'package:camera/camera.dart'; import 'package:clock/clock.dart'; import 'package:device_info_plus/device_info_plus.dart'; @@ -21,13 +22,14 @@ import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/qr.dart'; import 'package:twonly/src/utils/screenshot.dart'; import 'package:twonly/src/utils/storage.dart'; +import 'package:twonly/src/views/camera/camera_preview_components/face_filters.dart'; import 'package:twonly/src/views/camera/camera_preview_components/main_camera_controller.dart'; import 'package:twonly/src/views/camera/camera_preview_components/permissions_view.dart'; import 'package:twonly/src/views/camera/camera_preview_components/send_to.dart'; import 'package:twonly/src/views/camera/camera_preview_components/video_recording_time.dart'; import 'package:twonly/src/views/camera/camera_preview_components/zoom_selector.dart'; -import 'package:twonly/src/views/camera/image_editor/action_button.dart'; -import 'package:twonly/src/views/camera/share_image_editor_view.dart'; +import 'package:twonly/src/views/camera/share_image_editor.view.dart'; +import 'package:twonly/src/views/camera/share_image_editor/action_button.dart'; import 'package:twonly/src/views/components/avatar_icon.component.dart'; import 'package:twonly/src/views/components/loader.dart'; import 'package:twonly/src/views/components/media_view_sizing.dart'; @@ -36,55 +38,6 @@ import 'package:url_launcher/url_launcher_string.dart'; int maxVideoRecordingTime = 60; -Future<(SelectedCameraDetails, CameraController)?> initializeCameraController( - SelectedCameraDetails details, - int sCameraId, - bool init, -) async { - var cameraId = sCameraId; - if (cameraId >= gCameras.length) return null; - if (init) { - for (; cameraId < gCameras.length; cameraId++) { - if (gCameras[cameraId].lensDirection == CameraLensDirection.back) { - break; - } - } - } - details.isZoomAble = false; - if (details.cameraId != cameraId) { - // switch between front and back - details.scaleFactor = 1; - } - - final cameraController = CameraController( - gCameras[cameraId], - ResolutionPreset.high, - enableAudio: await Permission.microphone.isGranted, - imageFormatGroup: - Platform.isAndroid ? ImageFormatGroup.nv21 : ImageFormatGroup.bgra8888, - ); - - await cameraController.initialize().then((_) async { - await cameraController.setZoomLevel(details.scaleFactor); - await cameraController.lockCaptureOrientation(DeviceOrientation.portraitUp); - await cameraController - .setFlashMode(details.isFlashOn ? FlashMode.always : FlashMode.off); - await cameraController - .getMaxZoomLevel() - .then((double value) => details.maxAvailableZoom = value); - await cameraController - .getMinZoomLevel() - .then((double value) => details.minAvailableZoom = value); - details - ..isZoomAble = details.maxAvailableZoom != details.minAvailableZoom - ..cameraLoaded = true - ..cameraId = cameraId; - }).catchError((Object e) { - Log.error('$e'); - }); - return (details, cameraController); -} - class SelectedCameraDetails { double maxAvailableZoom = 1; double minAvailableZoom = 1; @@ -156,12 +109,10 @@ class CameraPreviewView extends StatefulWidget { } class _CameraPreviewViewState extends State { - bool _sharePreviewIsShown = false; bool _galleryLoadedImageIsShown = false; bool _showSelfieFlash = false; double _basePanY = 0; double _baseScaleFactor = 0; - bool _isVideoRecording = false; bool _hasAudioPermission = true; DateTime? _videoRecordingStarted; Timer? _videoRecordingTimer; @@ -317,10 +268,10 @@ class _CameraPreviewViewState extends State { } Future takePicture() async { - if (_sharePreviewIsShown || _isVideoRecording) return; + if (mc.isSharePreviewIsShown || mc.isVideoRecording) return; setState(() { - _sharePreviewIsShown = true; + mc.isSharePreviewIsShown = true; }); if (mc.selectedCameraDetails.isFlashOn) { if (isFront) { @@ -353,12 +304,12 @@ class _CameraPreviewViewState extends State { return; } setState(() { - _sharePreviewIsShown = false; + mc.isSharePreviewIsShown = false; }); } Future pushMediaEditor( - ScreenshotImage? imageBytes, + ScreenshotImage? screenshotImage, File? videoFilePath, { bool sharedFromGallery = false, MediaType? mediaType, @@ -394,11 +345,12 @@ class _CameraPreviewViewState extends State { PageRouteBuilder( opaque: false, pageBuilder: (context, a1, a2) => ShareImageEditorView( - imageBytesFuture: imageBytes, + screenshotImage: screenshotImage, sharedFromGallery: sharedFromGallery, sendToGroup: widget.sendToGroup, mediaFileService: mediaFileService, mainCameraController: mc, + previewLink: mc.sharedLinkForPreview, ), transitionsBuilder: (context, animation, secondaryAnimation, child) { return child; @@ -409,7 +361,7 @@ class _CameraPreviewViewState extends State { ) as bool?; if (mounted) { setState(() { - _sharePreviewIsShown = false; + mc.isSharePreviewIsShown = false; _showSelfieFlash = false; }); } @@ -459,7 +411,7 @@ class _CameraPreviewViewState extends State { Future pickImageFromGallery() async { setState(() { _galleryLoadedImageIsShown = true; - _sharePreviewIsShown = true; + mc.isSharePreviewIsShown = true; }); final picker = ImagePicker(); final pickedFile = await picker.pickMedia(); @@ -502,17 +454,47 @@ class _CameraPreviewViewState extends State { } setState(() { _galleryLoadedImageIsShown = false; - _sharePreviewIsShown = false; + mc.isSharePreviewIsShown = false; }); } + Future pressSideButtonLeft() async { + if (!mc.isSelectingFaceFilters) { + return pickImageFromGallery(); + } + if (mc.currentFilterType.index == 1) { + mc.setFilter(FaceFilterType.none); + setState(() { + mc.isSelectingFaceFilters = false; + }); + return; + } + mc.setFilter(mc.currentFilterType.goLeft()); + } + + Future pressSideButtonRight() async { + if (!mc.isSelectingFaceFilters) { + setState(() { + mc.isSelectingFaceFilters = true; + }); + } + if (mc.currentFilterType.index == FaceFilterType.values.length - 1) { + mc.setFilter(FaceFilterType.none); + setState(() { + mc.isSelectingFaceFilters = false; + }); + return; + } + mc.setFilter(mc.currentFilterType.goRight()); + } + Future startVideoRecording() async { if (mc.cameraController != null && mc.cameraController!.value.isRecordingVideo) { return; } setState(() { - _isVideoRecording = true; + mc.isVideoRecording = true; }); try { @@ -532,11 +514,11 @@ class _CameraPreviewViewState extends State { }); setState(() { _videoRecordingStarted = clock.now(); - _isVideoRecording = true; + mc.isVideoRecording = true; }); } on CameraException catch (e) { setState(() { - _isVideoRecording = false; + mc.isVideoRecording = false; }); _showCameraException(e); return; @@ -551,7 +533,7 @@ class _CameraPreviewViewState extends State { setState(() { _videoRecordingStarted = null; - _isVideoRecording = false; + mc.isVideoRecording = false; }); if (mc.cameraController == null || @@ -560,7 +542,7 @@ class _CameraPreviewViewState extends State { } setState(() { - _sharePreviewIsShown = true; + mc.isSharePreviewIsShown = true; }); try { @@ -646,12 +628,23 @@ class _CameraPreviewViewState extends State { ), ), ), - if (!_sharePreviewIsShown && + if (!mc.isSharePreviewIsShown && widget.sendToGroup != null && - !_isVideoRecording) - SendToWidget(sendTo: widget.sendToGroup!.groupName), - if (!_sharePreviewIsShown && - !_isVideoRecording && + !mc.isVideoRecording) + ShowTitleText( + title: widget.sendToGroup!.groupName, + desc: context.lang.cameraPreviewSendTo, + ), + if (!mc.isSharePreviewIsShown && + mc.sharedLinkForPreview != null && + !mc.isVideoRecording) + ShowTitleText( + title: mc.sharedLinkForPreview?.host ?? '', + desc: 'Link', + isLink: true, + ), + if (!mc.isSharePreviewIsShown && + !mc.isVideoRecording && !widget.hideControllers) Positioned( right: 5, @@ -707,7 +700,7 @@ class _CameraPreviewViewState extends State { ), ), ), - if (!_sharePreviewIsShown && !widget.hideControllers) + if (!mc.isSharePreviewIsShown && !widget.hideControllers) Positioned( bottom: 30, left: 0, @@ -718,7 +711,7 @@ class _CameraPreviewViewState extends State { children: [ if (mc.cameraController!.value.isInitialized && mc.selectedCameraDetails.isZoomAble && - !_isVideoRecording) + !mc.isVideoRecording) SizedBox( width: 120, child: CameraZoomButtons( @@ -734,17 +727,21 @@ class _CameraPreviewViewState extends State { Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - if (!_isVideoRecording) + if (!mc.isVideoRecording) GestureDetector( - onTap: pickImageFromGallery, + onTap: pressSideButtonLeft, child: Align( child: Container( height: 50, width: 80, padding: const EdgeInsets.all(2), - child: const Center( + child: Center( child: FaIcon( - FontAwesomeIcons.photoFilm, + mc.isSelectingFaceFilters + ? mc.currentFilterType.index == 1 + ? FontAwesomeIcons.xmark + : FontAwesomeIcons.arrowLeft + : FontAwesomeIcons.photoFilm, color: Colors.white, size: 25, ), @@ -766,15 +763,44 @@ class _CameraPreviewViewState extends State { shape: BoxShape.circle, border: Border.all( width: 7, - color: _isVideoRecording + color: mc.isVideoRecording ? Colors.red : Colors.white, ), ), + child: mc.currentFilterType.preview, ), ), ), - if (!_isVideoRecording) const SizedBox(width: 80), + if (!mc.isVideoRecording) + if (isFront) + GestureDetector( + onTap: pressSideButtonRight, + child: Align( + child: Container( + height: 50, + width: 80, + padding: const EdgeInsets.all(2), + child: Center( + child: FaIcon( + mc.isSelectingFaceFilters + ? mc.currentFilterType.index == + FaceFilterType + .values.length - + 1 + ? FontAwesomeIcons.xmark + : FontAwesomeIcons.arrowRight + : FontAwesomeIcons + .faceGrinTongueSquint, + color: Colors.white, + size: 25, + ), + ), + ), + ), + ) + else + const SizedBox(width: 80), ], ), ], @@ -785,7 +811,7 @@ class _CameraPreviewViewState extends State { videoRecordingStarted: _videoRecordingStarted, maxVideoRecordingTime: maxVideoRecordingTime, ), - if (!_sharePreviewIsShown && widget.sendToGroup != null || + if (!mc.isSharePreviewIsShown && widget.sendToGroup != null || widget.hideControllers) Positioned( left: 5, diff --git a/lib/src/views/camera/camera_preview_components/face_filters.dart b/lib/src/views/camera/camera_preview_components/face_filters.dart new file mode 100644 index 0000000..8c880a7 --- /dev/null +++ b/lib/src/views/camera/camera_preview_components/face_filters.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:twonly/src/views/camera/camera_preview_components/painters/face_filters/beard_filter_painter.dart'; +import 'package:twonly/src/views/camera/camera_preview_components/painters/face_filters/dog_filter_painter.dart'; + +enum FaceFilterType { + none, + dogBrown, + beardUpperLip, +} + +extension FaceFilterTypeExtension on FaceFilterType { + FaceFilterType goRight() { + final nextIndex = (index + 1) % FaceFilterType.values.length; + return FaceFilterType.values[nextIndex]; + } + + FaceFilterType goLeft() { + final prevIndex = (index - 1 + FaceFilterType.values.length) % + FaceFilterType.values.length; + return FaceFilterType.values[prevIndex]; + } + + Widget get preview { + switch (this) { + case FaceFilterType.none: + return Container(); + case FaceFilterType.dogBrown: + return DogFilterPainter.getPreview(); + case FaceFilterType.beardUpperLip: + return BeardFilterPainter.getPreview(); + } + } +} 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 a67d2fa..cfbef1a 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 @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:io'; import 'package:camera/camera.dart'; import 'package:collection/collection.dart'; @@ -5,6 +6,8 @@ import 'package:drift/drift.dart' show Value; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:google_mlkit_barcode_scanning/google_mlkit_barcode_scanning.dart'; +import 'package:google_mlkit_face_detection/google_mlkit_face_detection.dart'; +import 'package:permission_handler/permission_handler.dart'; import 'package:twonly/globals.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/twonly.db.dart'; @@ -15,7 +18,11 @@ import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/qr.dart'; import 'package:twonly/src/utils/screenshot.dart'; import 'package:twonly/src/views/camera/camera_preview_components/camera_preview_controller_view.dart'; -import 'package:twonly/src/views/camera/painters/barcode_detector_painter.dart'; +import 'package:twonly/src/views/camera/camera_preview_components/face_filters.dart'; +import 'package:twonly/src/views/camera/camera_preview_components/painters/barcode_detector_painter.dart'; +import 'package:twonly/src/views/camera/camera_preview_components/painters/face_filters/beard_filter_painter.dart'; +import 'package:twonly/src/views/camera/camera_preview_components/painters/face_filters/dog_filter_painter.dart'; +import 'package:twonly/src/views/camera/camera_preview_components/painters/face_filters/face_filter_painter.dart'; class ScannedVerifiedContact { ScannedVerifiedContact({ @@ -45,6 +52,34 @@ class MainCameraController { Map scannedNewProfiles = {}; String? scannedUrl; GlobalKey zoomButtonKey = GlobalKey(); + GlobalKey cameraPreviewKey = GlobalKey(); + bool isSelectingFaceFilters = false; + + bool isSharePreviewIsShown = false; + bool isVideoRecording = false; + + Uri? sharedLinkForPreview; + + void setSharedLinkForPreview(Uri url) { + sharedLinkForPreview = url; + setState(); + } + + final BarcodeScanner _barcodeScanner = BarcodeScanner(); + final FaceDetector _faceDetector = FaceDetector( + options: FaceDetectorOptions( + enableContours: true, + enableLandmarks: true, + ), + ); + bool _isBusy = false; + bool _isBusyFaces = false; + CustomPaint? customPaint; + CustomPaint? facePaint; + Offset? focusPointOffset; + + FaceFilterType _currentFilterType = FaceFilterType.beardUpperLip; + FaceFilterType get currentFilterType => _currentFilterType; Future closeCamera() async { contactsVerified = {}; @@ -57,51 +92,131 @@ class MainCameraController { } final cameraControllerTemp = cameraController; cameraController = null; - await cameraControllerTemp?.dispose(); + // prevents: CameraException(Disposed CameraController, buildPreview() was called on a disposed CameraController.) + Future.delayed(const Duration(milliseconds: 100), () async { + await cameraControllerTemp?.dispose(); + }); initCameraStarted = false; selectedCameraDetails = SelectedCameraDetails(); } - Future selectCamera(int sCameraId, bool init) async { + Future selectCamera(int sCameraId, bool init) async { initCameraStarted = true; - final opts = await initializeCameraController( - selectedCameraDetails, - sCameraId, - init, - ); - if (opts != null) { - selectedCameraDetails = opts.$1; - cameraController = opts.$2; - } - if (cameraController?.description.lensDirection == - CameraLensDirection.back) { - await cameraController?.startImageStream(_processCameraImage); - } - zoomButtonKey = GlobalKey(); - setState(); - return cameraController; - } - Future toggleSelectedCamera() async { - if (cameraController == null) return; - // do not allow switching camera when recording - if (cameraController!.value.isRecordingVideo) { + var cameraId = sCameraId; + if (cameraId >= gCameras.length) { + Log.warn( + 'Trying to select a non existing camera $cameraId >= ${gCameras.length}', + ); return; } - try { - await cameraController!.stopImageStream(); - } catch (e) { - // Log.warn(e); + + if (init) { + for (; cameraId < gCameras.length; cameraId++) { + if (gCameras[cameraId].lensDirection == CameraLensDirection.back) { + break; + } + } } - final tmp = cameraController; - cameraController = null; - await tmp!.dispose(); + + selectedCameraDetails.isZoomAble = false; + + if (cameraController == null) { + cameraController = CameraController( + gCameras[cameraId], + ResolutionPreset.high, + enableAudio: await Permission.microphone.isGranted, + imageFormatGroup: Platform.isAndroid + ? ImageFormatGroup.nv21 + : ImageFormatGroup.bgra8888, + ); + await cameraController?.initialize(); + await cameraController?.startImageStream(_processCameraImage); + await cameraController?.setZoomLevel(selectedCameraDetails.scaleFactor); + } else { + try { + if (!isVideoRecording) { + await cameraController?.stopImageStream(); + } + } catch (e) { + Log.info(e); + } + selectedCameraDetails.scaleFactor = 1; + + await cameraController?.setZoomLevel(1); + await cameraController?.setDescription(gCameras[cameraId]); + try { + if (!isVideoRecording) { + await cameraController?.startImageStream(_processCameraImage); + } + } catch (e) { + Log.info(e); + } + } + + await cameraController + ?.lockCaptureOrientation(DeviceOrientation.portraitUp); + await cameraController?.setFlashMode( + selectedCameraDetails.isFlashOn ? FlashMode.always : FlashMode.off, + ); + selectedCameraDetails.maxAvailableZoom = + await cameraController?.getMaxZoomLevel() ?? 1; + selectedCameraDetails.minAvailableZoom = + await cameraController?.getMinZoomLevel() ?? 1; + selectedCameraDetails + ..isZoomAble = selectedCameraDetails.maxAvailableZoom != + selectedCameraDetails.minAvailableZoom + ..cameraLoaded = true + ..cameraId = cameraId; + + facePaint = null; + customPaint = null; + isSelectingFaceFilters = false; + setFilter(FaceFilterType.none); + zoomButtonKey = GlobalKey(); + setState(); + } + + Future onDoubleTap() async { await selectCamera((selectedCameraDetails.cameraId + 1) % 2, false); } - final BarcodeScanner _barcodeScanner = BarcodeScanner(); - bool _isBusy = false; - CustomPaint? customPaint; + Future onTapDown(TapDownDetails details) async { + final box = + cameraPreviewKey.currentContext?.findRenderObject() as RenderBox?; + if (box == null) return; + final localPosition = box.globalToLocal(details.globalPosition); + + focusPointOffset = Offset(localPosition.dx, localPosition.dy); + + final dx = localPosition.dx / box.size.width; + final dy = localPosition.dy / box.size.height; + + setState(); + + await HapticFeedback.lightImpact(); + try { + await cameraController?.setFocusPoint(Offset(dx, dy)); + await cameraController?.setFocusMode(FocusMode.auto); + } catch (e) { + Log.error(e); + } + + focusPointOffset = null; + setState(); + } + + void setFilter(FaceFilterType type) { + _currentFilterType = type; + if (_currentFilterType == FaceFilterType.none) { + faceFilterPainter = null; + facePaint = null; + _isBusyFaces = false; + } + setState(); + } + + FaceFilterPainter? faceFilterPainter; final Map _orientations = { DeviceOrientation.portraitUp: 0, @@ -111,9 +226,21 @@ class MainCameraController { }; void _processCameraImage(CameraImage image) { + if (isVideoRecording || isSharePreviewIsShown) { + return; + } final inputImage = _inputImageFromCameraImage(image); if (inputImage == null) return; - _processImage(inputImage); + _processBarcode(inputImage); + // check if front camera is selected + if (cameraController?.description.lensDirection == + CameraLensDirection.front) { + if (_currentFilterType != FaceFilterType.none) { + _processFaces(inputImage); + } + } else { + _processBarcode(inputImage); + } } InputImage? _inputImageFromCameraImage(CameraImage image) { @@ -175,7 +302,7 @@ class MainCameraController { ); } - Future _processImage(InputImage inputImage) async { + Future _processBarcode(InputImage inputImage) async { if (_isBusy) return; _isBusy = true; final barcodes = await _barcodeScanner.processImage(inputImage); @@ -255,4 +382,48 @@ class MainCameraController { _isBusy = false; setState(); } + + Future _processFaces(InputImage inputImage) async { + if (_isBusyFaces) return; + _isBusyFaces = true; + final faces = await _faceDetector.processImage(inputImage); + if (inputImage.metadata?.size != null && + inputImage.metadata?.rotation != null && + cameraController != null) { + if (faces.isNotEmpty) { + CustomPainter? painter; + if (_currentFilterType == FaceFilterType.dogBrown) { + painter = DogFilterPainter( + faces, + inputImage.metadata!.size, + inputImage.metadata!.rotation, + cameraController!.description.lensDirection, + ); + } else if (_currentFilterType == FaceFilterType.beardUpperLip) { + painter = BeardFilterPainter( + faces, + inputImage.metadata!.size, + inputImage.metadata!.rotation, + cameraController!.description.lensDirection, + ); + } + + if (painter != null) { + facePaint = CustomPaint(painter: painter); + // Also set the correct FaceFilterPainter reference if needed for other logic, + // though currently facePaint is what's used for display. + if (painter is FaceFilterPainter) { + faceFilterPainter = painter; + } + } else { + facePaint = null; + faceFilterPainter = null; + } + } else { + facePaint = null; + } + } + _isBusyFaces = false; + setState(); + } } diff --git a/lib/src/views/camera/painters/barcode_detector_painter.dart b/lib/src/views/camera/camera_preview_components/painters/barcode_detector_painter.dart similarity index 100% rename from lib/src/views/camera/painters/barcode_detector_painter.dart rename to lib/src/views/camera/camera_preview_components/painters/barcode_detector_painter.dart diff --git a/lib/src/views/camera/painters/coordinates_translator.dart b/lib/src/views/camera/camera_preview_components/painters/coordinates_translator.dart similarity index 100% rename from lib/src/views/camera/painters/coordinates_translator.dart rename to lib/src/views/camera/camera_preview_components/painters/coordinates_translator.dart diff --git a/lib/src/views/camera/camera_preview_components/painters/face_filters/beard_filter_painter.dart b/lib/src/views/camera/camera_preview_components/painters/face_filters/beard_filter_painter.dart new file mode 100644 index 0000000..3477bb6 --- /dev/null +++ b/lib/src/views/camera/camera_preview_components/painters/face_filters/beard_filter_painter.dart @@ -0,0 +1,175 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:math'; +import 'dart:ui' as ui; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:google_mlkit_face_detection/google_mlkit_face_detection.dart'; +import 'package:twonly/src/utils/log.dart'; +import 'package:twonly/src/views/camera/camera_preview_components/painters/coordinates_translator.dart'; +import 'package:twonly/src/views/camera/camera_preview_components/painters/face_filters/face_filter_painter.dart'; + +class BeardFilterPainter extends FaceFilterPainter { + BeardFilterPainter( + super.faces, + super.imageSize, + super.rotation, + super.cameraLensDirection, + ) { + _loadAssets(); + } + + static ui.Image? _beardImage; + static bool _loading = false; + + static Future _loadAssets() async { + if (_loading || _beardImage != null) return; + _loading = true; + try { + _beardImage = await _loadImage('assets/filters/beard_upper_lip.webp'); + } catch (e) { + Log.error('Failed to load filter assets: $e'); + } finally { + _loading = false; + } + } + + static Future _loadImage(String assetPath) async { + final data = await rootBundle.load(assetPath); + final list = Uint8List.view(data.buffer); + final completer = Completer(); + ui.decodeImageFromList(list, completer.complete); + return completer.future; + } + + @override + void paint(Canvas canvas, Size size) { + if (_beardImage == null) return; + + for (final face in faces) { + final noseBase = face.landmarks[FaceLandmarkType.noseBase]; + final mouthLeft = face.landmarks[FaceLandmarkType.leftMouth]; + final mouthRight = face.landmarks[FaceLandmarkType.rightMouth]; + final bottomMouth = face.landmarks[FaceLandmarkType.bottomMouth]; + + if (noseBase != null && + mouthLeft != null && + mouthRight != null && + bottomMouth != null) { + final noseX = translateX( + noseBase.position.x.toDouble(), + size, + imageSize, + rotation, + cameraLensDirection, + ); + final noseY = translateY( + noseBase.position.y.toDouble(), + size, + imageSize, + rotation, + cameraLensDirection, + ); + + final mouthLeftX = translateX( + mouthLeft.position.x.toDouble(), + size, + imageSize, + rotation, + cameraLensDirection, + ); + final mouthLeftY = translateY( + mouthLeft.position.y.toDouble(), + size, + imageSize, + rotation, + cameraLensDirection, + ); + + final mouthRightX = translateX( + mouthRight.position.x.toDouble(), + size, + imageSize, + rotation, + cameraLensDirection, + ); + final mouthRightY = translateY( + mouthRight.position.y.toDouble(), + size, + imageSize, + rotation, + cameraLensDirection, + ); + + final mouthCenterX = (mouthLeftX + mouthRightX) / 2; + final mouthCenterY = (mouthLeftY + mouthRightY) / 2; + + final beardCenterX = (noseX + mouthCenterX) / 2; + final beardCenterY = (noseY + mouthCenterY) / 2; + + final dx = mouthRightX - mouthLeftX; + final dy = mouthRightY - mouthLeftY; + final angle = atan2(dy, dx); + + final mouthWidth = sqrt(dx * dx + dy * dy); + final beardWidth = mouthWidth * 1.5; + + final yaw = face.headEulerAngleY ?? 0; + final scaleX = cos(yaw * pi / 180).abs(); + + _drawImage( + canvas, + _beardImage!, + Offset(beardCenterX, beardCenterY), + beardWidth, + angle, + scaleX, + ); + } + } + } + + void _drawImage( + Canvas canvas, + ui.Image image, + Offset position, + double width, + double rotation, + double scaleX, + ) { + canvas + ..save() + ..translate(position.dx, position.dy) + ..rotate(rotation) + ..scale(scaleX, Platform.isAndroid ? -1 : 1); + + final srcRect = + Rect.fromLTWH(0, 0, image.width.toDouble(), image.height.toDouble()); + + final aspectRatio = image.width / image.height; + final dstWidth = width; + final dstHeight = width / aspectRatio; + + final dstRect = Rect.fromCenter( + center: Offset.zero, + width: dstWidth, + height: dstHeight, + ); + + canvas + ..drawImageRect(image, srcRect, dstRect, Paint()) + ..restore(); + } + + static Widget getPreview() { + return Preview( + child: Padding( + padding: const EdgeInsets.all(8), + child: Image.asset( + 'assets/filters/beard_upper_lip.webp', + fit: BoxFit.contain, + ), + ), + ); + } +} diff --git a/lib/src/views/camera/camera_preview_components/painters/face_filters/dog_filter_painter.dart b/lib/src/views/camera/camera_preview_components/painters/face_filters/dog_filter_painter.dart new file mode 100644 index 0000000..3643c33 --- /dev/null +++ b/lib/src/views/camera/camera_preview_components/painters/face_filters/dog_filter_painter.dart @@ -0,0 +1,243 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:math'; +import 'dart:ui' as ui; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:google_mlkit_face_detection/google_mlkit_face_detection.dart'; +import 'package:twonly/src/utils/log.dart'; +import 'package:twonly/src/views/camera/camera_preview_components/painters/coordinates_translator.dart'; +import 'package:twonly/src/views/camera/camera_preview_components/painters/face_filters/face_filter_painter.dart'; + +class DogFilterPainter extends FaceFilterPainter { + DogFilterPainter( + super.faces, + super.imageSize, + super.rotation, + super.cameraLensDirection, + ) { + _loadAssets(); + } + + static ui.Image? _earImage; + static ui.Image? _noseImage; + static bool _loading = false; + + static Future _loadAssets() async { + if (_loading || (_earImage != null && _noseImage != null)) return; + _loading = true; + try { + _earImage = await _loadImage('assets/filters/dog_brown_ear.webp'); + _noseImage = await _loadImage('assets/filters/dog_brown_nose.webp'); + } catch (e) { + Log.error('Failed to load filter assets: $e'); + } finally { + _loading = false; + } + } + + static Future _loadImage(String assetPath) async { + final data = await rootBundle.load(assetPath); + final list = Uint8List.view(data.buffer); + final completer = Completer(); + ui.decodeImageFromList(list, completer.complete); + return completer.future; + } + + @override + void paint(Canvas canvas, Size size) { + if (_earImage == null || _noseImage == null) return; + + for (final face in faces) { + final faceContour = face.contours[FaceContourType.face]; + final noseBase = face.landmarks[FaceLandmarkType.noseBase]; + + if (faceContour != null && noseBase != null) { + final points = faceContour.points; + if (points.isEmpty) continue; + + final upperPoints = + points.where((p) => p.y < noseBase.position.y).toList(); + + if (upperPoints.isEmpty) continue; + + Point? leftMost; + Point? rightMost; + Point? topMost; + + for (final point in upperPoints) { + if (leftMost == null || point.x < leftMost.x) { + leftMost = point; + } + if (rightMost == null || point.x > rightMost.x) { + rightMost = point; + } + if (topMost == null || point.y < topMost.y) { + topMost = point; + } + } + + if (leftMost == null || rightMost == null || topMost == null) continue; + + final leftEarX = translateX( + leftMost.x.toDouble(), + size, + imageSize, + rotation, + cameraLensDirection, + ); + final leftEarY = translateY( + topMost.y.toDouble(), + size, + imageSize, + rotation, + cameraLensDirection, + ); + + final rightEarX = translateX( + rightMost.x.toDouble(), + size, + imageSize, + rotation, + cameraLensDirection, + ); + final rightEarY = translateY( + topMost.y.toDouble(), + size, + imageSize, + rotation, + cameraLensDirection, + ); + + final noseX = translateX( + noseBase.position.x.toDouble(), + size, + imageSize, + rotation, + cameraLensDirection, + ); + final noseY = translateY( + noseBase.position.y.toDouble(), + size, + imageSize, + rotation, + cameraLensDirection, + ); + + final dx = rightEarX - leftEarX; + final dy = rightEarY - leftEarY; + + final faceWidth = sqrt(dx * dx + dy * dy) * 1.5; + final angle = atan2(dy, dx); + + final yaw = face.headEulerAngleY ?? 0; + final scaleX = cos(yaw * pi / 180).abs(); + + final earSize = faceWidth / 2.5; + + _drawImage( + canvas, + _earImage!, + Offset(leftEarX, leftEarY + earSize * 0.3), + earSize, + angle, + scaleX, + ); + + _drawImage( + canvas, + _earImage!, + Offset(rightEarX, rightEarY + earSize * 0.3), + earSize, + angle, + scaleX, + isFlipped: true, + ); + + final noseSize = faceWidth * 0.4; + _drawImage( + canvas, + _noseImage!, + Offset(noseX, noseY + noseSize * 0.1), + noseSize, + angle, + scaleX, + ); + } + } + } + + void _drawImage( + Canvas canvas, + ui.Image image, + Offset position, + double size, + double rotation, + double scaleX, { + bool isFlipped = false, + }) { + canvas + ..save() + ..translate(position.dx, position.dy) + ..rotate(rotation); + if (isFlipped) { + canvas.scale(-scaleX, Platform.isAndroid ? -1 : 1); + } else { + canvas.scale(scaleX, Platform.isAndroid ? -1 : 1); + } + + final srcRect = + Rect.fromLTWH(0, 0, image.width.toDouble(), image.height.toDouble()); + final aspectRatio = image.width / image.height; + final dstWidth = size; + final dstHeight = size / aspectRatio; + + final dstRect = Rect.fromCenter( + center: Offset.zero, + width: dstWidth, + height: dstHeight, + ); + + canvas + ..drawImageRect(image, srcRect, dstRect, Paint()) + ..restore(); + } + + static Widget getPreview() { + return Preview( + child: Stack( + alignment: Alignment.center, + children: [ + Padding( + padding: const EdgeInsets.only(top: 25), + child: Image.asset( + 'assets/filters/dog_brown_nose.webp', + width: 25, + ), + ), + Padding( + padding: const EdgeInsets.only(bottom: 10), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + 'assets/filters/dog_brown_ear.webp', + width: 20, + ), + const SizedBox(width: 15), + Transform.scale( + scaleX: -1, + child: Image.asset( + 'assets/filters/dog_brown_ear.webp', + width: 20, + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/src/views/camera/camera_preview_components/painters/face_filters/face_filter_painter.dart b/lib/src/views/camera/camera_preview_components/painters/face_filters/face_filter_painter.dart new file mode 100644 index 0000000..b32ec7d --- /dev/null +++ b/lib/src/views/camera/camera_preview_components/painters/face_filters/face_filter_painter.dart @@ -0,0 +1,44 @@ +import 'package:camera/camera.dart'; +import 'package:flutter/material.dart'; +import 'package:google_mlkit_face_detection/google_mlkit_face_detection.dart'; + +abstract class FaceFilterPainter extends CustomPainter { + FaceFilterPainter( + this.faces, + this.imageSize, + this.rotation, + this.cameraLensDirection, + ); + + final List faces; + final Size imageSize; + final InputImageRotation rotation; + final CameraLensDirection cameraLensDirection; + + @override + bool shouldRepaint(covariant FaceFilterPainter oldDelegate) { + return oldDelegate.imageSize != imageSize || + oldDelegate.faces != faces || + oldDelegate.rotation != rotation || + oldDelegate.cameraLensDirection != cameraLensDirection; + } +} + +class Preview extends StatelessWidget { + const Preview({required this.child, super.key}); + + final Widget child; + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.grey.withValues(alpha: 0.2), + ), + child: Center( + child: child, + ), + ); + } +} 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 85c57d4..282ccc5 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,5 +1,4 @@ import 'dart:async'; -import 'dart:typed_data'; import 'package:clock/clock.dart'; import 'package:drift/drift.dart' show Value; import 'package:flutter/material.dart'; @@ -8,6 +7,7 @@ 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'; +import 'package:twonly/src/utils/screenshot.dart'; class SaveToGalleryButton extends StatefulWidget { const SaveToGalleryButton({ @@ -17,7 +17,7 @@ class SaveToGalleryButton extends StatefulWidget { this.storeImageAsOriginal, super.key, }); - final Future Function()? storeImageAsOriginal; + final Future Function()? storeImageAsOriginal; final bool displayButtonLabel; final MediaFileService mediaService; final bool isLoading; diff --git a/lib/src/views/camera/camera_preview_components/send_to.dart b/lib/src/views/camera/camera_preview_components/send_to.dart index 76e7a7c..5b55bc0 100644 --- a/lib/src/views/camera/camera_preview_components/send_to.dart +++ b/lib/src/views/camera/camera_preview_components/send_to.dart @@ -1,22 +1,25 @@ import 'package:flutter/material.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart'; -import 'package:twonly/src/utils/misc.dart'; -class SendToWidget extends StatelessWidget { - const SendToWidget({ - required this.sendTo, +class ShowTitleText extends StatelessWidget { + const ShowTitleText({ + required this.desc, + required this.title, + this.isLink = false, super.key, }); - final String sendTo; + final String title; + final String desc; + final bool isLink; @override Widget build(BuildContext context) { - const textStyle = TextStyle( + final textStyle = TextStyle( color: Colors.white, fontWeight: FontWeight.bold, - fontSize: 24, + fontSize: isLink ? 14 : 24, decoration: TextDecoration.none, - shadows: [ + shadows: const [ Shadow( color: Color.fromARGB(122, 0, 0, 0), blurRadius: 5, @@ -26,7 +29,7 @@ class SendToWidget extends StatelessWidget { final boldTextStyle = textStyle.copyWith( fontWeight: FontWeight.normal, - fontSize: 28, + fontSize: isLink ? 17 : 28, ); return Positioned( @@ -36,12 +39,12 @@ class SendToWidget extends StatelessWidget { child: Column( children: [ Text( - context.lang.cameraPreviewSendTo, + desc, textAlign: TextAlign.center, style: textStyle, ), Text( - substringBy(sendTo, 20), + substringBy(title, isLink ? 30 : 20), textAlign: TextAlign.center, style: boldTextStyle, // Use the bold text style here ), diff --git a/lib/src/views/camera/camera_qr_scanner.view.dart b/lib/src/views/camera/camera_qr_scanner.view.dart index c9fa6ea..32703cc 100644 --- a/lib/src/views/camera/camera_qr_scanner.view.dart +++ b/lib/src/views/camera/camera_qr_scanner.view.dart @@ -32,7 +32,8 @@ class QrCodeScannerState extends State { Widget build(BuildContext context) { return Scaffold( body: GestureDetector( - onDoubleTap: _mainCameraController.toggleSelectedCamera, + onDoubleTap: _mainCameraController.onDoubleTap, + onTapDown: _mainCameraController.onTapDown, child: Stack( children: [ MainCameraPreview( diff --git a/lib/src/views/camera/camera_send_to_view.dart b/lib/src/views/camera/camera_send_to.view.dart similarity index 93% rename from lib/src/views/camera/camera_send_to_view.dart rename to lib/src/views/camera/camera_send_to.view.dart index 7dfefbf..cdda133 100644 --- a/lib/src/views/camera/camera_send_to_view.dart +++ b/lib/src/views/camera/camera_send_to.view.dart @@ -34,7 +34,8 @@ class CameraSendToViewState extends State { Widget build(BuildContext context) { return Scaffold( body: GestureDetector( - onDoubleTap: _mainCameraController.toggleSelectedCamera, + onDoubleTap: _mainCameraController.onDoubleTap, + onTapDown: _mainCameraController.onTapDown, child: Stack( children: [ MainCameraPreview( diff --git a/lib/src/views/camera/image_editor/data/data.dart b/lib/src/views/camera/image_editor/data/data.dart deleted file mode 100755 index a686020..0000000 --- a/lib/src/views/camera/image_editor/data/data.dart +++ /dev/null @@ -1,710 +0,0 @@ -Map emojiWeights = {}; - -List emojis = [ - '😀', - '😁', - '😂', - '🤣', - '😃', - '😄', - '😅', - '😆', - '😉', - '😊', - '😋', - '😎', - '😍', - '😘', - '🥰', - '😗', - '😙', - '😚', - '🙂️', - '🤗', - '🤩', - '🤔', - '🤔', - '🤨', - '😐', - '😑', - '😶', - '🙄', - '😏', - '😣', - '😥', - '😮', - '🤐', - '😯', - '😪', - '😫', - '😴', - '😌', - '😛', - '😜', - '😝', - '🤤', - '😒', - '😓', - '😔', - '😕', - '🙃', - '🤑', - '😲', - '🙁', - '😖', - '😞', - '😟', - '😤', - '😢', - '😭', - '😦', - '😧', - '😨', - '😩', - '🤯', - '😬', - '😰', - '😱', - '🥵', - '🥶', - '😳', - '🤪', - '😵', - '😡', - '😠', - '🤬', - '😷', - '🤒', - '🤕', - '🤢', - '🤮', - '🤧', - '😇', - '🤠', - '🤡', - '🥳', - '🥴', - '🤥', - '🤫', - '🤭', - '🤭', - '🧐', - '🤓', - '😈', - '👿', - '👹', - '👺', - '💀', - '👻', - '👽', - '🤖', - '💩', - '😺', - '😸', - '😹', - '😻', - '😼', - '😽', - '🙀', - '😿', - '😾', - '😾', - - /// People and Fantasy - '👶', - '👧', - '🧒', - '👩', - '🧑', - '👨', - '👵', - '👴', - '👲', - '👳‍♀️‍️', - '👳‍♂️️‍️', - '🧕️️‍️', - '🧔‍', - '👱‍♂️️‍', - '👱‍♀️️️‍', - '👨‍🦰️️️‍', - '👩‍🦰‍', - '👨‍🦱‍‍', - '👨‍🦲‍‍', - '👩‍🦲‍‍', - '👨‍🦳‍‍', - '👩‍🦳‍‍', - '🦸‍♀️‍‍', - '🦸‍♂️️‍‍', - '🦹‍♀️️️‍‍', - '🦹‍♂️️️️‍‍', - '👮‍♀️‍‍', - '👮‍♂️️‍‍', - '👷‍♀️️️‍‍', - '👷‍♂️️️️‍‍', - '💂‍♀️️️️️‍‍', - '💂‍♂️️️️️️‍‍', - '🕵️‍♀️️️️️️️‍‍', - '🕵️‍♂️️️️️️️️‍‍', - '👩‍⚕️️️️️️️️️‍‍', - '👨‍⚕️️️️️️️️️️‍‍', - '👩‍🌾️️️️️️️️️️‍‍', - '👨‍🌾‍‍', - '👩‍🍳‍‍', - '👨‍🍳‍‍', - '👩‍🎓‍‍', - '👨‍🎓‍‍', - '👩‍🎤‍‍', - '👨‍🎤‍‍', - '👩‍🏫‍‍', - '👨‍🏫‍‍', - '👩‍🏭‍‍', - '👨‍🏭‍‍', - '👩‍💻‍‍', - '👨‍💻‍‍', - '👩‍💼‍‍', - '👨‍💼‍‍', - '👩‍🔧‍‍', - '👨‍🔧‍‍', - '👩‍🔬‍‍', - '👨‍🔬‍‍', - '👩‍🎨‍‍', - '👨‍🎨‍‍', - '👩‍🚒‍‍', - '👨‍🚒‍‍', - '👩‍✈️‍‍', - '👨‍✈️️‍‍', - '👩‍🚀‍‍', - '👨‍🚀‍‍', - '👩‍⚖️‍‍', - '👨‍⚖️️‍‍', - '👰‍‍', - '🤵‍‍', - '👸‍‍', - '🤴‍‍', - '🤶‍‍', - '🎅‍‍', - '🧙‍♀️‍‍', - '🧙‍♂️️‍‍', - '🧝‍♀️️️‍‍', - '🧝‍♂️‍‍', - '🧛‍♀️️‍‍', - '🧛‍♂️️️‍‍', - '🧟‍♀️️️️‍‍', - '🧟‍♂️️️️️‍‍', - '🧞‍♀️️️️️️‍‍', - '🧞‍♂️️️️️️️‍‍', - '🧜‍♀️️️️️️️️‍‍', - '🧜‍♂️️️️️️️️️‍‍', - '🧚‍♀️️️️️️️️️️‍‍', - '🧚‍♂️️️️️️️️️️️‍‍', - '👼️️️️️️️️️️️‍‍', - '🤰‍‍', - '🤱‍‍', - '🙇‍♀️‍‍', - '🙇‍♂️‍‍', - '💁‍♀️️‍‍', - '💁‍♂️️️‍‍', - '🙅‍♀️️️️‍‍', - '🙅‍♂️‍‍', - '🙆‍♀️️‍‍', - '🙆‍♂️️️‍‍', - '🙋‍♀️️️️‍‍', - '🙋‍♂️‍‍', - '🤦‍♀️️‍‍', - '🤦‍♂️️️‍‍', - '🤷‍♀️️️️‍‍', - '🤷‍♂️️️️️‍‍', - '🙎‍♀️️️️️️‍‍', - '🙎‍♂️️️️️️️‍‍', - '🙍‍♀️️️️️️️️‍‍', - '🙍‍♂️️️️️️️️️‍‍', - '💇‍♀️️️️️️️️️️‍‍', - '💇‍♂️️️️️️️️️️️‍‍', - '💆‍♀️️️️️️️️️️️️‍‍', - '💆‍♂️️️️️️️️️️️️️‍‍', - '🧖‍♀️️️️️️️️️️️️️️‍‍', - '🧖‍♂️️️️️️️️️️️️️️️‍‍', - '💅️️️️️️️️️️️️️️️‍‍', - '🤳️️️️️️️️️️️️️️‍‍', - '💃️️️️️️️️️️️️️‍‍', - '🕺️️️️️️️️️️️️‍‍', - '👯‍♀️‍‍', - '👯‍♂️️‍‍', - '🕴️️‍‍', - '🚶‍♀️️‍‍', - '🚶‍♂️️️‍‍', - '🏃‍♀️️️️‍‍', - '🏃‍♂️‍‍', - '👫️‍‍', - '👭‍‍', - '👬‍‍', - '💑‍‍', - '👩‍❤️‍👩‍‍', - '👨‍❤️‍👨‍‍', - '💏‍‍', - '👩‍❤️‍💋‍👩‍‍', - '👨‍❤️‍💋‍👨‍‍', - '👪‍‍', - '👨‍👩‍👧‍‍', - '👨‍👩‍👧‍👦‍‍', - '👨‍👩‍👦‍👦‍‍', - '👨‍👩‍👧‍👧‍‍', - '👩‍👩‍👦‍‍', - '👩‍👩‍👧‍‍', - '👩‍👩‍👧‍👦‍‍', - '👩‍👩‍👦‍👦‍‍', - '👩‍👩‍👧‍👧‍‍', - '👨‍👨‍👦‍‍', - '👨‍👨‍👧‍‍', - '👨‍👨‍👧‍👦‍‍', - '👨‍👨‍👦‍👦‍‍', - '👨‍👨‍👧‍👧‍‍', - '👩‍👦‍‍', - '👩‍👧‍‍', - '👩‍👧‍👦‍‍', - '👩‍👦‍👦‍‍', - '👩‍👧‍👧‍‍', - '👨‍👦‍‍', - '👨‍👧‍‍', - '👨‍👧‍👦‍‍', - '👨‍👦‍👦‍‍', - '👨‍👧‍👧‍‍', - '🤲‍‍', - '👐‍‍', - '🙌‍‍', - '👏‍‍', - '🤝‍‍', - '👍‍‍', - '👎‍‍', - '👊‍‍', - '✊‍‍', - '🤛‍‍', - '🤜‍‍', - '🤞‍‍', - '✌️‍‍', - '🤟️‍‍', - '🤘‍‍', - '👌‍‍', - '👈‍‍', - '👉‍‍', - '👆‍‍', - '👇‍‍', - '☝️‍‍', - '✋️‍‍', - '🤚️‍‍', - '🤚️‍‍', - '🖐‍‍', - '🖖‍‍', - '👋‍‍', - '🤙‍‍', - '💪‍‍', - '🦵‍‍', - '🦶‍‍', - '🖕‍‍', - '✍️‍‍', - '🙏️‍‍', - '💍‍‍', - '💄‍‍', - '💋‍‍', - '👄‍‍', - '👅‍‍', - '👂‍‍', - '👃‍‍', - '👣‍‍', - '👁‍‍', - '👀‍‍', - '🧠‍‍', - '🦴‍‍', - '🦷‍‍', - '🗣‍‍', - '👤‍‍', - '👥‍‍', - '🧥‍‍', - '👚‍‍', - '👕‍‍', - '👖‍‍', - '👔‍‍', - '👗‍‍', - '👙‍‍', - '👘‍‍', - '👠‍‍', - '👡‍‍', - '👢‍‍', - '👞‍‍', - '👟‍‍', - '🥾‍‍', - '🥿‍‍', - '🧦‍‍', - '🧤‍‍', - '🧣‍‍', - '🎩‍‍', - '🧢‍‍', - '👒‍‍', - '🎓‍‍', - '⛑‍‍', - '👑‍‍', - '👝‍‍', - '👛‍‍', - '👜‍‍', - '💼‍‍', - '🎒‍‍', - '👓‍‍', - '🕶‍‍', - '🥽‍‍', - '🥼‍‍', - '🌂‍‍', - '🧵‍‍', - '🧶‍‍', - - /// Animals - '🐶‍‍', - '🐱‍‍', - '🐭‍‍', - '🐰‍‍', - '🦊‍‍', - '🦝‍‍', - '🐻‍‍', - '🦘‍‍', - '🦡‍‍', - '🐨‍‍', - '🐯‍‍', - '🦁‍‍', - '🐼‍‍', - '🐼‍‍', - '🐮‍‍', - '🐷‍‍', - '🐽‍‍', - '🐸‍‍', - '🐵‍‍', - '🙈‍‍', - '🙉‍‍', - '🙊‍‍', - '🐒‍‍', - '🐔‍‍', - '🐧‍‍', - '🐦‍‍', - '🐤‍‍', - '🐣‍‍', - '🐥‍‍', - '🦆‍‍', - '🦢‍‍', - '🦅‍‍', - '🦉‍‍', - '🦚‍‍', - '🦜‍‍', - '🦇‍‍', - '🐺‍‍', - '🐗‍‍', - '🐴‍‍', - '🦄‍‍', - '🐝‍‍', - '🐛‍‍', - '🦋‍‍', - '🐌‍‍', - '🐚‍‍', - '🐞‍‍', - '🐜‍‍', - '🦗‍‍', - '🕷‍‍', - '🕸‍‍', - '🦂‍‍', - '🦟‍‍', - '🦠‍‍', - '🐢‍‍', - '🐍‍‍', - '🦎‍‍', - '🦖‍‍', - '🦕‍‍', - '🐙‍‍', - '🦑‍‍', - '🦐‍‍', - '🦀‍‍', - '🐡‍‍', - '🐠‍‍', - '🐟‍‍', - '🐬‍‍', - '🐳‍‍', - '🐋‍‍', - '🦈‍‍', - '🐊‍‍', - '🐅‍‍', - '🐆‍‍', - '🦓‍‍', - '🦍‍‍', - '🐘‍‍', - '🦏‍‍', - '🦛‍‍', - '🐪‍‍', - '🐫‍‍', - '🦙‍‍', - '🦒‍‍', - '🐃‍‍', - '🐂‍‍', - '🐄‍‍', - '🐎‍‍', - '🐖‍‍', - '🐏‍‍', - '🐐‍‍', - '🦌‍‍', - '🐕‍‍', - '🐩‍‍', - '🐈‍‍', - '🐓‍‍', - '🦃‍‍', - '🕊‍‍', - '🐇‍‍', - '🐁‍‍', - '🐀‍‍', - '🐿‍‍', - '🦔‍‍', - '🐾‍', - '🐉‍', - '🐲‍', - '🌵‍', - '🎄‍', - '🌲‍', - '🌳‍', - '🌴‍', - '🌱‍', - '🌿‍', - '☘️‍', - '🎍️‍', - '🎋️‍', - '🍃‍', - '🍂‍', - '🍁‍', - '🍄‍', - '🌾️‍', - '💐️‍', - '🌷️‍', - '🌹‍', - '🥀‍', - '🌺‍', - '🌸‍', - '🌼‍', - '🌻️‍', - '🌞‍', - '🌝‍', - '🌛‍', - '🌜‍', - '🌚‍', - '🌕‍', - '🌖‍', - '🌗‍', - '🌘‍', - '🌑‍', - '🌒‍', - '🌔‍', - '🌙‍', - '🌎‍', - '🌍‍', - '🌏‍', - '💫‍', - '⭐️‍', - '🌟️‍', - '✨️‍', - '⚡️️‍', - '☄️️️‍', - '💥️️️‍', - '🔥‍', - '🌪‍', - '🌈‍', - '☀️‍', - '🌤️‍', - '⛅️️‍', - '🌥️️‍', - '☁️️‍', - '🌦️️‍', - '🌧️‍', - '⛈‍', - '🌩‍', - '🌨‍', - '❄️‍', - '☃️️‍', - '⛄️️️‍', - '🌬️️️‍', - '💨️️️‍', - '💧️️️‍', - '💦️️️‍', - '☔️️️️‍', - '☂️️️️️‍', - '🌊️️️️️‍', - '🌫️️️️‍', - - /// Foods - '🍏‍', - '🍎‍', - '🍐‍', - '🍊‍', - '🍋‍', - '🍌‍', - '🍉‍', - '🍇‍', - '🍓‍', - '🍈‍', - '🍒‍', - '🍑‍', - '🍍‍', - '🥭‍', - '🥥‍', - '🥝‍', - '🍅‍', - '🍆‍', - '🥑‍', - '🥦‍', - '🥒‍', - '🥬‍', - '🌶‍', - '🌽‍', - '🥕‍', - '🥔‍', - '🍠‍', - '🥐‍', - '🍞‍', - '🥖‍', - '🥨‍', - '🥯‍', - '🧀‍', - '🥚‍', - '🍳‍', - '🥞‍', - '🥓‍', - '🥩‍', - '🍗‍', - '🍖‍', - '🌭‍', - '🍔‍', - '🍟‍', - '🍕‍', - '🥪‍', - '🥙‍', - '🌮‍', - '🌯‍', - '🥗‍', - '🥘‍', - '🥫‍', - '🍝‍', - '🍜‍', - '🍲‍', - '🍛‍', - '🍣‍', - '🍱‍', - '🥟‍', - '🍤‍', - '🍙‍', - '🍚‍', - '🍘‍', - '🍥‍', - '🥮‍', - '🥠‍', - '🍢‍', - '🍧‍', - '🍨‍', - '🍦‍', - '🥧‍', - '🍰‍', - '🎂‍', - '🍮‍', - '🍭‍', - '🍬‍', - '🍫‍', - '🍿‍', - '🧂‍', - '🍩‍', - '🍪‍', - '🌰‍', - '🥜‍', - '🍯‍', - '🥛‍', - '🍼‍', - '☕️‍', - '🍵️‍', - '🥤️‍', - '🍶‍', - '🍺‍', - '🍻‍', - '🥂‍', - '🍷‍', - '🍸‍', - '🍹‍', - '🍾‍', - '🥄‍', - '🍴‍', - '🍽‍', - '🥣‍', - '🥡‍', - '🥢‍', - - /// Activity and Sports - '⚽️‍', - '🏀️‍', - '🏈‍', - '⚾️‍', - '🥎️‍', - '🏐️‍', - '🏉‍', - '🎾‍', - '🥏‍', - '🎱‍', - '🏓‍', - '🏸‍', - '🥅‍', - '🏒‍', - '🏑‍', - '🥍‍', - '🏏‍', - '⛳️‍', - '🏹️‍', - '🎣️‍', - '🥊‍', - '🥋‍', - '🎽‍', - '⛸‍', - '🥌‍', - '🛷‍', - '🛹‍', - '🎿‍', - '⛷‍', - '🏂‍', - '🏋️‍♀️‍', - '🏋🏼‍♀️‍', - '🏋🏽‍♀️️‍', - '🏋🏾‍♀️️️‍', - '🏋🏿‍♀️️️️‍', - '🏋️‍♂️️️️‍', - '🏋🏻‍♂️️️️‍', - '🏋🏼‍♂️️️️‍', - '🏋🏽‍♂️️️️‍', - '🏋🏾‍♂️️️️‍', - '🏋🏿‍♂️️️️‍', - '🤼‍♀️️️️‍', - '🤼‍♂️️️️‍', - '🤸‍♀️️️️‍', - '🤸🏻‍♀️️️️‍', - '🤸🏼‍♀️️️️‍', - '🤸🏽‍♀️️️️‍', - '🤸🏿‍♀️️️️️‍', - '🤸‍♂️️️️‍', - '🤸🏻‍♂️️️️‍', - '🤸🏼‍♂️️️️️‍', - '🤸🏽‍♂️️️️️️‍', - '🤸🏾‍♂️️️️️️‍', - '🤸🏿‍♂️️️️️️‍', - '⛹️‍♀️️️️️️‍', - '⛹🏻‍♀️️️️️️️‍', - '⛹🏼‍♀️️️️️️️️‍', - '⛹🏽‍♀️️️️️️️️️‍', - '⛹🏾‍♀️️️️️️️️️️‍', - '⛹🏿‍♀️️️️️️️️️️️‍', - '⛹️‍♂️️️️️️️️️️️️‍', - '⛹🏻‍♂️️️️️️️️️️️️️‍', - '⛹🏼‍♂️️️️️️️️️️️️️️‍', - '⛹🏽‍♂️️️️️️️️️️️️️️️‍', - '⛹🏾‍♂️️️️️️️️️️️️️️️️‍', - '⛹🏿‍♂️‍', - '🤺️‍', - '🤾‍♀️‍', - '🤾🏻‍♀️️‍', - '🤾🏼‍♀️️️‍', - '🤾🏾‍♀️️️️‍', -]; diff --git a/lib/src/views/camera/image_editor/data/image_item.dart b/lib/src/views/camera/image_editor/data/image_item.dart deleted file mode 100755 index 6468ca7..0000000 --- a/lib/src/views/camera/image_editor/data/image_item.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'dart:async'; -import 'dart:typed_data'; -import 'package:flutter/material.dart'; - -class ImageItem { - ImageItem([dynamic image]) { - if (image != null) unawaited(load(image)); - } - int width = 1; - int height = 1; - Uint8List bytes = Uint8List.fromList([]); - Completer loader = Completer(); - - Future load(dynamic image) async { - loader = Completer(); - - if (image is ImageItem) { - bytes = image.bytes; - - height = image.height; - width = image.width; - - return loader.complete(true); - } else if (image is Uint8List) { - bytes = image; - final decodedImage = await decodeImageFromList(bytes); - - height = decodedImage.height; - width = decodedImage.width; - - return loader.complete(true); - } else { - return loader.complete(false); - } - } -} diff --git a/lib/src/views/camera/image_editor/layers/background_layer.dart b/lib/src/views/camera/image_editor/layers/background_layer.dart deleted file mode 100755 index 300ae17..0000000 --- a/lib/src/views/camera/image_editor/layers/background_layer.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:twonly/src/views/camera/image_editor/data/layer.dart'; - -class BackgroundLayer extends StatefulWidget { - const BackgroundLayer({ - required this.layerData, - super.key, - this.onUpdate, - }); - final BackgroundLayerData layerData; - final VoidCallback? onUpdate; - - @override - State createState() => _BackgroundLayerState(); -} - -class _BackgroundLayerState extends State { - @override - Widget build(BuildContext context) { - return Container( - width: widget.layerData.image.width.toDouble(), - height: widget.layerData.image.height.toDouble(), - // color: Theme.of(context).colorScheme.surface, - padding: EdgeInsets.zero, - child: Image.memory( - widget.layerData.image.bytes, - frameBuilder: (context, child, frame, wasSynchronouslyLoaded) { - if (wasSynchronouslyLoaded || frame != null) { - WidgetsBinding.instance.addPostFrameCallback((_) { - widget.layerData.imageLoaded = true; - }); - } - return child; - }, - ), - ); - } -} diff --git a/lib/src/views/camera/image_editor/layers/emoji_layer.dart b/lib/src/views/camera/image_editor/layers/emoji_layer.dart deleted file mode 100755 index 32ddcc3..0000000 --- a/lib/src/views/camera/image_editor/layers/emoji_layer.dart +++ /dev/null @@ -1,173 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:twonly/src/views/camera/image_editor/action_button.dart'; -import 'package:twonly/src/views/camera/image_editor/data/layer.dart'; - -/// Emoji layer -class EmojiLayer extends StatefulWidget { - const EmojiLayer({ - required this.layerData, - super.key, - this.onUpdate, - }); - final EmojiLayerData layerData; - final VoidCallback? onUpdate; - - @override - State createState() => _EmojiLayerState(); -} - -class _EmojiLayerState extends State { - double initialRotation = 0; - Offset initialOffset = Offset.zero; - Offset initialFocalPoint = Offset.zero; - double initialScale = 1; - bool deleteLayer = false; - bool twoPointerWhereDown = false; - final GlobalKey outlineKey = GlobalKey(); - final GlobalKey emojiKey = GlobalKey(); - int pointers = 0; - bool display = false; - - @override - void initState() { - super.initState(); - - if (widget.layerData.offset.dy == 0) { - // Set the initial offset to the center of the screen - WidgetsBinding.instance.addPostFrameCallback((_) { - setState(() { - widget.layerData.offset = Offset( - MediaQuery.of(context).size.width / 2 - (153 / 2), - MediaQuery.of(context).size.height / 2 - (153 / 2) - 100, - ); - }); - display = true; - }); - } else { - display = true; - } - } - - @override - Widget build(BuildContext context) { - if (!display) return Container(); - if (widget.layerData.isDeleted) return Container(); - return Stack( - key: outlineKey, - children: [ - Positioned( - left: widget.layerData.offset.dx, - top: widget.layerData.offset.dy, - child: Listener( - onPointerUp: (details) { - setState(() { - pointers--; - if (pointers == 0) { - twoPointerWhereDown = false; - } - if (deleteLayer) { - widget.layerData.isDeleted = true; - widget.onUpdate!(); - } - }); - }, - onPointerDown: (details) { - setState(() { - pointers++; - }); - }, - child: GestureDetector( - onScaleStart: (details) { - initialScale = widget.layerData.size; - initialRotation = widget.layerData.rotation; - initialOffset = widget.layerData.offset; - initialFocalPoint = - Offset(details.focalPoint.dx, details.focalPoint.dy); - - setState(() {}); - }, - onScaleUpdate: (details) async { - if (twoPointerWhereDown && details.pointerCount != 2) { - return; - } - final outlineBox = - outlineKey.currentContext!.findRenderObject()! as RenderBox; - - final emojiBox = - emojiKey.currentContext!.findRenderObject()! as RenderBox; - - final isAtTheBottom = - (widget.layerData.offset.dy + emojiBox.size.height / 2) > - outlineBox.size.height - 80; - final isInTheCenter = MediaQuery.of(context).size.width / 2 - - 30 < - (widget.layerData.offset.dx + - emojiBox.size.width / 2) && - MediaQuery.of(context).size.width / 2 + 20 > - (widget.layerData.offset.dx + emojiBox.size.width / 2); - - if (isAtTheBottom && isInTheCenter) { - if (!deleteLayer) { - await HapticFeedback.heavyImpact(); - } - deleteLayer = true; - } else { - deleteLayer = false; - } - setState(() { - twoPointerWhereDown = details.pointerCount >= 2; - widget.layerData.size = initialScale * details.scale; - if (widget.layerData.size > 96) { - // https://github.com/twonlyapp/twonly-app/issues/349 - widget.layerData.size = 96; - } - // print(widget.layerData.size); - widget.layerData.rotation = - initialRotation + details.rotation; - - // Update the position based on the translation - final dx = (initialOffset.dx) + - (details.focalPoint.dx - initialFocalPoint.dx); - final dy = (initialOffset.dy) + - (details.focalPoint.dy - initialFocalPoint.dy); - widget.layerData.offset = Offset(dx, dy); - }); - }, - child: Transform.rotate( - angle: widget.layerData.rotation, - key: emojiKey, - child: Container( - padding: const EdgeInsets.all(44), - color: Colors.transparent, - child: Text( - widget.layerData.text, - style: TextStyle( - fontSize: widget.layerData.size, - ), - ), - ), - ), - ), - ), - ), - if (pointers > 0) - Positioned( - left: 0, - right: 0, - bottom: 20, - child: Center( - child: GestureDetector( - child: ActionButton( - FontAwesomeIcons.trashCan, - tooltipText: '', - color: deleteLayer ? Colors.red : Colors.white, - ), - ), - ), - ), - ], - ); - } -} diff --git a/lib/src/views/camera/share_image_view.dart b/lib/src/views/camera/share_image_contact_selection.view.dart similarity index 93% rename from lib/src/views/camera/share_image_view.dart rename to lib/src/views/camera/share_image_contact_selection.view.dart index 0c22e03..b93fadb 100644 --- a/lib/src/views/camera/share_image_view.dart +++ b/lib/src/views/camera/share_image_contact_selection.view.dart @@ -2,18 +2,20 @@ import 'dart:async'; import 'dart:collection'; -import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:twonly/globals.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; +import 'package:twonly/src/model/protobuf/client/generated/data.pb.dart'; import 'package:twonly/src/services/api/mediafiles/upload.service.dart'; import 'package:twonly/src/services/flame.service.dart'; import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/views/camera/share_image_components/best_friends_selector.dart'; +import 'package:twonly/src/utils/screenshot.dart'; +import 'package:twonly/src/views/camera/share_image_contact_selection/best_friends_selector.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/background.layer.dart'; import 'package:twonly/src/views/components/avatar_icon.component.dart'; import 'package:twonly/src/views/components/flame.dart'; import 'package:twonly/src/views/components/headline.dart'; @@ -24,12 +26,14 @@ class ShareImageView extends StatefulWidget { required this.updateSelectedGroupIds, required this.mediaStoreFuture, required this.mediaFileService, + required this.additionalData, super.key, }); final HashSet selectedGroupIds; final void Function(String, bool) updateSelectedGroupIds; - final Future? mediaStoreFuture; + final Future? mediaStoreFuture; final MediaFileService mediaFileService; + final AdditionalMessageData? additionalData; @override State createState() => _ShareImageView(); @@ -43,7 +47,7 @@ class _ShareImageView extends State { bool sendingImage = false; bool mediaStoreFutureReady = false; - Uint8List? _imageBytes; + ScreenshotImage? _screenshotImage; bool hideArchivedUsers = true; final TextEditingController searchUserName = TextEditingController(); late StreamSubscription> allGroupSub; @@ -66,7 +70,7 @@ class _ShareImageView extends State { Future initAsync() async { if (widget.mediaStoreFuture != null) { - _imageBytes = await widget.mediaStoreFuture; + _screenshotImage = await widget.mediaStoreFuture; } mediaStoreFutureReady = true; if (!mounted) return; @@ -244,10 +248,11 @@ class _ShareImageView extends State { mainAxisAlignment: MainAxisAlignment.end, children: [ if (widget.mediaFileService.mediaFile.type == MediaType.image && - _imageBytes != null && + _screenshotImage?.image != null && gUser.showShowImagePreviewWhenSending) SizedBox( height: 100, + width: 100 * 9 / 16, child: Container( clipBehavior: Clip.hardEdge, decoration: BoxDecoration( @@ -258,7 +263,9 @@ class _ShareImageView extends State { ), child: ClipRRect( borderRadius: BorderRadius.circular(12), - child: Image.memory(_imageBytes!), + child: CustomPaint( + painter: UiImagePainter(_screenshotImage!.image!), + ), ), ), ), @@ -283,19 +290,15 @@ class _ShareImageView extends State { sendingImage = true; }); + // in case mediaStoreFutureReady is ready, the image is stored in the originalPath await insertMediaFileInMessagesTable( widget.mediaFileService, widget.selectedGroupIds.toList(), + additionalData: widget.additionalData, ); if (context.mounted) { Navigator.pop(context, true); - // if (widget.preselectedUser != null) { - // Navigator.pop(context, true); - // } else { - // Navigator.popUntil(context, (route) => route.isFirst, true); - // globalUpdateOfHomeViewPageIndex(1); - // } } }, style: ButtonStyle( diff --git a/lib/src/views/camera/share_image_components/best_friends_selector.dart b/lib/src/views/camera/share_image_contact_selection/best_friends_selector.dart similarity index 100% rename from lib/src/views/camera/share_image_components/best_friends_selector.dart rename to lib/src/views/camera/share_image_contact_selection/best_friends_selector.dart diff --git a/lib/src/views/camera/share_image_components/select_show_time.dart b/lib/src/views/camera/share_image_contact_selection/select_show_time.dart similarity index 100% rename from lib/src/views/camera/share_image_components/select_show_time.dart rename to lib/src/views/camera/share_image_contact_selection/select_show_time.dart diff --git a/lib/src/views/camera/share_image_editor_view.dart b/lib/src/views/camera/share_image_editor.view.dart similarity index 88% rename from lib/src/views/camera/share_image_editor_view.dart rename to lib/src/views/camera/share_image_editor.view.dart index 28e3cb8..164fcf2 100644 --- a/lib/src/views/camera/share_image_editor_view.dart +++ b/lib/src/views/camera/share_image_editor.view.dart @@ -2,14 +2,15 @@ import 'dart:async'; import 'dart:collection'; + import 'package:drift/drift.dart' show Value; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:twonly/globals.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; +import 'package:twonly/src/model/protobuf/client/generated/data.pb.dart'; import 'package:twonly/src/services/api/mediafiles/upload.service.dart'; import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/utils/log.dart'; @@ -18,13 +19,13 @@ import 'package:twonly/src/utils/screenshot.dart'; import 'package:twonly/src/utils/storage.dart'; import 'package:twonly/src/views/camera/camera_preview_components/main_camera_controller.dart'; import 'package:twonly/src/views/camera/camera_preview_components/save_to_gallery.dart'; -import 'package:twonly/src/views/camera/image_editor/action_button.dart'; -import 'package:twonly/src/views/camera/image_editor/data/image_item.dart'; -import 'package:twonly/src/views/camera/image_editor/data/layer.dart'; -import 'package:twonly/src/views/camera/image_editor/layers_viewer.dart'; -import 'package:twonly/src/views/camera/image_editor/modules/all_emojis.dart'; -import 'package:twonly/src/views/camera/share_image_components/select_show_time.dart'; -import 'package:twonly/src/views/camera/share_image_view.dart'; +import 'package:twonly/src/views/camera/share_image_contact_selection.view.dart'; +import 'package:twonly/src/views/camera/share_image_contact_selection/select_show_time.dart'; +import 'package:twonly/src/views/camera/share_image_editor/action_button.dart'; +import 'package:twonly/src/views/camera/share_image_editor/image_item.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layer_data.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers_viewer.dart'; +import 'package:twonly/src/views/components/emoji_picker.bottom.dart'; import 'package:twonly/src/views/components/media_view_sizing.dart'; import 'package:twonly/src/views/components/notification_badge.dart'; import 'package:video_player/video_player.dart'; @@ -37,16 +38,18 @@ class ShareImageEditorView extends StatefulWidget { const ShareImageEditorView({ required this.sharedFromGallery, required this.mediaFileService, + this.screenshotImage, + this.previewLink, super.key, - this.imageBytesFuture, this.sendToGroup, this.mainCameraController, }); - final ScreenshotImage? imageBytesFuture; + final ScreenshotImage? screenshotImage; final Group? sendToGroup; final bool sharedFromGallery; final MediaFileService mediaFileService; final MainCameraController? mainCameraController; + final Uri? previewLink; @override State createState() => _ShareImageEditorView(); } @@ -60,7 +63,6 @@ class _ShareImageEditorView extends State { double widthRatio = 1; double heightRatio = 1; double pixelRatio = 1; - Uint8List? imageBytes; VideoPlayerController? videoController; ImageItem currentImage = ImageItem(); ScreenshotController screenshotController = ScreenshotController(); @@ -77,14 +79,20 @@ class _ShareImageEditorView extends State { layers.add(FilterLayerData(key: GlobalKey())); } + if (widget.previewLink != null) { + layers.add( + LinkPreviewLayerData(key: GlobalKey(), link: widget.previewLink!), + ); + } + if (widget.sendToGroup != null) { selectedGroupIds.add(widget.sendToGroup!.groupId); } if (widget.mediaFileService.mediaFile.type == MediaType.image || widget.mediaFileService.mediaFile.type == MediaType.gif) { - if (widget.imageBytesFuture != null) { - loadImage(widget.imageBytesFuture!); + if (widget.screenshotImage != null) { + loadImage(widget.screenshotImage!); } else { if (widget.mediaFileService.tempPath.existsSync()) { loadImage(ScreenshotImage(file: widget.mediaFileService.tempPath)); @@ -411,6 +419,7 @@ class _ShareImageEditorView extends State { updateSelectedGroupIds: updateSelectedGroupIds, mediaStoreFuture: mediaStoreFuture, mediaFileService: mediaService, + additionalData: getAdditionalData(), ), ), ) as bool?; @@ -424,8 +433,7 @@ class _ShareImageEditorView extends State { Future getEditedImageBytes() async { if (layers.length == 1) { if (layers.first is BackgroundLayerData) { - final image = (layers.first as BackgroundLayerData).image.bytes; - return ScreenshotImage(imageBytes: image); + return (layers.first as BackgroundLayerData).image.image; } } @@ -454,7 +462,7 @@ class _ShareImageEditorView extends State { return image; } - Future storeImageAsOriginal() async { + Future storeImageAsOriginal() async { if (mediaService.overlayImagePath.existsSync()) { mediaService.overlayImagePath.deleteSync(); } @@ -466,11 +474,16 @@ class _ShareImageEditorView extends State { mediaService.originalPath.deleteSync(); } } - var bytes = imageBytes; + ScreenshotImage? image; + var bytes = await widget.screenshotImage?.getBytes(); if (media.type == MediaType.gif) { - mediaService.originalPath.writeAsBytesSync(imageBytes!.toList()); + if (bytes != null) { + mediaService.originalPath.writeAsBytesSync(bytes.toList()); + } else { + Log.error('Could not load image bytes for gif!'); + } } else { - final image = await getEditedImageBytes(); + image = await getEditedImageBytes(); if (image == null) return null; bytes = await image.getBytes(); if (bytes == null) { @@ -485,16 +498,38 @@ class _ShareImageEditorView extends State { Log.error('MediaType not supported: ${media.type}'); } } - - return bytes; + return image; } - Future loadImage(ScreenshotImage imageBytesFuture) async { - imageBytes = await imageBytesFuture.getBytes(); - // store this image so it can be used as a draft in case the app is restarted + Future storeIoImageAsDraft(ScreenshotImage screenshotImage) async { + final imageBytes = await screenshotImage.getBytes(); mediaService.originalPath.writeAsBytesSync(imageBytes!.toList()); + } + + Future loadImage(ScreenshotImage screenshotImage) async { + if (screenshotImage.image == null && + screenshotImage.imageBytes == null && + screenshotImage.imageBytesFuture != null) { + // this ensures that the imageBytes are defined + await storeIoImageAsDraft(screenshotImage); + } else { + // store this image so it can be used as a draft in case the app is restarted + unawaited(storeIoImageAsDraft(screenshotImage)); + } + + if (screenshotImage.image == null) { + final imageBytes = await screenshotImage.getBytes(); + if (imageBytes != null) { + screenshotImage.image = await decodeImageFromList(imageBytes); + } + } + if (screenshotImage.image == null) { + Log.error('Could not load screenshotImage.image'); + return; + } + + currentImage.load(screenshotImage); - await currentImage.load(imageBytes); if (isDisposed) return; if (!context.mounted) return; @@ -536,6 +571,18 @@ class _ShareImageEditorView extends State { }); } + AdditionalMessageData? getAdditionalData() { + AdditionalMessageData? additionalData; + + if (widget.previewLink != null) { + additionalData = AdditionalMessageData( + type: AdditionalMessageData_Type.LINK, + link: widget.previewLink.toString(), + ); + } + return additionalData; + } + Future sendImageToSinglePerson() async { if (sendingOrLoadingImage) return; setState(() { @@ -551,6 +598,7 @@ class _ShareImageEditorView extends State { await insertMediaFileInMessagesTable( mediaService, [widget.sendToGroup!.groupId], + additionalData: getAdditionalData(), ); if (mounted) { diff --git a/lib/src/views/camera/image_editor/action_button.dart b/lib/src/views/camera/share_image_editor/action_button.dart similarity index 100% rename from lib/src/views/camera/image_editor/action_button.dart rename to lib/src/views/camera/share_image_editor/action_button.dart diff --git a/lib/src/views/camera/share_image_editor/image_item.dart b/lib/src/views/camera/share_image_editor/image_item.dart new file mode 100755 index 0000000..c2511d5 --- /dev/null +++ b/lib/src/views/camera/share_image_editor/image_item.dart @@ -0,0 +1,18 @@ +import 'dart:async'; +import 'package:twonly/src/utils/screenshot.dart'; + +class ImageItem { + ImageItem(); + int width = 1; + int height = 1; + ScreenshotImage? image; + Completer loader = Completer(); + + void load(ScreenshotImage img) { + image = img; + if (image?.image != null) { + height = image!.image!.height; + width = image!.image!.width; + } + } +} diff --git a/lib/src/views/camera/image_editor/data/layer.dart b/lib/src/views/camera/share_image_editor/layer_data.dart similarity index 82% rename from lib/src/views/camera/image_editor/data/layer.dart rename to lib/src/views/camera/share_image_editor/layer_data.dart index b8e8622..ffee88a 100755 --- a/lib/src/views/camera/image_editor/data/layer.dart +++ b/lib/src/views/camera/share_image_editor/layer_data.dart @@ -1,8 +1,7 @@ -// ignore_for_file: comment_references - import 'package:flutter/material.dart'; import 'package:hand_signature/signature.dart'; -import 'package:twonly/src/views/camera/image_editor/data/image_item.dart'; +import 'package:twonly/src/views/camera/share_image_editor/image_item.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/base.dart'; /// Layer class with some common properties class Layer { @@ -28,7 +27,6 @@ class Layer { bool showCustomButtons; } -/// Attributes used by [BackgroundLayer] class BackgroundLayerData extends Layer { BackgroundLayerData({ required super.key, @@ -38,6 +36,16 @@ class BackgroundLayerData extends Layer { bool imageLoaded = false; } +class LinkPreviewLayerData extends Layer { + LinkPreviewLayerData({ + required super.key, + required this.link, + }); + Uri link; + Metadata? metadata; + bool error = false; +} + class FilterLayerData extends Layer { FilterLayerData({ required super.key, @@ -46,12 +54,11 @@ class FilterLayerData extends Layer { int page = 1; } -/// Attributes used by [EmojiLayer] class EmojiLayerData extends Layer { EmojiLayerData({ required super.key, this.text = '', - this.size = 64, + this.size = 94, super.offset, super.opacity, super.rotation, @@ -62,7 +69,6 @@ class EmojiLayerData extends Layer { double size; } -/// Attributes used by [TextLayer] class TextLayerData extends Layer { TextLayerData({ required super.key, @@ -78,9 +84,7 @@ class TextLayerData extends Layer { int textLayersBefore; } -/// Attributes used by [DrawLayer] class DrawLayerData extends Layer { - // String text; DrawLayerData({ required super.key, super.offset, diff --git a/lib/src/views/camera/share_image_editor/layers/background.layer.dart b/lib/src/views/camera/share_image_editor/layers/background.layer.dart new file mode 100755 index 0000000..ecd3ab7 --- /dev/null +++ b/lib/src/views/camera/share_image_editor/layers/background.layer.dart @@ -0,0 +1,62 @@ +import 'dart:ui' as ui; + +import 'package:flutter/material.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layer_data.dart'; + +class BackgroundLayer extends StatefulWidget { + const BackgroundLayer({ + required this.layerData, + super.key, + this.onUpdate, + }); + final BackgroundLayerData layerData; + final VoidCallback? onUpdate; + + @override + State createState() => _BackgroundLayerState(); +} + +class _BackgroundLayerState extends State { + @override + void initState() { + WidgetsBinding.instance.addPostFrameCallback((_) { + widget.layerData.imageLoaded = true; + }); + super.initState(); + } + + @override + Widget build(BuildContext context) { + final scImage = widget.layerData.image.image; + if (scImage == null || scImage.image == null) return Container(); + return Container( + width: widget.layerData.image.width.toDouble(), + height: widget.layerData.image.height.toDouble(), + padding: EdgeInsets.zero, + color: Colors.green, + child: CustomPaint( + painter: UiImagePainter(scImage.image!), + ), + ); + } +} + +class UiImagePainter extends CustomPainter { + UiImagePainter(this.image); + final ui.Image image; + + @override + void paint(Canvas canvas, Size size) { + canvas.drawImageRect( + image, + Rect.fromLTWH(0, 0, image.width.toDouble(), image.height.toDouble()), + Rect.fromLTWH(0, 0, size.width, size.height), + Paint(), + ); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) { + return false; + } +} diff --git a/lib/src/views/camera/image_editor/layers/draw_layer.dart b/lib/src/views/camera/share_image_editor/layers/draw.layer.dart similarity index 79% rename from lib/src/views/camera/image_editor/layers/draw_layer.dart rename to lib/src/views/camera/share_image_editor/layers/draw.layer.dart index 4207c39..aabddfd 100644 --- a/lib/src/views/camera/image_editor/layers/draw_layer.dart +++ b/lib/src/views/camera/share_image_editor/layers/draw.layer.dart @@ -1,12 +1,11 @@ import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:hand_signature/signature.dart'; -// ignore: implementation_imports -import 'package:hand_signature/src/utils.dart'; + import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/utils/screenshot.dart'; -import 'package:twonly/src/views/camera/image_editor/action_button.dart'; -import 'package:twonly/src/views/camera/image_editor/data/layer.dart'; +import 'package:twonly/src/views/camera/share_image_editor/action_button.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layer_data.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/draw/custom_hand_signature.dart'; class DrawLayer extends StatefulWidget { const DrawLayer({ @@ -23,8 +22,6 @@ class DrawLayer extends StatefulWidget { class _DrawLayerState extends State { Color currentColor = Colors.red; - ScreenshotController screenshotController = ScreenshotController(); - List undoList = []; bool skipNextEvent = false; bool showMagnifyingGlass = false; @@ -85,17 +82,11 @@ class _DrawLayerState extends State { fit: StackFit.expand, children: [ Positioned.fill( - child: Container( - decoration: const BoxDecoration( - color: Colors.transparent, - ), - child: Screenshot( - controller: screenshotController, - child: HandSignature( - control: widget.layerData.control, - drawer: CustomSignatureDrawer(color: currentColor, width: 7), - ), - ), + child: CustomHandSignature( + control: widget.layerData.control, + isModificationEnabled: widget.layerData.isEditing, + currentColor: currentColor, + width: 7, ), ), if (widget.layerData.isEditing && widget.layerData.showCustomButtons) @@ -211,12 +202,12 @@ class _DrawLayerState extends State { top: 50 + (185 * _sliderValue), child: MagnifyingGlass(color: currentColor), ), - if (!widget.layerData.isEditing) - Positioned.fill( - child: Container( - color: Colors.transparent, - ), - ), + // if (!widget.layerData.isEditing) + // Positioned.fill( + // child: Container( + // color: Colors.transparent, + // ), + // ), ], ); } @@ -244,33 +235,3 @@ class MagnifyingGlass extends StatelessWidget { ); } } - -class CustomSignatureDrawer extends HandSignatureDrawer { - const CustomSignatureDrawer({ - this.width = 1.0, - this.color = Colors.black, - }); - final Color color; - final double width; - - @override - void paint(Canvas canvas, Size size, List paths) { - for (final path in paths) { - var lineColor = color; - if (path.setup.args!['color'] != null) { - lineColor = path.setup.args!['color'] as Color; - } else { - path.setup.args!['color'] = color; - } - final paint = Paint() - ..color = lineColor - ..style = PaintingStyle.stroke - ..strokeCap = StrokeCap.round - ..strokeJoin = StrokeJoin.round - ..strokeWidth = width; - if (path.isFilled) { - canvas.drawPath(PathUtil.toLinePath(path.lines), paint); - } - } - } -} diff --git a/lib/src/views/camera/share_image_editor/layers/draw/custom_hand_signature.dart b/lib/src/views/camera/share_image_editor/layers/draw/custom_hand_signature.dart new file mode 100644 index 0000000..e5f0e74 --- /dev/null +++ b/lib/src/views/camera/share_image_editor/layers/draw/custom_hand_signature.dart @@ -0,0 +1,77 @@ +import 'package:flutter/material.dart'; +import 'package:hand_signature/signature.dart'; +// ignore: implementation_imports +import 'package:hand_signature/src/utils.dart'; + +class CustomHandSignature extends StatelessWidget { + const CustomHandSignature({ + required this.control, + required this.isModificationEnabled, + required this.currentColor, + required this.width, + super.key, + }); + + /// The controller that manages the creation and manipulation of signature paths. + final HandSignatureControl control; + final bool isModificationEnabled; + final Color currentColor; + final double width; + + @override + Widget build(BuildContext context) { + control.params = SignaturePaintParams( + color: currentColor, + strokeWidth: 7, + ); + + final drawer = CustomSignatureDrawer(color: currentColor, width: width); + + if (isModificationEnabled) { + return HandSignature( + control: control, + drawer: drawer, + ); + } + + return IgnorePointer( + child: ClipRRect( + child: HandSignaturePaint( + control: control, + drawer: drawer, + onSize: control.notifyDimension, + ), + ), + ); + } +} + +class CustomSignatureDrawer extends HandSignatureDrawer { + const CustomSignatureDrawer({ + this.width = 1.0, + this.color = Colors.black, + }); + final Color color; + final double width; + + @override + void paint(Canvas canvas, Size size, List paths) { + for (final path in paths) { + var lineColor = color; + if (path.setup.args!['color'] != null) { + lineColor = path.setup.args!['color'] as Color; + } else { + path.setup.args!['color'] = color; + } + final paint = Paint() + ..color = lineColor + ..style = PaintingStyle.stroke + ..strokeCap = StrokeCap.round + ..strokeJoin = StrokeJoin.round + ..strokeWidth = width; + if (path.isFilled) { + canvas.drawPath(PathUtil.toLinePath(path.lines), paint); + } + } + } +} diff --git a/lib/src/views/camera/share_image_editor/layers/emoji.layer.dart b/lib/src/views/camera/share_image_editor/layers/emoji.layer.dart new file mode 100755 index 0000000..ac6020f --- /dev/null +++ b/lib/src/views/camera/share_image_editor/layers/emoji.layer.dart @@ -0,0 +1,248 @@ +import 'dart:ui' as ui; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:twonly/src/utils/log.dart'; +import 'package:twonly/src/views/camera/share_image_editor/action_button.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layer_data.dart'; + +/// Emoji layer +class EmojiLayer extends StatefulWidget { + const EmojiLayer({ + required this.layerData, + super.key, + this.onUpdate, + }); + final EmojiLayerData layerData; + final VoidCallback? onUpdate; + + @override + State createState() => _EmojiLayerState(); +} + +class _EmojiLayerState extends State { + double initialRotation = 0; + Offset initialOffset = Offset.zero; + Offset initialFocalPoint = Offset.zero; + double initialScale = 1; + bool deleteLayer = false; + bool twoPointerWhereDown = false; + final GlobalKey outlineKey = GlobalKey(); + final GlobalKey emojiKey = GlobalKey(); + int pointers = 0; + bool display = false; + + @override + void initState() { + super.initState(); + + if (widget.layerData.offset.dy == 0) { + // Set the initial offset to the center of the screen + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + widget.layerData.offset = Offset( + MediaQuery.of(context).size.width / 2 - (153 / 2), + MediaQuery.of(context).size.height / 2 - (153 / 2) - 100, + ); + }); + display = true; + }); + } else { + display = true; + } + } + + @override + Widget build(BuildContext context) { + if (!display) return Container(); + if (widget.layerData.isDeleted) return Container(); + return Stack( + key: outlineKey, + children: [ + Positioned( + left: widget.layerData.offset.dx, + top: widget.layerData.offset.dy, + child: PhysicalModel( + color: Colors.transparent, + borderRadius: BorderRadius.circular(180), + clipBehavior: Clip.antiAlias, + child: Listener( + onPointerUp: (details) { + setState(() { + pointers--; + if (pointers == 0) { + twoPointerWhereDown = false; + } + if (deleteLayer) { + widget.layerData.isDeleted = true; + widget.onUpdate!(); + } + }); + }, + onPointerDown: (details) { + setState(() { + pointers++; + }); + }, + child: GestureDetector( + onScaleStart: (details) { + initialScale = widget.layerData.size; + initialRotation = widget.layerData.rotation; + initialOffset = widget.layerData.offset; + initialFocalPoint = + Offset(details.focalPoint.dx, details.focalPoint.dy); + + setState(() {}); + }, + onScaleUpdate: (details) async { + if (twoPointerWhereDown && details.pointerCount != 2) { + return; + } + final outlineBox = outlineKey.currentContext! + .findRenderObject()! as RenderBox; + + final emojiBox = + emojiKey.currentContext!.findRenderObject()! as RenderBox; + + final isAtTheBottom = + (widget.layerData.offset.dy + emojiBox.size.height / 2) > + outlineBox.size.height - 80; + final isInTheCenter = + MediaQuery.of(context).size.width / 2 - 30 < + (widget.layerData.offset.dx + + emojiBox.size.width / 2) && + MediaQuery.of(context).size.width / 2 + 20 > + (widget.layerData.offset.dx + + emojiBox.size.width / 2); + + if (isAtTheBottom && isInTheCenter) { + if (!deleteLayer) { + await HapticFeedback.heavyImpact(); + } + deleteLayer = true; + } else { + deleteLayer = false; + } + setState(() { + twoPointerWhereDown = details.pointerCount >= 2; + widget.layerData.size = initialScale * details.scale; + // print(widget.layerData.size); + widget.layerData.rotation = + initialRotation + details.rotation; + + // Update the position based on the translation + final dx = (initialOffset.dx) + + (details.focalPoint.dx - initialFocalPoint.dx); + final dy = (initialOffset.dy) + + (details.focalPoint.dy - initialFocalPoint.dy); + widget.layerData.offset = Offset(dx, dy); + }); + }, + child: Transform.rotate( + angle: widget.layerData.rotation, + key: emojiKey, + child: Container( + padding: const EdgeInsets.all(44), + color: Colors.transparent, + child: ScreenshotEmoji( + emoji: widget.layerData.text, + displaySize: widget.layerData.size, + ), + ), + ), + ), + ), + ), + ), + if (pointers > 0) + Positioned( + left: 0, + right: 0, + bottom: 20, + child: Center( + child: GestureDetector( + child: ActionButton( + FontAwesomeIcons.trashCan, + tooltipText: '', + color: deleteLayer ? Colors.red : Colors.white, + ), + ), + ), + ), + ], + ); + } +} + +// Workaround: https://github.com/twonlyapp/twonly-app/issues/349 +class ScreenshotEmoji extends StatefulWidget { + const ScreenshotEmoji({ + required this.emoji, + required this.displaySize, + super.key, + }); + final String emoji; + final double displaySize; + + @override + State createState() => _ScreenshotEmojiState(); +} + +class _ScreenshotEmojiState extends State { + final GlobalKey _boundaryKey = GlobalKey(); + ui.Image? _capturedImage; + + @override + void initState() { + super.initState(); + // Capture the emoji immediately after the first frame + WidgetsBinding.instance.addPostFrameCallback((_) => _captureEmoji()); + } + + Future _captureEmoji() async { + try { + final boundary = _boundaryKey.currentContext?.findRenderObject() + as RenderRepaintBoundary?; + if (boundary == null) return; + + final image = await boundary.toImage(pixelRatio: 4); + setState(() { + _capturedImage = image; + }); + } catch (e) { + Log.error('Error capturing emoji: $e'); + } + } + + @override + Widget build(BuildContext context) { + if (_capturedImage != null) { + return SizedBox( + width: widget.displaySize, + height: widget.displaySize, + child: RawImage( + image: _capturedImage, + fit: BoxFit.contain, + ), + ); + } + + return Stack( + children: [ + Positioned( + top: -200, // hide from the user as the size changes with the image + child: RepaintBoundary( + key: _boundaryKey, + child: Text( + widget.emoji, + style: const TextStyle(fontSize: 94), + ), + ), + ), + SizedBox(width: widget.displaySize, height: widget.displaySize), + ], + ); + } +} diff --git a/lib/src/views/camera/image_editor/layers/filter_layer.dart b/lib/src/views/camera/share_image_editor/layers/filter.layer.dart similarity index 89% rename from lib/src/views/camera/image_editor/layers/filter_layer.dart rename to lib/src/views/camera/share_image_editor/layers/filter.layer.dart index 813ea66..8ee46fe 100644 --- a/lib/src/views/camera/image_editor/layers/filter_layer.dart +++ b/lib/src/views/camera/share_image_editor/layers/filter.layer.dart @@ -2,10 +2,10 @@ import 'dart:async'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; -import 'package:twonly/src/views/camera/image_editor/data/layer.dart'; -import 'package:twonly/src/views/camera/image_editor/layers/filters/datetime_filter.dart'; -import 'package:twonly/src/views/camera/image_editor/layers/filters/image_filter.dart'; -import 'package:twonly/src/views/camera/image_editor/layers/filters/location_filter.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layer_data.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/filters/datetime_filter.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/filters/image_filter.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/filters/location_filter.dart'; /// Main layer class FilterLayer extends StatefulWidget { diff --git a/lib/src/views/camera/image_editor/layers/filters/datetime_filter.dart b/lib/src/views/camera/share_image_editor/layers/filters/datetime_filter.dart similarity index 89% rename from lib/src/views/camera/image_editor/layers/filters/datetime_filter.dart rename to lib/src/views/camera/share_image_editor/layers/filters/datetime_filter.dart index d85e8b7..78bae80 100644 --- a/lib/src/views/camera/image_editor/layers/filters/datetime_filter.dart +++ b/lib/src/views/camera/share_image_editor/layers/filters/datetime_filter.dart @@ -1,7 +1,7 @@ import 'package:clock/clock.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; -import 'package:twonly/src/views/camera/image_editor/layers/filter_layer.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/filter.layer.dart'; class DateTimeFilter extends StatelessWidget { const DateTimeFilter({super.key, this.color = Colors.white}); diff --git a/lib/src/views/camera/image_editor/layers/filters/image_filter.dart b/lib/src/views/camera/share_image_editor/layers/filters/image_filter.dart similarity index 87% rename from lib/src/views/camera/image_editor/layers/filters/image_filter.dart rename to lib/src/views/camera/share_image_editor/layers/filters/image_filter.dart index 25e1b90..920a9e4 100644 --- a/lib/src/views/camera/image_editor/layers/filters/image_filter.dart +++ b/lib/src/views/camera/share_image_editor/layers/filters/image_filter.dart @@ -1,6 +1,6 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; -import 'package:twonly/src/views/camera/image_editor/layers/filter_layer.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/filter.layer.dart'; class ImageFilter extends StatelessWidget { const ImageFilter({required this.imagePath, super.key}); diff --git a/lib/src/views/camera/image_editor/layers/filters/location_filter.dart b/lib/src/views/camera/share_image_editor/layers/filters/location_filter.dart similarity index 96% rename from lib/src/views/camera/image_editor/layers/filters/location_filter.dart rename to lib/src/views/camera/share_image_editor/layers/filters/location_filter.dart index fa38367..d2d36fa 100644 --- a/lib/src/views/camera/image_editor/layers/filters/location_filter.dart +++ b/lib/src/views/camera/share_image_editor/layers/filters/location_filter.dart @@ -11,8 +11,8 @@ import 'package:path_provider/path_provider.dart'; import 'package:twonly/globals.dart'; import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart'; import 'package:twonly/src/utils/log.dart'; -import 'package:twonly/src/views/camera/image_editor/layers/filter_layer.dart'; -import 'package:twonly/src/views/camera/image_editor/layers/filters/datetime_filter.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/filter.layer.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/filters/datetime_filter.dart'; class LocationFilter extends StatefulWidget { const LocationFilter({super.key}); diff --git a/lib/src/views/camera/share_image_editor/layers/link_preview.layer.dart b/lib/src/views/camera/share_image_editor/layers/link_preview.layer.dart new file mode 100644 index 0000000..c34065f --- /dev/null +++ b/lib/src/views/camera/share_image_editor/layers/link_preview.layer.dart @@ -0,0 +1,69 @@ +import 'package:flutter/material.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layer_data.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/cards/custom.card.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/cards/mastodon.card.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/cards/twitter.card.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/cards/youtube.card.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parse_link.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/base.dart'; +import 'package:twonly/src/views/components/loader.dart'; + +class LinkPreviewLayer extends StatefulWidget { + const LinkPreviewLayer({ + required this.layerData, + super.key, + this.onUpdate, + }); + final LinkPreviewLayerData layerData; + final VoidCallback? onUpdate; + + @override + State createState() => _LinkPreviewLayerState(); +} + +class _LinkPreviewLayerState extends State { + Metadata? metadata; + + @override + void initState() { + initAsync(); + super.initState(); + } + + Future initAsync() async { + if (widget.layerData.metadata == null) { + widget.layerData.metadata = + await getMetadata(widget.layerData.link.toString()); + if (widget.layerData.metadata == null) { + widget.layerData.error = true; + } + if (mounted) setState(() {}); + } + } + + @override + Widget build(BuildContext context) { + if (widget.layerData.error) { + return Container(); + } + final meta = widget.layerData.metadata; + late Widget child; + if (meta == null) { + child = const ThreeRotatingDots(size: 30); + } else if (meta.title == null) { + return Container(); + } else if (meta.vendor == Vendor.mastodonSocialMediaPosting) { + child = MastodonPostCard(info: meta); + } else if (meta.vendor == Vendor.twitterPosting) { + child = TwitterPostCard(info: meta); + } else if (meta.vendor == Vendor.youtubeVideo) { + child = YouTubePostCard(info: meta); + } else { + child = CustomLinkCard(info: meta); + } + + return Center( + child: child, + ); + } +} diff --git a/lib/src/views/camera/share_image_editor/layers/link_preview/cards/custom.card.dart b/lib/src/views/camera/share_image_editor/layers/link_preview/cards/custom.card.dart new file mode 100644 index 0000000..f7d7e23 --- /dev/null +++ b/lib/src/views/camera/share_image_editor/layers/link_preview/cards/custom.card.dart @@ -0,0 +1,87 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:twonly/src/database/daos/contacts.dao.dart'; +import 'package:twonly/src/utils/misc.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/base.dart'; + +class CustomLinkCard extends StatelessWidget { + const CustomLinkCard({required this.info, super.key}); + final Metadata info; + + @override + Widget build(BuildContext context) { + return FractionallySizedBox( + widthFactor: 0.8, + child: Container( + decoration: BoxDecoration( + color: const Color(0xFF1E1E1E), + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.white10), + ), + clipBehavior: Clip.antiAlias, + child: IntrinsicHeight( + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + Uri.parse(info.url).host.toUpperCase(), + style: TextStyle( + color: context.color.primary, + fontSize: 10, + fontWeight: FontWeight.bold, + letterSpacing: 1.2, + ), + ), + const SizedBox(height: 4), + Text( + substringBy(info.title ?? 'Link Preview', 35), + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + if (info.desc != null && info.desc != 'null') ...[ + const SizedBox(height: 6), + Text( + substringBy(info.desc!, 500), + maxLines: 3, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + color: Color(0xFFB0B0B0), + fontSize: 13, + height: 1.4, + ), + ), + ], + if (info.image != null && info.image != 'null') + Padding( + padding: const EdgeInsets.only(top: 12), + child: ClipRRect( + borderRadius: BorderRadius.circular(4), + child: CachedNetworkImage( + imageUrl: info.image!, + fit: BoxFit.cover, + width: double.infinity, + ), + ), + ), + ], + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/src/views/camera/share_image_editor/layers/link_preview/cards/mastodon.card.dart b/lib/src/views/camera/share_image_editor/layers/link_preview/cards/mastodon.card.dart new file mode 100644 index 0000000..f69d78e --- /dev/null +++ b/lib/src/views/camera/share_image_editor/layers/link_preview/cards/mastodon.card.dart @@ -0,0 +1,110 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:twonly/src/database/daos/contacts.dao.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/base.dart'; +import 'package:twonly/src/views/components/loader.dart'; + +class MastodonPostCard extends StatelessWidget { + const MastodonPostCard({required this.info, super.key}); + final Metadata info; + + @override + Widget build(BuildContext context) { + const backgroundColor = Color(0xFF282C37); + const secondaryTextColor = Color(0xFF9BA3AF); + const accentColor = Color(0xFF6364FF); + + return FractionallySizedBox( + widthFactor: 0.8, + child: Container( + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(12), + ), + padding: const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + const FaIcon( + FontAwesomeIcons.mastodon, + color: accentColor, + size: 20, + ), + const SizedBox(width: 10), + Text( + substringBy(info.title ?? 'Mastodon User', 37), + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 15, + ), + ), + ], + ), + const SizedBox(height: 4), + if (info.desc != null && info.desc != 'null') + Text( + substringBy(info.desc!, 1000), + style: const TextStyle(color: Colors.white, fontSize: 14), + ), + if (info.image != null && info.image != 'null') + Padding( + padding: const EdgeInsets.only(top: 8), + child: ClipRRect( + borderRadius: BorderRadius.circular(12), + child: ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 250), + child: CachedNetworkImage( + imageUrl: info.image!, + fit: BoxFit.contain, + width: double.infinity, + placeholder: (context, url) => Container( + height: 150, + color: Colors.black12, + child: const Center( + child: ThreeRotatingDots(size: 20), + ), + ), + ), + ), + ), + ), + const SizedBox(height: 12), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _buildAction( + Icons.repeat, + '${info.shareAction ?? 0}', + secondaryTextColor, + ), + const SizedBox(width: 20), + _buildAction( + Icons.star_border, + '${info.likeAction ?? 0}', + secondaryTextColor, + ), + ], + ), + ], + ), + ), + ); + } + + Widget _buildAction(IconData icon, String count, Color color) { + return Row( + children: [ + Icon(icon, size: 18, color: color), + if (count.isNotEmpty && count != '0') ...[ + const SizedBox(width: 5), + Text(count, style: TextStyle(color: color, fontSize: 13)), + ], + ], + ); + } +} diff --git a/lib/src/views/camera/share_image_editor/layers/link_preview/cards/twitter.card.dart b/lib/src/views/camera/share_image_editor/layers/link_preview/cards/twitter.card.dart new file mode 100644 index 0000000..7216b3f --- /dev/null +++ b/lib/src/views/camera/share_image_editor/layers/link_preview/cards/twitter.card.dart @@ -0,0 +1,101 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:twonly/src/database/daos/contacts.dao.dart'; +// Assuming the same Metadata import structure +import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/base.dart'; + +class TwitterPostCard extends StatelessWidget { + const TwitterPostCard({required this.info, super.key}); + final Metadata info; + + @override + Widget build(BuildContext context) { + // Classic Twitter Brand Colors + const twitterBlue = Color(0xFF1DA1F2); + const backgroundWhite = Colors.white; + const primaryText = Color(0xFF14171A); + const borderColor = Color(0xFFE1E8ED); + + return FractionallySizedBox( + widthFactor: 0.9, // Twitter cards often feel a bit wider + child: Container( + decoration: BoxDecoration( + color: backgroundWhite, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: borderColor), + ), + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + const FaIcon( + FontAwesomeIcons.twitter, + color: twitterBlue, + size: 22, + ), + const SizedBox(width: 12), + Expanded( + child: Text( + substringBy(info.title ?? 'Twitter User', 37), + style: const TextStyle( + color: primaryText, + fontWeight: FontWeight.w800, + fontSize: 16, + letterSpacing: -0.5, + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + const SizedBox(height: 8), + if (info.desc != null && info.desc != 'null') + Text( + substringBy(info.desc!, 1000), + style: const TextStyle( + color: primaryText, + fontSize: 15, + height: 1.3, + ), + ), + if (info.image != null && info.image != 'null') + Padding( + padding: const EdgeInsets.only(top: 12), + child: ClipRRect( + borderRadius: BorderRadius.circular(14), + child: Container( + decoration: BoxDecoration( + border: Border.all(color: borderColor), + borderRadius: BorderRadius.circular(14), + ), + child: ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 300), + child: CachedNetworkImage( + imageUrl: info.image!, + fit: BoxFit.cover, + width: double.infinity, + placeholder: (context, url) => Container( + height: 150, + color: const Color(0xFFF5F8FA), + child: const Center( + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation(twitterBlue), + ), + ), + ), + ), + ), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/src/views/camera/share_image_editor/layers/link_preview/cards/youtube.card.dart b/lib/src/views/camera/share_image_editor/layers/link_preview/cards/youtube.card.dart new file mode 100644 index 0000000..c9bbb85 --- /dev/null +++ b/lib/src/views/camera/share_image_editor/layers/link_preview/cards/youtube.card.dart @@ -0,0 +1,90 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:twonly/src/database/daos/contacts.dao.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/base.dart'; + +class YouTubePostCard extends StatelessWidget { + const YouTubePostCard({required this.info, super.key}); + final Metadata info; + + @override + Widget build(BuildContext context) { + const ytBlack = Color(0xFF0F0F0F); + const ytWhite = Colors.white; + const ytRed = Color.fromARGB(255, 255, 1, 51); + + return FractionallySizedBox( + widthFactor: 0.8, + child: Container( + decoration: BoxDecoration( + color: ytBlack, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Stack( + alignment: Alignment.bottomRight, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(12), + child: AspectRatio( + aspectRatio: 16 / 9, + child: CachedNetworkImage( + imageUrl: info.image ?? '', + fit: BoxFit.cover, + placeholder: (context, url) => + Container(color: Colors.white10), + errorWidget: (context, url, error) => const ColoredBox( + color: Colors.white10, + child: Icon( + Icons.play_circle_outline, + color: ytWhite, + size: 50, + ), + ), + ), + ), + ), + ], + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 12), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const FaIcon( + FontAwesomeIcons.youtube, + color: ytRed, + size: 20, + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + substringBy(info.title ?? 'Video Title', 600), + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + color: ytWhite, + fontSize: 16, + fontWeight: FontWeight.w500, + height: 1.2, + ), + ), + ], + ), + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/src/views/camera/share_image_editor/layers/link_preview/parse_link.dart b/lib/src/views/camera/share_image_editor/layers/link_preview/parse_link.dart new file mode 100644 index 0000000..0923f5c --- /dev/null +++ b/lib/src/views/camera/share_image_editor/layers/link_preview/parse_link.dart @@ -0,0 +1,147 @@ +// Based on: https://github.com/sur950/any_link_preview +// Copyright (c) 2020-2024 Konakanchi Venkata Suresh Babu + +import 'dart:convert'; + +import 'package:html/dom.dart' show Document; +import 'package:html/parser.dart'; +import 'package:http/http.dart' as http; +import 'package:twonly/src/utils/log.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/base.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/html.parser.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/json_ld.parser.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/mastodon.parser.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/og.parser.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/other.parser.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/twitter.parser.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/util.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/youtube.parser.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/utils.dart'; + +Future getMetadata(String link) async { + const userAgent = 'WhatsApp/2.21.12.21 A'; + try { + final linkToFetch = link.trim(); + final info = await getInfo(linkToFetch, userAgent); + + final img = info?.image ?? ''; + if (img.isNotEmpty) { + info?.image = resolveImageUrl(link, img); + } + + return info; + } catch (error) { + return null; + } +} + +String resolveImageUrl(String baseUrl, String imageUrl) { + try { + final baseUri = Uri.parse(baseUrl); + return baseUri.resolve(imageUrl).toString(); + } catch (e) { + return imageUrl; + } +} + +Future getInfo( + String url, + String userAgent, +) async { + Metadata? info; + + info = Metadata() + ..title = getDomain(url) + ..desc = url + ..siteName = getDomain(url) + ..url = url; + + try { + final videoId = getYouTubeVideoId(url); + final response = videoId == null + ? await fetchWithRedirects( + Uri.parse(url), + userAgent: userAgent, + ) + : await getYoutubeData( + videoId, + userAgent, + ); + + final headerContentType = response.headers['content-type']; + + if (headerContentType != null && headerContentType.startsWith('image/')) { + info + ..title = '' + ..desc = '' + ..siteName = '' + ..image = url; + return info; + } + + final document = responseToDocument(response); + if (document == null) return info; + + final data_ = _parse(document, url); + + return data_; + } catch (error) { + Log.warn('Error in $url response ($error)'); + return info; + } +} + +Document? responseToDocument(http.Response response) { + if (response.statusCode != 200) return null; + + Document? document; + try { + document = parse(utf8.decode(response.bodyBytes)); + } catch (err) { + return document; + } + + return document; +} + +Metadata _parse(Document? document, String url) { + final output = Metadata()..url = url; + + final allParsers = [ + // start with vendor specific to parse the vendor type + MastodonParser(document), + YoutubeParser(document, url), + TwitterParser(document, url), + + JsonLdParser(document), + OpenGraphParser(document), + HtmlMetaParser(document), + OtherParser(document), + ]; + + for (final parser in allParsers) { + try { + output.vendor ??= parser.vendor; + output.title ??= parser.title; + output.desc ??= parser.desc; + if (output.vendor == Vendor.twitterPosting) { + if (output.image == null) { + if (parser.image?.contains('/media/') ?? false) { + output.image ??= parser.image; + } + } + } else { + output.image ??= parser.image; + } + output.siteName ??= parser.siteName; + output.publishDate ??= parser.publishDate; + output.likeAction ??= parser.likeAction; + output.shareAction ??= parser.shareAction; + if (output.hasAllMetadata) break; + } catch (e) { + Log.error(e); + } + } + + return output; +} diff --git a/lib/src/views/camera/share_image_editor/layers/link_preview/parser/base.dart b/lib/src/views/camera/share_image_editor/layers/link_preview/parser/base.dart new file mode 100644 index 0000000..da3ba60 --- /dev/null +++ b/lib/src/views/camera/share_image_editor/layers/link_preview/parser/base.dart @@ -0,0 +1,29 @@ +enum Vendor { mastodonSocialMediaPosting, youtubeVideo, twitterPosting } + +mixin BaseMetaInfo { + late String url; + String? title; + String? desc; + String? image; + String? siteName; + + Vendor? vendor; + + DateTime? publishDate; + int? likeAction; // https://schema.org/LikeAction + int? shareAction; // https://schema.org/ShareAction + + /// Returns `true` if any parameter other than [url] is filled. + bool get hasData => + ((title?.isNotEmpty ?? false) && title != 'null') || + ((desc?.isNotEmpty ?? false) && desc != 'null') || + ((image?.isNotEmpty ?? false) && image != 'null'); +} + +/// Container class for Metadata. +class Metadata with BaseMetaInfo { + Metadata(); + bool get hasAllMetadata { + return title != null && desc != null && image != null; + } +} diff --git a/lib/src/views/camera/share_image_editor/layers/link_preview/parser/html.parser.dart b/lib/src/views/camera/share_image_editor/layers/link_preview/parser/html.parser.dart new file mode 100644 index 0000000..b386ab6 --- /dev/null +++ b/lib/src/views/camera/share_image_editor/layers/link_preview/parser/html.parser.dart @@ -0,0 +1,37 @@ +import 'package:html/dom.dart'; + +import 'base.dart'; +import 'util.dart'; + +/// Parses [Metadata] from ``, ``, and `<img>` tags. +class HtmlMetaParser with BaseMetaInfo { + HtmlMetaParser(this._document); + + /// The [Document] to parse. + final Document? _document; + + /// Get the [Metadata.title] from the <title> tag. + @override + String? get title => _document?.head?.querySelector('title')?.text; + + /// Get the [Metadata.desc] from the content of the + /// <meta name="description"> tag. + @override + String? get desc => _document?.head + ?.querySelector("meta[name='description']") + ?.attributes + .get('content'); + + /// Get the [Metadata.image] from the first <img> tag in the body. + @override + String? get image => + _document?.body?.querySelector('img')?.attributes.get('src'); + + /// Get the [Metadata.siteName] from the content of the + /// <meta name="site_name"> meta tag. + @override + String? get siteName => _document?.head + ?.querySelector("meta[name='site_name']") + ?.attributes + .get('content'); +} diff --git a/lib/src/views/camera/share_image_editor/layers/link_preview/parser/json_ld.parser.dart b/lib/src/views/camera/share_image_editor/layers/link_preview/parser/json_ld.parser.dart new file mode 100644 index 0000000..beb8ef3 --- /dev/null +++ b/lib/src/views/camera/share_image_editor/layers/link_preview/parser/json_ld.parser.dart @@ -0,0 +1,101 @@ +import 'dart:convert'; + +import 'package:html/dom.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/base.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/og.parser.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/util.dart'; + +/// Parses [Metadata] from `json-ld` data in `<script>` tags. +class JsonLdParser with BaseMetaInfo { + JsonLdParser(this.document) { + _parseToJson(document); + } + + Document? document; + Map<String, dynamic>? _jsonData; + + void _parseToJson(Document? document) { + try { + final data = document?.head + ?.querySelector("script[type='application/ld+json']") + ?.innerHtml; + if (data == null) return; + // For multiline json file + // Replacing all new line characters with empty space + // before performing json decode on data + _jsonData = + jsonDecode(data.replaceAll('\n', ' ')) as Map<String, dynamic>; + // ignore: empty_catches + } catch (e) {} + } + + /// Get the [Metadata.title] from the <title> tag. + @override + String? get title { + final data = _jsonData; + if (data is Map<String, dynamic>) { + return data['name'] as String? ?? data['headline'] as String?; + } + return null; + } + + @override + int? get shareAction { + final statistics = _jsonData?['interactionStatistic'] as List<dynamic>?; + if (statistics != null) { + for (final statsDy in statistics) { + final stats = statsDy as Map<String, dynamic>?; + if (stats != null) { + if (stats['interactionType'] == 'https://schema.org/ShareAction') { + return stats['userInteractionCount'] as int?; + } + } + } + } + return null; + } + + @override + int? get likeAction { + final statistics = _jsonData?['interactionStatistic'] as List<dynamic>?; + if (statistics != null) { + for (final statsDy in statistics) { + final stats = statsDy as Map<String, dynamic>?; + if (stats != null) { + if (stats['interactionType'] == 'https://schema.org/LikeAction') { + return stats['userInteractionCount'] as int?; + } + } + } + } + return null; + } + + @override + String? get desc { + return _jsonData?['description'] as String?; + } + + /// Get the [Metadata.image] from the first <img> tag in the body. + @override + String? get image { + final data = _jsonData; + return _imgResultToStr( + data?.getDynamic('logo') ?? data?.getDynamic('image'), + ); + } + + /// JSON LD does not have a siteName property, so we get it from + /// [og:site_name] if available. + @override + String? get siteName => OpenGraphParser(document).siteName; + + String? _imgResultToStr(dynamic result) { + if (result is List && result.isNotEmpty) { + final tmp = result.first; + if (tmp is String) return tmp; + } + if (result is String) return result; + return null; + } +} diff --git a/lib/src/views/camera/share_image_editor/layers/link_preview/parser/mastodon.parser.dart b/lib/src/views/camera/share_image_editor/layers/link_preview/parser/mastodon.parser.dart new file mode 100644 index 0000000..a2f466b --- /dev/null +++ b/lib/src/views/camera/share_image_editor/layers/link_preview/parser/mastodon.parser.dart @@ -0,0 +1,15 @@ +import 'package:html/dom.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/base.dart'; + +class MastodonParser with BaseMetaInfo { + MastodonParser(this._document); + final Document? _document; + + @override + Vendor? get vendor => ((_document?.head?.innerHtml + .contains('"repository":"mastodon/mastodon"') ?? + false) && + (_document?.head?.innerHtml.contains('SocialMediaPosting') ?? false)) + ? Vendor.mastodonSocialMediaPosting + : null; +} diff --git a/lib/src/views/camera/share_image_editor/layers/link_preview/parser/og.parser.dart b/lib/src/views/camera/share_image_editor/layers/link_preview/parser/og.parser.dart new file mode 100644 index 0000000..62952de --- /dev/null +++ b/lib/src/views/camera/share_image_editor/layers/link_preview/parser/og.parser.dart @@ -0,0 +1,28 @@ +import 'package:html/dom.dart'; + +import 'base.dart'; +import 'util.dart'; + +/// Parses [Metadata] from `<meta property='og:*'>` tags. +class OpenGraphParser with BaseMetaInfo { + OpenGraphParser(this._document); + + /// The [Document] to parse. + final Document? _document; + + /// Get [Metadata.title] from 'og:title'. + @override + String? get title => getProperty(_document, property: 'og:title'); + + /// Get [Metadata.desc] from 'og:description'. + @override + String? get desc => getProperty(_document, property: 'og:description'); + + /// Get [Metadata.image] from 'og:image'. + @override + String? get image => getProperty(_document, property: 'og:image'); + + /// Get [Metadata.siteName] from 'og:site_name'. + @override + String? get siteName => getProperty(_document, property: 'og:site_name'); +} diff --git a/lib/src/views/camera/share_image_editor/layers/link_preview/parser/other.parser.dart b/lib/src/views/camera/share_image_editor/layers/link_preview/parser/other.parser.dart new file mode 100644 index 0000000..d8d1dd8 --- /dev/null +++ b/lib/src/views/camera/share_image_editor/layers/link_preview/parser/other.parser.dart @@ -0,0 +1,27 @@ +import 'package:html/dom.dart'; + +import 'base.dart'; +import 'util.dart'; + +/// Parses [Metadata] from `<meta attribute: 'name' property='*'>` tags. +class OtherParser with BaseMetaInfo { + OtherParser(this._document); + + final Document? _document; + + @override + String? get title => + getProperty(_document, attribute: 'name', property: 'title'); + + @override + String? get desc => + getProperty(_document, attribute: 'name', property: 'description'); + + @override + String? get image => + getProperty(_document, attribute: 'name', property: 'image'); + + @override + String? get siteName => + getProperty(_document, attribute: 'name', property: 'site_name'); +} diff --git a/lib/src/views/camera/share_image_editor/layers/link_preview/parser/twitter.parser.dart b/lib/src/views/camera/share_image_editor/layers/link_preview/parser/twitter.parser.dart new file mode 100644 index 0000000..39cba4a --- /dev/null +++ b/lib/src/views/camera/share_image_editor/layers/link_preview/parser/twitter.parser.dart @@ -0,0 +1,35 @@ +import 'package:html/dom.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/base.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/util.dart'; + +/// Parses [Metadata] from `<meta property='twitter:*'>` tags. +class TwitterParser with BaseMetaInfo { + TwitterParser(this._document, this._url); + final Document? _document; + final String _url; + + @override + String? get title => + getProperty(_document, attribute: 'name', property: 'twitter:title') ?? + getProperty(_document, property: 'twitter:title'); + + @override + String? get desc => + getProperty( + _document, + attribute: 'name', + property: 'twitter:description', + ) ?? + getProperty(_document, property: 'twitter:description'); + + @override + String? get image => + getProperty(_document, attribute: 'name', property: 'twitter:image') ?? + getProperty(_document, property: 'twitter:image'); + + @override + Vendor? get vendor => + _url.startsWith('https://x.com/') && _url.contains('/status/') + ? Vendor.twitterPosting + : null; +} diff --git a/lib/src/views/camera/share_image_editor/layers/link_preview/parser/util.dart b/lib/src/views/camera/share_image_editor/layers/link_preview/parser/util.dart new file mode 100644 index 0000000..66f9d51 --- /dev/null +++ b/lib/src/views/camera/share_image_editor/layers/link_preview/parser/util.dart @@ -0,0 +1,38 @@ +import 'package:html/dom.dart'; + +// ignore: strict_raw_type +extension GetMethod on Map { + String? get(dynamic key) { + final value = this[key]; + if (value is List<String>) return value.first; + return value?.toString(); + } + + dynamic getDynamic(dynamic key) { + return this[key]; + } +} + +String? getDomain(String url) { + return Uri.parse(url).host.split('.')[0]; +} + +String? getProperty( + Document? document, { + String tag = 'meta', + String attribute = 'property', + String? property, + String key = 'content', +}) { + final value_ = document + ?.getElementsByTagName(tag) + .cast<Element?>() + .firstWhere( + (element) => element?.attributes[attribute] == property, + orElse: () => null, + ) + ?.attributes + .get(key); + + return value_; +} diff --git a/lib/src/views/camera/share_image_editor/layers/link_preview/parser/youtube.parser.dart b/lib/src/views/camera/share_image_editor/layers/link_preview/parser/youtube.parser.dart new file mode 100644 index 0000000..85ef9e2 --- /dev/null +++ b/lib/src/views/camera/share_image_editor/layers/link_preview/parser/youtube.parser.dart @@ -0,0 +1,78 @@ +import 'dart:convert'; +import 'package:html/dom.dart'; +import 'base.dart'; +import 'util.dart'; + +class YoutubeParser with BaseMetaInfo { + YoutubeParser(this.document, this._url) { + _jsonData = _parseToJson(document); + } + + final String _url; + + Document? document; + dynamic _jsonData; + + dynamic _parseToJson(Document? document) { + try { + final data = document?.outerHtml + .replaceAll('<html><head></head><body>', '') + .replaceAll('</body></html>', ''); + if (data == null) return null; + /* For multiline json file */ + // Replacing all new line characters with empty space + // before performing json decode on data + final d = jsonDecode(data.replaceAll('\n', ' ')); + return d; + } catch (e) { + return null; + } + } + + @override + String? get title { + final data = _jsonData; + if (data is List<Map<String, dynamic>>) { + return data.first['title'] as String?; + } else if (data is Map) { + return data.get('title'); + } + return null; + } + + @override + String? get image { + final data = _jsonData; + if (data is List<Map<String, dynamic>> && data.isNotEmpty) { + return _imgResultToStr(data.first['thumbnail_url']); + } else if (data is Map) { + return _imgResultToStr(data.getDynamic('thumbnail_url')); + } + return null; + } + + @override + String? get siteName { + final data = _jsonData; + if (data is List<Map<String, dynamic>>) { + return data.first['provider_name'] as String?; + } else if (data is Map) { + return data.get('provider_name'); + } + return null; + } + + @override + Vendor? get vendor => (Uri.parse(_url).host.contains('youtube.com')) + ? Vendor.youtubeVideo + : null; + + String? _imgResultToStr(dynamic result) { + if (result is List && result.isNotEmpty) { + final tmp = result.first; + if (tmp is String) return tmp; + } + if (result is String) return result; + return null; + } +} diff --git a/lib/src/views/camera/share_image_editor/layers/link_preview/utils.dart b/lib/src/views/camera/share_image_editor/layers/link_preview/utils.dart new file mode 100644 index 0000000..4729f17 --- /dev/null +++ b/lib/src/views/camera/share_image_editor/layers/link_preview/utils.dart @@ -0,0 +1,62 @@ +import 'package:http/http.dart' as http; + +Future<http.Response> fetchWithRedirects( + Uri uri, { + int maxRedirects = 7, + Map<String, String> headers = const {}, + String? userAgent, +}) async { + const userAgentFallback = 'WhatsApp/2.21.12.21 A'; + final allHeaders = <String, String>{ + ...headers, + 'User-Agent': userAgent ?? userAgentFallback, + }; + var response = await http.get(uri, headers: allHeaders); + var redirectCount = 0; + + while (_isRedirect(response) && redirectCount < maxRedirects) { + final location = response.headers['location']; + if (location == null) { + throw Exception('HTTP redirect without Location header'); + } + + response = await http.get(Uri.parse(location), headers: allHeaders); + redirectCount++; + } + + if (redirectCount >= maxRedirects) { + throw Exception('Maximum redirect limit reached'); + } + + return response; +} + +bool _isRedirect(http.Response response) { + return [301, 302, 303, 307, 308].contains(response.statusCode); +} + +Future<http.Response> getYoutubeData(String videoId, String userAgent) async { + final response = await http.get( + Uri.parse( + 'https://www.youtube.com/oembed?url=https://www.youtube.com/watch?v=$videoId&format=json', + ), + headers: { + 'User-Agent': userAgent, + }, + ); + return response; +} + +String? getYouTubeVideoId(String url) { + // Regular expression pattern to detect YouTube URLs + // with or without a proxy prefix + final regExp = RegExp( + r'(?:https?:\/\/)?(?:[^\/]+\.)?(?:youtube\.com\/(?:watch\?v=|embed\/|v\/|v\/|.+\?v=)|youtu\.be\/)([a-zA-Z0-9_-]{11})', + ); + + // Apply the regex to the URL + final match = regExp.firstMatch(url); + + // If a match is found, return the first capture group, which is the video ID + return match?.group(1); +} diff --git a/lib/src/views/camera/image_editor/layers/text_layer.dart b/lib/src/views/camera/share_image_editor/layers/text.layer.dart similarity index 98% rename from lib/src/views/camera/image_editor/layers/text_layer.dart rename to lib/src/views/camera/share_image_editor/layers/text.layer.dart index 5e9627f..7294b00 100755 --- a/lib/src/views/camera/image_editor/layers/text_layer.dart +++ b/lib/src/views/camera/share_image_editor/layers/text.layer.dart @@ -5,8 +5,8 @@ import 'package:flutter/services.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:provider/provider.dart'; import 'package:twonly/src/providers/image_editor.provider.dart'; -import 'package:twonly/src/views/camera/image_editor/action_button.dart'; -import 'package:twonly/src/views/camera/image_editor/data/layer.dart'; +import 'package:twonly/src/views/camera/share_image_editor/action_button.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layer_data.dart'; /// Text layer class TextLayer extends StatefulWidget { diff --git a/lib/src/views/camera/image_editor/layers_viewer.dart b/lib/src/views/camera/share_image_editor/layers_viewer.dart similarity index 53% rename from lib/src/views/camera/image_editor/layers_viewer.dart rename to lib/src/views/camera/share_image_editor/layers_viewer.dart index 11312d1..e53e3a8 100644 --- a/lib/src/views/camera/image_editor/layers_viewer.dart +++ b/lib/src/views/camera/share_image_editor/layers_viewer.dart @@ -1,10 +1,11 @@ import 'package:flutter/material.dart'; -import 'package:twonly/src/views/camera/image_editor/data/layer.dart'; -import 'package:twonly/src/views/camera/image_editor/layers/background_layer.dart'; -import 'package:twonly/src/views/camera/image_editor/layers/draw_layer.dart'; -import 'package:twonly/src/views/camera/image_editor/layers/emoji_layer.dart'; -import 'package:twonly/src/views/camera/image_editor/layers/filter_layer.dart'; -import 'package:twonly/src/views/camera/image_editor/layers/text_layer.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layer_data.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/background.layer.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/draw.layer.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/emoji.layer.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/filter.layer.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview.layer.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/text.layer.dart'; /// View stacked layers (unbounded height, width) class LayersViewer extends StatelessWidget { @@ -37,7 +38,10 @@ class LayersViewer extends StatelessWidget { ...layers .where( (layerItem) => - layerItem is EmojiLayerData || layerItem is DrawLayerData, + layerItem is EmojiLayerData || + layerItem is DrawLayerData || + layerItem is LinkPreviewLayerData || + layerItem is TextLayerData, ) .map((layerItem) { if (layerItem is EmojiLayerData) { @@ -52,16 +56,21 @@ class LayersViewer extends StatelessWidget { layerData: layerItem, onUpdate: onUpdate, ); + } else if (layerItem is TextLayerData) { + return TextLayer( + key: layerItem.key, + layerData: layerItem, + onUpdate: onUpdate, + ); + } else if (layerItem is LinkPreviewLayerData) { + return LinkPreviewLayer( + key: layerItem.key, + layerData: layerItem, + onUpdate: onUpdate, + ); } return Container(); }), - ...layers.whereType<TextLayerData>().map((layerItem) { - return TextLayer( - key: layerItem.key, - layerData: layerItem, - onUpdate: onUpdate, - ); - }), ], ); } diff --git a/lib/src/views/chats/chat_list.view.dart b/lib/src/views/chats/chat_list.view.dart index 663a088..e15f2d0 100644 --- a/lib/src/views/chats/chat_list.view.dart +++ b/lib/src/views/chats/chat_list.view.dart @@ -25,6 +25,7 @@ import 'package:twonly/src/views/settings/help/changelog.view.dart'; import 'package:twonly/src/views/settings/profile/profile.view.dart'; import 'package:twonly/src/views/settings/settings_main.view.dart'; import 'package:twonly/src/views/settings/subscription/subscription.view.dart'; +import 'package:twonly/src/views/user_study/user_study_welcome.view.dart'; class ChatListView extends StatefulWidget { const ChatListView({super.key}); @@ -58,31 +59,49 @@ class _ChatListViewState extends State<ChatListView> { }); }); - final changeLog = await rootBundle.loadString('CHANGELOG.md'); - final changeLogHash = - (await compute(Sha256().hash, changeLog.codeUnits)).bytes; - if (!gUser.hideChangeLog && - gUser.lastChangeLogHash.toString() != changeLogHash.toString()) { - await updateUserdata((u) { - u.lastChangeLogHash = changeLogHash; - return u; - }); - if (!mounted) return; - // only show changelog to people who already have contacts - // this prevents that this is shown directly after the user registered - if (_groupsNotPinned.isNotEmpty) { + // In case the user is already a Tester, ask him for permission. + + WidgetsBinding.instance.addPostFrameCallback((_) async { + if (gUser.subscriptionPlan == SubscriptionPlan.Tester.name && + !gUser.askedForUserStudyPermission) { await Navigator.push( context, MaterialPageRoute( builder: (context) { - return ChangeLogView( - changeLog: changeLog, + return const UserStudyWelcomeView( + wasOpenedAutomatic: true, ); }, ), ); } - } + + final changeLog = await rootBundle.loadString('CHANGELOG.md'); + final changeLogHash = + (await compute(Sha256().hash, changeLog.codeUnits)).bytes; + if (!gUser.hideChangeLog && + gUser.lastChangeLogHash.toString() != changeLogHash.toString()) { + await updateUserdata((u) { + u.lastChangeLogHash = changeLogHash; + return u; + }); + if (!mounted) return; + // only show changelog to people who already have contacts + // this prevents that this is shown directly after the user registered + if (_groupsNotPinned.isNotEmpty) { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return ChangeLogView( + changeLog: changeLog, + ); + }, + ), + ); + } + } + }); } @override @@ -295,21 +314,32 @@ class _ChatListViewState extends State<ChatListView> { child: Column( mainAxisAlignment: MainAxisAlignment.end, children: [ - FloatingActionButton.small( - backgroundColor: context.color.primary, - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const PublicProfileView(); - }, + Material( + elevation: 3, + shape: const CircleBorder(), + color: context.color.primary, + child: InkWell( + borderRadius: BorderRadius.circular(12), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return const PublicProfileView(); + }, + ), + ); + }, + child: SizedBox( + width: 45, + height: 45, + child: Center( + child: FaIcon( + FontAwesomeIcons.qrcode, + color: isDarkMode(context) ? Colors.black : Colors.white, + ), ), - ); - }, - child: FaIcon( - FontAwesomeIcons.qrcode, - color: isDarkMode(context) ? Colors.black : Colors.white, + ), ), ), const SizedBox(height: 12), diff --git a/lib/src/views/chats/chat_list_components/group_list_item.dart b/lib/src/views/chats/chat_list_components/group_list_item.dart index a87e7d8..763e389 100644 --- a/lib/src/views/chats/chat_list_components/group_list_item.dart +++ b/lib/src/views/chats/chat_list_components/group_list_item.dart @@ -9,7 +9,7 @@ import 'package:twonly/src/database/tables/messages.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/services/api/mediafiles/download.service.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/views/camera/camera_send_to_view.dart'; +import 'package:twonly/src/views/camera/camera_send_to.view.dart'; import 'package:twonly/src/views/chats/chat_list_components/last_message_time.dart'; import 'package:twonly/src/views/chats/chat_messages.view.dart'; import 'package:twonly/src/views/chats/chat_messages_components/message_send_state_icon.dart'; @@ -135,6 +135,7 @@ class _UserListItem extends State<GroupListItem> { _previewMessages.where((x) => x.type == MessageType.media).toList(); if (msgs.isNotEmpty && msgs.first.type == MessageType.media && + !msgs.first.isDeletedFromSender && msgs.first.senderId != null && msgs.first.openedAt == null) { _hasNonOpenedMediaFile = true; diff --git a/lib/src/views/chats/chat_messages_components/entries/friendly_message_time.comp.dart b/lib/src/views/chats/chat_messages_components/entries/friendly_message_time.comp.dart index 24900de..ef5ea52 100644 --- a/lib/src/views/chats/chat_messages_components/entries/friendly_message_time.comp.dart +++ b/lib/src/views/chats/chat_messages_components/entries/friendly_message_time.comp.dart @@ -18,7 +18,7 @@ class FriendlyMessageTime extends StatelessWidget { padding: const EdgeInsets.only(left: 6), child: Row( children: [ - if (message.modifiedAt != null) + if (message.modifiedAt != null && !message.isDeletedFromSender) Padding( padding: const EdgeInsets.only(right: 5), child: SizedBox( 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 62a9456..eb868e4 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 @@ -14,11 +14,11 @@ import 'package:twonly/src/model/protobuf/client/generated/messages.pbserver.dar 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/camera/share_image_editor/layer_data.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/components/emoji_picker.bottom.dart'; import 'package:twonly/src/views/memories/memories_photo_slider.view.dart'; class MessageContextMenu extends StatelessWidget { diff --git a/lib/src/views/chats/chat_messages_components/message_input.dart b/lib/src/views/chats/chat_messages_components/message_input.dart index 24d252a..6c195c7 100644 --- a/lib/src/views/chats/chat_messages_components/message_input.dart +++ b/lib/src/views/chats/chat_messages_components/message_input.dart @@ -14,7 +14,7 @@ import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/services/api/mediafiles/upload.service.dart'; import 'package:twonly/src/services/api/messages.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/views/camera/camera_send_to_view.dart'; +import 'package:twonly/src/views/camera/camera_send_to.view.dart'; import 'package:twonly/src/views/chats/chat_messages_components/entries/chat_audio_entry.dart'; class MessageInput extends StatefulWidget { diff --git a/lib/src/views/chats/chat_messages_components/message_send_state_icon.dart b/lib/src/views/chats/chat_messages_components/message_send_state_icon.dart index 95485ba..a2cec19 100644 --- a/lib/src/views/chats/chat_messages_components/message_send_state_icon.dart +++ b/lib/src/views/chats/chat_messages_components/message_send_state_icon.dart @@ -112,7 +112,7 @@ class _MessageSendStateIconState extends State<MessageSendStateIcon> { case MessageSendState.receivedOpened: icon = Icon(Icons.crop_square, size: 14, color: color); if (message.content != null) { - if (isEmoji(message.content!)) { + if (isOneEmoji(message.content!)) { icon = Text( message.content!, style: const TextStyle(fontSize: 12), diff --git a/lib/src/views/chats/media_viewer.view.dart b/lib/src/views/chats/media_viewer.view.dart index 9544731..c20edfd 100644 --- a/lib/src/views/chats/media_viewer.view.dart +++ b/lib/src/views/chats/media_viewer.view.dart @@ -20,7 +20,8 @@ import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/services/notifications/background.notifications.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/views/camera/camera_send_to_view.dart'; +import 'package:twonly/src/views/camera/camera_send_to.view.dart'; +import 'package:twonly/src/views/chats/media_viewer_components/additional_message_content.dart'; import 'package:twonly/src/views/chats/media_viewer_components/reaction_buttons.component.dart'; import 'package:twonly/src/views/components/animate_icon.dart'; import 'package:twonly/src/views/components/loader.dart'; @@ -91,8 +92,9 @@ class _MediaViewerViewState extends State<MediaViewerView> { _noScreenshot.screenshotOn(); _subscription.cancel(); downloadStateListener?.cancel(); - videoController?.dispose(); + final tmp = videoController; videoController = null; + tmp?.dispose(); super.dispose(); } @@ -493,6 +495,19 @@ class _MediaViewerViewState extends State<MediaViewerView> { ); } + Widget _loader() { + return Center( + child: SizedBox( + height: 60, + width: 60, + child: ThreeRotatingDots( + size: 40, + color: context.color.primary, + ), + ), + ); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -500,17 +515,7 @@ class _MediaViewerViewState extends State<MediaViewerView> { child: Stack( fit: StackFit.expand, children: [ - if (_showDownloadingLoader) - Center( - child: SizedBox( - height: 60, - width: 60, - child: ThreeRotatingDots( - size: 40, - color: context.color.primary, - ), - ), - ), + if (_showDownloadingLoader) _loader(), if ((currentMedia != null || videoController != null) && (canBeSeenUntil == null || progress >= 0)) GestureDetector( @@ -549,9 +554,7 @@ class _MediaViewerViewState extends State<MediaViewerView> { if (displayTwonlyPresent) Positioned.fill( child: GestureDetector( - onTap: () { - loadCurrentMediaFile(showTwonly: true); - }, + onTap: () => loadCurrentMediaFile(showTwonly: true), child: Column( children: [ Expanded( @@ -575,26 +578,14 @@ class _MediaViewerViewState extends State<MediaViewerView> { IconButton( icon: const Icon(Icons.close, size: 30), color: Colors.white, - onPressed: () async { - Navigator.pop(context); - }, + onPressed: () => Navigator.pop(context), ), ], ), ), if (currentMedia != null && currentMedia?.mediaFile.downloadState != DownloadState.ready) - const Positioned.fill( - child: Center( - child: SizedBox( - height: 60, - width: 60, - child: CircularProgressIndicator( - strokeWidth: 6, - ), - ), - ), - ), + Positioned.fill(child: _loader()), if (canBeSeenUntil != null || progress >= 0) Positioned( right: 20, @@ -700,6 +691,8 @@ class _MediaViewerViewState extends State<MediaViewerView> { ), ), ), + if (currentMessage != null) + AdditionalMessageContent(currentMessage!), if (currentMedia != null) ReactionButtons( show: showShortReactions, diff --git a/lib/src/views/chats/media_viewer_components/additional_message_content.dart b/lib/src/views/chats/media_viewer_components/additional_message_content.dart new file mode 100644 index 0000000..7f810c2 --- /dev/null +++ b/lib/src/views/chats/media_viewer_components/additional_message_content.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:twonly/src/database/daos/contacts.dao.dart'; +import 'package:twonly/src/database/twonly.db.dart'; +import 'package:twonly/src/model/protobuf/client/generated/data.pb.dart'; +import 'package:url_launcher/url_launcher_string.dart'; + +class AdditionalMessageContent extends StatelessWidget { + const AdditionalMessageContent(this.message, {super.key}); + + final Message message; + + @override + Widget build(BuildContext context) { + if (message.additionalMessageData == null) return Container(); + try { + final data = + AdditionalMessageData.fromBuffer(message.additionalMessageData!); + + switch (data.type) { + case AdditionalMessageData_Type.LINK: + if (!data.link.startsWith('http://') && + !data.link.startsWith('https://')) { + return Container(); + } + return Positioned( + bottom: 150, + right: 0, + left: 0, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + FilledButton.icon( + icon: const FaIcon(FontAwesomeIcons.shareFromSquare), + onPressed: () => launchUrlString(data.link), + label: Text( + substringBy( + data.link + .replaceAll('http://', '') + .replaceAll('https://', ''), + 30, + ), + ), + ), + ], + ), + ); + default: + } + // ignore: empty_catches + } catch (e) {} + return Container(); + } +} diff --git a/lib/src/views/chats/media_viewer_components/reaction_buttons.component.dart b/lib/src/views/chats/media_viewer_components/reaction_buttons.component.dart index 5da9f3d..108beac 100644 --- a/lib/src/views/chats/media_viewer_components/reaction_buttons.component.dart +++ b/lib/src/views/chats/media_viewer_components/reaction_buttons.component.dart @@ -1,14 +1,15 @@ import 'dart:async'; import 'dart:math'; + import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:twonly/globals.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/camera/share_image_editor/layer_data.dart'; import 'package:twonly/src/views/chats/media_viewer_components/emoji_reactions_row.component.dart'; import 'package:twonly/src/views/components/animate_icon.dart'; +import 'package:twonly/src/views/components/emoji_picker.bottom.dart'; class ReactionButtons extends StatefulWidget { const ReactionButtons({ diff --git a/lib/src/views/components/animate_icon.dart b/lib/src/views/components/animate_icon.dart index 28f6462..eb77cd9 100644 --- a/lib/src/views/components/animate_icon.dart +++ b/lib/src/views/components/animate_icon.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:lottie/lottie.dart'; -import 'package:twonly/src/views/camera/image_editor/data/data.dart'; // animations from: https://googlefonts.github.io/noto-emoji-animation/ // from https://github.com/eitanliu/emoji_regex/tree/master @@ -9,11 +8,8 @@ RegExp emojiRegex() => RegExp( r'[#*0-9]\uFE0F?\u20E3|[\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23ED-\u23EF\u23F1\u23F2\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB\u25FC\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692\u2694-\u2697\u2699\u269B\u269C\u26A0\u26A7\u26AA\u26B0\u26B1\u26BD\u26BE\u26C4\u26C8\u26CF\u26D1\u26D3\u26E9\u26F0-\u26F5\u26F7\u26F8\u26FA\u2702\u2708\u2709\u270F\u2712\u2714\u2716\u271D\u2721\u2733\u2734\u2744\u2747\u2757\u2763\u27A1\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B55\u3030\u303D\u3297\u3299]\uFE0F?|[\u261D\u270C\u270D](?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?|[\u270A\u270B](?:\uD83C[\uDFFB-\uDFFF])?|[\u23E9-\u23EC\u23F0\u23F3\u25FD\u2693\u26A1\u26AB\u26C5\u26CE\u26D4\u26EA\u26FD\u2705\u2728\u274C\u274E\u2753-\u2755\u2795-\u2797\u27B0\u27BF\u2B50]|\u26F9(?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|\u2764\uFE0F?(?:\u200D(?:\uD83D\uDD25|\uD83E\uDE79))?|\uD83C(?:[\uDC04\uDD70\uDD71\uDD7E\uDD7F\uDE02\uDE37\uDF21\uDF24-\uDF2C\uDF36\uDF7D\uDF96\uDF97\uDF99-\uDF9B\uDF9E\uDF9F\uDFCD\uDFCE\uDFD4-\uDFDF\uDFF5\uDFF7]\uFE0F?|[\uDF85\uDFC2\uDFC7](?:\uD83C[\uDFFB-\uDFFF])?|[\uDFC3\uDFC4\uDFCA](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDFCB\uDFCC](?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDCCF\uDD8E\uDD91-\uDD9A\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF84\uDF86-\uDF93\uDFA0-\uDFC1\uDFC5\uDFC6\uDFC8\uDFC9\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF8-\uDFFF]|\uDDE6\uD83C[\uDDE8-\uDDEC\uDDEE\uDDF1\uDDF2\uDDF4\uDDF6-\uDDFA\uDDFC\uDDFD\uDDFF]|\uDDE7\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEF\uDDF1-\uDDF4\uDDF6-\uDDF9\uDDFB\uDDFC\uDDFE\uDDFF]|\uDDE8\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDEE\uDDF0-\uDDF5\uDDF7\uDDFA-\uDDFF]|\uDDE9\uD83C[\uDDEA\uDDEC\uDDEF\uDDF0\uDDF2\uDDF4\uDDFF]|\uDDEA\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDED\uDDF7-\uDDFA]|\uDDEB\uD83C[\uDDEE-\uDDF0\uDDF2\uDDF4\uDDF7]|\uDDEC\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEE\uDDF1-\uDDF3\uDDF5-\uDDFA\uDDFC\uDDFE]|\uDDED\uD83C[\uDDF0\uDDF2\uDDF3\uDDF7\uDDF9\uDDFA]|\uDDEE\uD83C[\uDDE8-\uDDEA\uDDF1-\uDDF4\uDDF6-\uDDF9]|\uDDEF\uD83C[\uDDEA\uDDF2\uDDF4\uDDF5]|\uDDF0\uD83C[\uDDEA\uDDEC-\uDDEE\uDDF2\uDDF3\uDDF5\uDDF7\uDDFC\uDDFE\uDDFF]|\uDDF1\uD83C[\uDDE6-\uDDE8\uDDEE\uDDF0\uDDF7-\uDDFB\uDDFE]|\uDDF2\uD83C[\uDDE6\uDDE8-\uDDED\uDDF0-\uDDFF]|\uDDF3\uD83C[\uDDE6\uDDE8\uDDEA-\uDDEC\uDDEE\uDDF1\uDDF4\uDDF5\uDDF7\uDDFA\uDDFF]|\uDDF4\uD83C\uDDF2|\uDDF5\uD83C[\uDDE6\uDDEA-\uDDED\uDDF0-\uDDF3\uDDF7-\uDDF9\uDDFC\uDDFE]|\uDDF6\uD83C\uDDE6|\uDDF7\uD83C[\uDDEA\uDDF4\uDDF8\uDDFA\uDDFC]|\uDDF8\uD83C[\uDDE6-\uDDEA\uDDEC-\uDDF4\uDDF7-\uDDF9\uDDFB\uDDFD-\uDDFF]|\uDDF9\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDED\uDDEF-\uDDF4\uDDF7\uDDF9\uDDFB\uDDFC\uDDFF]|\uDDFA\uD83C[\uDDE6\uDDEC\uDDF2\uDDF3\uDDF8\uDDFE\uDDFF]|\uDDFB\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDEE\uDDF3\uDDFA]|\uDDFC\uD83C[\uDDEB\uDDF8]|\uDDFD\uD83C\uDDF0|\uDDFE\uD83C[\uDDEA\uDDF9]|\uDDFF\uD83C[\uDDE6\uDDF2\uDDFC]|\uDFF3\uFE0F?(?:\u200D(?:\u26A7\uFE0F?|\uD83C\uDF08))?|\uDFF4(?:\u200D\u2620\uFE0F?|\uDB40\uDC67\uDB40\uDC62\uDB40(?:\uDC65\uDB40\uDC6E\uDB40\uDC67|\uDC73\uDB40\uDC63\uDB40\uDC74|\uDC77\uDB40\uDC6C\uDB40\uDC73)\uDB40\uDC7F)?)|\uD83D(?:[\uDC08\uDC26](?:\u200D\u2B1B)?|[\uDC3F\uDCFD\uDD49\uDD4A\uDD6F\uDD70\uDD73\uDD76-\uDD79\uDD87\uDD8A-\uDD8D\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA\uDECB\uDECD-\uDECF\uDEE0-\uDEE5\uDEE9\uDEF0\uDEF3]\uFE0F?|[\uDC42\uDC43\uDC46-\uDC50\uDC66\uDC67\uDC6B-\uDC6D\uDC72\uDC74-\uDC76\uDC78\uDC7C\uDC83\uDC85\uDC8F\uDC91\uDCAA\uDD7A\uDD95\uDD96\uDE4C\uDE4F\uDEC0\uDECC](?:\uD83C[\uDFFB-\uDFFF])?|[\uDC6E\uDC70\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDD74\uDD90](?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?|[\uDC00-\uDC07\uDC09-\uDC14\uDC16-\uDC25\uDC27-\uDC3A\uDC3C-\uDC3E\uDC40\uDC44\uDC45\uDC51-\uDC65\uDC6A\uDC79-\uDC7B\uDC7D-\uDC80\uDC84\uDC88-\uDC8E\uDC90\uDC92-\uDCA9\uDCAB-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDDA4\uDDFB-\uDE2D\uDE2F-\uDE34\uDE37-\uDE44\uDE48-\uDE4A\uDE80-\uDEA2\uDEA4-\uDEB3\uDEB7-\uDEBF\uDEC1-\uDEC5\uDED0-\uDED2\uDED5-\uDED7\uDEDC-\uDEDF\uDEEB\uDEEC\uDEF4-\uDEFC\uDFE0-\uDFEB\uDFF0]|\uDC15(?:\u200D\uD83E\uDDBA)?|\uDC3B(?:\u200D\u2744\uFE0F?)?|\uDC41\uFE0F?(?:\u200D\uD83D\uDDE8\uFE0F?)?|\uDC68(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDC68\uDC69]\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFC-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFD-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFD\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFE])))?))?|\uDC69(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?[\uDC68\uDC69]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?|\uDC69\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?))|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFC-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB\uDFFD-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB-\uDFFD\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB-\uDFFE])))?))?|\uDC6F(?:\u200D[\u2640\u2642]\uFE0F?)?|\uDD75(?:\uFE0F|\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|\uDE2E(?:\u200D\uD83D\uDCA8)?|\uDE35(?:\u200D\uD83D\uDCAB)?|\uDE36(?:\u200D\uD83C\uDF2B\uFE0F?)?)|\uD83E(?:[\uDD0C\uDD0F\uDD18-\uDD1F\uDD30-\uDD34\uDD36\uDD77\uDDB5\uDDB6\uDDBB\uDDD2\uDDD3\uDDD5\uDEC3-\uDEC5\uDEF0\uDEF2-\uDEF8](?:\uD83C[\uDFFB-\uDFFF])?|[\uDD26\uDD35\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD-\uDDCF\uDDD4\uDDD6-\uDDDD](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDDDE\uDDDF](?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDD0D\uDD0E\uDD10-\uDD17\uDD20-\uDD25\uDD27-\uDD2F\uDD3A\uDD3F-\uDD45\uDD47-\uDD76\uDD78-\uDDB4\uDDB7\uDDBA\uDDBC-\uDDCC\uDDD0\uDDE0-\uDDFF\uDE70-\uDE7C\uDE80-\uDE88\uDE90-\uDEBD\uDEBF-\uDEC2\uDECE-\uDEDB\uDEE0-\uDEE8]|\uDD3C(?:\u200D[\u2640\u2642]\uFE0F?|\uD83C[\uDFFB-\uDFFF])?|\uDDD1(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83E\uDDD1))|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFC-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB\uDFFD-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB-\uDFFD\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB-\uDFFE]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF-\uDDB3\uDDBC\uDDBD]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?))?|\uDEF1(?:\uD83C(?:\uDFFB(?:\u200D\uD83E\uDEF2\uD83C[\uDFFC-\uDFFF])?|\uDFFC(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB\uDFFD-\uDFFF])?|\uDFFD(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])?|\uDFFE(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB-\uDFFD\uDFFF])?|\uDFFF(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB-\uDFFE])?))?)', ); -bool isEmoji(String character) { +bool isOneEmoji(String character) { final matches = emojiRegex().allMatches(character); - if (emojis.contains(character)) { - return true; - } if (matches.length == 1) { final match = matches.first; return match.start == 0 && match.end == character.length; @@ -226,7 +222,7 @@ class EmojiAnimation extends StatelessWidget { static bool supported(String emoji) { if (emoji.length > 4) return false; - return animatedIcons.containsKey(emoji) || isEmoji(emoji); + return animatedIcons.containsKey(emoji) || isOneEmoji(emoji); } @override @@ -239,7 +235,7 @@ class EmojiAnimation extends StatelessWidget { 'assets/animated_icons/${animatedIcons[emoji]}', repeat: repeat, ); - } else if (isEmoji(emoji)) { + } else if (isOneEmoji(emoji)) { return Text( emoji, style: const TextStyle(fontSize: 60), diff --git a/lib/src/views/components/blink.component.dart b/lib/src/views/components/blink.component.dart index 718cfb4..b114b4b 100644 --- a/lib/src/views/components/blink.component.dart +++ b/lib/src/views/components/blink.component.dart @@ -6,8 +6,8 @@ class BlinkWidget extends StatefulWidget { required this.child, required this.enabled, super.key, - this.blinkDuration = const Duration(milliseconds: 2500), - this.interval = const Duration(milliseconds: 100), + this.blinkDuration = const Duration(milliseconds: 2000), + this.interval = const Duration(milliseconds: 300), this.visibleOpacity = 1.0, this.hiddenOpacity = 0.05, }); diff --git a/lib/src/views/camera/image_editor/modules/all_emojis.dart b/lib/src/views/components/emoji_picker.bottom.dart similarity index 97% rename from lib/src/views/camera/image_editor/modules/all_emojis.dart rename to lib/src/views/components/emoji_picker.bottom.dart index a19d30d..6139966 100755 --- a/lib/src/views/camera/image_editor/modules/all_emojis.dart +++ b/lib/src/views/components/emoji_picker.bottom.dart @@ -2,7 +2,7 @@ import 'dart:io'; import 'package:emoji_picker_flutter/emoji_picker_flutter.dart'; import 'package:flutter/material.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/share_image_editor/layer_data.dart'; class EmojiPickerBottom extends StatelessWidget { const EmojiPickerBottom({super.key}); diff --git a/lib/src/views/home.view.dart b/lib/src/views/home.view.dart index b87ab11..355d38e 100644 --- a/lib/src/views/home.view.dart +++ b/lib/src/views/home.view.dart @@ -14,7 +14,7 @@ import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/views/camera/camera_preview_components/camera_preview.dart'; import 'package:twonly/src/views/camera/camera_preview_components/camera_preview_controller_view.dart'; import 'package:twonly/src/views/camera/camera_preview_components/main_camera_controller.dart'; -import 'package:twonly/src/views/camera/share_image_editor_view.dart'; +import 'package:twonly/src/views/camera/share_image_editor.view.dart'; import 'package:twonly/src/views/chats/chat_list.view.dart'; import 'package:twonly/src/views/memories/memories.view.dart'; @@ -120,7 +120,13 @@ class HomeViewState extends State<HomeView> { _intentStreamSub = FlutterSharingIntent.instance.getMediaStream().listen( (f) { - if (mounted) handleIntentSharedFile(context, f); + if (mounted) { + handleIntentSharedFile( + context, + f, + _mainCameraController.setSharedLinkForPreview, + ); + } }, // ignore: inference_failure_on_untyped_parameter onError: (err) { @@ -129,7 +135,13 @@ class HomeViewState extends State<HomeView> { ); FlutterSharingIntent.instance.getInitialSharing().then((f) { - if (mounted) handleIntentSharedFile(context, f); + if (mounted) { + handleIntentSharedFile( + context, + f, + _mainCameraController.setSharedLinkForPreview, + ); + } }); } @@ -172,9 +184,9 @@ class HomeViewState extends State<HomeView> { Widget build(BuildContext context) { return Scaffold( body: GestureDetector( - onDoubleTap: offsetRatio == 0 - ? _mainCameraController.toggleSelectedCamera - : null, + onDoubleTap: + offsetRatio == 0 ? _mainCameraController.onDoubleTap : null, + onTapDown: offsetRatio == 0 ? _mainCameraController.onTapDown : null, child: Stack( children: <Widget>[ MainCameraPreview(mainCameraController: _mainCameraController), diff --git a/lib/src/views/memories/memories_photo_slider.view.dart b/lib/src/views/memories/memories_photo_slider.view.dart index b0cfaf5..65c16ce 100644 --- a/lib/src/views/memories/memories_photo_slider.view.dart +++ b/lib/src/views/memories/memories_photo_slider.view.dart @@ -9,7 +9,7 @@ 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/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'; import 'package:twonly/src/views/components/video_player_wrapper.dart'; @@ -105,7 +105,11 @@ class _MemoriesPhotoSliderViewState extends State<MemoriesPhotoSliderView> { return; } - orgMediaService.storedPath.copySync(newMediaService.originalPath.path); + if (orgMediaService.storedPath.existsSync()) { + orgMediaService.storedPath.copySync(newMediaService.originalPath.path); + } else if (orgMediaService.tempPath.existsSync()) { + orgMediaService.tempPath.copySync(newMediaService.originalPath.path); + } if (!mounted) return; diff --git a/lib/src/views/settings/help/credits.view.dart b/lib/src/views/settings/help/credits.view.dart index ca50aa8..4a3cfaa 100644 --- a/lib/src/views/settings/help/credits.view.dart +++ b/lib/src/views/settings/help/credits.view.dart @@ -4,7 +4,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/views/camera/image_editor/layers/filters/location_filter.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/filters/location_filter.dart'; import 'package:url_launcher/url_launcher.dart'; class UrlListTitle extends StatelessWidget { diff --git a/lib/src/views/settings/help/help.view.dart b/lib/src/views/settings/help/help.view.dart index 5dee0ff..89b3dd2 100644 --- a/lib/src/views/settings/help/help.view.dart +++ b/lib/src/views/settings/help/help.view.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:package_info_plus/package_info_plus.dart'; @@ -10,6 +11,7 @@ import 'package:twonly/src/views/settings/help/contact_us.view.dart'; import 'package:twonly/src/views/settings/help/credits.view.dart'; import 'package:twonly/src/views/settings/help/diagnostics.view.dart'; import 'package:twonly/src/views/settings/help/faq.view.dart'; +import 'package:twonly/src/views/user_study/user_study_welcome.view.dart'; import 'package:url_launcher/url_launcher.dart'; class HelpView extends StatefulWidget { @@ -109,6 +111,21 @@ class _HelpViewState extends State<HelpView> { }, ), const Divider(), + if (gUser.userStudyParticipantsToken == null || kDebugMode) + ListTile( + title: const Text('Teilnahme an Nutzerstudie'), + onTap: () async { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return const UserStudyWelcomeView(); + }, + ), + ); + setState(() {}); // gUser has changed + }, + ), FutureBuilder( future: PackageInfo.fromPlatform(), builder: (context, snap) { diff --git a/lib/src/views/user_study/user_study_data_collection.dart b/lib/src/views/user_study/user_study_data_collection.dart new file mode 100644 index 0000000..c38f213 --- /dev/null +++ b/lib/src/views/user_study/user_study_data_collection.dart @@ -0,0 +1,76 @@ +import 'dart:convert'; + +import 'package:http/http.dart' as http; +import 'package:twonly/globals.dart'; +import 'package:twonly/src/utils/keyvalue.dart'; +import 'package:twonly/src/utils/log.dart'; +import 'package:twonly/src/utils/storage.dart'; + +const userStudySurveyKey = 'user_study_survey'; + +// LEASE DO NOT SPAM OR TRY SENDING DIRECTLY TO THIS URL! +// You're just making my master's thesis more difficult and destroy scientific data. :/ +const surveyUrlBase = 'https://survey.twonly.org/upload.php'; + +Future<void> handleUserStudyUpload() async { + try { + final token = gUser.userStudyParticipantsToken; + if (token == null) return; + + // in case the survey was taken offline try again + final userStudySurvey = await KeyValueStore.get(userStudySurveyKey); + if (userStudySurvey != null) { + final response = await http.post( + Uri.parse('$surveyUrlBase/create/$token'), + body: jsonEncode(userStudySurvey), + headers: {'Content-Type': 'application/json'}, + ); + if (response.statusCode != 200) { + Log.warn( + 'Got different status code for survey upload: ${response.statusCode}', + ); + return; + } + await KeyValueStore.delete(userStudySurveyKey); + } + + if (gUser.lastUserStudyDataUpload + ?.isAfter(DateTime.now().subtract(const Duration(days: 1))) ?? + false) { + // Only send updates once a day. + // This enables to see if improvements to actually work. + return; + } + + final contacts = await twonlyDB.contactsDao.getAllContacts(); + + final dataCollection = { + 'total_contacts': contacts.length, + 'accepted_contacts': contacts.where((c) => c.accepted).length, + 'verified_contacts': contacts.where((c) => c.verified).length, + }; + + final response = await http.post( + Uri.parse('$surveyUrlBase/push/$token'), + body: jsonEncode(dataCollection), + headers: {'Content-Type': 'application/json'}, + ); + if (response.statusCode == 200) { + await updateUserdata((u) { + u.lastUserStudyDataUpload = DateTime.now(); + return u; + }); + } + if (response.statusCode == 404) { + // Token is unknown to the server... + await updateUserdata((u) { + u + ..lastUserStudyDataUpload = null + ..userStudyParticipantsToken = null; + return u; + }); + } + } catch (e) { + Log.error(e); + } +} diff --git a/lib/src/views/user_study/user_study_questionnaire.view.dart b/lib/src/views/user_study/user_study_questionnaire.view.dart new file mode 100644 index 0000000..7058c5c --- /dev/null +++ b/lib/src/views/user_study/user_study_questionnaire.view.dart @@ -0,0 +1,267 @@ +// ignore_for_file: avoid_dynamic_calls + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:twonly/src/utils/keyvalue.dart'; +import 'package:twonly/src/utils/misc.dart'; +import 'package:twonly/src/utils/storage.dart'; +import 'package:twonly/src/views/user_study/user_study_data_collection.dart'; + +class UserStudyQuestionnaire extends StatefulWidget { + const UserStudyQuestionnaire({super.key}); + + @override + State<UserStudyQuestionnaire> createState() => _UserStudyQuestionnaireState(); +} + +class _UserStudyQuestionnaireState extends State<UserStudyQuestionnaire> { + final Map<String, dynamic> _responses = { + 'gender': null, + 'gender_free': '', + 'age': null, + 'education': null, + 'education_free': '', + 'vocational': null, + 'vocational_free': '', + 'enrolled': null, + 'study_program': '', + 'working': null, + 'work_field': '', + 'smartphone_2years': null, + 'comp_knowledge': null, + 'security_knowledge': null, + 'messengers': [], + 'is_release_mode': kReleaseMode, + }; + + final List<String> _messengerOptions = [ + 'WhatsApp', + 'Signal', + 'Telegram', + 'Facebook Messenger', + 'iMessage', + 'Teams', + 'Viber', + 'Element', + 'Andere', + ]; + + Future<void> _submitData() async { + await KeyValueStore.put(userStudySurveyKey, _responses); + + await updateUserdata((u) { + // generate a random participants id to identify data send later while keeping the user anonym + u.userStudyParticipantsToken = getRandomString(25); + return u; + }); + + await handleUserStudyUpload(); + + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Vielen Dank für deine Teilnahme!')), + ); + + Navigator.pop(context); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Befragung')), + body: SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _sectionTitle('Demografische Daten'), + _questionText('Was ist dein Geschlecht?'), + _buildRadioList( + ['Männlich', 'Weiblich', 'Divers', 'Keine Angabe'], + 'gender', + ), + _buildTextField( + 'Freitext (optional)', + (val) => _responses['gender_free'] = val, + ), + _questionText('Wie alt bist du?'), + _buildRadioList( + [ + '18-22', + '23-27', + '28-32', + '33-37', + '38-42', + '43-47', + '48-52', + '53-57', + '58-62', + '63-67', + '68-72', + '73-77', + '77 oder älter', + 'Keine Angabe', + ], + 'age', + ), + _questionText('Was ist dein höchster Schulabschluss?'), + _buildRadioList( + [ + 'Noch in der Schule', + 'Hauptschulabschluss', + 'POS (Polytechnische Oberschule)', + 'Realschulabschluss', + 'Abitur / Hochschulreife', + 'Kein Abschluss', + 'Keine Angabe', + ], + 'education', + ), + _buildTextField( + 'Freitext (optional)', + (val) => _responses['education_free'] = val, + ), + _questionText('Was ist dein höchster beruflicher Abschluss?'), + _buildRadioList( + [ + 'Berufsausbildung / Duales System', + 'Fachschulabschluss', + 'Fachschulabschluss (ehem. DDR)', + 'Bachelor', + 'Master', + 'Diplom', + 'Promotion (PhD)', + 'Kein beruflicher Abschluss', + 'Keine Angabe', + ], + 'vocational', + ), + _buildTextField( + 'Freitext (optional)', + (val) => _responses['vocational_free'] = val, + ), + _questionText( + 'Bist du derzeit in einem Studiengang eingeschrieben? (Bachelor, Master, Diplom, Staatsexamen, Magister)', + ), + _buildRadioList(['Ja', 'Nein', 'Keine Angabe'], 'enrolled'), + _questionText('Wenn ja, welcher Studiengang?'), + _buildTextField( + 'Studiengang eingeben', + (val) => _responses['study_program'] = val, + ), + _questionText('Bist du derzeit berufstätig?'), + _buildRadioList(['Ja', 'Nein', 'Keine Angabe'], 'working'), + _questionText('Wenn ja, in welchem Bereich arbeiten Sie?'), + _buildTextField( + 'Arbeitsbereich eingeben', + (val) => _responses['work_field'] = val, + ), + const SizedBox(height: 30), + // const Divider(), + _sectionTitle('Technisches Wissen'), + _questionText( + 'Nutzt du seit mehr als zwei Jahren ein Smartphone?', + ), + _buildRadioList(['Ja', 'Nein'], 'smartphone_2years'), + _questionText( + 'Wie schätzt du deine allgemeinen Computerkenntnisse ein?', + ), + _buildRadioList( + ['Anfänger', 'Mittel', 'Fortgeschritten'], + 'comp_knowledge', + ), + _questionText( + 'Wie schätzt du dein Wissen im Bereich IT-Sicherheit ein?', + ), + _buildRadioList( + ['Anfänger', 'Mittel', 'Fortgeschritten'], + 'security_knowledge', + ), + _questionText( + 'Welche der folgenden Messenger hast du schon einmal benutzt?', + ), + ..._messengerOptions.map( + (m) => CheckboxListTile( + title: Text(m), + visualDensity: const VisualDensity(vertical: -4), + value: (_responses['messengers'] as List<dynamic>).contains(m), + onChanged: (bool? value) { + setState(() { + value! + ? _responses['messengers'].add(m) + : _responses['messengers'].remove(m); + }); + }, + ), + ), + const SizedBox(height: 30), + Center( + child: FilledButton( + onPressed: _submitData, + child: const Padding( + padding: EdgeInsets.symmetric(horizontal: 40, vertical: 15), + child: + Text('Jetzt teilnehmen', style: TextStyle(fontSize: 18)), + ), + ), + ), + const SizedBox(height: 50), + ], + ), + ), + ); + } + + // Hilfsmethoden für das UI + Widget _sectionTitle(String title) => Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: Text( + title, + style: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + ); + + Widget _questionText(String text) => Padding( + padding: const EdgeInsets.only(top: 20, bottom: 5), + child: Text( + text, + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600), + ), + ); + + Widget _buildRadioList(List<String> options, String key) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: DropdownButtonFormField<String>( + decoration: const InputDecoration( + border: OutlineInputBorder(), + contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8), + labelText: 'Bitte wählen...', + ), + initialValue: _responses[key] as String?, + items: options.map((String value) { + return DropdownMenuItem<String>( + value: value, + child: Text(value), + ); + }).toList(), + onChanged: (val) { + setState(() { + _responses[key] = val; + }); + }, + ), + ); + } + + Widget _buildTextField(String hint, void Function(String) onChanged) { + return TextField( + decoration: + InputDecoration(hintText: hint, border: const OutlineInputBorder()), + onChanged: onChanged, + ); + } +} diff --git a/lib/src/views/user_study/user_study_welcome.view.dart b/lib/src/views/user_study/user_study_welcome.view.dart new file mode 100644 index 0000000..ed8adb1 --- /dev/null +++ b/lib/src/views/user_study/user_study_welcome.view.dart @@ -0,0 +1,120 @@ +import 'package:flutter/material.dart'; +import 'package:twonly/src/utils/storage.dart'; +import 'package:twonly/src/views/user_study/user_study_questionnaire.view.dart'; + +class UserStudyWelcomeView extends StatefulWidget { + const UserStudyWelcomeView({super.key, this.wasOpenedAutomatic = false}); + + final bool wasOpenedAutomatic; + + @override + State<UserStudyWelcomeView> createState() => _UserStudyWelcomeViewState(); +} + +class _UserStudyWelcomeViewState extends State<UserStudyWelcomeView> { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Teilnahme an Nutzerstudie'), + ), + body: Padding( + padding: const EdgeInsets.all(12), + child: ListView( + children: [ + const SizedBox(height: 30), + const Text( + 'Es dauert nur ein paar Minuten.', + textAlign: TextAlign.center, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20, + ), + ), + const SizedBox(height: 20), + const Text( + 'Im Rahmen meiner Masterarbeit möchte ich die Benutzerfreundlichkeit von anonymen und dezentralen Messenger-Diensten verbessern.', + textAlign: TextAlign.center, + ), + const SizedBox(height: 10), + const Text( + 'Zu diesem Zweck werden in den nächsten Monaten verschiedene Änderungen an der App vorgenommen. Um die Wirksamkeit der Änderungen zu messen, möchte ich einige Daten über deine Nutzung der App sammeln sowie eine kurze Befragung durchführen.', + textAlign: TextAlign.center, + ), + const SizedBox(height: 10), + const Text( + 'Die Daten bestehen ausschließlich aus Zahlen, z. B. Anzahl deiner Kontakte. Alle Daten werden anonym übermittelt und können nicht mit deinem Benutzerkonto verknüpft werden.', + textAlign: TextAlign.center, + ), + const SizedBox(height: 10), + const Text( + 'Die Masterarbeit und damit die Nutzerstudie wird bis September durchgeführt. Nach Abschluss erhältst du eine Benachrichtigung und wirst über die Ergebnisse informiert.', + textAlign: TextAlign.center, + ), + const SizedBox(height: 40), + Center( + child: FilledButton( + onPressed: () { + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) { + return const UserStudyQuestionnaire(); + }, + ), + ); + }, + child: const Padding( + padding: EdgeInsets.symmetric(horizontal: 30, vertical: 15), + child: Text( + 'Weiter zur Befragung', + style: TextStyle(fontSize: 18), + ), + ), + ), + ), + const SizedBox(height: 10), + if (widget.wasOpenedAutomatic) + Center( + child: OutlinedButton( + onPressed: () { + Navigator.pop(context); + }, + child: const Padding( + padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10), + child: Text( + 'Frag mich später noch mal', + style: TextStyle(fontSize: 18), + ), + ), + ), + ), + const SizedBox(height: 10), + if (widget.wasOpenedAutomatic) + Center( + child: GestureDetector( + onTap: () async { + await updateUserdata((u) { + u.askedForUserStudyPermission = true; + return u; + }); + if (context.mounted) Navigator.pop(context); + }, + child: const Text( + 'Nicht mehr anzeigen', + style: TextStyle(fontSize: 12), + ), + ), + ), + const SizedBox(height: 10), + const Text( + 'PS: twonly ist Open Source, wenn du also genau wissen willst, welche Daten übertragen werden, schau dir einfach die Datei "lib/src/views/user_study/user_study_data_collection.dart" im Repository an :).', + textAlign: TextAlign.center, + style: TextStyle(fontSize: 10), + ), + ], + ), + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 7df1a10..6b30fbb 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -868,6 +868,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.11.0" + google_mlkit_face_detection: + dependency: "direct main" + description: + name: google_mlkit_face_detection + sha256: f336737d5b8a86797fd4368f42a5c26aeaa9c6dcc5243f0a16b5f6f663cfb70a + url: "https://pub.dev" + source: hosted + version: "0.13.1" graphs: dependency: transitive description: @@ -906,7 +914,7 @@ packages: source: path version: "3.0.1" html: - dependency: transitive + dependency: "direct main" description: name: html sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" diff --git a/pubspec.yaml b/pubspec.yaml index ce61f24..0cb22ee 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: "twonly, a privacy-friendly way to connect with friends through sec publish_to: 'none' -version: 0.0.85+85 +version: 0.0.86+86 environment: sdk: ^3.6.0 @@ -26,6 +26,7 @@ dependencies: convert: ^3.1.2 crypto: ^3.0.7 clock: ^1.1.2 + html: ^0.15.6 # Trusted publisher flutter.dev @@ -110,6 +111,7 @@ dependencies: hand_signature: ^3.0.3 flutter_sharing_intent: ^2.0.4 no_screenshot: ^0.3.1 + google_mlkit_face_detection: ^0.13.1 dependency_overrides: dots_indicator: @@ -203,5 +205,6 @@ flutter: - assets/animated_icons/ - assets/animations/ - assets/passwords/ + - assets/filters/ - CHANGELOG.md diff --git a/scripts/generate_proto.sh b/scripts/generate_proto.sh index 20fd3b6..40880f9 100755 --- a/scripts/generate_proto.sh +++ b/scripts/generate_proto.sh @@ -15,6 +15,7 @@ protoc --proto_path="$CLIENT_DIR" --dart_out="$GENERATED_DIR" "backup.proto" protoc --proto_path="$CLIENT_DIR" --dart_out="$GENERATED_DIR" "messages.proto" protoc --proto_path="$CLIENT_DIR" --dart_out="$GENERATED_DIR" "groups.proto" protoc --proto_path="$CLIENT_DIR" --dart_out="$GENERATED_DIR" "qr.proto" +protoc --proto_path="$CLIENT_DIR" --dart_out="$GENERATED_DIR" "data.proto" protoc --proto_path="$CLIENT_DIR" --dart_out="$GENERATED_DIR" "push_notification.proto" protoc --proto_path="$CLIENT_DIR" --swift_out="./ios/NotificationService/" "push_notification.proto" diff --git a/test/drift/twonly_db/generated/schema.dart b/test/drift/twonly_db/generated/schema.dart index d59002b..87de919 100644 --- a/test/drift/twonly_db/generated/schema.dart +++ b/test/drift/twonly_db/generated/schema.dart @@ -9,6 +9,7 @@ import 'schema_v3.dart' as v3; import 'schema_v4.dart' as v4; import 'schema_v5.dart' as v5; import 'schema_v6.dart' as v6; +import 'schema_v7.dart' as v7; class GeneratedHelper implements SchemaInstantiationHelper { @override @@ -26,10 +27,12 @@ class GeneratedHelper implements SchemaInstantiationHelper { return v5.DatabaseAtV5(db); case 6: return v6.DatabaseAtV6(db); + case 7: + return v7.DatabaseAtV7(db); default: throw MissingSchemaException(version, versions); } } - static const versions = const [1, 2, 3, 4, 5, 6]; + static const versions = const [1, 2, 3, 4, 5, 6, 7]; } diff --git a/test/drift/twonly_db/generated/schema_v7.dart b/test/drift/twonly_db/generated/schema_v7.dart new file mode 100644 index 0000000..ef2ae77 --- /dev/null +++ b/test/drift/twonly_db/generated/schema_v7.dart @@ -0,0 +1,6582 @@ +// 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<Contacts, ContactsData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + Contacts(this.attachedDatabase, [this._alias]); + late final GeneratedColumn<int> userId = GeneratedColumn<int>( + 'user_id', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: false); + late final GeneratedColumn<String> username = GeneratedColumn<String>( + 'username', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn<String> displayName = GeneratedColumn<String>( + 'display_name', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + late final GeneratedColumn<String> nickName = GeneratedColumn<String>( + 'nick_name', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + late final GeneratedColumn<i2.Uint8List> avatarSvgCompressed = + GeneratedColumn<i2.Uint8List>('avatar_svg_compressed', aliasedName, true, + type: DriftSqlType.blob, requiredDuringInsert: false); + late final GeneratedColumn<int> senderProfileCounter = GeneratedColumn<int>( + 'sender_profile_counter', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0')); + late final GeneratedColumn<bool> accepted = GeneratedColumn<bool>( + 'accepted', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("accepted" IN (0, 1))'), + defaultValue: const CustomExpression('0')); + late final GeneratedColumn<bool> deletedByUser = GeneratedColumn<bool>( + '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<bool> requested = GeneratedColumn<bool>( + 'requested', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("requested" IN (0, 1))'), + defaultValue: const CustomExpression('0')); + late final GeneratedColumn<bool> blocked = GeneratedColumn<bool>( + 'blocked', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("blocked" IN (0, 1))'), + defaultValue: const CustomExpression('0')); + late final GeneratedColumn<bool> verified = GeneratedColumn<bool>( + 'verified', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("verified" IN (0, 1))'), + defaultValue: const CustomExpression('0')); + late final GeneratedColumn<bool> accountDeleted = GeneratedColumn<bool>( + '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<DateTime> createdAt = GeneratedColumn<DateTime>( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression( + 'CAST(strftime(\'%s\', CURRENT_TIMESTAMP) AS INTEGER)')); + @override + List<GeneratedColumn> 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<GeneratedColumn> get $primaryKey => {userId}; + @override + ContactsData map(Map<String, dynamic> 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<ContactsData> { + 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<String, Expression> toColumns(bool nullToAbsent) { + final map = <String, Expression>{}; + map['user_id'] = Variable<int>(userId); + map['username'] = Variable<String>(username); + if (!nullToAbsent || displayName != null) { + map['display_name'] = Variable<String>(displayName); + } + if (!nullToAbsent || nickName != null) { + map['nick_name'] = Variable<String>(nickName); + } + if (!nullToAbsent || avatarSvgCompressed != null) { + map['avatar_svg_compressed'] = + Variable<i2.Uint8List>(avatarSvgCompressed); + } + map['sender_profile_counter'] = Variable<int>(senderProfileCounter); + map['accepted'] = Variable<bool>(accepted); + map['deleted_by_user'] = Variable<bool>(deletedByUser); + map['requested'] = Variable<bool>(requested); + map['blocked'] = Variable<bool>(blocked); + map['verified'] = Variable<bool>(verified); + map['account_deleted'] = Variable<bool>(accountDeleted); + map['created_at'] = Variable<DateTime>(createdAt); + return map; + } + + ContactsCompanion toCompanion(bool nullToAbsent) { + return ContactsCompanion( + userId: Value(userId), + username: Value(username), + displayName: displayName == null && nullToAbsent + ? const Value.absent() + : Value(displayName), + nickName: nickName == null && nullToAbsent + ? const Value.absent() + : Value(nickName), + avatarSvgCompressed: avatarSvgCompressed == null && nullToAbsent + ? const Value.absent() + : Value(avatarSvgCompressed), + senderProfileCounter: Value(senderProfileCounter), + accepted: Value(accepted), + deletedByUser: Value(deletedByUser), + requested: Value(requested), + blocked: Value(blocked), + verified: Value(verified), + accountDeleted: Value(accountDeleted), + createdAt: Value(createdAt), + ); + } + + factory ContactsData.fromJson(Map<String, dynamic> json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return ContactsData( + userId: serializer.fromJson<int>(json['userId']), + username: serializer.fromJson<String>(json['username']), + displayName: serializer.fromJson<String?>(json['displayName']), + nickName: serializer.fromJson<String?>(json['nickName']), + avatarSvgCompressed: + serializer.fromJson<i2.Uint8List?>(json['avatarSvgCompressed']), + senderProfileCounter: + serializer.fromJson<int>(json['senderProfileCounter']), + accepted: serializer.fromJson<bool>(json['accepted']), + deletedByUser: serializer.fromJson<bool>(json['deletedByUser']), + requested: serializer.fromJson<bool>(json['requested']), + blocked: serializer.fromJson<bool>(json['blocked']), + verified: serializer.fromJson<bool>(json['verified']), + accountDeleted: serializer.fromJson<bool>(json['accountDeleted']), + createdAt: serializer.fromJson<DateTime>(json['createdAt']), + ); + } + @override + Map<String, dynamic> toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return <String, dynamic>{ + 'userId': serializer.toJson<int>(userId), + 'username': serializer.toJson<String>(username), + 'displayName': serializer.toJson<String?>(displayName), + 'nickName': serializer.toJson<String?>(nickName), + 'avatarSvgCompressed': + serializer.toJson<i2.Uint8List?>(avatarSvgCompressed), + 'senderProfileCounter': serializer.toJson<int>(senderProfileCounter), + 'accepted': serializer.toJson<bool>(accepted), + 'deletedByUser': serializer.toJson<bool>(deletedByUser), + 'requested': serializer.toJson<bool>(requested), + 'blocked': serializer.toJson<bool>(blocked), + 'verified': serializer.toJson<bool>(verified), + 'accountDeleted': serializer.toJson<bool>(accountDeleted), + 'createdAt': serializer.toJson<DateTime>(createdAt), + }; + } + + ContactsData copyWith( + {int? userId, + String? username, + Value<String?> displayName = const Value.absent(), + Value<String?> nickName = const Value.absent(), + Value<i2.Uint8List?> avatarSvgCompressed = const Value.absent(), + int? senderProfileCounter, + bool? accepted, + bool? deletedByUser, + bool? requested, + bool? blocked, + bool? verified, + bool? accountDeleted, + DateTime? createdAt}) => + ContactsData( + userId: userId ?? this.userId, + username: username ?? this.username, + displayName: displayName.present ? displayName.value : this.displayName, + nickName: nickName.present ? nickName.value : this.nickName, + avatarSvgCompressed: avatarSvgCompressed.present + ? avatarSvgCompressed.value + : this.avatarSvgCompressed, + senderProfileCounter: senderProfileCounter ?? this.senderProfileCounter, + accepted: accepted ?? this.accepted, + deletedByUser: deletedByUser ?? this.deletedByUser, + requested: requested ?? this.requested, + blocked: blocked ?? this.blocked, + verified: verified ?? this.verified, + accountDeleted: accountDeleted ?? this.accountDeleted, + createdAt: createdAt ?? this.createdAt, + ); + ContactsData copyWithCompanion(ContactsCompanion data) { + return ContactsData( + userId: data.userId.present ? data.userId.value : this.userId, + username: data.username.present ? data.username.value : this.username, + displayName: + data.displayName.present ? data.displayName.value : this.displayName, + nickName: data.nickName.present ? data.nickName.value : this.nickName, + avatarSvgCompressed: data.avatarSvgCompressed.present + ? data.avatarSvgCompressed.value + : this.avatarSvgCompressed, + senderProfileCounter: data.senderProfileCounter.present + ? data.senderProfileCounter.value + : this.senderProfileCounter, + accepted: data.accepted.present ? data.accepted.value : this.accepted, + deletedByUser: data.deletedByUser.present + ? data.deletedByUser.value + : this.deletedByUser, + requested: data.requested.present ? data.requested.value : this.requested, + blocked: data.blocked.present ? data.blocked.value : this.blocked, + verified: data.verified.present ? data.verified.value : this.verified, + accountDeleted: data.accountDeleted.present + ? data.accountDeleted.value + : this.accountDeleted, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + ); + } + + @override + String toString() { + return (StringBuffer('ContactsData(') + ..write('userId: $userId, ') + ..write('username: $username, ') + ..write('displayName: $displayName, ') + ..write('nickName: $nickName, ') + ..write('avatarSvgCompressed: $avatarSvgCompressed, ') + ..write('senderProfileCounter: $senderProfileCounter, ') + ..write('accepted: $accepted, ') + ..write('deletedByUser: $deletedByUser, ') + ..write('requested: $requested, ') + ..write('blocked: $blocked, ') + ..write('verified: $verified, ') + ..write('accountDeleted: $accountDeleted, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + userId, + username, + displayName, + nickName, + $driftBlobEquality.hash(avatarSvgCompressed), + senderProfileCounter, + accepted, + deletedByUser, + requested, + blocked, + verified, + accountDeleted, + createdAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is ContactsData && + other.userId == this.userId && + other.username == this.username && + other.displayName == this.displayName && + other.nickName == this.nickName && + $driftBlobEquality.equals( + other.avatarSvgCompressed, this.avatarSvgCompressed) && + other.senderProfileCounter == this.senderProfileCounter && + other.accepted == this.accepted && + other.deletedByUser == this.deletedByUser && + other.requested == this.requested && + other.blocked == this.blocked && + other.verified == this.verified && + other.accountDeleted == this.accountDeleted && + other.createdAt == this.createdAt); +} + +class ContactsCompanion extends UpdateCompanion<ContactsData> { + final Value<int> userId; + final Value<String> username; + final Value<String?> displayName; + final Value<String?> nickName; + final Value<i2.Uint8List?> avatarSvgCompressed; + final Value<int> senderProfileCounter; + final Value<bool> accepted; + final Value<bool> deletedByUser; + final Value<bool> requested; + final Value<bool> blocked; + final Value<bool> verified; + final Value<bool> accountDeleted; + final Value<DateTime> createdAt; + const ContactsCompanion({ + this.userId = const Value.absent(), + this.username = const Value.absent(), + this.displayName = const Value.absent(), + this.nickName = const Value.absent(), + this.avatarSvgCompressed = const Value.absent(), + this.senderProfileCounter = const Value.absent(), + this.accepted = const Value.absent(), + this.deletedByUser = const Value.absent(), + this.requested = const Value.absent(), + this.blocked = const Value.absent(), + this.verified = const Value.absent(), + this.accountDeleted = const Value.absent(), + this.createdAt = const Value.absent(), + }); + ContactsCompanion.insert({ + this.userId = const Value.absent(), + required String username, + this.displayName = const Value.absent(), + this.nickName = const Value.absent(), + this.avatarSvgCompressed = const Value.absent(), + this.senderProfileCounter = const Value.absent(), + this.accepted = const Value.absent(), + this.deletedByUser = const Value.absent(), + this.requested = const Value.absent(), + this.blocked = const Value.absent(), + this.verified = const Value.absent(), + this.accountDeleted = const Value.absent(), + this.createdAt = const Value.absent(), + }) : username = Value(username); + static Insertable<ContactsData> custom({ + Expression<int>? userId, + Expression<String>? username, + Expression<String>? displayName, + Expression<String>? nickName, + Expression<i2.Uint8List>? avatarSvgCompressed, + Expression<int>? senderProfileCounter, + Expression<bool>? accepted, + Expression<bool>? deletedByUser, + Expression<bool>? requested, + Expression<bool>? blocked, + Expression<bool>? verified, + Expression<bool>? accountDeleted, + Expression<DateTime>? createdAt, + }) { + return RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (username != null) 'username': username, + if (displayName != null) 'display_name': displayName, + if (nickName != null) 'nick_name': nickName, + if (avatarSvgCompressed != null) + 'avatar_svg_compressed': avatarSvgCompressed, + if (senderProfileCounter != null) + 'sender_profile_counter': senderProfileCounter, + if (accepted != null) 'accepted': accepted, + if (deletedByUser != null) 'deleted_by_user': deletedByUser, + if (requested != null) 'requested': requested, + if (blocked != null) 'blocked': blocked, + if (verified != null) 'verified': verified, + if (accountDeleted != null) 'account_deleted': accountDeleted, + if (createdAt != null) 'created_at': createdAt, + }); + } + + ContactsCompanion copyWith( + {Value<int>? userId, + Value<String>? username, + Value<String?>? displayName, + Value<String?>? nickName, + Value<i2.Uint8List?>? avatarSvgCompressed, + Value<int>? senderProfileCounter, + Value<bool>? accepted, + Value<bool>? deletedByUser, + Value<bool>? requested, + Value<bool>? blocked, + Value<bool>? verified, + Value<bool>? accountDeleted, + Value<DateTime>? createdAt}) { + return ContactsCompanion( + userId: userId ?? this.userId, + username: username ?? this.username, + displayName: displayName ?? this.displayName, + nickName: nickName ?? this.nickName, + avatarSvgCompressed: avatarSvgCompressed ?? this.avatarSvgCompressed, + senderProfileCounter: senderProfileCounter ?? this.senderProfileCounter, + accepted: accepted ?? this.accepted, + deletedByUser: deletedByUser ?? this.deletedByUser, + requested: requested ?? this.requested, + blocked: blocked ?? this.blocked, + verified: verified ?? this.verified, + accountDeleted: accountDeleted ?? this.accountDeleted, + createdAt: createdAt ?? this.createdAt, + ); + } + + @override + Map<String, Expression> toColumns(bool nullToAbsent) { + final map = <String, Expression>{}; + if (userId.present) { + map['user_id'] = Variable<int>(userId.value); + } + if (username.present) { + map['username'] = Variable<String>(username.value); + } + if (displayName.present) { + map['display_name'] = Variable<String>(displayName.value); + } + if (nickName.present) { + map['nick_name'] = Variable<String>(nickName.value); + } + if (avatarSvgCompressed.present) { + map['avatar_svg_compressed'] = + Variable<i2.Uint8List>(avatarSvgCompressed.value); + } + if (senderProfileCounter.present) { + map['sender_profile_counter'] = Variable<int>(senderProfileCounter.value); + } + if (accepted.present) { + map['accepted'] = Variable<bool>(accepted.value); + } + if (deletedByUser.present) { + map['deleted_by_user'] = Variable<bool>(deletedByUser.value); + } + if (requested.present) { + map['requested'] = Variable<bool>(requested.value); + } + if (blocked.present) { + map['blocked'] = Variable<bool>(blocked.value); + } + if (verified.present) { + map['verified'] = Variable<bool>(verified.value); + } + if (accountDeleted.present) { + map['account_deleted'] = Variable<bool>(accountDeleted.value); + } + if (createdAt.present) { + map['created_at'] = Variable<DateTime>(createdAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('ContactsCompanion(') + ..write('userId: $userId, ') + ..write('username: $username, ') + ..write('displayName: $displayName, ') + ..write('nickName: $nickName, ') + ..write('avatarSvgCompressed: $avatarSvgCompressed, ') + ..write('senderProfileCounter: $senderProfileCounter, ') + ..write('accepted: $accepted, ') + ..write('deletedByUser: $deletedByUser, ') + ..write('requested: $requested, ') + ..write('blocked: $blocked, ') + ..write('verified: $verified, ') + ..write('accountDeleted: $accountDeleted, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } +} + +class Groups extends Table with TableInfo<Groups, GroupsData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + Groups(this.attachedDatabase, [this._alias]); + late final GeneratedColumn<String> groupId = GeneratedColumn<String>( + 'group_id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn<bool> isGroupAdmin = GeneratedColumn<bool>( + 'is_group_admin', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_group_admin" IN (0, 1))'), + defaultValue: const CustomExpression('0')); + late final GeneratedColumn<bool> isDirectChat = GeneratedColumn<bool>( + 'is_direct_chat', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_direct_chat" IN (0, 1))'), + defaultValue: const CustomExpression('0')); + late final GeneratedColumn<bool> pinned = GeneratedColumn<bool>( + 'pinned', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("pinned" IN (0, 1))'), + defaultValue: const CustomExpression('0')); + late final GeneratedColumn<bool> archived = GeneratedColumn<bool>( + 'archived', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("archived" IN (0, 1))'), + defaultValue: const CustomExpression('0')); + late final GeneratedColumn<bool> joinedGroup = GeneratedColumn<bool>( + 'joined_group', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("joined_group" IN (0, 1))'), + defaultValue: const CustomExpression('0')); + late final GeneratedColumn<bool> leftGroup = GeneratedColumn<bool>( + 'left_group', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("left_group" IN (0, 1))'), + defaultValue: const CustomExpression('0')); + late final GeneratedColumn<bool> deletedContent = GeneratedColumn<bool>( + 'deleted_content', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("deleted_content" IN (0, 1))'), + defaultValue: const CustomExpression('0')); + late final GeneratedColumn<int> stateVersionId = GeneratedColumn<int>( + 'state_version_id', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0')); + late final GeneratedColumn<i2.Uint8List> stateEncryptionKey = + GeneratedColumn<i2.Uint8List>('state_encryption_key', aliasedName, true, + type: DriftSqlType.blob, requiredDuringInsert: false); + late final GeneratedColumn<i2.Uint8List> myGroupPrivateKey = + GeneratedColumn<i2.Uint8List>('my_group_private_key', aliasedName, true, + type: DriftSqlType.blob, requiredDuringInsert: false); + late final GeneratedColumn<String> groupName = GeneratedColumn<String>( + 'group_name', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn<String> draftMessage = GeneratedColumn<String>( + 'draft_message', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + late final GeneratedColumn<int> totalMediaCounter = GeneratedColumn<int>( + 'total_media_counter', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0')); + late final GeneratedColumn<bool> alsoBestFriend = GeneratedColumn<bool>( + 'also_best_friend', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("also_best_friend" IN (0, 1))'), + defaultValue: const CustomExpression('0')); + late final GeneratedColumn<int> deleteMessagesAfterMilliseconds = + GeneratedColumn<int>( + 'delete_messages_after_milliseconds', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('86400000')); + late final GeneratedColumn<DateTime> createdAt = GeneratedColumn<DateTime>( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression( + 'CAST(strftime(\'%s\', CURRENT_TIMESTAMP) AS INTEGER)')); + late final GeneratedColumn<DateTime> lastMessageSend = + GeneratedColumn<DateTime>('last_message_send', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + late final GeneratedColumn<DateTime> lastMessageReceived = + GeneratedColumn<DateTime>('last_message_received', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + late final GeneratedColumn<DateTime> lastFlameCounterChange = + GeneratedColumn<DateTime>('last_flame_counter_change', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + late final GeneratedColumn<DateTime> lastFlameSync = + GeneratedColumn<DateTime>('last_flame_sync', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + late final GeneratedColumn<int> flameCounter = GeneratedColumn<int>( + 'flame_counter', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0')); + late final GeneratedColumn<int> maxFlameCounter = GeneratedColumn<int>( + 'max_flame_counter', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0')); + late final GeneratedColumn<DateTime> maxFlameCounterFrom = + GeneratedColumn<DateTime>('max_flame_counter_from', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + late final GeneratedColumn<DateTime> lastMessageExchange = + GeneratedColumn<DateTime>('last_message_exchange', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression( + 'CAST(strftime(\'%s\', CURRENT_TIMESTAMP) AS INTEGER)')); + @override + List<GeneratedColumn> get $columns => [ + groupId, + isGroupAdmin, + isDirectChat, + pinned, + archived, + joinedGroup, + leftGroup, + deletedContent, + stateVersionId, + stateEncryptionKey, + myGroupPrivateKey, + groupName, + draftMessage, + totalMediaCounter, + alsoBestFriend, + deleteMessagesAfterMilliseconds, + createdAt, + lastMessageSend, + lastMessageReceived, + lastFlameCounterChange, + lastFlameSync, + flameCounter, + maxFlameCounter, + maxFlameCounterFrom, + lastMessageExchange + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'groups'; + @override + Set<GeneratedColumn> get $primaryKey => {groupId}; + @override + GroupsData map(Map<String, dynamic> data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return GroupsData( + groupId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}group_id'])!, + isGroupAdmin: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}is_group_admin'])!, + isDirectChat: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}is_direct_chat'])!, + pinned: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}pinned'])!, + archived: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}archived'])!, + joinedGroup: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}joined_group'])!, + leftGroup: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}left_group'])!, + deletedContent: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}deleted_content'])!, + stateVersionId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}state_version_id'])!, + stateEncryptionKey: attachedDatabase.typeMapping.read( + DriftSqlType.blob, data['${effectivePrefix}state_encryption_key']), + myGroupPrivateKey: attachedDatabase.typeMapping.read( + DriftSqlType.blob, data['${effectivePrefix}my_group_private_key']), + groupName: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}group_name'])!, + draftMessage: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}draft_message']), + totalMediaCounter: attachedDatabase.typeMapping.read( + DriftSqlType.int, data['${effectivePrefix}total_media_counter'])!, + alsoBestFriend: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}also_best_friend'])!, + deleteMessagesAfterMilliseconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}delete_messages_after_milliseconds'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + lastMessageSend: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, data['${effectivePrefix}last_message_send']), + lastMessageReceived: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}last_message_received']), + lastFlameCounterChange: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}last_flame_counter_change']), + lastFlameSync: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, data['${effectivePrefix}last_flame_sync']), + flameCounter: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}flame_counter'])!, + maxFlameCounter: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}max_flame_counter'])!, + maxFlameCounterFrom: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}max_flame_counter_from']), + lastMessageExchange: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}last_message_exchange'])!, + ); + } + + @override + Groups createAlias(String alias) { + return Groups(attachedDatabase, alias); + } +} + +class GroupsData extends DataClass implements Insertable<GroupsData> { + final String groupId; + final bool isGroupAdmin; + final bool isDirectChat; + final bool pinned; + final bool archived; + final bool joinedGroup; + final bool leftGroup; + final bool deletedContent; + final int stateVersionId; + final i2.Uint8List? stateEncryptionKey; + final i2.Uint8List? myGroupPrivateKey; + final String groupName; + final String? draftMessage; + final int totalMediaCounter; + final bool alsoBestFriend; + final int deleteMessagesAfterMilliseconds; + final DateTime createdAt; + final DateTime? lastMessageSend; + final DateTime? lastMessageReceived; + final DateTime? lastFlameCounterChange; + final DateTime? lastFlameSync; + final int flameCounter; + final int maxFlameCounter; + final DateTime? maxFlameCounterFrom; + final DateTime lastMessageExchange; + const GroupsData( + {required this.groupId, + required this.isGroupAdmin, + required this.isDirectChat, + required this.pinned, + required this.archived, + required this.joinedGroup, + required this.leftGroup, + required this.deletedContent, + required this.stateVersionId, + this.stateEncryptionKey, + this.myGroupPrivateKey, + required this.groupName, + this.draftMessage, + required this.totalMediaCounter, + required this.alsoBestFriend, + required this.deleteMessagesAfterMilliseconds, + required this.createdAt, + this.lastMessageSend, + this.lastMessageReceived, + this.lastFlameCounterChange, + this.lastFlameSync, + required this.flameCounter, + required this.maxFlameCounter, + this.maxFlameCounterFrom, + required this.lastMessageExchange}); + @override + Map<String, Expression> toColumns(bool nullToAbsent) { + final map = <String, Expression>{}; + map['group_id'] = Variable<String>(groupId); + map['is_group_admin'] = Variable<bool>(isGroupAdmin); + map['is_direct_chat'] = Variable<bool>(isDirectChat); + map['pinned'] = Variable<bool>(pinned); + map['archived'] = Variable<bool>(archived); + map['joined_group'] = Variable<bool>(joinedGroup); + map['left_group'] = Variable<bool>(leftGroup); + map['deleted_content'] = Variable<bool>(deletedContent); + map['state_version_id'] = Variable<int>(stateVersionId); + if (!nullToAbsent || stateEncryptionKey != null) { + map['state_encryption_key'] = Variable<i2.Uint8List>(stateEncryptionKey); + } + if (!nullToAbsent || myGroupPrivateKey != null) { + map['my_group_private_key'] = Variable<i2.Uint8List>(myGroupPrivateKey); + } + map['group_name'] = Variable<String>(groupName); + if (!nullToAbsent || draftMessage != null) { + map['draft_message'] = Variable<String>(draftMessage); + } + map['total_media_counter'] = Variable<int>(totalMediaCounter); + map['also_best_friend'] = Variable<bool>(alsoBestFriend); + map['delete_messages_after_milliseconds'] = + Variable<int>(deleteMessagesAfterMilliseconds); + map['created_at'] = Variable<DateTime>(createdAt); + if (!nullToAbsent || lastMessageSend != null) { + map['last_message_send'] = Variable<DateTime>(lastMessageSend); + } + if (!nullToAbsent || lastMessageReceived != null) { + map['last_message_received'] = Variable<DateTime>(lastMessageReceived); + } + if (!nullToAbsent || lastFlameCounterChange != null) { + map['last_flame_counter_change'] = + Variable<DateTime>(lastFlameCounterChange); + } + if (!nullToAbsent || lastFlameSync != null) { + map['last_flame_sync'] = Variable<DateTime>(lastFlameSync); + } + map['flame_counter'] = Variable<int>(flameCounter); + map['max_flame_counter'] = Variable<int>(maxFlameCounter); + if (!nullToAbsent || maxFlameCounterFrom != null) { + map['max_flame_counter_from'] = Variable<DateTime>(maxFlameCounterFrom); + } + map['last_message_exchange'] = Variable<DateTime>(lastMessageExchange); + return map; + } + + GroupsCompanion toCompanion(bool nullToAbsent) { + return GroupsCompanion( + groupId: Value(groupId), + isGroupAdmin: Value(isGroupAdmin), + isDirectChat: Value(isDirectChat), + pinned: Value(pinned), + archived: Value(archived), + joinedGroup: Value(joinedGroup), + leftGroup: Value(leftGroup), + deletedContent: Value(deletedContent), + stateVersionId: Value(stateVersionId), + stateEncryptionKey: stateEncryptionKey == null && nullToAbsent + ? const Value.absent() + : Value(stateEncryptionKey), + myGroupPrivateKey: myGroupPrivateKey == null && nullToAbsent + ? const Value.absent() + : Value(myGroupPrivateKey), + groupName: Value(groupName), + draftMessage: draftMessage == null && nullToAbsent + ? const Value.absent() + : Value(draftMessage), + totalMediaCounter: Value(totalMediaCounter), + alsoBestFriend: Value(alsoBestFriend), + deleteMessagesAfterMilliseconds: Value(deleteMessagesAfterMilliseconds), + createdAt: Value(createdAt), + lastMessageSend: lastMessageSend == null && nullToAbsent + ? const Value.absent() + : Value(lastMessageSend), + lastMessageReceived: lastMessageReceived == null && nullToAbsent + ? const Value.absent() + : Value(lastMessageReceived), + lastFlameCounterChange: lastFlameCounterChange == null && nullToAbsent + ? const Value.absent() + : Value(lastFlameCounterChange), + lastFlameSync: lastFlameSync == null && nullToAbsent + ? const Value.absent() + : Value(lastFlameSync), + flameCounter: Value(flameCounter), + maxFlameCounter: Value(maxFlameCounter), + maxFlameCounterFrom: maxFlameCounterFrom == null && nullToAbsent + ? const Value.absent() + : Value(maxFlameCounterFrom), + lastMessageExchange: Value(lastMessageExchange), + ); + } + + factory GroupsData.fromJson(Map<String, dynamic> json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return GroupsData( + groupId: serializer.fromJson<String>(json['groupId']), + isGroupAdmin: serializer.fromJson<bool>(json['isGroupAdmin']), + isDirectChat: serializer.fromJson<bool>(json['isDirectChat']), + pinned: serializer.fromJson<bool>(json['pinned']), + archived: serializer.fromJson<bool>(json['archived']), + joinedGroup: serializer.fromJson<bool>(json['joinedGroup']), + leftGroup: serializer.fromJson<bool>(json['leftGroup']), + deletedContent: serializer.fromJson<bool>(json['deletedContent']), + stateVersionId: serializer.fromJson<int>(json['stateVersionId']), + stateEncryptionKey: + serializer.fromJson<i2.Uint8List?>(json['stateEncryptionKey']), + myGroupPrivateKey: + serializer.fromJson<i2.Uint8List?>(json['myGroupPrivateKey']), + groupName: serializer.fromJson<String>(json['groupName']), + draftMessage: serializer.fromJson<String?>(json['draftMessage']), + totalMediaCounter: serializer.fromJson<int>(json['totalMediaCounter']), + alsoBestFriend: serializer.fromJson<bool>(json['alsoBestFriend']), + deleteMessagesAfterMilliseconds: + serializer.fromJson<int>(json['deleteMessagesAfterMilliseconds']), + createdAt: serializer.fromJson<DateTime>(json['createdAt']), + lastMessageSend: serializer.fromJson<DateTime?>(json['lastMessageSend']), + lastMessageReceived: + serializer.fromJson<DateTime?>(json['lastMessageReceived']), + lastFlameCounterChange: + serializer.fromJson<DateTime?>(json['lastFlameCounterChange']), + lastFlameSync: serializer.fromJson<DateTime?>(json['lastFlameSync']), + flameCounter: serializer.fromJson<int>(json['flameCounter']), + maxFlameCounter: serializer.fromJson<int>(json['maxFlameCounter']), + maxFlameCounterFrom: + serializer.fromJson<DateTime?>(json['maxFlameCounterFrom']), + lastMessageExchange: + serializer.fromJson<DateTime>(json['lastMessageExchange']), + ); + } + @override + Map<String, dynamic> toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return <String, dynamic>{ + 'groupId': serializer.toJson<String>(groupId), + 'isGroupAdmin': serializer.toJson<bool>(isGroupAdmin), + 'isDirectChat': serializer.toJson<bool>(isDirectChat), + 'pinned': serializer.toJson<bool>(pinned), + 'archived': serializer.toJson<bool>(archived), + 'joinedGroup': serializer.toJson<bool>(joinedGroup), + 'leftGroup': serializer.toJson<bool>(leftGroup), + 'deletedContent': serializer.toJson<bool>(deletedContent), + 'stateVersionId': serializer.toJson<int>(stateVersionId), + 'stateEncryptionKey': + serializer.toJson<i2.Uint8List?>(stateEncryptionKey), + 'myGroupPrivateKey': serializer.toJson<i2.Uint8List?>(myGroupPrivateKey), + 'groupName': serializer.toJson<String>(groupName), + 'draftMessage': serializer.toJson<String?>(draftMessage), + 'totalMediaCounter': serializer.toJson<int>(totalMediaCounter), + 'alsoBestFriend': serializer.toJson<bool>(alsoBestFriend), + 'deleteMessagesAfterMilliseconds': + serializer.toJson<int>(deleteMessagesAfterMilliseconds), + 'createdAt': serializer.toJson<DateTime>(createdAt), + 'lastMessageSend': serializer.toJson<DateTime?>(lastMessageSend), + 'lastMessageReceived': serializer.toJson<DateTime?>(lastMessageReceived), + 'lastFlameCounterChange': + serializer.toJson<DateTime?>(lastFlameCounterChange), + 'lastFlameSync': serializer.toJson<DateTime?>(lastFlameSync), + 'flameCounter': serializer.toJson<int>(flameCounter), + 'maxFlameCounter': serializer.toJson<int>(maxFlameCounter), + 'maxFlameCounterFrom': serializer.toJson<DateTime?>(maxFlameCounterFrom), + 'lastMessageExchange': serializer.toJson<DateTime>(lastMessageExchange), + }; + } + + GroupsData copyWith( + {String? groupId, + bool? isGroupAdmin, + bool? isDirectChat, + bool? pinned, + bool? archived, + bool? joinedGroup, + bool? leftGroup, + bool? deletedContent, + int? stateVersionId, + Value<i2.Uint8List?> stateEncryptionKey = const Value.absent(), + Value<i2.Uint8List?> myGroupPrivateKey = const Value.absent(), + String? groupName, + Value<String?> draftMessage = const Value.absent(), + int? totalMediaCounter, + bool? alsoBestFriend, + int? deleteMessagesAfterMilliseconds, + DateTime? createdAt, + Value<DateTime?> lastMessageSend = const Value.absent(), + Value<DateTime?> lastMessageReceived = const Value.absent(), + Value<DateTime?> lastFlameCounterChange = const Value.absent(), + Value<DateTime?> lastFlameSync = const Value.absent(), + int? flameCounter, + int? maxFlameCounter, + Value<DateTime?> maxFlameCounterFrom = const Value.absent(), + DateTime? lastMessageExchange}) => + GroupsData( + groupId: groupId ?? this.groupId, + isGroupAdmin: isGroupAdmin ?? this.isGroupAdmin, + isDirectChat: isDirectChat ?? this.isDirectChat, + pinned: pinned ?? this.pinned, + archived: archived ?? this.archived, + joinedGroup: joinedGroup ?? this.joinedGroup, + leftGroup: leftGroup ?? this.leftGroup, + deletedContent: deletedContent ?? this.deletedContent, + stateVersionId: stateVersionId ?? this.stateVersionId, + stateEncryptionKey: stateEncryptionKey.present + ? stateEncryptionKey.value + : this.stateEncryptionKey, + myGroupPrivateKey: myGroupPrivateKey.present + ? myGroupPrivateKey.value + : this.myGroupPrivateKey, + groupName: groupName ?? this.groupName, + draftMessage: + draftMessage.present ? draftMessage.value : this.draftMessage, + totalMediaCounter: totalMediaCounter ?? this.totalMediaCounter, + alsoBestFriend: alsoBestFriend ?? this.alsoBestFriend, + deleteMessagesAfterMilliseconds: deleteMessagesAfterMilliseconds ?? + this.deleteMessagesAfterMilliseconds, + createdAt: createdAt ?? this.createdAt, + lastMessageSend: lastMessageSend.present + ? lastMessageSend.value + : this.lastMessageSend, + lastMessageReceived: lastMessageReceived.present + ? lastMessageReceived.value + : this.lastMessageReceived, + lastFlameCounterChange: lastFlameCounterChange.present + ? lastFlameCounterChange.value + : this.lastFlameCounterChange, + lastFlameSync: + lastFlameSync.present ? lastFlameSync.value : this.lastFlameSync, + flameCounter: flameCounter ?? this.flameCounter, + maxFlameCounter: maxFlameCounter ?? this.maxFlameCounter, + maxFlameCounterFrom: maxFlameCounterFrom.present + ? maxFlameCounterFrom.value + : this.maxFlameCounterFrom, + lastMessageExchange: lastMessageExchange ?? this.lastMessageExchange, + ); + GroupsData copyWithCompanion(GroupsCompanion data) { + return GroupsData( + groupId: data.groupId.present ? data.groupId.value : this.groupId, + isGroupAdmin: data.isGroupAdmin.present + ? data.isGroupAdmin.value + : this.isGroupAdmin, + isDirectChat: data.isDirectChat.present + ? data.isDirectChat.value + : this.isDirectChat, + pinned: data.pinned.present ? data.pinned.value : this.pinned, + archived: data.archived.present ? data.archived.value : this.archived, + joinedGroup: + data.joinedGroup.present ? data.joinedGroup.value : this.joinedGroup, + leftGroup: data.leftGroup.present ? data.leftGroup.value : this.leftGroup, + deletedContent: data.deletedContent.present + ? data.deletedContent.value + : this.deletedContent, + stateVersionId: data.stateVersionId.present + ? data.stateVersionId.value + : this.stateVersionId, + stateEncryptionKey: data.stateEncryptionKey.present + ? data.stateEncryptionKey.value + : this.stateEncryptionKey, + myGroupPrivateKey: data.myGroupPrivateKey.present + ? data.myGroupPrivateKey.value + : this.myGroupPrivateKey, + groupName: data.groupName.present ? data.groupName.value : this.groupName, + draftMessage: data.draftMessage.present + ? data.draftMessage.value + : this.draftMessage, + totalMediaCounter: data.totalMediaCounter.present + ? data.totalMediaCounter.value + : this.totalMediaCounter, + alsoBestFriend: data.alsoBestFriend.present + ? data.alsoBestFriend.value + : this.alsoBestFriend, + deleteMessagesAfterMilliseconds: + data.deleteMessagesAfterMilliseconds.present + ? data.deleteMessagesAfterMilliseconds.value + : this.deleteMessagesAfterMilliseconds, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + lastMessageSend: data.lastMessageSend.present + ? data.lastMessageSend.value + : this.lastMessageSend, + lastMessageReceived: data.lastMessageReceived.present + ? data.lastMessageReceived.value + : this.lastMessageReceived, + lastFlameCounterChange: data.lastFlameCounterChange.present + ? data.lastFlameCounterChange.value + : this.lastFlameCounterChange, + lastFlameSync: data.lastFlameSync.present + ? data.lastFlameSync.value + : this.lastFlameSync, + flameCounter: data.flameCounter.present + ? data.flameCounter.value + : this.flameCounter, + maxFlameCounter: data.maxFlameCounter.present + ? data.maxFlameCounter.value + : this.maxFlameCounter, + maxFlameCounterFrom: data.maxFlameCounterFrom.present + ? data.maxFlameCounterFrom.value + : this.maxFlameCounterFrom, + lastMessageExchange: data.lastMessageExchange.present + ? data.lastMessageExchange.value + : this.lastMessageExchange, + ); + } + + @override + String toString() { + return (StringBuffer('GroupsData(') + ..write('groupId: $groupId, ') + ..write('isGroupAdmin: $isGroupAdmin, ') + ..write('isDirectChat: $isDirectChat, ') + ..write('pinned: $pinned, ') + ..write('archived: $archived, ') + ..write('joinedGroup: $joinedGroup, ') + ..write('leftGroup: $leftGroup, ') + ..write('deletedContent: $deletedContent, ') + ..write('stateVersionId: $stateVersionId, ') + ..write('stateEncryptionKey: $stateEncryptionKey, ') + ..write('myGroupPrivateKey: $myGroupPrivateKey, ') + ..write('groupName: $groupName, ') + ..write('draftMessage: $draftMessage, ') + ..write('totalMediaCounter: $totalMediaCounter, ') + ..write('alsoBestFriend: $alsoBestFriend, ') + ..write( + 'deleteMessagesAfterMilliseconds: $deleteMessagesAfterMilliseconds, ') + ..write('createdAt: $createdAt, ') + ..write('lastMessageSend: $lastMessageSend, ') + ..write('lastMessageReceived: $lastMessageReceived, ') + ..write('lastFlameCounterChange: $lastFlameCounterChange, ') + ..write('lastFlameSync: $lastFlameSync, ') + ..write('flameCounter: $flameCounter, ') + ..write('maxFlameCounter: $maxFlameCounter, ') + ..write('maxFlameCounterFrom: $maxFlameCounterFrom, ') + ..write('lastMessageExchange: $lastMessageExchange') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hashAll([ + groupId, + isGroupAdmin, + isDirectChat, + pinned, + archived, + joinedGroup, + leftGroup, + deletedContent, + stateVersionId, + $driftBlobEquality.hash(stateEncryptionKey), + $driftBlobEquality.hash(myGroupPrivateKey), + groupName, + draftMessage, + totalMediaCounter, + alsoBestFriend, + deleteMessagesAfterMilliseconds, + createdAt, + lastMessageSend, + lastMessageReceived, + lastFlameCounterChange, + lastFlameSync, + flameCounter, + maxFlameCounter, + maxFlameCounterFrom, + lastMessageExchange + ]); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is GroupsData && + other.groupId == this.groupId && + other.isGroupAdmin == this.isGroupAdmin && + other.isDirectChat == this.isDirectChat && + other.pinned == this.pinned && + other.archived == this.archived && + other.joinedGroup == this.joinedGroup && + other.leftGroup == this.leftGroup && + other.deletedContent == this.deletedContent && + other.stateVersionId == this.stateVersionId && + $driftBlobEquality.equals( + other.stateEncryptionKey, this.stateEncryptionKey) && + $driftBlobEquality.equals( + other.myGroupPrivateKey, this.myGroupPrivateKey) && + other.groupName == this.groupName && + other.draftMessage == this.draftMessage && + other.totalMediaCounter == this.totalMediaCounter && + other.alsoBestFriend == this.alsoBestFriend && + other.deleteMessagesAfterMilliseconds == + this.deleteMessagesAfterMilliseconds && + other.createdAt == this.createdAt && + other.lastMessageSend == this.lastMessageSend && + other.lastMessageReceived == this.lastMessageReceived && + other.lastFlameCounterChange == this.lastFlameCounterChange && + other.lastFlameSync == this.lastFlameSync && + other.flameCounter == this.flameCounter && + other.maxFlameCounter == this.maxFlameCounter && + other.maxFlameCounterFrom == this.maxFlameCounterFrom && + other.lastMessageExchange == this.lastMessageExchange); +} + +class GroupsCompanion extends UpdateCompanion<GroupsData> { + final Value<String> groupId; + final Value<bool> isGroupAdmin; + final Value<bool> isDirectChat; + final Value<bool> pinned; + final Value<bool> archived; + final Value<bool> joinedGroup; + final Value<bool> leftGroup; + final Value<bool> deletedContent; + final Value<int> stateVersionId; + final Value<i2.Uint8List?> stateEncryptionKey; + final Value<i2.Uint8List?> myGroupPrivateKey; + final Value<String> groupName; + final Value<String?> draftMessage; + final Value<int> totalMediaCounter; + final Value<bool> alsoBestFriend; + final Value<int> deleteMessagesAfterMilliseconds; + final Value<DateTime> createdAt; + final Value<DateTime?> lastMessageSend; + final Value<DateTime?> lastMessageReceived; + final Value<DateTime?> lastFlameCounterChange; + final Value<DateTime?> lastFlameSync; + final Value<int> flameCounter; + final Value<int> maxFlameCounter; + final Value<DateTime?> maxFlameCounterFrom; + final Value<DateTime> lastMessageExchange; + final Value<int> rowid; + const GroupsCompanion({ + this.groupId = const Value.absent(), + this.isGroupAdmin = const Value.absent(), + this.isDirectChat = const Value.absent(), + this.pinned = const Value.absent(), + this.archived = const Value.absent(), + this.joinedGroup = const Value.absent(), + this.leftGroup = const Value.absent(), + this.deletedContent = const Value.absent(), + this.stateVersionId = const Value.absent(), + this.stateEncryptionKey = const Value.absent(), + this.myGroupPrivateKey = const Value.absent(), + this.groupName = const Value.absent(), + this.draftMessage = const Value.absent(), + this.totalMediaCounter = const Value.absent(), + this.alsoBestFriend = const Value.absent(), + this.deleteMessagesAfterMilliseconds = const Value.absent(), + this.createdAt = const Value.absent(), + this.lastMessageSend = const Value.absent(), + this.lastMessageReceived = const Value.absent(), + this.lastFlameCounterChange = const Value.absent(), + this.lastFlameSync = const Value.absent(), + this.flameCounter = const Value.absent(), + this.maxFlameCounter = const Value.absent(), + this.maxFlameCounterFrom = const Value.absent(), + this.lastMessageExchange = const Value.absent(), + this.rowid = const Value.absent(), + }); + GroupsCompanion.insert({ + required String groupId, + this.isGroupAdmin = const Value.absent(), + this.isDirectChat = const Value.absent(), + this.pinned = const Value.absent(), + this.archived = const Value.absent(), + this.joinedGroup = const Value.absent(), + this.leftGroup = const Value.absent(), + this.deletedContent = const Value.absent(), + this.stateVersionId = const Value.absent(), + this.stateEncryptionKey = const Value.absent(), + this.myGroupPrivateKey = const Value.absent(), + required String groupName, + this.draftMessage = const Value.absent(), + this.totalMediaCounter = const Value.absent(), + this.alsoBestFriend = const Value.absent(), + this.deleteMessagesAfterMilliseconds = const Value.absent(), + this.createdAt = const Value.absent(), + this.lastMessageSend = const Value.absent(), + this.lastMessageReceived = const Value.absent(), + this.lastFlameCounterChange = const Value.absent(), + this.lastFlameSync = const Value.absent(), + this.flameCounter = const Value.absent(), + this.maxFlameCounter = const Value.absent(), + this.maxFlameCounterFrom = const Value.absent(), + this.lastMessageExchange = const Value.absent(), + this.rowid = const Value.absent(), + }) : groupId = Value(groupId), + groupName = Value(groupName); + static Insertable<GroupsData> custom({ + Expression<String>? groupId, + Expression<bool>? isGroupAdmin, + Expression<bool>? isDirectChat, + Expression<bool>? pinned, + Expression<bool>? archived, + Expression<bool>? joinedGroup, + Expression<bool>? leftGroup, + Expression<bool>? deletedContent, + Expression<int>? stateVersionId, + Expression<i2.Uint8List>? stateEncryptionKey, + Expression<i2.Uint8List>? myGroupPrivateKey, + Expression<String>? groupName, + Expression<String>? draftMessage, + Expression<int>? totalMediaCounter, + Expression<bool>? alsoBestFriend, + Expression<int>? deleteMessagesAfterMilliseconds, + Expression<DateTime>? createdAt, + Expression<DateTime>? lastMessageSend, + Expression<DateTime>? lastMessageReceived, + Expression<DateTime>? lastFlameCounterChange, + Expression<DateTime>? lastFlameSync, + Expression<int>? flameCounter, + Expression<int>? maxFlameCounter, + Expression<DateTime>? maxFlameCounterFrom, + Expression<DateTime>? lastMessageExchange, + Expression<int>? rowid, + }) { + return RawValuesInsertable({ + if (groupId != null) 'group_id': groupId, + if (isGroupAdmin != null) 'is_group_admin': isGroupAdmin, + if (isDirectChat != null) 'is_direct_chat': isDirectChat, + if (pinned != null) 'pinned': pinned, + if (archived != null) 'archived': archived, + if (joinedGroup != null) 'joined_group': joinedGroup, + if (leftGroup != null) 'left_group': leftGroup, + if (deletedContent != null) 'deleted_content': deletedContent, + if (stateVersionId != null) 'state_version_id': stateVersionId, + if (stateEncryptionKey != null) + 'state_encryption_key': stateEncryptionKey, + if (myGroupPrivateKey != null) 'my_group_private_key': myGroupPrivateKey, + if (groupName != null) 'group_name': groupName, + if (draftMessage != null) 'draft_message': draftMessage, + if (totalMediaCounter != null) 'total_media_counter': totalMediaCounter, + if (alsoBestFriend != null) 'also_best_friend': alsoBestFriend, + if (deleteMessagesAfterMilliseconds != null) + 'delete_messages_after_milliseconds': deleteMessagesAfterMilliseconds, + if (createdAt != null) 'created_at': createdAt, + if (lastMessageSend != null) 'last_message_send': lastMessageSend, + if (lastMessageReceived != null) + 'last_message_received': lastMessageReceived, + if (lastFlameCounterChange != null) + 'last_flame_counter_change': lastFlameCounterChange, + if (lastFlameSync != null) 'last_flame_sync': lastFlameSync, + if (flameCounter != null) 'flame_counter': flameCounter, + if (maxFlameCounter != null) 'max_flame_counter': maxFlameCounter, + if (maxFlameCounterFrom != null) + 'max_flame_counter_from': maxFlameCounterFrom, + if (lastMessageExchange != null) + 'last_message_exchange': lastMessageExchange, + if (rowid != null) 'rowid': rowid, + }); + } + + GroupsCompanion copyWith( + {Value<String>? groupId, + Value<bool>? isGroupAdmin, + Value<bool>? isDirectChat, + Value<bool>? pinned, + Value<bool>? archived, + Value<bool>? joinedGroup, + Value<bool>? leftGroup, + Value<bool>? deletedContent, + Value<int>? stateVersionId, + Value<i2.Uint8List?>? stateEncryptionKey, + Value<i2.Uint8List?>? myGroupPrivateKey, + Value<String>? groupName, + Value<String?>? draftMessage, + Value<int>? totalMediaCounter, + Value<bool>? alsoBestFriend, + Value<int>? deleteMessagesAfterMilliseconds, + Value<DateTime>? createdAt, + Value<DateTime?>? lastMessageSend, + Value<DateTime?>? lastMessageReceived, + Value<DateTime?>? lastFlameCounterChange, + Value<DateTime?>? lastFlameSync, + Value<int>? flameCounter, + Value<int>? maxFlameCounter, + Value<DateTime?>? maxFlameCounterFrom, + Value<DateTime>? lastMessageExchange, + Value<int>? rowid}) { + return GroupsCompanion( + groupId: groupId ?? this.groupId, + isGroupAdmin: isGroupAdmin ?? this.isGroupAdmin, + isDirectChat: isDirectChat ?? this.isDirectChat, + pinned: pinned ?? this.pinned, + archived: archived ?? this.archived, + joinedGroup: joinedGroup ?? this.joinedGroup, + leftGroup: leftGroup ?? this.leftGroup, + deletedContent: deletedContent ?? this.deletedContent, + stateVersionId: stateVersionId ?? this.stateVersionId, + stateEncryptionKey: stateEncryptionKey ?? this.stateEncryptionKey, + myGroupPrivateKey: myGroupPrivateKey ?? this.myGroupPrivateKey, + groupName: groupName ?? this.groupName, + draftMessage: draftMessage ?? this.draftMessage, + totalMediaCounter: totalMediaCounter ?? this.totalMediaCounter, + alsoBestFriend: alsoBestFriend ?? this.alsoBestFriend, + deleteMessagesAfterMilliseconds: deleteMessagesAfterMilliseconds ?? + this.deleteMessagesAfterMilliseconds, + createdAt: createdAt ?? this.createdAt, + lastMessageSend: lastMessageSend ?? this.lastMessageSend, + lastMessageReceived: lastMessageReceived ?? this.lastMessageReceived, + lastFlameCounterChange: + lastFlameCounterChange ?? this.lastFlameCounterChange, + lastFlameSync: lastFlameSync ?? this.lastFlameSync, + flameCounter: flameCounter ?? this.flameCounter, + maxFlameCounter: maxFlameCounter ?? this.maxFlameCounter, + maxFlameCounterFrom: maxFlameCounterFrom ?? this.maxFlameCounterFrom, + lastMessageExchange: lastMessageExchange ?? this.lastMessageExchange, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map<String, Expression> toColumns(bool nullToAbsent) { + final map = <String, Expression>{}; + if (groupId.present) { + map['group_id'] = Variable<String>(groupId.value); + } + if (isGroupAdmin.present) { + map['is_group_admin'] = Variable<bool>(isGroupAdmin.value); + } + if (isDirectChat.present) { + map['is_direct_chat'] = Variable<bool>(isDirectChat.value); + } + if (pinned.present) { + map['pinned'] = Variable<bool>(pinned.value); + } + if (archived.present) { + map['archived'] = Variable<bool>(archived.value); + } + if (joinedGroup.present) { + map['joined_group'] = Variable<bool>(joinedGroup.value); + } + if (leftGroup.present) { + map['left_group'] = Variable<bool>(leftGroup.value); + } + if (deletedContent.present) { + map['deleted_content'] = Variable<bool>(deletedContent.value); + } + if (stateVersionId.present) { + map['state_version_id'] = Variable<int>(stateVersionId.value); + } + if (stateEncryptionKey.present) { + map['state_encryption_key'] = + Variable<i2.Uint8List>(stateEncryptionKey.value); + } + if (myGroupPrivateKey.present) { + map['my_group_private_key'] = + Variable<i2.Uint8List>(myGroupPrivateKey.value); + } + if (groupName.present) { + map['group_name'] = Variable<String>(groupName.value); + } + if (draftMessage.present) { + map['draft_message'] = Variable<String>(draftMessage.value); + } + if (totalMediaCounter.present) { + map['total_media_counter'] = Variable<int>(totalMediaCounter.value); + } + if (alsoBestFriend.present) { + map['also_best_friend'] = Variable<bool>(alsoBestFriend.value); + } + if (deleteMessagesAfterMilliseconds.present) { + map['delete_messages_after_milliseconds'] = + Variable<int>(deleteMessagesAfterMilliseconds.value); + } + if (createdAt.present) { + map['created_at'] = Variable<DateTime>(createdAt.value); + } + if (lastMessageSend.present) { + map['last_message_send'] = Variable<DateTime>(lastMessageSend.value); + } + if (lastMessageReceived.present) { + map['last_message_received'] = + Variable<DateTime>(lastMessageReceived.value); + } + if (lastFlameCounterChange.present) { + map['last_flame_counter_change'] = + Variable<DateTime>(lastFlameCounterChange.value); + } + if (lastFlameSync.present) { + map['last_flame_sync'] = Variable<DateTime>(lastFlameSync.value); + } + if (flameCounter.present) { + map['flame_counter'] = Variable<int>(flameCounter.value); + } + if (maxFlameCounter.present) { + map['max_flame_counter'] = Variable<int>(maxFlameCounter.value); + } + if (maxFlameCounterFrom.present) { + map['max_flame_counter_from'] = + Variable<DateTime>(maxFlameCounterFrom.value); + } + if (lastMessageExchange.present) { + map['last_message_exchange'] = + Variable<DateTime>(lastMessageExchange.value); + } + if (rowid.present) { + map['rowid'] = Variable<int>(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('GroupsCompanion(') + ..write('groupId: $groupId, ') + ..write('isGroupAdmin: $isGroupAdmin, ') + ..write('isDirectChat: $isDirectChat, ') + ..write('pinned: $pinned, ') + ..write('archived: $archived, ') + ..write('joinedGroup: $joinedGroup, ') + ..write('leftGroup: $leftGroup, ') + ..write('deletedContent: $deletedContent, ') + ..write('stateVersionId: $stateVersionId, ') + ..write('stateEncryptionKey: $stateEncryptionKey, ') + ..write('myGroupPrivateKey: $myGroupPrivateKey, ') + ..write('groupName: $groupName, ') + ..write('draftMessage: $draftMessage, ') + ..write('totalMediaCounter: $totalMediaCounter, ') + ..write('alsoBestFriend: $alsoBestFriend, ') + ..write( + 'deleteMessagesAfterMilliseconds: $deleteMessagesAfterMilliseconds, ') + ..write('createdAt: $createdAt, ') + ..write('lastMessageSend: $lastMessageSend, ') + ..write('lastMessageReceived: $lastMessageReceived, ') + ..write('lastFlameCounterChange: $lastFlameCounterChange, ') + ..write('lastFlameSync: $lastFlameSync, ') + ..write('flameCounter: $flameCounter, ') + ..write('maxFlameCounter: $maxFlameCounter, ') + ..write('maxFlameCounterFrom: $maxFlameCounterFrom, ') + ..write('lastMessageExchange: $lastMessageExchange, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class MediaFiles extends Table with TableInfo<MediaFiles, MediaFilesData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MediaFiles(this.attachedDatabase, [this._alias]); + late final GeneratedColumn<String> mediaId = GeneratedColumn<String>( + 'media_id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn<String> type = GeneratedColumn<String>( + 'type', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn<String> uploadState = GeneratedColumn<String>( + 'upload_state', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + late final GeneratedColumn<String> downloadState = GeneratedColumn<String>( + 'download_state', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + late final GeneratedColumn<bool> requiresAuthentication = + GeneratedColumn<bool>('requires_authentication', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("requires_authentication" IN (0, 1))'), + defaultValue: const CustomExpression('0')); + late final GeneratedColumn<bool> stored = GeneratedColumn<bool>( + 'stored', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("stored" IN (0, 1))'), + defaultValue: const CustomExpression('0')); + late final GeneratedColumn<bool> isDraftMedia = GeneratedColumn<bool>( + 'is_draft_media', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_draft_media" IN (0, 1))'), + defaultValue: const CustomExpression('0')); + late final GeneratedColumn<String> reuploadRequestedBy = + GeneratedColumn<String>('reupload_requested_by', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + late final GeneratedColumn<int> displayLimitInMilliseconds = + GeneratedColumn<int>('display_limit_in_milliseconds', aliasedName, true, + type: DriftSqlType.int, requiredDuringInsert: false); + late final GeneratedColumn<bool> removeAudio = GeneratedColumn<bool>( + 'remove_audio', aliasedName, true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("remove_audio" IN (0, 1))')); + late final GeneratedColumn<i2.Uint8List> downloadToken = + GeneratedColumn<i2.Uint8List>('download_token', aliasedName, true, + type: DriftSqlType.blob, requiredDuringInsert: false); + late final GeneratedColumn<i2.Uint8List> encryptionKey = + GeneratedColumn<i2.Uint8List>('encryption_key', aliasedName, true, + type: DriftSqlType.blob, requiredDuringInsert: false); + late final GeneratedColumn<i2.Uint8List> encryptionMac = + GeneratedColumn<i2.Uint8List>('encryption_mac', aliasedName, true, + type: DriftSqlType.blob, requiredDuringInsert: false); + late final GeneratedColumn<i2.Uint8List> encryptionNonce = + GeneratedColumn<i2.Uint8List>('encryption_nonce', aliasedName, true, + type: DriftSqlType.blob, requiredDuringInsert: false); + late final GeneratedColumn<i2.Uint8List> storedFileHash = + GeneratedColumn<i2.Uint8List>('stored_file_hash', aliasedName, true, + type: DriftSqlType.blob, requiredDuringInsert: false); + late final GeneratedColumn<DateTime> createdAt = GeneratedColumn<DateTime>( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression( + 'CAST(strftime(\'%s\', CURRENT_TIMESTAMP) AS INTEGER)')); + @override + List<GeneratedColumn> get $columns => [ + mediaId, + type, + uploadState, + downloadState, + requiresAuthentication, + stored, + isDraftMedia, + reuploadRequestedBy, + displayLimitInMilliseconds, + removeAudio, + downloadToken, + encryptionKey, + encryptionMac, + encryptionNonce, + storedFileHash, + createdAt + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'media_files'; + @override + Set<GeneratedColumn> get $primaryKey => {mediaId}; + @override + MediaFilesData map(Map<String, dynamic> data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MediaFilesData( + mediaId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}media_id'])!, + type: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}type'])!, + uploadState: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}upload_state']), + downloadState: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}download_state']), + requiresAuthentication: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}requires_authentication'])!, + stored: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}stored'])!, + isDraftMedia: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}is_draft_media'])!, + reuploadRequestedBy: attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}reupload_requested_by']), + displayLimitInMilliseconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}display_limit_in_milliseconds']), + removeAudio: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}remove_audio']), + downloadToken: attachedDatabase.typeMapping + .read(DriftSqlType.blob, data['${effectivePrefix}download_token']), + encryptionKey: attachedDatabase.typeMapping + .read(DriftSqlType.blob, data['${effectivePrefix}encryption_key']), + encryptionMac: attachedDatabase.typeMapping + .read(DriftSqlType.blob, data['${effectivePrefix}encryption_mac']), + encryptionNonce: attachedDatabase.typeMapping + .read(DriftSqlType.blob, data['${effectivePrefix}encryption_nonce']), + storedFileHash: attachedDatabase.typeMapping + .read(DriftSqlType.blob, data['${effectivePrefix}stored_file_hash']), + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + ); + } + + @override + MediaFiles createAlias(String alias) { + return MediaFiles(attachedDatabase, alias); + } +} + +class MediaFilesData extends DataClass implements Insertable<MediaFilesData> { + final String mediaId; + final String type; + final String? uploadState; + final String? downloadState; + final bool requiresAuthentication; + final bool stored; + final bool isDraftMedia; + final String? reuploadRequestedBy; + final int? displayLimitInMilliseconds; + final bool? removeAudio; + final i2.Uint8List? downloadToken; + final i2.Uint8List? encryptionKey; + final i2.Uint8List? encryptionMac; + final i2.Uint8List? encryptionNonce; + final i2.Uint8List? storedFileHash; + final DateTime createdAt; + const MediaFilesData( + {required this.mediaId, + required this.type, + this.uploadState, + this.downloadState, + required this.requiresAuthentication, + required this.stored, + required this.isDraftMedia, + this.reuploadRequestedBy, + this.displayLimitInMilliseconds, + this.removeAudio, + this.downloadToken, + this.encryptionKey, + this.encryptionMac, + this.encryptionNonce, + this.storedFileHash, + required this.createdAt}); + @override + Map<String, Expression> toColumns(bool nullToAbsent) { + final map = <String, Expression>{}; + map['media_id'] = Variable<String>(mediaId); + map['type'] = Variable<String>(type); + if (!nullToAbsent || uploadState != null) { + map['upload_state'] = Variable<String>(uploadState); + } + if (!nullToAbsent || downloadState != null) { + map['download_state'] = Variable<String>(downloadState); + } + map['requires_authentication'] = Variable<bool>(requiresAuthentication); + map['stored'] = Variable<bool>(stored); + map['is_draft_media'] = Variable<bool>(isDraftMedia); + if (!nullToAbsent || reuploadRequestedBy != null) { + map['reupload_requested_by'] = Variable<String>(reuploadRequestedBy); + } + if (!nullToAbsent || displayLimitInMilliseconds != null) { + map['display_limit_in_milliseconds'] = + Variable<int>(displayLimitInMilliseconds); + } + if (!nullToAbsent || removeAudio != null) { + map['remove_audio'] = Variable<bool>(removeAudio); + } + if (!nullToAbsent || downloadToken != null) { + map['download_token'] = Variable<i2.Uint8List>(downloadToken); + } + if (!nullToAbsent || encryptionKey != null) { + map['encryption_key'] = Variable<i2.Uint8List>(encryptionKey); + } + if (!nullToAbsent || encryptionMac != null) { + map['encryption_mac'] = Variable<i2.Uint8List>(encryptionMac); + } + if (!nullToAbsent || encryptionNonce != null) { + map['encryption_nonce'] = Variable<i2.Uint8List>(encryptionNonce); + } + if (!nullToAbsent || storedFileHash != null) { + map['stored_file_hash'] = Variable<i2.Uint8List>(storedFileHash); + } + map['created_at'] = Variable<DateTime>(createdAt); + return map; + } + + MediaFilesCompanion toCompanion(bool nullToAbsent) { + return MediaFilesCompanion( + mediaId: Value(mediaId), + type: Value(type), + uploadState: uploadState == null && nullToAbsent + ? const Value.absent() + : Value(uploadState), + downloadState: downloadState == null && nullToAbsent + ? const Value.absent() + : Value(downloadState), + requiresAuthentication: Value(requiresAuthentication), + stored: Value(stored), + isDraftMedia: Value(isDraftMedia), + reuploadRequestedBy: reuploadRequestedBy == null && nullToAbsent + ? const Value.absent() + : Value(reuploadRequestedBy), + displayLimitInMilliseconds: + displayLimitInMilliseconds == null && nullToAbsent + ? const Value.absent() + : Value(displayLimitInMilliseconds), + removeAudio: removeAudio == null && nullToAbsent + ? const Value.absent() + : Value(removeAudio), + downloadToken: downloadToken == null && nullToAbsent + ? const Value.absent() + : Value(downloadToken), + encryptionKey: encryptionKey == null && nullToAbsent + ? const Value.absent() + : Value(encryptionKey), + encryptionMac: encryptionMac == null && nullToAbsent + ? const Value.absent() + : Value(encryptionMac), + encryptionNonce: encryptionNonce == null && nullToAbsent + ? const Value.absent() + : Value(encryptionNonce), + storedFileHash: storedFileHash == null && nullToAbsent + ? const Value.absent() + : Value(storedFileHash), + createdAt: Value(createdAt), + ); + } + + factory MediaFilesData.fromJson(Map<String, dynamic> json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MediaFilesData( + mediaId: serializer.fromJson<String>(json['mediaId']), + type: serializer.fromJson<String>(json['type']), + uploadState: serializer.fromJson<String?>(json['uploadState']), + downloadState: serializer.fromJson<String?>(json['downloadState']), + requiresAuthentication: + serializer.fromJson<bool>(json['requiresAuthentication']), + stored: serializer.fromJson<bool>(json['stored']), + isDraftMedia: serializer.fromJson<bool>(json['isDraftMedia']), + reuploadRequestedBy: + serializer.fromJson<String?>(json['reuploadRequestedBy']), + displayLimitInMilliseconds: + serializer.fromJson<int?>(json['displayLimitInMilliseconds']), + removeAudio: serializer.fromJson<bool?>(json['removeAudio']), + downloadToken: serializer.fromJson<i2.Uint8List?>(json['downloadToken']), + encryptionKey: serializer.fromJson<i2.Uint8List?>(json['encryptionKey']), + encryptionMac: serializer.fromJson<i2.Uint8List?>(json['encryptionMac']), + encryptionNonce: + serializer.fromJson<i2.Uint8List?>(json['encryptionNonce']), + storedFileHash: + serializer.fromJson<i2.Uint8List?>(json['storedFileHash']), + createdAt: serializer.fromJson<DateTime>(json['createdAt']), + ); + } + @override + Map<String, dynamic> toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return <String, dynamic>{ + 'mediaId': serializer.toJson<String>(mediaId), + 'type': serializer.toJson<String>(type), + 'uploadState': serializer.toJson<String?>(uploadState), + 'downloadState': serializer.toJson<String?>(downloadState), + 'requiresAuthentication': serializer.toJson<bool>(requiresAuthentication), + 'stored': serializer.toJson<bool>(stored), + 'isDraftMedia': serializer.toJson<bool>(isDraftMedia), + 'reuploadRequestedBy': serializer.toJson<String?>(reuploadRequestedBy), + 'displayLimitInMilliseconds': + serializer.toJson<int?>(displayLimitInMilliseconds), + 'removeAudio': serializer.toJson<bool?>(removeAudio), + 'downloadToken': serializer.toJson<i2.Uint8List?>(downloadToken), + 'encryptionKey': serializer.toJson<i2.Uint8List?>(encryptionKey), + 'encryptionMac': serializer.toJson<i2.Uint8List?>(encryptionMac), + 'encryptionNonce': serializer.toJson<i2.Uint8List?>(encryptionNonce), + 'storedFileHash': serializer.toJson<i2.Uint8List?>(storedFileHash), + 'createdAt': serializer.toJson<DateTime>(createdAt), + }; + } + + MediaFilesData copyWith( + {String? mediaId, + String? type, + Value<String?> uploadState = const Value.absent(), + Value<String?> downloadState = const Value.absent(), + bool? requiresAuthentication, + bool? stored, + bool? isDraftMedia, + Value<String?> reuploadRequestedBy = const Value.absent(), + Value<int?> displayLimitInMilliseconds = const Value.absent(), + Value<bool?> removeAudio = const Value.absent(), + Value<i2.Uint8List?> downloadToken = const Value.absent(), + Value<i2.Uint8List?> encryptionKey = const Value.absent(), + Value<i2.Uint8List?> encryptionMac = const Value.absent(), + Value<i2.Uint8List?> encryptionNonce = const Value.absent(), + Value<i2.Uint8List?> storedFileHash = const Value.absent(), + DateTime? createdAt}) => + MediaFilesData( + mediaId: mediaId ?? this.mediaId, + type: type ?? this.type, + uploadState: uploadState.present ? uploadState.value : this.uploadState, + downloadState: + downloadState.present ? downloadState.value : this.downloadState, + requiresAuthentication: + requiresAuthentication ?? this.requiresAuthentication, + stored: stored ?? this.stored, + isDraftMedia: isDraftMedia ?? this.isDraftMedia, + reuploadRequestedBy: reuploadRequestedBy.present + ? reuploadRequestedBy.value + : this.reuploadRequestedBy, + displayLimitInMilliseconds: displayLimitInMilliseconds.present + ? displayLimitInMilliseconds.value + : this.displayLimitInMilliseconds, + removeAudio: removeAudio.present ? removeAudio.value : this.removeAudio, + downloadToken: + downloadToken.present ? downloadToken.value : this.downloadToken, + encryptionKey: + encryptionKey.present ? encryptionKey.value : this.encryptionKey, + encryptionMac: + encryptionMac.present ? encryptionMac.value : this.encryptionMac, + encryptionNonce: encryptionNonce.present + ? encryptionNonce.value + : this.encryptionNonce, + storedFileHash: + storedFileHash.present ? storedFileHash.value : this.storedFileHash, + createdAt: createdAt ?? this.createdAt, + ); + MediaFilesData copyWithCompanion(MediaFilesCompanion data) { + return MediaFilesData( + mediaId: data.mediaId.present ? data.mediaId.value : this.mediaId, + type: data.type.present ? data.type.value : this.type, + uploadState: + data.uploadState.present ? data.uploadState.value : this.uploadState, + downloadState: data.downloadState.present + ? data.downloadState.value + : this.downloadState, + requiresAuthentication: data.requiresAuthentication.present + ? data.requiresAuthentication.value + : this.requiresAuthentication, + stored: data.stored.present ? data.stored.value : this.stored, + isDraftMedia: data.isDraftMedia.present + ? data.isDraftMedia.value + : this.isDraftMedia, + reuploadRequestedBy: data.reuploadRequestedBy.present + ? data.reuploadRequestedBy.value + : this.reuploadRequestedBy, + displayLimitInMilliseconds: data.displayLimitInMilliseconds.present + ? data.displayLimitInMilliseconds.value + : this.displayLimitInMilliseconds, + removeAudio: + data.removeAudio.present ? data.removeAudio.value : this.removeAudio, + downloadToken: data.downloadToken.present + ? data.downloadToken.value + : this.downloadToken, + encryptionKey: data.encryptionKey.present + ? data.encryptionKey.value + : this.encryptionKey, + encryptionMac: data.encryptionMac.present + ? data.encryptionMac.value + : this.encryptionMac, + encryptionNonce: data.encryptionNonce.present + ? data.encryptionNonce.value + : this.encryptionNonce, + storedFileHash: data.storedFileHash.present + ? data.storedFileHash.value + : this.storedFileHash, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + ); + } + + @override + String toString() { + return (StringBuffer('MediaFilesData(') + ..write('mediaId: $mediaId, ') + ..write('type: $type, ') + ..write('uploadState: $uploadState, ') + ..write('downloadState: $downloadState, ') + ..write('requiresAuthentication: $requiresAuthentication, ') + ..write('stored: $stored, ') + ..write('isDraftMedia: $isDraftMedia, ') + ..write('reuploadRequestedBy: $reuploadRequestedBy, ') + ..write('displayLimitInMilliseconds: $displayLimitInMilliseconds, ') + ..write('removeAudio: $removeAudio, ') + ..write('downloadToken: $downloadToken, ') + ..write('encryptionKey: $encryptionKey, ') + ..write('encryptionMac: $encryptionMac, ') + ..write('encryptionNonce: $encryptionNonce, ') + ..write('storedFileHash: $storedFileHash, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + mediaId, + type, + uploadState, + downloadState, + requiresAuthentication, + stored, + isDraftMedia, + reuploadRequestedBy, + displayLimitInMilliseconds, + removeAudio, + $driftBlobEquality.hash(downloadToken), + $driftBlobEquality.hash(encryptionKey), + $driftBlobEquality.hash(encryptionMac), + $driftBlobEquality.hash(encryptionNonce), + $driftBlobEquality.hash(storedFileHash), + createdAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MediaFilesData && + other.mediaId == this.mediaId && + other.type == this.type && + other.uploadState == this.uploadState && + other.downloadState == this.downloadState && + other.requiresAuthentication == this.requiresAuthentication && + other.stored == this.stored && + other.isDraftMedia == this.isDraftMedia && + other.reuploadRequestedBy == this.reuploadRequestedBy && + other.displayLimitInMilliseconds == this.displayLimitInMilliseconds && + other.removeAudio == this.removeAudio && + $driftBlobEquality.equals(other.downloadToken, this.downloadToken) && + $driftBlobEquality.equals(other.encryptionKey, this.encryptionKey) && + $driftBlobEquality.equals(other.encryptionMac, this.encryptionMac) && + $driftBlobEquality.equals( + other.encryptionNonce, this.encryptionNonce) && + $driftBlobEquality.equals( + other.storedFileHash, this.storedFileHash) && + other.createdAt == this.createdAt); +} + +class MediaFilesCompanion extends UpdateCompanion<MediaFilesData> { + final Value<String> mediaId; + final Value<String> type; + final Value<String?> uploadState; + final Value<String?> downloadState; + final Value<bool> requiresAuthentication; + final Value<bool> stored; + final Value<bool> isDraftMedia; + final Value<String?> reuploadRequestedBy; + final Value<int?> displayLimitInMilliseconds; + final Value<bool?> removeAudio; + final Value<i2.Uint8List?> downloadToken; + final Value<i2.Uint8List?> encryptionKey; + final Value<i2.Uint8List?> encryptionMac; + final Value<i2.Uint8List?> encryptionNonce; + final Value<i2.Uint8List?> storedFileHash; + final Value<DateTime> createdAt; + final Value<int> rowid; + const MediaFilesCompanion({ + this.mediaId = const Value.absent(), + this.type = const Value.absent(), + this.uploadState = const Value.absent(), + this.downloadState = const Value.absent(), + this.requiresAuthentication = const Value.absent(), + this.stored = const Value.absent(), + this.isDraftMedia = const Value.absent(), + this.reuploadRequestedBy = const Value.absent(), + this.displayLimitInMilliseconds = const Value.absent(), + this.removeAudio = const Value.absent(), + this.downloadToken = const Value.absent(), + this.encryptionKey = const Value.absent(), + this.encryptionMac = const Value.absent(), + this.encryptionNonce = const Value.absent(), + this.storedFileHash = const Value.absent(), + this.createdAt = const Value.absent(), + this.rowid = const Value.absent(), + }); + MediaFilesCompanion.insert({ + required String mediaId, + required String type, + this.uploadState = const Value.absent(), + this.downloadState = const Value.absent(), + this.requiresAuthentication = const Value.absent(), + this.stored = const Value.absent(), + this.isDraftMedia = const Value.absent(), + this.reuploadRequestedBy = const Value.absent(), + this.displayLimitInMilliseconds = const Value.absent(), + this.removeAudio = const Value.absent(), + this.downloadToken = const Value.absent(), + this.encryptionKey = const Value.absent(), + this.encryptionMac = const Value.absent(), + this.encryptionNonce = const Value.absent(), + this.storedFileHash = const Value.absent(), + this.createdAt = const Value.absent(), + this.rowid = const Value.absent(), + }) : mediaId = Value(mediaId), + type = Value(type); + static Insertable<MediaFilesData> custom({ + Expression<String>? mediaId, + Expression<String>? type, + Expression<String>? uploadState, + Expression<String>? downloadState, + Expression<bool>? requiresAuthentication, + Expression<bool>? stored, + Expression<bool>? isDraftMedia, + Expression<String>? reuploadRequestedBy, + Expression<int>? displayLimitInMilliseconds, + Expression<bool>? removeAudio, + Expression<i2.Uint8List>? downloadToken, + Expression<i2.Uint8List>? encryptionKey, + Expression<i2.Uint8List>? encryptionMac, + Expression<i2.Uint8List>? encryptionNonce, + Expression<i2.Uint8List>? storedFileHash, + Expression<DateTime>? createdAt, + Expression<int>? rowid, + }) { + return RawValuesInsertable({ + if (mediaId != null) 'media_id': mediaId, + if (type != null) 'type': type, + if (uploadState != null) 'upload_state': uploadState, + if (downloadState != null) 'download_state': downloadState, + if (requiresAuthentication != null) + 'requires_authentication': requiresAuthentication, + if (stored != null) 'stored': stored, + if (isDraftMedia != null) 'is_draft_media': isDraftMedia, + if (reuploadRequestedBy != null) + 'reupload_requested_by': reuploadRequestedBy, + if (displayLimitInMilliseconds != null) + 'display_limit_in_milliseconds': displayLimitInMilliseconds, + if (removeAudio != null) 'remove_audio': removeAudio, + if (downloadToken != null) 'download_token': downloadToken, + if (encryptionKey != null) 'encryption_key': encryptionKey, + if (encryptionMac != null) 'encryption_mac': encryptionMac, + if (encryptionNonce != null) 'encryption_nonce': encryptionNonce, + if (storedFileHash != null) 'stored_file_hash': storedFileHash, + if (createdAt != null) 'created_at': createdAt, + if (rowid != null) 'rowid': rowid, + }); + } + + MediaFilesCompanion copyWith( + {Value<String>? mediaId, + Value<String>? type, + Value<String?>? uploadState, + Value<String?>? downloadState, + Value<bool>? requiresAuthentication, + Value<bool>? stored, + Value<bool>? isDraftMedia, + Value<String?>? reuploadRequestedBy, + Value<int?>? displayLimitInMilliseconds, + Value<bool?>? removeAudio, + Value<i2.Uint8List?>? downloadToken, + Value<i2.Uint8List?>? encryptionKey, + Value<i2.Uint8List?>? encryptionMac, + Value<i2.Uint8List?>? encryptionNonce, + Value<i2.Uint8List?>? storedFileHash, + Value<DateTime>? createdAt, + Value<int>? rowid}) { + return MediaFilesCompanion( + mediaId: mediaId ?? this.mediaId, + type: type ?? this.type, + uploadState: uploadState ?? this.uploadState, + downloadState: downloadState ?? this.downloadState, + requiresAuthentication: + requiresAuthentication ?? this.requiresAuthentication, + stored: stored ?? this.stored, + isDraftMedia: isDraftMedia ?? this.isDraftMedia, + reuploadRequestedBy: reuploadRequestedBy ?? this.reuploadRequestedBy, + displayLimitInMilliseconds: + displayLimitInMilliseconds ?? this.displayLimitInMilliseconds, + removeAudio: removeAudio ?? this.removeAudio, + downloadToken: downloadToken ?? this.downloadToken, + encryptionKey: encryptionKey ?? this.encryptionKey, + encryptionMac: encryptionMac ?? this.encryptionMac, + encryptionNonce: encryptionNonce ?? this.encryptionNonce, + storedFileHash: storedFileHash ?? this.storedFileHash, + createdAt: createdAt ?? this.createdAt, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map<String, Expression> toColumns(bool nullToAbsent) { + final map = <String, Expression>{}; + if (mediaId.present) { + map['media_id'] = Variable<String>(mediaId.value); + } + if (type.present) { + map['type'] = Variable<String>(type.value); + } + if (uploadState.present) { + map['upload_state'] = Variable<String>(uploadState.value); + } + if (downloadState.present) { + map['download_state'] = Variable<String>(downloadState.value); + } + if (requiresAuthentication.present) { + map['requires_authentication'] = + Variable<bool>(requiresAuthentication.value); + } + if (stored.present) { + map['stored'] = Variable<bool>(stored.value); + } + if (isDraftMedia.present) { + map['is_draft_media'] = Variable<bool>(isDraftMedia.value); + } + if (reuploadRequestedBy.present) { + map['reupload_requested_by'] = + Variable<String>(reuploadRequestedBy.value); + } + if (displayLimitInMilliseconds.present) { + map['display_limit_in_milliseconds'] = + Variable<int>(displayLimitInMilliseconds.value); + } + if (removeAudio.present) { + map['remove_audio'] = Variable<bool>(removeAudio.value); + } + if (downloadToken.present) { + map['download_token'] = Variable<i2.Uint8List>(downloadToken.value); + } + if (encryptionKey.present) { + map['encryption_key'] = Variable<i2.Uint8List>(encryptionKey.value); + } + if (encryptionMac.present) { + map['encryption_mac'] = Variable<i2.Uint8List>(encryptionMac.value); + } + if (encryptionNonce.present) { + map['encryption_nonce'] = Variable<i2.Uint8List>(encryptionNonce.value); + } + if (storedFileHash.present) { + map['stored_file_hash'] = Variable<i2.Uint8List>(storedFileHash.value); + } + if (createdAt.present) { + map['created_at'] = Variable<DateTime>(createdAt.value); + } + if (rowid.present) { + map['rowid'] = Variable<int>(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MediaFilesCompanion(') + ..write('mediaId: $mediaId, ') + ..write('type: $type, ') + ..write('uploadState: $uploadState, ') + ..write('downloadState: $downloadState, ') + ..write('requiresAuthentication: $requiresAuthentication, ') + ..write('stored: $stored, ') + ..write('isDraftMedia: $isDraftMedia, ') + ..write('reuploadRequestedBy: $reuploadRequestedBy, ') + ..write('displayLimitInMilliseconds: $displayLimitInMilliseconds, ') + ..write('removeAudio: $removeAudio, ') + ..write('downloadToken: $downloadToken, ') + ..write('encryptionKey: $encryptionKey, ') + ..write('encryptionMac: $encryptionMac, ') + ..write('encryptionNonce: $encryptionNonce, ') + ..write('storedFileHash: $storedFileHash, ') + ..write('createdAt: $createdAt, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class Messages extends Table with TableInfo<Messages, MessagesData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + Messages(this.attachedDatabase, [this._alias]); + late final GeneratedColumn<String> groupId = GeneratedColumn<String>( + 'group_id', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES "groups" (group_id) ON DELETE CASCADE')); + late final GeneratedColumn<String> messageId = GeneratedColumn<String>( + 'message_id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn<int> senderId = GeneratedColumn<int>( + 'sender_id', aliasedName, true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('REFERENCES contacts (user_id)')); + late final GeneratedColumn<String> type = GeneratedColumn<String>( + 'type', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn<String> content = GeneratedColumn<String>( + 'content', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + late final GeneratedColumn<String> mediaId = GeneratedColumn<String>( + 'media_id', aliasedName, true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES media_files (media_id) ON DELETE SET NULL')); + late final GeneratedColumn<i2.Uint8List> additionalMessageData = + GeneratedColumn<i2.Uint8List>( + 'additional_message_data', aliasedName, true, + type: DriftSqlType.blob, requiredDuringInsert: false); + late final GeneratedColumn<bool> mediaStored = GeneratedColumn<bool>( + 'media_stored', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("media_stored" IN (0, 1))'), + defaultValue: const CustomExpression('0')); + late final GeneratedColumn<bool> mediaReopened = GeneratedColumn<bool>( + 'media_reopened', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("media_reopened" IN (0, 1))'), + defaultValue: const CustomExpression('0')); + late final GeneratedColumn<i2.Uint8List> downloadToken = + GeneratedColumn<i2.Uint8List>('download_token', aliasedName, true, + type: DriftSqlType.blob, requiredDuringInsert: false); + late final GeneratedColumn<String> quotesMessageId = GeneratedColumn<String>( + 'quotes_message_id', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + late final GeneratedColumn<bool> isDeletedFromSender = GeneratedColumn<bool>( + 'is_deleted_from_sender', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_deleted_from_sender" IN (0, 1))'), + defaultValue: const CustomExpression('0')); + late final GeneratedColumn<DateTime> openedAt = GeneratedColumn<DateTime>( + 'opened_at', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + late final GeneratedColumn<DateTime> openedByAll = GeneratedColumn<DateTime>( + 'opened_by_all', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + late final GeneratedColumn<DateTime> createdAt = GeneratedColumn<DateTime>( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression( + 'CAST(strftime(\'%s\', CURRENT_TIMESTAMP) AS INTEGER)')); + late final GeneratedColumn<DateTime> modifiedAt = GeneratedColumn<DateTime>( + 'modified_at', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + late final GeneratedColumn<DateTime> ackByUser = GeneratedColumn<DateTime>( + 'ack_by_user', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + late final GeneratedColumn<DateTime> ackByServer = GeneratedColumn<DateTime>( + 'ack_by_server', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + @override + List<GeneratedColumn> get $columns => [ + groupId, + messageId, + senderId, + type, + content, + mediaId, + additionalMessageData, + mediaStored, + mediaReopened, + downloadToken, + quotesMessageId, + isDeletedFromSender, + openedAt, + openedByAll, + createdAt, + modifiedAt, + ackByUser, + ackByServer + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'messages'; + @override + Set<GeneratedColumn> get $primaryKey => {messageId}; + @override + MessagesData map(Map<String, dynamic> data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MessagesData( + groupId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}group_id'])!, + messageId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}message_id'])!, + senderId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}sender_id']), + type: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}type'])!, + content: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}content']), + mediaId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}media_id']), + additionalMessageData: attachedDatabase.typeMapping.read( + DriftSqlType.blob, data['${effectivePrefix}additional_message_data']), + mediaStored: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}media_stored'])!, + mediaReopened: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}media_reopened'])!, + downloadToken: attachedDatabase.typeMapping + .read(DriftSqlType.blob, data['${effectivePrefix}download_token']), + quotesMessageId: attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}quotes_message_id']), + isDeletedFromSender: attachedDatabase.typeMapping.read( + DriftSqlType.bool, data['${effectivePrefix}is_deleted_from_sender'])!, + openedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}opened_at']), + openedByAll: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}opened_by_all']), + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + modifiedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}modified_at']), + ackByUser: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}ack_by_user']), + ackByServer: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}ack_by_server']), + ); + } + + @override + Messages createAlias(String alias) { + return Messages(attachedDatabase, alias); + } +} + +class MessagesData extends DataClass implements Insertable<MessagesData> { + final String groupId; + final String messageId; + final int? senderId; + final String type; + final String? content; + final String? mediaId; + final i2.Uint8List? additionalMessageData; + final bool mediaStored; + final bool mediaReopened; + final i2.Uint8List? downloadToken; + final String? quotesMessageId; + final bool isDeletedFromSender; + final DateTime? openedAt; + final DateTime? openedByAll; + final DateTime createdAt; + final DateTime? modifiedAt; + final DateTime? ackByUser; + final DateTime? ackByServer; + const MessagesData( + {required this.groupId, + required this.messageId, + this.senderId, + required this.type, + this.content, + this.mediaId, + this.additionalMessageData, + required this.mediaStored, + required this.mediaReopened, + this.downloadToken, + this.quotesMessageId, + required this.isDeletedFromSender, + this.openedAt, + this.openedByAll, + required this.createdAt, + this.modifiedAt, + this.ackByUser, + this.ackByServer}); + @override + Map<String, Expression> toColumns(bool nullToAbsent) { + final map = <String, Expression>{}; + map['group_id'] = Variable<String>(groupId); + map['message_id'] = Variable<String>(messageId); + if (!nullToAbsent || senderId != null) { + map['sender_id'] = Variable<int>(senderId); + } + map['type'] = Variable<String>(type); + if (!nullToAbsent || content != null) { + map['content'] = Variable<String>(content); + } + if (!nullToAbsent || mediaId != null) { + map['media_id'] = Variable<String>(mediaId); + } + if (!nullToAbsent || additionalMessageData != null) { + map['additional_message_data'] = + Variable<i2.Uint8List>(additionalMessageData); + } + map['media_stored'] = Variable<bool>(mediaStored); + map['media_reopened'] = Variable<bool>(mediaReopened); + if (!nullToAbsent || downloadToken != null) { + map['download_token'] = Variable<i2.Uint8List>(downloadToken); + } + if (!nullToAbsent || quotesMessageId != null) { + map['quotes_message_id'] = Variable<String>(quotesMessageId); + } + map['is_deleted_from_sender'] = Variable<bool>(isDeletedFromSender); + if (!nullToAbsent || openedAt != null) { + map['opened_at'] = Variable<DateTime>(openedAt); + } + if (!nullToAbsent || openedByAll != null) { + map['opened_by_all'] = Variable<DateTime>(openedByAll); + } + map['created_at'] = Variable<DateTime>(createdAt); + if (!nullToAbsent || modifiedAt != null) { + map['modified_at'] = Variable<DateTime>(modifiedAt); + } + if (!nullToAbsent || ackByUser != null) { + map['ack_by_user'] = Variable<DateTime>(ackByUser); + } + if (!nullToAbsent || ackByServer != null) { + map['ack_by_server'] = Variable<DateTime>(ackByServer); + } + return map; + } + + MessagesCompanion toCompanion(bool nullToAbsent) { + return MessagesCompanion( + groupId: Value(groupId), + messageId: Value(messageId), + senderId: senderId == null && nullToAbsent + ? const Value.absent() + : Value(senderId), + type: Value(type), + content: content == null && nullToAbsent + ? const Value.absent() + : Value(content), + mediaId: mediaId == null && nullToAbsent + ? const Value.absent() + : Value(mediaId), + additionalMessageData: additionalMessageData == null && nullToAbsent + ? const Value.absent() + : Value(additionalMessageData), + mediaStored: Value(mediaStored), + mediaReopened: Value(mediaReopened), + downloadToken: downloadToken == null && nullToAbsent + ? const Value.absent() + : Value(downloadToken), + quotesMessageId: quotesMessageId == null && nullToAbsent + ? const Value.absent() + : Value(quotesMessageId), + isDeletedFromSender: Value(isDeletedFromSender), + openedAt: openedAt == null && nullToAbsent + ? const Value.absent() + : Value(openedAt), + openedByAll: openedByAll == null && nullToAbsent + ? const Value.absent() + : Value(openedByAll), + createdAt: Value(createdAt), + modifiedAt: modifiedAt == null && nullToAbsent + ? const Value.absent() + : Value(modifiedAt), + ackByUser: ackByUser == null && nullToAbsent + ? const Value.absent() + : Value(ackByUser), + ackByServer: ackByServer == null && nullToAbsent + ? const Value.absent() + : Value(ackByServer), + ); + } + + factory MessagesData.fromJson(Map<String, dynamic> json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MessagesData( + groupId: serializer.fromJson<String>(json['groupId']), + messageId: serializer.fromJson<String>(json['messageId']), + senderId: serializer.fromJson<int?>(json['senderId']), + type: serializer.fromJson<String>(json['type']), + content: serializer.fromJson<String?>(json['content']), + mediaId: serializer.fromJson<String?>(json['mediaId']), + additionalMessageData: + serializer.fromJson<i2.Uint8List?>(json['additionalMessageData']), + mediaStored: serializer.fromJson<bool>(json['mediaStored']), + mediaReopened: serializer.fromJson<bool>(json['mediaReopened']), + downloadToken: serializer.fromJson<i2.Uint8List?>(json['downloadToken']), + quotesMessageId: serializer.fromJson<String?>(json['quotesMessageId']), + isDeletedFromSender: + serializer.fromJson<bool>(json['isDeletedFromSender']), + openedAt: serializer.fromJson<DateTime?>(json['openedAt']), + openedByAll: serializer.fromJson<DateTime?>(json['openedByAll']), + createdAt: serializer.fromJson<DateTime>(json['createdAt']), + modifiedAt: serializer.fromJson<DateTime?>(json['modifiedAt']), + ackByUser: serializer.fromJson<DateTime?>(json['ackByUser']), + ackByServer: serializer.fromJson<DateTime?>(json['ackByServer']), + ); + } + @override + Map<String, dynamic> toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return <String, dynamic>{ + 'groupId': serializer.toJson<String>(groupId), + 'messageId': serializer.toJson<String>(messageId), + 'senderId': serializer.toJson<int?>(senderId), + 'type': serializer.toJson<String>(type), + 'content': serializer.toJson<String?>(content), + 'mediaId': serializer.toJson<String?>(mediaId), + 'additionalMessageData': + serializer.toJson<i2.Uint8List?>(additionalMessageData), + 'mediaStored': serializer.toJson<bool>(mediaStored), + 'mediaReopened': serializer.toJson<bool>(mediaReopened), + 'downloadToken': serializer.toJson<i2.Uint8List?>(downloadToken), + 'quotesMessageId': serializer.toJson<String?>(quotesMessageId), + 'isDeletedFromSender': serializer.toJson<bool>(isDeletedFromSender), + 'openedAt': serializer.toJson<DateTime?>(openedAt), + 'openedByAll': serializer.toJson<DateTime?>(openedByAll), + 'createdAt': serializer.toJson<DateTime>(createdAt), + 'modifiedAt': serializer.toJson<DateTime?>(modifiedAt), + 'ackByUser': serializer.toJson<DateTime?>(ackByUser), + 'ackByServer': serializer.toJson<DateTime?>(ackByServer), + }; + } + + MessagesData copyWith( + {String? groupId, + String? messageId, + Value<int?> senderId = const Value.absent(), + String? type, + Value<String?> content = const Value.absent(), + Value<String?> mediaId = const Value.absent(), + Value<i2.Uint8List?> additionalMessageData = const Value.absent(), + bool? mediaStored, + bool? mediaReopened, + Value<i2.Uint8List?> downloadToken = const Value.absent(), + Value<String?> quotesMessageId = const Value.absent(), + bool? isDeletedFromSender, + Value<DateTime?> openedAt = const Value.absent(), + Value<DateTime?> openedByAll = const Value.absent(), + DateTime? createdAt, + Value<DateTime?> modifiedAt = const Value.absent(), + Value<DateTime?> ackByUser = const Value.absent(), + Value<DateTime?> ackByServer = const Value.absent()}) => + MessagesData( + groupId: groupId ?? this.groupId, + messageId: messageId ?? this.messageId, + senderId: senderId.present ? senderId.value : this.senderId, + type: type ?? this.type, + content: content.present ? content.value : this.content, + mediaId: mediaId.present ? mediaId.value : this.mediaId, + additionalMessageData: additionalMessageData.present + ? additionalMessageData.value + : this.additionalMessageData, + mediaStored: mediaStored ?? this.mediaStored, + mediaReopened: mediaReopened ?? this.mediaReopened, + downloadToken: + downloadToken.present ? downloadToken.value : this.downloadToken, + quotesMessageId: quotesMessageId.present + ? quotesMessageId.value + : this.quotesMessageId, + isDeletedFromSender: isDeletedFromSender ?? this.isDeletedFromSender, + openedAt: openedAt.present ? openedAt.value : this.openedAt, + openedByAll: openedByAll.present ? openedByAll.value : this.openedByAll, + createdAt: createdAt ?? this.createdAt, + modifiedAt: modifiedAt.present ? modifiedAt.value : this.modifiedAt, + ackByUser: ackByUser.present ? ackByUser.value : this.ackByUser, + ackByServer: ackByServer.present ? ackByServer.value : this.ackByServer, + ); + MessagesData copyWithCompanion(MessagesCompanion data) { + return MessagesData( + groupId: data.groupId.present ? data.groupId.value : this.groupId, + messageId: data.messageId.present ? data.messageId.value : this.messageId, + senderId: data.senderId.present ? data.senderId.value : this.senderId, + type: data.type.present ? data.type.value : this.type, + content: data.content.present ? data.content.value : this.content, + mediaId: data.mediaId.present ? data.mediaId.value : this.mediaId, + additionalMessageData: data.additionalMessageData.present + ? data.additionalMessageData.value + : this.additionalMessageData, + mediaStored: + data.mediaStored.present ? data.mediaStored.value : this.mediaStored, + mediaReopened: data.mediaReopened.present + ? data.mediaReopened.value + : this.mediaReopened, + downloadToken: data.downloadToken.present + ? data.downloadToken.value + : this.downloadToken, + quotesMessageId: data.quotesMessageId.present + ? data.quotesMessageId.value + : this.quotesMessageId, + isDeletedFromSender: data.isDeletedFromSender.present + ? data.isDeletedFromSender.value + : this.isDeletedFromSender, + openedAt: data.openedAt.present ? data.openedAt.value : this.openedAt, + openedByAll: + data.openedByAll.present ? data.openedByAll.value : this.openedByAll, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + modifiedAt: + data.modifiedAt.present ? data.modifiedAt.value : this.modifiedAt, + ackByUser: data.ackByUser.present ? data.ackByUser.value : this.ackByUser, + ackByServer: + data.ackByServer.present ? data.ackByServer.value : this.ackByServer, + ); + } + + @override + String toString() { + return (StringBuffer('MessagesData(') + ..write('groupId: $groupId, ') + ..write('messageId: $messageId, ') + ..write('senderId: $senderId, ') + ..write('type: $type, ') + ..write('content: $content, ') + ..write('mediaId: $mediaId, ') + ..write('additionalMessageData: $additionalMessageData, ') + ..write('mediaStored: $mediaStored, ') + ..write('mediaReopened: $mediaReopened, ') + ..write('downloadToken: $downloadToken, ') + ..write('quotesMessageId: $quotesMessageId, ') + ..write('isDeletedFromSender: $isDeletedFromSender, ') + ..write('openedAt: $openedAt, ') + ..write('openedByAll: $openedByAll, ') + ..write('createdAt: $createdAt, ') + ..write('modifiedAt: $modifiedAt, ') + ..write('ackByUser: $ackByUser, ') + ..write('ackByServer: $ackByServer') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + groupId, + messageId, + senderId, + type, + content, + mediaId, + $driftBlobEquality.hash(additionalMessageData), + mediaStored, + mediaReopened, + $driftBlobEquality.hash(downloadToken), + quotesMessageId, + isDeletedFromSender, + openedAt, + openedByAll, + createdAt, + modifiedAt, + ackByUser, + ackByServer); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MessagesData && + other.groupId == this.groupId && + other.messageId == this.messageId && + other.senderId == this.senderId && + other.type == this.type && + other.content == this.content && + other.mediaId == this.mediaId && + $driftBlobEquality.equals( + other.additionalMessageData, this.additionalMessageData) && + other.mediaStored == this.mediaStored && + other.mediaReopened == this.mediaReopened && + $driftBlobEquality.equals(other.downloadToken, this.downloadToken) && + other.quotesMessageId == this.quotesMessageId && + other.isDeletedFromSender == this.isDeletedFromSender && + other.openedAt == this.openedAt && + other.openedByAll == this.openedByAll && + other.createdAt == this.createdAt && + other.modifiedAt == this.modifiedAt && + other.ackByUser == this.ackByUser && + other.ackByServer == this.ackByServer); +} + +class MessagesCompanion extends UpdateCompanion<MessagesData> { + final Value<String> groupId; + final Value<String> messageId; + final Value<int?> senderId; + final Value<String> type; + final Value<String?> content; + final Value<String?> mediaId; + final Value<i2.Uint8List?> additionalMessageData; + final Value<bool> mediaStored; + final Value<bool> mediaReopened; + final Value<i2.Uint8List?> downloadToken; + final Value<String?> quotesMessageId; + final Value<bool> isDeletedFromSender; + final Value<DateTime?> openedAt; + final Value<DateTime?> openedByAll; + final Value<DateTime> createdAt; + final Value<DateTime?> modifiedAt; + final Value<DateTime?> ackByUser; + final Value<DateTime?> ackByServer; + final Value<int> rowid; + const MessagesCompanion({ + this.groupId = const Value.absent(), + this.messageId = const Value.absent(), + this.senderId = const Value.absent(), + this.type = const Value.absent(), + this.content = const Value.absent(), + this.mediaId = const Value.absent(), + this.additionalMessageData = const Value.absent(), + this.mediaStored = const Value.absent(), + this.mediaReopened = const Value.absent(), + this.downloadToken = const Value.absent(), + this.quotesMessageId = const Value.absent(), + this.isDeletedFromSender = const Value.absent(), + this.openedAt = const Value.absent(), + this.openedByAll = const Value.absent(), + this.createdAt = const Value.absent(), + this.modifiedAt = const Value.absent(), + this.ackByUser = const Value.absent(), + this.ackByServer = const Value.absent(), + this.rowid = const Value.absent(), + }); + MessagesCompanion.insert({ + required String groupId, + required String messageId, + this.senderId = const Value.absent(), + required String type, + this.content = const Value.absent(), + this.mediaId = const Value.absent(), + this.additionalMessageData = const Value.absent(), + this.mediaStored = const Value.absent(), + this.mediaReopened = const Value.absent(), + this.downloadToken = const Value.absent(), + this.quotesMessageId = const Value.absent(), + this.isDeletedFromSender = const Value.absent(), + this.openedAt = const Value.absent(), + this.openedByAll = const Value.absent(), + this.createdAt = const Value.absent(), + this.modifiedAt = const Value.absent(), + this.ackByUser = const Value.absent(), + this.ackByServer = const Value.absent(), + this.rowid = const Value.absent(), + }) : groupId = Value(groupId), + messageId = Value(messageId), + type = Value(type); + static Insertable<MessagesData> custom({ + Expression<String>? groupId, + Expression<String>? messageId, + Expression<int>? senderId, + Expression<String>? type, + Expression<String>? content, + Expression<String>? mediaId, + Expression<i2.Uint8List>? additionalMessageData, + Expression<bool>? mediaStored, + Expression<bool>? mediaReopened, + Expression<i2.Uint8List>? downloadToken, + Expression<String>? quotesMessageId, + Expression<bool>? isDeletedFromSender, + Expression<DateTime>? openedAt, + Expression<DateTime>? openedByAll, + Expression<DateTime>? createdAt, + Expression<DateTime>? modifiedAt, + Expression<DateTime>? ackByUser, + Expression<DateTime>? ackByServer, + Expression<int>? rowid, + }) { + return RawValuesInsertable({ + if (groupId != null) 'group_id': groupId, + if (messageId != null) 'message_id': messageId, + if (senderId != null) 'sender_id': senderId, + if (type != null) 'type': type, + if (content != null) 'content': content, + if (mediaId != null) 'media_id': mediaId, + if (additionalMessageData != null) + 'additional_message_data': additionalMessageData, + if (mediaStored != null) 'media_stored': mediaStored, + if (mediaReopened != null) 'media_reopened': mediaReopened, + if (downloadToken != null) 'download_token': downloadToken, + if (quotesMessageId != null) 'quotes_message_id': quotesMessageId, + if (isDeletedFromSender != null) + 'is_deleted_from_sender': isDeletedFromSender, + if (openedAt != null) 'opened_at': openedAt, + if (openedByAll != null) 'opened_by_all': openedByAll, + if (createdAt != null) 'created_at': createdAt, + if (modifiedAt != null) 'modified_at': modifiedAt, + if (ackByUser != null) 'ack_by_user': ackByUser, + if (ackByServer != null) 'ack_by_server': ackByServer, + if (rowid != null) 'rowid': rowid, + }); + } + + MessagesCompanion copyWith( + {Value<String>? groupId, + Value<String>? messageId, + Value<int?>? senderId, + Value<String>? type, + Value<String?>? content, + Value<String?>? mediaId, + Value<i2.Uint8List?>? additionalMessageData, + Value<bool>? mediaStored, + Value<bool>? mediaReopened, + Value<i2.Uint8List?>? downloadToken, + Value<String?>? quotesMessageId, + Value<bool>? isDeletedFromSender, + Value<DateTime?>? openedAt, + Value<DateTime?>? openedByAll, + Value<DateTime>? createdAt, + Value<DateTime?>? modifiedAt, + Value<DateTime?>? ackByUser, + Value<DateTime?>? ackByServer, + Value<int>? rowid}) { + return MessagesCompanion( + groupId: groupId ?? this.groupId, + messageId: messageId ?? this.messageId, + senderId: senderId ?? this.senderId, + type: type ?? this.type, + content: content ?? this.content, + mediaId: mediaId ?? this.mediaId, + additionalMessageData: + additionalMessageData ?? this.additionalMessageData, + mediaStored: mediaStored ?? this.mediaStored, + mediaReopened: mediaReopened ?? this.mediaReopened, + downloadToken: downloadToken ?? this.downloadToken, + quotesMessageId: quotesMessageId ?? this.quotesMessageId, + isDeletedFromSender: isDeletedFromSender ?? this.isDeletedFromSender, + openedAt: openedAt ?? this.openedAt, + openedByAll: openedByAll ?? this.openedByAll, + createdAt: createdAt ?? this.createdAt, + modifiedAt: modifiedAt ?? this.modifiedAt, + ackByUser: ackByUser ?? this.ackByUser, + ackByServer: ackByServer ?? this.ackByServer, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map<String, Expression> toColumns(bool nullToAbsent) { + final map = <String, Expression>{}; + if (groupId.present) { + map['group_id'] = Variable<String>(groupId.value); + } + if (messageId.present) { + map['message_id'] = Variable<String>(messageId.value); + } + if (senderId.present) { + map['sender_id'] = Variable<int>(senderId.value); + } + if (type.present) { + map['type'] = Variable<String>(type.value); + } + if (content.present) { + map['content'] = Variable<String>(content.value); + } + if (mediaId.present) { + map['media_id'] = Variable<String>(mediaId.value); + } + if (additionalMessageData.present) { + map['additional_message_data'] = + Variable<i2.Uint8List>(additionalMessageData.value); + } + if (mediaStored.present) { + map['media_stored'] = Variable<bool>(mediaStored.value); + } + if (mediaReopened.present) { + map['media_reopened'] = Variable<bool>(mediaReopened.value); + } + if (downloadToken.present) { + map['download_token'] = Variable<i2.Uint8List>(downloadToken.value); + } + if (quotesMessageId.present) { + map['quotes_message_id'] = Variable<String>(quotesMessageId.value); + } + if (isDeletedFromSender.present) { + map['is_deleted_from_sender'] = Variable<bool>(isDeletedFromSender.value); + } + if (openedAt.present) { + map['opened_at'] = Variable<DateTime>(openedAt.value); + } + if (openedByAll.present) { + map['opened_by_all'] = Variable<DateTime>(openedByAll.value); + } + if (createdAt.present) { + map['created_at'] = Variable<DateTime>(createdAt.value); + } + if (modifiedAt.present) { + map['modified_at'] = Variable<DateTime>(modifiedAt.value); + } + if (ackByUser.present) { + map['ack_by_user'] = Variable<DateTime>(ackByUser.value); + } + if (ackByServer.present) { + map['ack_by_server'] = Variable<DateTime>(ackByServer.value); + } + if (rowid.present) { + map['rowid'] = Variable<int>(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MessagesCompanion(') + ..write('groupId: $groupId, ') + ..write('messageId: $messageId, ') + ..write('senderId: $senderId, ') + ..write('type: $type, ') + ..write('content: $content, ') + ..write('mediaId: $mediaId, ') + ..write('additionalMessageData: $additionalMessageData, ') + ..write('mediaStored: $mediaStored, ') + ..write('mediaReopened: $mediaReopened, ') + ..write('downloadToken: $downloadToken, ') + ..write('quotesMessageId: $quotesMessageId, ') + ..write('isDeletedFromSender: $isDeletedFromSender, ') + ..write('openedAt: $openedAt, ') + ..write('openedByAll: $openedByAll, ') + ..write('createdAt: $createdAt, ') + ..write('modifiedAt: $modifiedAt, ') + ..write('ackByUser: $ackByUser, ') + ..write('ackByServer: $ackByServer, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class MessageHistories extends Table + with TableInfo<MessageHistories, MessageHistoriesData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MessageHistories(this.attachedDatabase, [this._alias]); + late final GeneratedColumn<int> id = GeneratedColumn<int>( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + late final GeneratedColumn<String> messageId = GeneratedColumn<String>( + 'message_id', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES messages (message_id) ON DELETE CASCADE')); + late final GeneratedColumn<int> contactId = GeneratedColumn<int>( + 'contact_id', aliasedName, true, + type: DriftSqlType.int, requiredDuringInsert: false); + late final GeneratedColumn<String> content = GeneratedColumn<String>( + 'content', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + late final GeneratedColumn<DateTime> createdAt = GeneratedColumn<DateTime>( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression( + 'CAST(strftime(\'%s\', CURRENT_TIMESTAMP) AS INTEGER)')); + @override + List<GeneratedColumn> get $columns => + [id, messageId, contactId, content, createdAt]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'message_histories'; + @override + Set<GeneratedColumn> get $primaryKey => {id}; + @override + MessageHistoriesData map(Map<String, dynamic> data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MessageHistoriesData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + messageId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}message_id'])!, + contactId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}contact_id']), + content: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}content']), + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + ); + } + + @override + MessageHistories createAlias(String alias) { + return MessageHistories(attachedDatabase, alias); + } +} + +class MessageHistoriesData extends DataClass + implements Insertable<MessageHistoriesData> { + final int id; + final String messageId; + final int? contactId; + final String? content; + final DateTime createdAt; + const MessageHistoriesData( + {required this.id, + required this.messageId, + this.contactId, + this.content, + required this.createdAt}); + @override + Map<String, Expression> toColumns(bool nullToAbsent) { + final map = <String, Expression>{}; + map['id'] = Variable<int>(id); + map['message_id'] = Variable<String>(messageId); + if (!nullToAbsent || contactId != null) { + map['contact_id'] = Variable<int>(contactId); + } + if (!nullToAbsent || content != null) { + map['content'] = Variable<String>(content); + } + map['created_at'] = Variable<DateTime>(createdAt); + return map; + } + + MessageHistoriesCompanion toCompanion(bool nullToAbsent) { + return MessageHistoriesCompanion( + id: Value(id), + messageId: Value(messageId), + contactId: contactId == null && nullToAbsent + ? const Value.absent() + : Value(contactId), + content: content == null && nullToAbsent + ? const Value.absent() + : Value(content), + createdAt: Value(createdAt), + ); + } + + factory MessageHistoriesData.fromJson(Map<String, dynamic> json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MessageHistoriesData( + id: serializer.fromJson<int>(json['id']), + messageId: serializer.fromJson<String>(json['messageId']), + contactId: serializer.fromJson<int?>(json['contactId']), + content: serializer.fromJson<String?>(json['content']), + createdAt: serializer.fromJson<DateTime>(json['createdAt']), + ); + } + @override + Map<String, dynamic> toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return <String, dynamic>{ + 'id': serializer.toJson<int>(id), + 'messageId': serializer.toJson<String>(messageId), + 'contactId': serializer.toJson<int?>(contactId), + 'content': serializer.toJson<String?>(content), + 'createdAt': serializer.toJson<DateTime>(createdAt), + }; + } + + MessageHistoriesData copyWith( + {int? id, + String? messageId, + Value<int?> contactId = const Value.absent(), + Value<String?> content = const Value.absent(), + DateTime? createdAt}) => + MessageHistoriesData( + id: id ?? this.id, + messageId: messageId ?? this.messageId, + contactId: contactId.present ? contactId.value : this.contactId, + content: content.present ? content.value : this.content, + createdAt: createdAt ?? this.createdAt, + ); + MessageHistoriesData copyWithCompanion(MessageHistoriesCompanion data) { + return MessageHistoriesData( + id: data.id.present ? data.id.value : this.id, + messageId: data.messageId.present ? data.messageId.value : this.messageId, + contactId: data.contactId.present ? data.contactId.value : this.contactId, + content: data.content.present ? data.content.value : this.content, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + ); + } + + @override + String toString() { + return (StringBuffer('MessageHistoriesData(') + ..write('id: $id, ') + ..write('messageId: $messageId, ') + ..write('contactId: $contactId, ') + ..write('content: $content, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, messageId, contactId, content, createdAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MessageHistoriesData && + other.id == this.id && + other.messageId == this.messageId && + other.contactId == this.contactId && + other.content == this.content && + other.createdAt == this.createdAt); +} + +class MessageHistoriesCompanion extends UpdateCompanion<MessageHistoriesData> { + final Value<int> id; + final Value<String> messageId; + final Value<int?> contactId; + final Value<String?> content; + final Value<DateTime> createdAt; + const MessageHistoriesCompanion({ + this.id = const Value.absent(), + this.messageId = const Value.absent(), + this.contactId = const Value.absent(), + this.content = const Value.absent(), + this.createdAt = const Value.absent(), + }); + MessageHistoriesCompanion.insert({ + this.id = const Value.absent(), + required String messageId, + this.contactId = const Value.absent(), + this.content = const Value.absent(), + this.createdAt = const Value.absent(), + }) : messageId = Value(messageId); + static Insertable<MessageHistoriesData> custom({ + Expression<int>? id, + Expression<String>? messageId, + Expression<int>? contactId, + Expression<String>? content, + Expression<DateTime>? createdAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (messageId != null) 'message_id': messageId, + if (contactId != null) 'contact_id': contactId, + if (content != null) 'content': content, + if (createdAt != null) 'created_at': createdAt, + }); + } + + MessageHistoriesCompanion copyWith( + {Value<int>? id, + Value<String>? messageId, + Value<int?>? contactId, + Value<String?>? content, + Value<DateTime>? createdAt}) { + return MessageHistoriesCompanion( + id: id ?? this.id, + messageId: messageId ?? this.messageId, + contactId: contactId ?? this.contactId, + content: content ?? this.content, + createdAt: createdAt ?? this.createdAt, + ); + } + + @override + Map<String, Expression> toColumns(bool nullToAbsent) { + final map = <String, Expression>{}; + if (id.present) { + map['id'] = Variable<int>(id.value); + } + if (messageId.present) { + map['message_id'] = Variable<String>(messageId.value); + } + if (contactId.present) { + map['contact_id'] = Variable<int>(contactId.value); + } + if (content.present) { + map['content'] = Variable<String>(content.value); + } + if (createdAt.present) { + map['created_at'] = Variable<DateTime>(createdAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MessageHistoriesCompanion(') + ..write('id: $id, ') + ..write('messageId: $messageId, ') + ..write('contactId: $contactId, ') + ..write('content: $content, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } +} + +class Reactions extends Table with TableInfo<Reactions, ReactionsData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + Reactions(this.attachedDatabase, [this._alias]); + late final GeneratedColumn<String> messageId = GeneratedColumn<String>( + 'message_id', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES messages (message_id) ON DELETE CASCADE')); + late final GeneratedColumn<String> emoji = GeneratedColumn<String>( + 'emoji', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn<int> senderId = GeneratedColumn<int>( + 'sender_id', aliasedName, true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES contacts (user_id) ON DELETE CASCADE')); + late final GeneratedColumn<DateTime> createdAt = GeneratedColumn<DateTime>( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression( + 'CAST(strftime(\'%s\', CURRENT_TIMESTAMP) AS INTEGER)')); + @override + List<GeneratedColumn> get $columns => [messageId, emoji, senderId, createdAt]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'reactions'; + @override + Set<GeneratedColumn> get $primaryKey => {messageId, senderId, emoji}; + @override + ReactionsData map(Map<String, dynamic> data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return ReactionsData( + messageId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}message_id'])!, + emoji: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}emoji'])!, + senderId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}sender_id']), + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + ); + } + + @override + Reactions createAlias(String alias) { + return Reactions(attachedDatabase, alias); + } +} + +class ReactionsData extends DataClass implements Insertable<ReactionsData> { + final String messageId; + final String emoji; + final int? senderId; + final DateTime createdAt; + const ReactionsData( + {required this.messageId, + required this.emoji, + this.senderId, + required this.createdAt}); + @override + Map<String, Expression> toColumns(bool nullToAbsent) { + final map = <String, Expression>{}; + map['message_id'] = Variable<String>(messageId); + map['emoji'] = Variable<String>(emoji); + if (!nullToAbsent || senderId != null) { + map['sender_id'] = Variable<int>(senderId); + } + map['created_at'] = Variable<DateTime>(createdAt); + return map; + } + + ReactionsCompanion toCompanion(bool nullToAbsent) { + return ReactionsCompanion( + messageId: Value(messageId), + emoji: Value(emoji), + senderId: senderId == null && nullToAbsent + ? const Value.absent() + : Value(senderId), + createdAt: Value(createdAt), + ); + } + + factory ReactionsData.fromJson(Map<String, dynamic> json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return ReactionsData( + messageId: serializer.fromJson<String>(json['messageId']), + emoji: serializer.fromJson<String>(json['emoji']), + senderId: serializer.fromJson<int?>(json['senderId']), + createdAt: serializer.fromJson<DateTime>(json['createdAt']), + ); + } + @override + Map<String, dynamic> toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return <String, dynamic>{ + 'messageId': serializer.toJson<String>(messageId), + 'emoji': serializer.toJson<String>(emoji), + 'senderId': serializer.toJson<int?>(senderId), + 'createdAt': serializer.toJson<DateTime>(createdAt), + }; + } + + ReactionsData copyWith( + {String? messageId, + String? emoji, + Value<int?> senderId = const Value.absent(), + DateTime? createdAt}) => + ReactionsData( + messageId: messageId ?? this.messageId, + emoji: emoji ?? this.emoji, + senderId: senderId.present ? senderId.value : this.senderId, + createdAt: createdAt ?? this.createdAt, + ); + ReactionsData copyWithCompanion(ReactionsCompanion data) { + return ReactionsData( + messageId: data.messageId.present ? data.messageId.value : this.messageId, + emoji: data.emoji.present ? data.emoji.value : this.emoji, + senderId: data.senderId.present ? data.senderId.value : this.senderId, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + ); + } + + @override + String toString() { + return (StringBuffer('ReactionsData(') + ..write('messageId: $messageId, ') + ..write('emoji: $emoji, ') + ..write('senderId: $senderId, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(messageId, emoji, senderId, createdAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is ReactionsData && + other.messageId == this.messageId && + other.emoji == this.emoji && + other.senderId == this.senderId && + other.createdAt == this.createdAt); +} + +class ReactionsCompanion extends UpdateCompanion<ReactionsData> { + final Value<String> messageId; + final Value<String> emoji; + final Value<int?> senderId; + final Value<DateTime> createdAt; + final Value<int> rowid; + const ReactionsCompanion({ + this.messageId = const Value.absent(), + this.emoji = const Value.absent(), + this.senderId = const Value.absent(), + this.createdAt = const Value.absent(), + this.rowid = const Value.absent(), + }); + ReactionsCompanion.insert({ + required String messageId, + required String emoji, + this.senderId = const Value.absent(), + this.createdAt = const Value.absent(), + this.rowid = const Value.absent(), + }) : messageId = Value(messageId), + emoji = Value(emoji); + static Insertable<ReactionsData> custom({ + Expression<String>? messageId, + Expression<String>? emoji, + Expression<int>? senderId, + Expression<DateTime>? createdAt, + Expression<int>? rowid, + }) { + return RawValuesInsertable({ + if (messageId != null) 'message_id': messageId, + if (emoji != null) 'emoji': emoji, + if (senderId != null) 'sender_id': senderId, + if (createdAt != null) 'created_at': createdAt, + if (rowid != null) 'rowid': rowid, + }); + } + + ReactionsCompanion copyWith( + {Value<String>? messageId, + Value<String>? emoji, + Value<int?>? senderId, + Value<DateTime>? createdAt, + Value<int>? rowid}) { + return ReactionsCompanion( + messageId: messageId ?? this.messageId, + emoji: emoji ?? this.emoji, + senderId: senderId ?? this.senderId, + createdAt: createdAt ?? this.createdAt, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map<String, Expression> toColumns(bool nullToAbsent) { + final map = <String, Expression>{}; + if (messageId.present) { + map['message_id'] = Variable<String>(messageId.value); + } + if (emoji.present) { + map['emoji'] = Variable<String>(emoji.value); + } + if (senderId.present) { + map['sender_id'] = Variable<int>(senderId.value); + } + if (createdAt.present) { + map['created_at'] = Variable<DateTime>(createdAt.value); + } + if (rowid.present) { + map['rowid'] = Variable<int>(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('ReactionsCompanion(') + ..write('messageId: $messageId, ') + ..write('emoji: $emoji, ') + ..write('senderId: $senderId, ') + ..write('createdAt: $createdAt, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class GroupMembers extends Table + with TableInfo<GroupMembers, GroupMembersData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + GroupMembers(this.attachedDatabase, [this._alias]); + late final GeneratedColumn<String> groupId = GeneratedColumn<String>( + 'group_id', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES "groups" (group_id) ON DELETE CASCADE')); + late final GeneratedColumn<int> contactId = GeneratedColumn<int>( + 'contact_id', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: + GeneratedColumn.constraintIsAlways('REFERENCES contacts (user_id)')); + late final GeneratedColumn<String> memberState = GeneratedColumn<String>( + 'member_state', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + late final GeneratedColumn<i2.Uint8List> groupPublicKey = + GeneratedColumn<i2.Uint8List>('group_public_key', aliasedName, true, + type: DriftSqlType.blob, requiredDuringInsert: false); + late final GeneratedColumn<DateTime> lastMessage = GeneratedColumn<DateTime>( + 'last_message', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + late final GeneratedColumn<DateTime> createdAt = GeneratedColumn<DateTime>( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression( + 'CAST(strftime(\'%s\', CURRENT_TIMESTAMP) AS INTEGER)')); + @override + List<GeneratedColumn> get $columns => + [groupId, contactId, memberState, groupPublicKey, lastMessage, createdAt]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'group_members'; + @override + Set<GeneratedColumn> get $primaryKey => {groupId, contactId}; + @override + GroupMembersData map(Map<String, dynamic> data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return GroupMembersData( + groupId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}group_id'])!, + contactId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}contact_id'])!, + memberState: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}member_state']), + groupPublicKey: attachedDatabase.typeMapping + .read(DriftSqlType.blob, data['${effectivePrefix}group_public_key']), + lastMessage: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}last_message']), + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + ); + } + + @override + GroupMembers createAlias(String alias) { + return GroupMembers(attachedDatabase, alias); + } +} + +class GroupMembersData extends DataClass + implements Insertable<GroupMembersData> { + final String groupId; + final int contactId; + final String? memberState; + final i2.Uint8List? groupPublicKey; + final DateTime? lastMessage; + final DateTime createdAt; + const GroupMembersData( + {required this.groupId, + required this.contactId, + this.memberState, + this.groupPublicKey, + this.lastMessage, + required this.createdAt}); + @override + Map<String, Expression> toColumns(bool nullToAbsent) { + final map = <String, Expression>{}; + map['group_id'] = Variable<String>(groupId); + map['contact_id'] = Variable<int>(contactId); + if (!nullToAbsent || memberState != null) { + map['member_state'] = Variable<String>(memberState); + } + if (!nullToAbsent || groupPublicKey != null) { + map['group_public_key'] = Variable<i2.Uint8List>(groupPublicKey); + } + if (!nullToAbsent || lastMessage != null) { + map['last_message'] = Variable<DateTime>(lastMessage); + } + map['created_at'] = Variable<DateTime>(createdAt); + return map; + } + + GroupMembersCompanion toCompanion(bool nullToAbsent) { + return GroupMembersCompanion( + groupId: Value(groupId), + contactId: Value(contactId), + memberState: memberState == null && nullToAbsent + ? const Value.absent() + : Value(memberState), + groupPublicKey: groupPublicKey == null && nullToAbsent + ? const Value.absent() + : Value(groupPublicKey), + lastMessage: lastMessage == null && nullToAbsent + ? const Value.absent() + : Value(lastMessage), + createdAt: Value(createdAt), + ); + } + + factory GroupMembersData.fromJson(Map<String, dynamic> json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return GroupMembersData( + groupId: serializer.fromJson<String>(json['groupId']), + contactId: serializer.fromJson<int>(json['contactId']), + memberState: serializer.fromJson<String?>(json['memberState']), + groupPublicKey: + serializer.fromJson<i2.Uint8List?>(json['groupPublicKey']), + lastMessage: serializer.fromJson<DateTime?>(json['lastMessage']), + createdAt: serializer.fromJson<DateTime>(json['createdAt']), + ); + } + @override + Map<String, dynamic> toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return <String, dynamic>{ + 'groupId': serializer.toJson<String>(groupId), + 'contactId': serializer.toJson<int>(contactId), + 'memberState': serializer.toJson<String?>(memberState), + 'groupPublicKey': serializer.toJson<i2.Uint8List?>(groupPublicKey), + 'lastMessage': serializer.toJson<DateTime?>(lastMessage), + 'createdAt': serializer.toJson<DateTime>(createdAt), + }; + } + + GroupMembersData copyWith( + {String? groupId, + int? contactId, + Value<String?> memberState = const Value.absent(), + Value<i2.Uint8List?> groupPublicKey = const Value.absent(), + Value<DateTime?> lastMessage = const Value.absent(), + DateTime? createdAt}) => + GroupMembersData( + groupId: groupId ?? this.groupId, + contactId: contactId ?? this.contactId, + memberState: memberState.present ? memberState.value : this.memberState, + groupPublicKey: + groupPublicKey.present ? groupPublicKey.value : this.groupPublicKey, + lastMessage: lastMessage.present ? lastMessage.value : this.lastMessage, + createdAt: createdAt ?? this.createdAt, + ); + GroupMembersData copyWithCompanion(GroupMembersCompanion data) { + return GroupMembersData( + groupId: data.groupId.present ? data.groupId.value : this.groupId, + contactId: data.contactId.present ? data.contactId.value : this.contactId, + memberState: + data.memberState.present ? data.memberState.value : this.memberState, + groupPublicKey: data.groupPublicKey.present + ? data.groupPublicKey.value + : this.groupPublicKey, + lastMessage: + data.lastMessage.present ? data.lastMessage.value : this.lastMessage, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + ); + } + + @override + String toString() { + return (StringBuffer('GroupMembersData(') + ..write('groupId: $groupId, ') + ..write('contactId: $contactId, ') + ..write('memberState: $memberState, ') + ..write('groupPublicKey: $groupPublicKey, ') + ..write('lastMessage: $lastMessage, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(groupId, contactId, memberState, + $driftBlobEquality.hash(groupPublicKey), lastMessage, createdAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is GroupMembersData && + other.groupId == this.groupId && + other.contactId == this.contactId && + other.memberState == this.memberState && + $driftBlobEquality.equals( + other.groupPublicKey, this.groupPublicKey) && + other.lastMessage == this.lastMessage && + other.createdAt == this.createdAt); +} + +class GroupMembersCompanion extends UpdateCompanion<GroupMembersData> { + final Value<String> groupId; + final Value<int> contactId; + final Value<String?> memberState; + final Value<i2.Uint8List?> groupPublicKey; + final Value<DateTime?> lastMessage; + final Value<DateTime> createdAt; + final Value<int> rowid; + const GroupMembersCompanion({ + this.groupId = const Value.absent(), + this.contactId = const Value.absent(), + this.memberState = const Value.absent(), + this.groupPublicKey = const Value.absent(), + this.lastMessage = const Value.absent(), + this.createdAt = const Value.absent(), + this.rowid = const Value.absent(), + }); + GroupMembersCompanion.insert({ + required String groupId, + required int contactId, + this.memberState = const Value.absent(), + this.groupPublicKey = const Value.absent(), + this.lastMessage = const Value.absent(), + this.createdAt = const Value.absent(), + this.rowid = const Value.absent(), + }) : groupId = Value(groupId), + contactId = Value(contactId); + static Insertable<GroupMembersData> custom({ + Expression<String>? groupId, + Expression<int>? contactId, + Expression<String>? memberState, + Expression<i2.Uint8List>? groupPublicKey, + Expression<DateTime>? lastMessage, + Expression<DateTime>? createdAt, + Expression<int>? rowid, + }) { + return RawValuesInsertable({ + if (groupId != null) 'group_id': groupId, + if (contactId != null) 'contact_id': contactId, + if (memberState != null) 'member_state': memberState, + if (groupPublicKey != null) 'group_public_key': groupPublicKey, + if (lastMessage != null) 'last_message': lastMessage, + if (createdAt != null) 'created_at': createdAt, + if (rowid != null) 'rowid': rowid, + }); + } + + GroupMembersCompanion copyWith( + {Value<String>? groupId, + Value<int>? contactId, + Value<String?>? memberState, + Value<i2.Uint8List?>? groupPublicKey, + Value<DateTime?>? lastMessage, + Value<DateTime>? createdAt, + Value<int>? rowid}) { + return GroupMembersCompanion( + groupId: groupId ?? this.groupId, + contactId: contactId ?? this.contactId, + memberState: memberState ?? this.memberState, + groupPublicKey: groupPublicKey ?? this.groupPublicKey, + lastMessage: lastMessage ?? this.lastMessage, + createdAt: createdAt ?? this.createdAt, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map<String, Expression> toColumns(bool nullToAbsent) { + final map = <String, Expression>{}; + if (groupId.present) { + map['group_id'] = Variable<String>(groupId.value); + } + if (contactId.present) { + map['contact_id'] = Variable<int>(contactId.value); + } + if (memberState.present) { + map['member_state'] = Variable<String>(memberState.value); + } + if (groupPublicKey.present) { + map['group_public_key'] = Variable<i2.Uint8List>(groupPublicKey.value); + } + if (lastMessage.present) { + map['last_message'] = Variable<DateTime>(lastMessage.value); + } + if (createdAt.present) { + map['created_at'] = Variable<DateTime>(createdAt.value); + } + if (rowid.present) { + map['rowid'] = Variable<int>(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('GroupMembersCompanion(') + ..write('groupId: $groupId, ') + ..write('contactId: $contactId, ') + ..write('memberState: $memberState, ') + ..write('groupPublicKey: $groupPublicKey, ') + ..write('lastMessage: $lastMessage, ') + ..write('createdAt: $createdAt, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class Receipts extends Table with TableInfo<Receipts, ReceiptsData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + Receipts(this.attachedDatabase, [this._alias]); + late final GeneratedColumn<String> receiptId = GeneratedColumn<String>( + 'receipt_id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn<int> contactId = GeneratedColumn<int>( + 'contact_id', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES contacts (user_id) ON DELETE CASCADE')); + late final GeneratedColumn<String> messageId = GeneratedColumn<String>( + 'message_id', aliasedName, true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES messages (message_id) ON DELETE CASCADE')); + late final GeneratedColumn<i2.Uint8List> message = + GeneratedColumn<i2.Uint8List>('message', aliasedName, false, + type: DriftSqlType.blob, requiredDuringInsert: true); + late final GeneratedColumn<bool> contactWillSendsReceipt = + GeneratedColumn<bool>('contact_will_sends_receipt', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("contact_will_sends_receipt" IN (0, 1))'), + defaultValue: const CustomExpression('1')); + late final GeneratedColumn<DateTime> markForRetry = GeneratedColumn<DateTime>( + 'mark_for_retry', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + late final GeneratedColumn<DateTime> markForRetryAfterAccepted = + GeneratedColumn<DateTime>( + 'mark_for_retry_after_accepted', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + late final GeneratedColumn<DateTime> ackByServerAt = + GeneratedColumn<DateTime>('ack_by_server_at', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + late final GeneratedColumn<int> retryCount = GeneratedColumn<int>( + 'retry_count', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0')); + late final GeneratedColumn<DateTime> lastRetry = GeneratedColumn<DateTime>( + 'last_retry', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + late final GeneratedColumn<DateTime> createdAt = GeneratedColumn<DateTime>( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression( + 'CAST(strftime(\'%s\', CURRENT_TIMESTAMP) AS INTEGER)')); + @override + List<GeneratedColumn> get $columns => [ + receiptId, + contactId, + messageId, + message, + contactWillSendsReceipt, + markForRetry, + markForRetryAfterAccepted, + ackByServerAt, + retryCount, + lastRetry, + createdAt + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'receipts'; + @override + Set<GeneratedColumn> get $primaryKey => {receiptId}; + @override + ReceiptsData map(Map<String, dynamic> data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return ReceiptsData( + receiptId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}receipt_id'])!, + contactId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}contact_id'])!, + messageId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}message_id']), + message: attachedDatabase.typeMapping + .read(DriftSqlType.blob, data['${effectivePrefix}message'])!, + contactWillSendsReceipt: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + 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 + .read(DriftSqlType.int, data['${effectivePrefix}retry_count'])!, + lastRetry: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}last_retry']), + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + ); + } + + @override + Receipts createAlias(String alias) { + return Receipts(attachedDatabase, alias); + } +} + +class ReceiptsData extends DataClass implements Insertable<ReceiptsData> { + final String receiptId; + final int contactId; + final String? messageId; + final i2.Uint8List message; + final bool contactWillSendsReceipt; + final DateTime? markForRetry; + final DateTime? markForRetryAfterAccepted; + final DateTime? ackByServerAt; + final int retryCount; + final DateTime? lastRetry; + final DateTime createdAt; + const ReceiptsData( + {required this.receiptId, + required this.contactId, + this.messageId, + required this.message, + required this.contactWillSendsReceipt, + this.markForRetry, + this.markForRetryAfterAccepted, + this.ackByServerAt, + required this.retryCount, + this.lastRetry, + required this.createdAt}); + @override + Map<String, Expression> toColumns(bool nullToAbsent) { + final map = <String, Expression>{}; + map['receipt_id'] = Variable<String>(receiptId); + map['contact_id'] = Variable<int>(contactId); + if (!nullToAbsent || messageId != null) { + map['message_id'] = Variable<String>(messageId); + } + map['message'] = Variable<i2.Uint8List>(message); + map['contact_will_sends_receipt'] = Variable<bool>(contactWillSendsReceipt); + if (!nullToAbsent || markForRetry != null) { + map['mark_for_retry'] = Variable<DateTime>(markForRetry); + } + if (!nullToAbsent || markForRetryAfterAccepted != null) { + map['mark_for_retry_after_accepted'] = + Variable<DateTime>(markForRetryAfterAccepted); + } + if (!nullToAbsent || ackByServerAt != null) { + map['ack_by_server_at'] = Variable<DateTime>(ackByServerAt); + } + map['retry_count'] = Variable<int>(retryCount); + if (!nullToAbsent || lastRetry != null) { + map['last_retry'] = Variable<DateTime>(lastRetry); + } + map['created_at'] = Variable<DateTime>(createdAt); + return map; + } + + ReceiptsCompanion toCompanion(bool nullToAbsent) { + return ReceiptsCompanion( + receiptId: Value(receiptId), + contactId: Value(contactId), + messageId: messageId == null && nullToAbsent + ? const Value.absent() + : Value(messageId), + message: Value(message), + contactWillSendsReceipt: Value(contactWillSendsReceipt), + 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), + retryCount: Value(retryCount), + lastRetry: lastRetry == null && nullToAbsent + ? const Value.absent() + : Value(lastRetry), + createdAt: Value(createdAt), + ); + } + + factory ReceiptsData.fromJson(Map<String, dynamic> json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return ReceiptsData( + receiptId: serializer.fromJson<String>(json['receiptId']), + contactId: serializer.fromJson<int>(json['contactId']), + messageId: serializer.fromJson<String?>(json['messageId']), + message: serializer.fromJson<i2.Uint8List>(json['message']), + contactWillSendsReceipt: + serializer.fromJson<bool>(json['contactWillSendsReceipt']), + markForRetry: serializer.fromJson<DateTime?>(json['markForRetry']), + markForRetryAfterAccepted: + serializer.fromJson<DateTime?>(json['markForRetryAfterAccepted']), + ackByServerAt: serializer.fromJson<DateTime?>(json['ackByServerAt']), + retryCount: serializer.fromJson<int>(json['retryCount']), + lastRetry: serializer.fromJson<DateTime?>(json['lastRetry']), + createdAt: serializer.fromJson<DateTime>(json['createdAt']), + ); + } + @override + Map<String, dynamic> toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return <String, dynamic>{ + 'receiptId': serializer.toJson<String>(receiptId), + 'contactId': serializer.toJson<int>(contactId), + 'messageId': serializer.toJson<String?>(messageId), + 'message': serializer.toJson<i2.Uint8List>(message), + 'contactWillSendsReceipt': + serializer.toJson<bool>(contactWillSendsReceipt), + 'markForRetry': serializer.toJson<DateTime?>(markForRetry), + 'markForRetryAfterAccepted': + serializer.toJson<DateTime?>(markForRetryAfterAccepted), + 'ackByServerAt': serializer.toJson<DateTime?>(ackByServerAt), + 'retryCount': serializer.toJson<int>(retryCount), + 'lastRetry': serializer.toJson<DateTime?>(lastRetry), + 'createdAt': serializer.toJson<DateTime>(createdAt), + }; + } + + ReceiptsData copyWith( + {String? receiptId, + int? contactId, + Value<String?> messageId = const Value.absent(), + i2.Uint8List? message, + bool? contactWillSendsReceipt, + Value<DateTime?> markForRetry = const Value.absent(), + Value<DateTime?> markForRetryAfterAccepted = const Value.absent(), + Value<DateTime?> ackByServerAt = const Value.absent(), + int? retryCount, + Value<DateTime?> lastRetry = const Value.absent(), + DateTime? createdAt}) => + ReceiptsData( + receiptId: receiptId ?? this.receiptId, + contactId: contactId ?? this.contactId, + messageId: messageId.present ? messageId.value : this.messageId, + message: message ?? this.message, + contactWillSendsReceipt: + 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, + lastRetry: lastRetry.present ? lastRetry.value : this.lastRetry, + createdAt: createdAt ?? this.createdAt, + ); + ReceiptsData copyWithCompanion(ReceiptsCompanion data) { + return ReceiptsData( + receiptId: data.receiptId.present ? data.receiptId.value : this.receiptId, + contactId: data.contactId.present ? data.contactId.value : this.contactId, + messageId: data.messageId.present ? data.messageId.value : this.messageId, + message: data.message.present ? data.message.value : this.message, + contactWillSendsReceipt: data.contactWillSendsReceipt.present + ? data.contactWillSendsReceipt.value + : this.contactWillSendsReceipt, + 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, + retryCount: + data.retryCount.present ? data.retryCount.value : this.retryCount, + lastRetry: data.lastRetry.present ? data.lastRetry.value : this.lastRetry, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + ); + } + + @override + String toString() { + return (StringBuffer('ReceiptsData(') + ..write('receiptId: $receiptId, ') + ..write('contactId: $contactId, ') + ..write('messageId: $messageId, ') + ..write('message: $message, ') + ..write('contactWillSendsReceipt: $contactWillSendsReceipt, ') + ..write('markForRetry: $markForRetry, ') + ..write('markForRetryAfterAccepted: $markForRetryAfterAccepted, ') + ..write('ackByServerAt: $ackByServerAt, ') + ..write('retryCount: $retryCount, ') + ..write('lastRetry: $lastRetry, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + receiptId, + contactId, + messageId, + $driftBlobEquality.hash(message), + contactWillSendsReceipt, + markForRetry, + markForRetryAfterAccepted, + ackByServerAt, + retryCount, + lastRetry, + createdAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is ReceiptsData && + other.receiptId == this.receiptId && + other.contactId == this.contactId && + other.messageId == this.messageId && + $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 && + other.createdAt == this.createdAt); +} + +class ReceiptsCompanion extends UpdateCompanion<ReceiptsData> { + final Value<String> receiptId; + final Value<int> contactId; + final Value<String?> messageId; + final Value<i2.Uint8List> message; + final Value<bool> contactWillSendsReceipt; + final Value<DateTime?> markForRetry; + final Value<DateTime?> markForRetryAfterAccepted; + final Value<DateTime?> ackByServerAt; + final Value<int> retryCount; + final Value<DateTime?> lastRetry; + final Value<DateTime> createdAt; + final Value<int> rowid; + const ReceiptsCompanion({ + this.receiptId = const Value.absent(), + this.contactId = const Value.absent(), + this.messageId = const Value.absent(), + 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(), + this.createdAt = const Value.absent(), + this.rowid = const Value.absent(), + }); + ReceiptsCompanion.insert({ + required String receiptId, + required int contactId, + this.messageId = const Value.absent(), + required i2.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(), + this.createdAt = const Value.absent(), + this.rowid = const Value.absent(), + }) : receiptId = Value(receiptId), + contactId = Value(contactId), + message = Value(message); + static Insertable<ReceiptsData> custom({ + Expression<String>? receiptId, + Expression<int>? contactId, + Expression<String>? messageId, + Expression<i2.Uint8List>? message, + Expression<bool>? contactWillSendsReceipt, + Expression<DateTime>? markForRetry, + Expression<DateTime>? markForRetryAfterAccepted, + Expression<DateTime>? ackByServerAt, + Expression<int>? retryCount, + Expression<DateTime>? lastRetry, + Expression<DateTime>? createdAt, + Expression<int>? rowid, + }) { + return RawValuesInsertable({ + if (receiptId != null) 'receipt_id': receiptId, + if (contactId != null) 'contact_id': contactId, + if (messageId != null) 'message_id': messageId, + if (message != null) 'message': message, + 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, + if (createdAt != null) 'created_at': createdAt, + if (rowid != null) 'rowid': rowid, + }); + } + + ReceiptsCompanion copyWith( + {Value<String>? receiptId, + Value<int>? contactId, + Value<String?>? messageId, + Value<i2.Uint8List>? message, + Value<bool>? contactWillSendsReceipt, + Value<DateTime?>? markForRetry, + Value<DateTime?>? markForRetryAfterAccepted, + Value<DateTime?>? ackByServerAt, + Value<int>? retryCount, + Value<DateTime?>? lastRetry, + Value<DateTime>? createdAt, + Value<int>? rowid}) { + return ReceiptsCompanion( + receiptId: receiptId ?? this.receiptId, + contactId: contactId ?? this.contactId, + messageId: messageId ?? this.messageId, + message: message ?? this.message, + contactWillSendsReceipt: + contactWillSendsReceipt ?? this.contactWillSendsReceipt, + markForRetry: markForRetry ?? this.markForRetry, + markForRetryAfterAccepted: + markForRetryAfterAccepted ?? this.markForRetryAfterAccepted, + ackByServerAt: ackByServerAt ?? this.ackByServerAt, + retryCount: retryCount ?? this.retryCount, + lastRetry: lastRetry ?? this.lastRetry, + createdAt: createdAt ?? this.createdAt, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map<String, Expression> toColumns(bool nullToAbsent) { + final map = <String, Expression>{}; + if (receiptId.present) { + map['receipt_id'] = Variable<String>(receiptId.value); + } + if (contactId.present) { + map['contact_id'] = Variable<int>(contactId.value); + } + if (messageId.present) { + map['message_id'] = Variable<String>(messageId.value); + } + if (message.present) { + map['message'] = Variable<i2.Uint8List>(message.value); + } + if (contactWillSendsReceipt.present) { + map['contact_will_sends_receipt'] = + Variable<bool>(contactWillSendsReceipt.value); + } + if (markForRetry.present) { + map['mark_for_retry'] = Variable<DateTime>(markForRetry.value); + } + if (markForRetryAfterAccepted.present) { + map['mark_for_retry_after_accepted'] = + Variable<DateTime>(markForRetryAfterAccepted.value); + } + if (ackByServerAt.present) { + map['ack_by_server_at'] = Variable<DateTime>(ackByServerAt.value); + } + if (retryCount.present) { + map['retry_count'] = Variable<int>(retryCount.value); + } + if (lastRetry.present) { + map['last_retry'] = Variable<DateTime>(lastRetry.value); + } + if (createdAt.present) { + map['created_at'] = Variable<DateTime>(createdAt.value); + } + if (rowid.present) { + map['rowid'] = Variable<int>(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('ReceiptsCompanion(') + ..write('receiptId: $receiptId, ') + ..write('contactId: $contactId, ') + ..write('messageId: $messageId, ') + ..write('message: $message, ') + ..write('contactWillSendsReceipt: $contactWillSendsReceipt, ') + ..write('markForRetry: $markForRetry, ') + ..write('markForRetryAfterAccepted: $markForRetryAfterAccepted, ') + ..write('ackByServerAt: $ackByServerAt, ') + ..write('retryCount: $retryCount, ') + ..write('lastRetry: $lastRetry, ') + ..write('createdAt: $createdAt, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class ReceivedReceipts extends Table + with TableInfo<ReceivedReceipts, ReceivedReceiptsData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + ReceivedReceipts(this.attachedDatabase, [this._alias]); + late final GeneratedColumn<String> receiptId = GeneratedColumn<String>( + 'receipt_id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn<DateTime> createdAt = GeneratedColumn<DateTime>( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression( + 'CAST(strftime(\'%s\', CURRENT_TIMESTAMP) AS INTEGER)')); + @override + List<GeneratedColumn> get $columns => [receiptId, createdAt]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'received_receipts'; + @override + Set<GeneratedColumn> get $primaryKey => {receiptId}; + @override + ReceivedReceiptsData map(Map<String, dynamic> data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return ReceivedReceiptsData( + receiptId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}receipt_id'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + ); + } + + @override + ReceivedReceipts createAlias(String alias) { + return ReceivedReceipts(attachedDatabase, alias); + } +} + +class ReceivedReceiptsData extends DataClass + implements Insertable<ReceivedReceiptsData> { + final String receiptId; + final DateTime createdAt; + const ReceivedReceiptsData( + {required this.receiptId, required this.createdAt}); + @override + Map<String, Expression> toColumns(bool nullToAbsent) { + final map = <String, Expression>{}; + map['receipt_id'] = Variable<String>(receiptId); + map['created_at'] = Variable<DateTime>(createdAt); + return map; + } + + ReceivedReceiptsCompanion toCompanion(bool nullToAbsent) { + return ReceivedReceiptsCompanion( + receiptId: Value(receiptId), + createdAt: Value(createdAt), + ); + } + + factory ReceivedReceiptsData.fromJson(Map<String, dynamic> json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return ReceivedReceiptsData( + receiptId: serializer.fromJson<String>(json['receiptId']), + createdAt: serializer.fromJson<DateTime>(json['createdAt']), + ); + } + @override + Map<String, dynamic> toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return <String, dynamic>{ + 'receiptId': serializer.toJson<String>(receiptId), + 'createdAt': serializer.toJson<DateTime>(createdAt), + }; + } + + ReceivedReceiptsData copyWith({String? receiptId, DateTime? createdAt}) => + ReceivedReceiptsData( + receiptId: receiptId ?? this.receiptId, + createdAt: createdAt ?? this.createdAt, + ); + ReceivedReceiptsData copyWithCompanion(ReceivedReceiptsCompanion data) { + return ReceivedReceiptsData( + receiptId: data.receiptId.present ? data.receiptId.value : this.receiptId, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + ); + } + + @override + String toString() { + return (StringBuffer('ReceivedReceiptsData(') + ..write('receiptId: $receiptId, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(receiptId, createdAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is ReceivedReceiptsData && + other.receiptId == this.receiptId && + other.createdAt == this.createdAt); +} + +class ReceivedReceiptsCompanion extends UpdateCompanion<ReceivedReceiptsData> { + final Value<String> receiptId; + final Value<DateTime> createdAt; + final Value<int> rowid; + const ReceivedReceiptsCompanion({ + this.receiptId = const Value.absent(), + this.createdAt = const Value.absent(), + this.rowid = const Value.absent(), + }); + ReceivedReceiptsCompanion.insert({ + required String receiptId, + this.createdAt = const Value.absent(), + this.rowid = const Value.absent(), + }) : receiptId = Value(receiptId); + static Insertable<ReceivedReceiptsData> custom({ + Expression<String>? receiptId, + Expression<DateTime>? createdAt, + Expression<int>? rowid, + }) { + return RawValuesInsertable({ + if (receiptId != null) 'receipt_id': receiptId, + if (createdAt != null) 'created_at': createdAt, + if (rowid != null) 'rowid': rowid, + }); + } + + ReceivedReceiptsCompanion copyWith( + {Value<String>? receiptId, + Value<DateTime>? createdAt, + Value<int>? rowid}) { + return ReceivedReceiptsCompanion( + receiptId: receiptId ?? this.receiptId, + createdAt: createdAt ?? this.createdAt, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map<String, Expression> toColumns(bool nullToAbsent) { + final map = <String, Expression>{}; + if (receiptId.present) { + map['receipt_id'] = Variable<String>(receiptId.value); + } + if (createdAt.present) { + map['created_at'] = Variable<DateTime>(createdAt.value); + } + if (rowid.present) { + map['rowid'] = Variable<int>(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('ReceivedReceiptsCompanion(') + ..write('receiptId: $receiptId, ') + ..write('createdAt: $createdAt, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class SignalIdentityKeyStores extends Table + with TableInfo<SignalIdentityKeyStores, SignalIdentityKeyStoresData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + SignalIdentityKeyStores(this.attachedDatabase, [this._alias]); + late final GeneratedColumn<int> deviceId = GeneratedColumn<int>( + 'device_id', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + late final GeneratedColumn<String> name = GeneratedColumn<String>( + 'name', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn<i2.Uint8List> identityKey = + GeneratedColumn<i2.Uint8List>('identity_key', aliasedName, false, + type: DriftSqlType.blob, requiredDuringInsert: true); + late final GeneratedColumn<DateTime> createdAt = GeneratedColumn<DateTime>( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression( + 'CAST(strftime(\'%s\', CURRENT_TIMESTAMP) AS INTEGER)')); + @override + List<GeneratedColumn> get $columns => + [deviceId, name, identityKey, createdAt]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'signal_identity_key_stores'; + @override + Set<GeneratedColumn> get $primaryKey => {deviceId, name}; + @override + SignalIdentityKeyStoresData map(Map<String, dynamic> data, + {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return SignalIdentityKeyStoresData( + deviceId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}device_id'])!, + name: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}name'])!, + identityKey: attachedDatabase.typeMapping + .read(DriftSqlType.blob, data['${effectivePrefix}identity_key'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + ); + } + + @override + SignalIdentityKeyStores createAlias(String alias) { + return SignalIdentityKeyStores(attachedDatabase, alias); + } +} + +class SignalIdentityKeyStoresData extends DataClass + implements Insertable<SignalIdentityKeyStoresData> { + final int deviceId; + final String name; + final i2.Uint8List identityKey; + final DateTime createdAt; + const SignalIdentityKeyStoresData( + {required this.deviceId, + required this.name, + required this.identityKey, + required this.createdAt}); + @override + Map<String, Expression> toColumns(bool nullToAbsent) { + final map = <String, Expression>{}; + map['device_id'] = Variable<int>(deviceId); + map['name'] = Variable<String>(name); + map['identity_key'] = Variable<i2.Uint8List>(identityKey); + map['created_at'] = Variable<DateTime>(createdAt); + return map; + } + + SignalIdentityKeyStoresCompanion toCompanion(bool nullToAbsent) { + return SignalIdentityKeyStoresCompanion( + deviceId: Value(deviceId), + name: Value(name), + identityKey: Value(identityKey), + createdAt: Value(createdAt), + ); + } + + factory SignalIdentityKeyStoresData.fromJson(Map<String, dynamic> json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return SignalIdentityKeyStoresData( + deviceId: serializer.fromJson<int>(json['deviceId']), + name: serializer.fromJson<String>(json['name']), + identityKey: serializer.fromJson<i2.Uint8List>(json['identityKey']), + createdAt: serializer.fromJson<DateTime>(json['createdAt']), + ); + } + @override + Map<String, dynamic> toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return <String, dynamic>{ + 'deviceId': serializer.toJson<int>(deviceId), + 'name': serializer.toJson<String>(name), + 'identityKey': serializer.toJson<i2.Uint8List>(identityKey), + 'createdAt': serializer.toJson<DateTime>(createdAt), + }; + } + + SignalIdentityKeyStoresData copyWith( + {int? deviceId, + String? name, + i2.Uint8List? identityKey, + DateTime? createdAt}) => + SignalIdentityKeyStoresData( + deviceId: deviceId ?? this.deviceId, + name: name ?? this.name, + identityKey: identityKey ?? this.identityKey, + createdAt: createdAt ?? this.createdAt, + ); + SignalIdentityKeyStoresData copyWithCompanion( + SignalIdentityKeyStoresCompanion data) { + return SignalIdentityKeyStoresData( + deviceId: data.deviceId.present ? data.deviceId.value : this.deviceId, + name: data.name.present ? data.name.value : this.name, + identityKey: + data.identityKey.present ? data.identityKey.value : this.identityKey, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + ); + } + + @override + String toString() { + return (StringBuffer('SignalIdentityKeyStoresData(') + ..write('deviceId: $deviceId, ') + ..write('name: $name, ') + ..write('identityKey: $identityKey, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + deviceId, name, $driftBlobEquality.hash(identityKey), createdAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is SignalIdentityKeyStoresData && + other.deviceId == this.deviceId && + other.name == this.name && + $driftBlobEquality.equals(other.identityKey, this.identityKey) && + other.createdAt == this.createdAt); +} + +class SignalIdentityKeyStoresCompanion + extends UpdateCompanion<SignalIdentityKeyStoresData> { + final Value<int> deviceId; + final Value<String> name; + final Value<i2.Uint8List> identityKey; + final Value<DateTime> createdAt; + final Value<int> rowid; + const SignalIdentityKeyStoresCompanion({ + this.deviceId = const Value.absent(), + this.name = const Value.absent(), + this.identityKey = const Value.absent(), + this.createdAt = const Value.absent(), + this.rowid = const Value.absent(), + }); + SignalIdentityKeyStoresCompanion.insert({ + required int deviceId, + required String name, + required i2.Uint8List identityKey, + this.createdAt = const Value.absent(), + this.rowid = const Value.absent(), + }) : deviceId = Value(deviceId), + name = Value(name), + identityKey = Value(identityKey); + static Insertable<SignalIdentityKeyStoresData> custom({ + Expression<int>? deviceId, + Expression<String>? name, + Expression<i2.Uint8List>? identityKey, + Expression<DateTime>? createdAt, + Expression<int>? rowid, + }) { + return RawValuesInsertable({ + if (deviceId != null) 'device_id': deviceId, + if (name != null) 'name': name, + if (identityKey != null) 'identity_key': identityKey, + if (createdAt != null) 'created_at': createdAt, + if (rowid != null) 'rowid': rowid, + }); + } + + SignalIdentityKeyStoresCompanion copyWith( + {Value<int>? deviceId, + Value<String>? name, + Value<i2.Uint8List>? identityKey, + Value<DateTime>? createdAt, + Value<int>? rowid}) { + return SignalIdentityKeyStoresCompanion( + deviceId: deviceId ?? this.deviceId, + name: name ?? this.name, + identityKey: identityKey ?? this.identityKey, + createdAt: createdAt ?? this.createdAt, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map<String, Expression> toColumns(bool nullToAbsent) { + final map = <String, Expression>{}; + if (deviceId.present) { + map['device_id'] = Variable<int>(deviceId.value); + } + if (name.present) { + map['name'] = Variable<String>(name.value); + } + if (identityKey.present) { + map['identity_key'] = Variable<i2.Uint8List>(identityKey.value); + } + if (createdAt.present) { + map['created_at'] = Variable<DateTime>(createdAt.value); + } + if (rowid.present) { + map['rowid'] = Variable<int>(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('SignalIdentityKeyStoresCompanion(') + ..write('deviceId: $deviceId, ') + ..write('name: $name, ') + ..write('identityKey: $identityKey, ') + ..write('createdAt: $createdAt, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class SignalPreKeyStores extends Table + with TableInfo<SignalPreKeyStores, SignalPreKeyStoresData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + SignalPreKeyStores(this.attachedDatabase, [this._alias]); + late final GeneratedColumn<int> preKeyId = GeneratedColumn<int>( + 'pre_key_id', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: false); + late final GeneratedColumn<i2.Uint8List> preKey = + GeneratedColumn<i2.Uint8List>('pre_key', aliasedName, false, + type: DriftSqlType.blob, requiredDuringInsert: true); + late final GeneratedColumn<DateTime> createdAt = GeneratedColumn<DateTime>( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression( + 'CAST(strftime(\'%s\', CURRENT_TIMESTAMP) AS INTEGER)')); + @override + List<GeneratedColumn> get $columns => [preKeyId, preKey, createdAt]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'signal_pre_key_stores'; + @override + Set<GeneratedColumn> get $primaryKey => {preKeyId}; + @override + SignalPreKeyStoresData map(Map<String, dynamic> data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return SignalPreKeyStoresData( + preKeyId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}pre_key_id'])!, + preKey: attachedDatabase.typeMapping + .read(DriftSqlType.blob, data['${effectivePrefix}pre_key'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + ); + } + + @override + SignalPreKeyStores createAlias(String alias) { + return SignalPreKeyStores(attachedDatabase, alias); + } +} + +class SignalPreKeyStoresData extends DataClass + implements Insertable<SignalPreKeyStoresData> { + final int preKeyId; + final i2.Uint8List preKey; + final DateTime createdAt; + const SignalPreKeyStoresData( + {required this.preKeyId, required this.preKey, required this.createdAt}); + @override + Map<String, Expression> toColumns(bool nullToAbsent) { + final map = <String, Expression>{}; + map['pre_key_id'] = Variable<int>(preKeyId); + map['pre_key'] = Variable<i2.Uint8List>(preKey); + map['created_at'] = Variable<DateTime>(createdAt); + return map; + } + + SignalPreKeyStoresCompanion toCompanion(bool nullToAbsent) { + return SignalPreKeyStoresCompanion( + preKeyId: Value(preKeyId), + preKey: Value(preKey), + createdAt: Value(createdAt), + ); + } + + factory SignalPreKeyStoresData.fromJson(Map<String, dynamic> json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return SignalPreKeyStoresData( + preKeyId: serializer.fromJson<int>(json['preKeyId']), + preKey: serializer.fromJson<i2.Uint8List>(json['preKey']), + createdAt: serializer.fromJson<DateTime>(json['createdAt']), + ); + } + @override + Map<String, dynamic> toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return <String, dynamic>{ + 'preKeyId': serializer.toJson<int>(preKeyId), + 'preKey': serializer.toJson<i2.Uint8List>(preKey), + 'createdAt': serializer.toJson<DateTime>(createdAt), + }; + } + + SignalPreKeyStoresData copyWith( + {int? preKeyId, i2.Uint8List? preKey, DateTime? createdAt}) => + SignalPreKeyStoresData( + preKeyId: preKeyId ?? this.preKeyId, + preKey: preKey ?? this.preKey, + createdAt: createdAt ?? this.createdAt, + ); + SignalPreKeyStoresData copyWithCompanion(SignalPreKeyStoresCompanion data) { + return SignalPreKeyStoresData( + preKeyId: data.preKeyId.present ? data.preKeyId.value : this.preKeyId, + preKey: data.preKey.present ? data.preKey.value : this.preKey, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + ); + } + + @override + String toString() { + return (StringBuffer('SignalPreKeyStoresData(') + ..write('preKeyId: $preKeyId, ') + ..write('preKey: $preKey, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(preKeyId, $driftBlobEquality.hash(preKey), createdAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is SignalPreKeyStoresData && + other.preKeyId == this.preKeyId && + $driftBlobEquality.equals(other.preKey, this.preKey) && + other.createdAt == this.createdAt); +} + +class SignalPreKeyStoresCompanion + extends UpdateCompanion<SignalPreKeyStoresData> { + final Value<int> preKeyId; + final Value<i2.Uint8List> preKey; + final Value<DateTime> createdAt; + const SignalPreKeyStoresCompanion({ + this.preKeyId = const Value.absent(), + this.preKey = const Value.absent(), + this.createdAt = const Value.absent(), + }); + SignalPreKeyStoresCompanion.insert({ + this.preKeyId = const Value.absent(), + required i2.Uint8List preKey, + this.createdAt = const Value.absent(), + }) : preKey = Value(preKey); + static Insertable<SignalPreKeyStoresData> custom({ + Expression<int>? preKeyId, + Expression<i2.Uint8List>? preKey, + Expression<DateTime>? createdAt, + }) { + return RawValuesInsertable({ + if (preKeyId != null) 'pre_key_id': preKeyId, + if (preKey != null) 'pre_key': preKey, + if (createdAt != null) 'created_at': createdAt, + }); + } + + SignalPreKeyStoresCompanion copyWith( + {Value<int>? preKeyId, + Value<i2.Uint8List>? preKey, + Value<DateTime>? createdAt}) { + return SignalPreKeyStoresCompanion( + preKeyId: preKeyId ?? this.preKeyId, + preKey: preKey ?? this.preKey, + createdAt: createdAt ?? this.createdAt, + ); + } + + @override + Map<String, Expression> toColumns(bool nullToAbsent) { + final map = <String, Expression>{}; + if (preKeyId.present) { + map['pre_key_id'] = Variable<int>(preKeyId.value); + } + if (preKey.present) { + map['pre_key'] = Variable<i2.Uint8List>(preKey.value); + } + if (createdAt.present) { + map['created_at'] = Variable<DateTime>(createdAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('SignalPreKeyStoresCompanion(') + ..write('preKeyId: $preKeyId, ') + ..write('preKey: $preKey, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } +} + +class SignalSenderKeyStores extends Table + with TableInfo<SignalSenderKeyStores, SignalSenderKeyStoresData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + SignalSenderKeyStores(this.attachedDatabase, [this._alias]); + late final GeneratedColumn<String> senderKeyName = GeneratedColumn<String>( + 'sender_key_name', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn<i2.Uint8List> senderKey = + GeneratedColumn<i2.Uint8List>('sender_key', aliasedName, false, + type: DriftSqlType.blob, requiredDuringInsert: true); + @override + List<GeneratedColumn> get $columns => [senderKeyName, senderKey]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'signal_sender_key_stores'; + @override + Set<GeneratedColumn> get $primaryKey => {senderKeyName}; + @override + SignalSenderKeyStoresData map(Map<String, dynamic> data, + {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return SignalSenderKeyStoresData( + senderKeyName: attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}sender_key_name'])!, + senderKey: attachedDatabase.typeMapping + .read(DriftSqlType.blob, data['${effectivePrefix}sender_key'])!, + ); + } + + @override + SignalSenderKeyStores createAlias(String alias) { + return SignalSenderKeyStores(attachedDatabase, alias); + } +} + +class SignalSenderKeyStoresData extends DataClass + implements Insertable<SignalSenderKeyStoresData> { + final String senderKeyName; + final i2.Uint8List senderKey; + const SignalSenderKeyStoresData( + {required this.senderKeyName, required this.senderKey}); + @override + Map<String, Expression> toColumns(bool nullToAbsent) { + final map = <String, Expression>{}; + map['sender_key_name'] = Variable<String>(senderKeyName); + map['sender_key'] = Variable<i2.Uint8List>(senderKey); + return map; + } + + SignalSenderKeyStoresCompanion toCompanion(bool nullToAbsent) { + return SignalSenderKeyStoresCompanion( + senderKeyName: Value(senderKeyName), + senderKey: Value(senderKey), + ); + } + + factory SignalSenderKeyStoresData.fromJson(Map<String, dynamic> json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return SignalSenderKeyStoresData( + senderKeyName: serializer.fromJson<String>(json['senderKeyName']), + senderKey: serializer.fromJson<i2.Uint8List>(json['senderKey']), + ); + } + @override + Map<String, dynamic> toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return <String, dynamic>{ + 'senderKeyName': serializer.toJson<String>(senderKeyName), + 'senderKey': serializer.toJson<i2.Uint8List>(senderKey), + }; + } + + SignalSenderKeyStoresData copyWith( + {String? senderKeyName, i2.Uint8List? senderKey}) => + SignalSenderKeyStoresData( + senderKeyName: senderKeyName ?? this.senderKeyName, + senderKey: senderKey ?? this.senderKey, + ); + SignalSenderKeyStoresData copyWithCompanion( + SignalSenderKeyStoresCompanion data) { + return SignalSenderKeyStoresData( + senderKeyName: data.senderKeyName.present + ? data.senderKeyName.value + : this.senderKeyName, + senderKey: data.senderKey.present ? data.senderKey.value : this.senderKey, + ); + } + + @override + String toString() { + return (StringBuffer('SignalSenderKeyStoresData(') + ..write('senderKeyName: $senderKeyName, ') + ..write('senderKey: $senderKey') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(senderKeyName, $driftBlobEquality.hash(senderKey)); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is SignalSenderKeyStoresData && + other.senderKeyName == this.senderKeyName && + $driftBlobEquality.equals(other.senderKey, this.senderKey)); +} + +class SignalSenderKeyStoresCompanion + extends UpdateCompanion<SignalSenderKeyStoresData> { + final Value<String> senderKeyName; + final Value<i2.Uint8List> senderKey; + final Value<int> rowid; + const SignalSenderKeyStoresCompanion({ + this.senderKeyName = const Value.absent(), + this.senderKey = const Value.absent(), + this.rowid = const Value.absent(), + }); + SignalSenderKeyStoresCompanion.insert({ + required String senderKeyName, + required i2.Uint8List senderKey, + this.rowid = const Value.absent(), + }) : senderKeyName = Value(senderKeyName), + senderKey = Value(senderKey); + static Insertable<SignalSenderKeyStoresData> custom({ + Expression<String>? senderKeyName, + Expression<i2.Uint8List>? senderKey, + Expression<int>? rowid, + }) { + return RawValuesInsertable({ + if (senderKeyName != null) 'sender_key_name': senderKeyName, + if (senderKey != null) 'sender_key': senderKey, + if (rowid != null) 'rowid': rowid, + }); + } + + SignalSenderKeyStoresCompanion copyWith( + {Value<String>? senderKeyName, + Value<i2.Uint8List>? senderKey, + Value<int>? rowid}) { + return SignalSenderKeyStoresCompanion( + senderKeyName: senderKeyName ?? this.senderKeyName, + senderKey: senderKey ?? this.senderKey, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map<String, Expression> toColumns(bool nullToAbsent) { + final map = <String, Expression>{}; + if (senderKeyName.present) { + map['sender_key_name'] = Variable<String>(senderKeyName.value); + } + if (senderKey.present) { + map['sender_key'] = Variable<i2.Uint8List>(senderKey.value); + } + if (rowid.present) { + map['rowid'] = Variable<int>(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('SignalSenderKeyStoresCompanion(') + ..write('senderKeyName: $senderKeyName, ') + ..write('senderKey: $senderKey, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class SignalSessionStores extends Table + with TableInfo<SignalSessionStores, SignalSessionStoresData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + SignalSessionStores(this.attachedDatabase, [this._alias]); + late final GeneratedColumn<int> deviceId = GeneratedColumn<int>( + 'device_id', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + late final GeneratedColumn<String> name = GeneratedColumn<String>( + 'name', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn<i2.Uint8List> sessionRecord = + GeneratedColumn<i2.Uint8List>('session_record', aliasedName, false, + type: DriftSqlType.blob, requiredDuringInsert: true); + late final GeneratedColumn<DateTime> createdAt = GeneratedColumn<DateTime>( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression( + 'CAST(strftime(\'%s\', CURRENT_TIMESTAMP) AS INTEGER)')); + @override + List<GeneratedColumn> get $columns => + [deviceId, name, sessionRecord, createdAt]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'signal_session_stores'; + @override + Set<GeneratedColumn> get $primaryKey => {deviceId, name}; + @override + SignalSessionStoresData map(Map<String, dynamic> data, + {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return SignalSessionStoresData( + deviceId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}device_id'])!, + name: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}name'])!, + sessionRecord: attachedDatabase.typeMapping + .read(DriftSqlType.blob, data['${effectivePrefix}session_record'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + ); + } + + @override + SignalSessionStores createAlias(String alias) { + return SignalSessionStores(attachedDatabase, alias); + } +} + +class SignalSessionStoresData extends DataClass + implements Insertable<SignalSessionStoresData> { + final int deviceId; + final String name; + final i2.Uint8List sessionRecord; + final DateTime createdAt; + const SignalSessionStoresData( + {required this.deviceId, + required this.name, + required this.sessionRecord, + required this.createdAt}); + @override + Map<String, Expression> toColumns(bool nullToAbsent) { + final map = <String, Expression>{}; + map['device_id'] = Variable<int>(deviceId); + map['name'] = Variable<String>(name); + map['session_record'] = Variable<i2.Uint8List>(sessionRecord); + map['created_at'] = Variable<DateTime>(createdAt); + return map; + } + + SignalSessionStoresCompanion toCompanion(bool nullToAbsent) { + return SignalSessionStoresCompanion( + deviceId: Value(deviceId), + name: Value(name), + sessionRecord: Value(sessionRecord), + createdAt: Value(createdAt), + ); + } + + factory SignalSessionStoresData.fromJson(Map<String, dynamic> json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return SignalSessionStoresData( + deviceId: serializer.fromJson<int>(json['deviceId']), + name: serializer.fromJson<String>(json['name']), + sessionRecord: serializer.fromJson<i2.Uint8List>(json['sessionRecord']), + createdAt: serializer.fromJson<DateTime>(json['createdAt']), + ); + } + @override + Map<String, dynamic> toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return <String, dynamic>{ + 'deviceId': serializer.toJson<int>(deviceId), + 'name': serializer.toJson<String>(name), + 'sessionRecord': serializer.toJson<i2.Uint8List>(sessionRecord), + 'createdAt': serializer.toJson<DateTime>(createdAt), + }; + } + + SignalSessionStoresData copyWith( + {int? deviceId, + String? name, + i2.Uint8List? sessionRecord, + DateTime? createdAt}) => + SignalSessionStoresData( + deviceId: deviceId ?? this.deviceId, + name: name ?? this.name, + sessionRecord: sessionRecord ?? this.sessionRecord, + createdAt: createdAt ?? this.createdAt, + ); + SignalSessionStoresData copyWithCompanion(SignalSessionStoresCompanion data) { + return SignalSessionStoresData( + deviceId: data.deviceId.present ? data.deviceId.value : this.deviceId, + name: data.name.present ? data.name.value : this.name, + sessionRecord: data.sessionRecord.present + ? data.sessionRecord.value + : this.sessionRecord, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + ); + } + + @override + String toString() { + return (StringBuffer('SignalSessionStoresData(') + ..write('deviceId: $deviceId, ') + ..write('name: $name, ') + ..write('sessionRecord: $sessionRecord, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + deviceId, name, $driftBlobEquality.hash(sessionRecord), createdAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is SignalSessionStoresData && + other.deviceId == this.deviceId && + other.name == this.name && + $driftBlobEquality.equals(other.sessionRecord, this.sessionRecord) && + other.createdAt == this.createdAt); +} + +class SignalSessionStoresCompanion + extends UpdateCompanion<SignalSessionStoresData> { + final Value<int> deviceId; + final Value<String> name; + final Value<i2.Uint8List> sessionRecord; + final Value<DateTime> createdAt; + final Value<int> rowid; + const SignalSessionStoresCompanion({ + this.deviceId = const Value.absent(), + this.name = const Value.absent(), + this.sessionRecord = const Value.absent(), + this.createdAt = const Value.absent(), + this.rowid = const Value.absent(), + }); + SignalSessionStoresCompanion.insert({ + required int deviceId, + required String name, + required i2.Uint8List sessionRecord, + this.createdAt = const Value.absent(), + this.rowid = const Value.absent(), + }) : deviceId = Value(deviceId), + name = Value(name), + sessionRecord = Value(sessionRecord); + static Insertable<SignalSessionStoresData> custom({ + Expression<int>? deviceId, + Expression<String>? name, + Expression<i2.Uint8List>? sessionRecord, + Expression<DateTime>? createdAt, + Expression<int>? rowid, + }) { + return RawValuesInsertable({ + if (deviceId != null) 'device_id': deviceId, + if (name != null) 'name': name, + if (sessionRecord != null) 'session_record': sessionRecord, + if (createdAt != null) 'created_at': createdAt, + if (rowid != null) 'rowid': rowid, + }); + } + + SignalSessionStoresCompanion copyWith( + {Value<int>? deviceId, + Value<String>? name, + Value<i2.Uint8List>? sessionRecord, + Value<DateTime>? createdAt, + Value<int>? rowid}) { + return SignalSessionStoresCompanion( + deviceId: deviceId ?? this.deviceId, + name: name ?? this.name, + sessionRecord: sessionRecord ?? this.sessionRecord, + createdAt: createdAt ?? this.createdAt, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map<String, Expression> toColumns(bool nullToAbsent) { + final map = <String, Expression>{}; + if (deviceId.present) { + map['device_id'] = Variable<int>(deviceId.value); + } + if (name.present) { + map['name'] = Variable<String>(name.value); + } + if (sessionRecord.present) { + map['session_record'] = Variable<i2.Uint8List>(sessionRecord.value); + } + if (createdAt.present) { + map['created_at'] = Variable<DateTime>(createdAt.value); + } + if (rowid.present) { + map['rowid'] = Variable<int>(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('SignalSessionStoresCompanion(') + ..write('deviceId: $deviceId, ') + ..write('name: $name, ') + ..write('sessionRecord: $sessionRecord, ') + ..write('createdAt: $createdAt, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class SignalContactPreKeys extends Table + with TableInfo<SignalContactPreKeys, SignalContactPreKeysData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + SignalContactPreKeys(this.attachedDatabase, [this._alias]); + late final GeneratedColumn<int> contactId = GeneratedColumn<int>( + 'contact_id', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES contacts (user_id) ON DELETE CASCADE')); + late final GeneratedColumn<int> preKeyId = GeneratedColumn<int>( + 'pre_key_id', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + late final GeneratedColumn<i2.Uint8List> preKey = + GeneratedColumn<i2.Uint8List>('pre_key', aliasedName, false, + type: DriftSqlType.blob, requiredDuringInsert: true); + late final GeneratedColumn<DateTime> createdAt = GeneratedColumn<DateTime>( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression( + 'CAST(strftime(\'%s\', CURRENT_TIMESTAMP) AS INTEGER)')); + @override + List<GeneratedColumn> get $columns => + [contactId, preKeyId, preKey, createdAt]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'signal_contact_pre_keys'; + @override + Set<GeneratedColumn> get $primaryKey => {contactId, preKeyId}; + @override + SignalContactPreKeysData map(Map<String, dynamic> data, + {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return SignalContactPreKeysData( + contactId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}contact_id'])!, + preKeyId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}pre_key_id'])!, + preKey: attachedDatabase.typeMapping + .read(DriftSqlType.blob, data['${effectivePrefix}pre_key'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + ); + } + + @override + SignalContactPreKeys createAlias(String alias) { + return SignalContactPreKeys(attachedDatabase, alias); + } +} + +class SignalContactPreKeysData extends DataClass + implements Insertable<SignalContactPreKeysData> { + final int contactId; + final int preKeyId; + final i2.Uint8List preKey; + final DateTime createdAt; + const SignalContactPreKeysData( + {required this.contactId, + required this.preKeyId, + required this.preKey, + required this.createdAt}); + @override + Map<String, Expression> toColumns(bool nullToAbsent) { + final map = <String, Expression>{}; + map['contact_id'] = Variable<int>(contactId); + map['pre_key_id'] = Variable<int>(preKeyId); + map['pre_key'] = Variable<i2.Uint8List>(preKey); + map['created_at'] = Variable<DateTime>(createdAt); + return map; + } + + SignalContactPreKeysCompanion toCompanion(bool nullToAbsent) { + return SignalContactPreKeysCompanion( + contactId: Value(contactId), + preKeyId: Value(preKeyId), + preKey: Value(preKey), + createdAt: Value(createdAt), + ); + } + + factory SignalContactPreKeysData.fromJson(Map<String, dynamic> json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return SignalContactPreKeysData( + contactId: serializer.fromJson<int>(json['contactId']), + preKeyId: serializer.fromJson<int>(json['preKeyId']), + preKey: serializer.fromJson<i2.Uint8List>(json['preKey']), + createdAt: serializer.fromJson<DateTime>(json['createdAt']), + ); + } + @override + Map<String, dynamic> toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return <String, dynamic>{ + 'contactId': serializer.toJson<int>(contactId), + 'preKeyId': serializer.toJson<int>(preKeyId), + 'preKey': serializer.toJson<i2.Uint8List>(preKey), + 'createdAt': serializer.toJson<DateTime>(createdAt), + }; + } + + SignalContactPreKeysData copyWith( + {int? contactId, + int? preKeyId, + i2.Uint8List? preKey, + DateTime? createdAt}) => + SignalContactPreKeysData( + contactId: contactId ?? this.contactId, + preKeyId: preKeyId ?? this.preKeyId, + preKey: preKey ?? this.preKey, + createdAt: createdAt ?? this.createdAt, + ); + SignalContactPreKeysData copyWithCompanion( + SignalContactPreKeysCompanion data) { + return SignalContactPreKeysData( + contactId: data.contactId.present ? data.contactId.value : this.contactId, + preKeyId: data.preKeyId.present ? data.preKeyId.value : this.preKeyId, + preKey: data.preKey.present ? data.preKey.value : this.preKey, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + ); + } + + @override + String toString() { + return (StringBuffer('SignalContactPreKeysData(') + ..write('contactId: $contactId, ') + ..write('preKeyId: $preKeyId, ') + ..write('preKey: $preKey, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + contactId, preKeyId, $driftBlobEquality.hash(preKey), createdAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is SignalContactPreKeysData && + other.contactId == this.contactId && + other.preKeyId == this.preKeyId && + $driftBlobEquality.equals(other.preKey, this.preKey) && + other.createdAt == this.createdAt); +} + +class SignalContactPreKeysCompanion + extends UpdateCompanion<SignalContactPreKeysData> { + final Value<int> contactId; + final Value<int> preKeyId; + final Value<i2.Uint8List> preKey; + final Value<DateTime> createdAt; + final Value<int> rowid; + const SignalContactPreKeysCompanion({ + this.contactId = const Value.absent(), + this.preKeyId = const Value.absent(), + this.preKey = const Value.absent(), + this.createdAt = const Value.absent(), + this.rowid = const Value.absent(), + }); + SignalContactPreKeysCompanion.insert({ + required int contactId, + required int preKeyId, + required i2.Uint8List preKey, + this.createdAt = const Value.absent(), + this.rowid = const Value.absent(), + }) : contactId = Value(contactId), + preKeyId = Value(preKeyId), + preKey = Value(preKey); + static Insertable<SignalContactPreKeysData> custom({ + Expression<int>? contactId, + Expression<int>? preKeyId, + Expression<i2.Uint8List>? preKey, + Expression<DateTime>? createdAt, + Expression<int>? rowid, + }) { + return RawValuesInsertable({ + if (contactId != null) 'contact_id': contactId, + if (preKeyId != null) 'pre_key_id': preKeyId, + if (preKey != null) 'pre_key': preKey, + if (createdAt != null) 'created_at': createdAt, + if (rowid != null) 'rowid': rowid, + }); + } + + SignalContactPreKeysCompanion copyWith( + {Value<int>? contactId, + Value<int>? preKeyId, + Value<i2.Uint8List>? preKey, + Value<DateTime>? createdAt, + Value<int>? rowid}) { + return SignalContactPreKeysCompanion( + contactId: contactId ?? this.contactId, + preKeyId: preKeyId ?? this.preKeyId, + preKey: preKey ?? this.preKey, + createdAt: createdAt ?? this.createdAt, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map<String, Expression> toColumns(bool nullToAbsent) { + final map = <String, Expression>{}; + if (contactId.present) { + map['contact_id'] = Variable<int>(contactId.value); + } + if (preKeyId.present) { + map['pre_key_id'] = Variable<int>(preKeyId.value); + } + if (preKey.present) { + map['pre_key'] = Variable<i2.Uint8List>(preKey.value); + } + if (createdAt.present) { + map['created_at'] = Variable<DateTime>(createdAt.value); + } + if (rowid.present) { + map['rowid'] = Variable<int>(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('SignalContactPreKeysCompanion(') + ..write('contactId: $contactId, ') + ..write('preKeyId: $preKeyId, ') + ..write('preKey: $preKey, ') + ..write('createdAt: $createdAt, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class SignalContactSignedPreKeys extends Table + with TableInfo<SignalContactSignedPreKeys, SignalContactSignedPreKeysData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + SignalContactSignedPreKeys(this.attachedDatabase, [this._alias]); + late final GeneratedColumn<int> contactId = GeneratedColumn<int>( + 'contact_id', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES contacts (user_id) ON DELETE CASCADE')); + late final GeneratedColumn<int> signedPreKeyId = GeneratedColumn<int>( + 'signed_pre_key_id', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + late final GeneratedColumn<i2.Uint8List> signedPreKey = + GeneratedColumn<i2.Uint8List>('signed_pre_key', aliasedName, false, + type: DriftSqlType.blob, requiredDuringInsert: true); + late final GeneratedColumn<i2.Uint8List> signedPreKeySignature = + GeneratedColumn<i2.Uint8List>( + 'signed_pre_key_signature', aliasedName, false, + type: DriftSqlType.blob, requiredDuringInsert: true); + late final GeneratedColumn<DateTime> createdAt = GeneratedColumn<DateTime>( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression( + 'CAST(strftime(\'%s\', CURRENT_TIMESTAMP) AS INTEGER)')); + @override + List<GeneratedColumn> get $columns => [ + contactId, + signedPreKeyId, + signedPreKey, + signedPreKeySignature, + createdAt + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'signal_contact_signed_pre_keys'; + @override + Set<GeneratedColumn> get $primaryKey => {contactId}; + @override + SignalContactSignedPreKeysData map(Map<String, dynamic> data, + {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return SignalContactSignedPreKeysData( + contactId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}contact_id'])!, + signedPreKeyId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}signed_pre_key_id'])!, + signedPreKey: attachedDatabase.typeMapping + .read(DriftSqlType.blob, data['${effectivePrefix}signed_pre_key'])!, + signedPreKeySignature: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}signed_pre_key_signature'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + ); + } + + @override + SignalContactSignedPreKeys createAlias(String alias) { + return SignalContactSignedPreKeys(attachedDatabase, alias); + } +} + +class SignalContactSignedPreKeysData extends DataClass + implements Insertable<SignalContactSignedPreKeysData> { + final int contactId; + final int signedPreKeyId; + final i2.Uint8List signedPreKey; + final i2.Uint8List signedPreKeySignature; + final DateTime createdAt; + const SignalContactSignedPreKeysData( + {required this.contactId, + required this.signedPreKeyId, + required this.signedPreKey, + required this.signedPreKeySignature, + required this.createdAt}); + @override + Map<String, Expression> toColumns(bool nullToAbsent) { + final map = <String, Expression>{}; + map['contact_id'] = Variable<int>(contactId); + map['signed_pre_key_id'] = Variable<int>(signedPreKeyId); + map['signed_pre_key'] = Variable<i2.Uint8List>(signedPreKey); + map['signed_pre_key_signature'] = + Variable<i2.Uint8List>(signedPreKeySignature); + map['created_at'] = Variable<DateTime>(createdAt); + return map; + } + + SignalContactSignedPreKeysCompanion toCompanion(bool nullToAbsent) { + return SignalContactSignedPreKeysCompanion( + contactId: Value(contactId), + signedPreKeyId: Value(signedPreKeyId), + signedPreKey: Value(signedPreKey), + signedPreKeySignature: Value(signedPreKeySignature), + createdAt: Value(createdAt), + ); + } + + factory SignalContactSignedPreKeysData.fromJson(Map<String, dynamic> json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return SignalContactSignedPreKeysData( + contactId: serializer.fromJson<int>(json['contactId']), + signedPreKeyId: serializer.fromJson<int>(json['signedPreKeyId']), + signedPreKey: serializer.fromJson<i2.Uint8List>(json['signedPreKey']), + signedPreKeySignature: + serializer.fromJson<i2.Uint8List>(json['signedPreKeySignature']), + createdAt: serializer.fromJson<DateTime>(json['createdAt']), + ); + } + @override + Map<String, dynamic> toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return <String, dynamic>{ + 'contactId': serializer.toJson<int>(contactId), + 'signedPreKeyId': serializer.toJson<int>(signedPreKeyId), + 'signedPreKey': serializer.toJson<i2.Uint8List>(signedPreKey), + 'signedPreKeySignature': + serializer.toJson<i2.Uint8List>(signedPreKeySignature), + 'createdAt': serializer.toJson<DateTime>(createdAt), + }; + } + + SignalContactSignedPreKeysData copyWith( + {int? contactId, + int? signedPreKeyId, + i2.Uint8List? signedPreKey, + i2.Uint8List? signedPreKeySignature, + DateTime? createdAt}) => + SignalContactSignedPreKeysData( + contactId: contactId ?? this.contactId, + signedPreKeyId: signedPreKeyId ?? this.signedPreKeyId, + signedPreKey: signedPreKey ?? this.signedPreKey, + signedPreKeySignature: + signedPreKeySignature ?? this.signedPreKeySignature, + createdAt: createdAt ?? this.createdAt, + ); + SignalContactSignedPreKeysData copyWithCompanion( + SignalContactSignedPreKeysCompanion data) { + return SignalContactSignedPreKeysData( + contactId: data.contactId.present ? data.contactId.value : this.contactId, + signedPreKeyId: data.signedPreKeyId.present + ? data.signedPreKeyId.value + : this.signedPreKeyId, + signedPreKey: data.signedPreKey.present + ? data.signedPreKey.value + : this.signedPreKey, + signedPreKeySignature: data.signedPreKeySignature.present + ? data.signedPreKeySignature.value + : this.signedPreKeySignature, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + ); + } + + @override + String toString() { + return (StringBuffer('SignalContactSignedPreKeysData(') + ..write('contactId: $contactId, ') + ..write('signedPreKeyId: $signedPreKeyId, ') + ..write('signedPreKey: $signedPreKey, ') + ..write('signedPreKeySignature: $signedPreKeySignature, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + contactId, + signedPreKeyId, + $driftBlobEquality.hash(signedPreKey), + $driftBlobEquality.hash(signedPreKeySignature), + createdAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is SignalContactSignedPreKeysData && + other.contactId == this.contactId && + other.signedPreKeyId == this.signedPreKeyId && + $driftBlobEquality.equals(other.signedPreKey, this.signedPreKey) && + $driftBlobEquality.equals( + other.signedPreKeySignature, this.signedPreKeySignature) && + other.createdAt == this.createdAt); +} + +class SignalContactSignedPreKeysCompanion + extends UpdateCompanion<SignalContactSignedPreKeysData> { + final Value<int> contactId; + final Value<int> signedPreKeyId; + final Value<i2.Uint8List> signedPreKey; + final Value<i2.Uint8List> signedPreKeySignature; + final Value<DateTime> createdAt; + const SignalContactSignedPreKeysCompanion({ + this.contactId = const Value.absent(), + this.signedPreKeyId = const Value.absent(), + this.signedPreKey = const Value.absent(), + this.signedPreKeySignature = const Value.absent(), + this.createdAt = const Value.absent(), + }); + SignalContactSignedPreKeysCompanion.insert({ + this.contactId = const Value.absent(), + required int signedPreKeyId, + required i2.Uint8List signedPreKey, + required i2.Uint8List signedPreKeySignature, + this.createdAt = const Value.absent(), + }) : signedPreKeyId = Value(signedPreKeyId), + signedPreKey = Value(signedPreKey), + signedPreKeySignature = Value(signedPreKeySignature); + static Insertable<SignalContactSignedPreKeysData> custom({ + Expression<int>? contactId, + Expression<int>? signedPreKeyId, + Expression<i2.Uint8List>? signedPreKey, + Expression<i2.Uint8List>? signedPreKeySignature, + Expression<DateTime>? createdAt, + }) { + return RawValuesInsertable({ + if (contactId != null) 'contact_id': contactId, + if (signedPreKeyId != null) 'signed_pre_key_id': signedPreKeyId, + if (signedPreKey != null) 'signed_pre_key': signedPreKey, + if (signedPreKeySignature != null) + 'signed_pre_key_signature': signedPreKeySignature, + if (createdAt != null) 'created_at': createdAt, + }); + } + + SignalContactSignedPreKeysCompanion copyWith( + {Value<int>? contactId, + Value<int>? signedPreKeyId, + Value<i2.Uint8List>? signedPreKey, + Value<i2.Uint8List>? signedPreKeySignature, + Value<DateTime>? createdAt}) { + return SignalContactSignedPreKeysCompanion( + contactId: contactId ?? this.contactId, + signedPreKeyId: signedPreKeyId ?? this.signedPreKeyId, + signedPreKey: signedPreKey ?? this.signedPreKey, + signedPreKeySignature: + signedPreKeySignature ?? this.signedPreKeySignature, + createdAt: createdAt ?? this.createdAt, + ); + } + + @override + Map<String, Expression> toColumns(bool nullToAbsent) { + final map = <String, Expression>{}; + if (contactId.present) { + map['contact_id'] = Variable<int>(contactId.value); + } + if (signedPreKeyId.present) { + map['signed_pre_key_id'] = Variable<int>(signedPreKeyId.value); + } + if (signedPreKey.present) { + map['signed_pre_key'] = Variable<i2.Uint8List>(signedPreKey.value); + } + if (signedPreKeySignature.present) { + map['signed_pre_key_signature'] = + Variable<i2.Uint8List>(signedPreKeySignature.value); + } + if (createdAt.present) { + map['created_at'] = Variable<DateTime>(createdAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('SignalContactSignedPreKeysCompanion(') + ..write('contactId: $contactId, ') + ..write('signedPreKeyId: $signedPreKeyId, ') + ..write('signedPreKey: $signedPreKey, ') + ..write('signedPreKeySignature: $signedPreKeySignature, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } +} + +class MessageActions extends Table + with TableInfo<MessageActions, MessageActionsData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MessageActions(this.attachedDatabase, [this._alias]); + late final GeneratedColumn<String> messageId = GeneratedColumn<String>( + 'message_id', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES messages (message_id) ON DELETE CASCADE')); + late final GeneratedColumn<int> contactId = GeneratedColumn<int>( + 'contact_id', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + late final GeneratedColumn<String> type = GeneratedColumn<String>( + 'type', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn<DateTime> actionAt = GeneratedColumn<DateTime>( + 'action_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression( + 'CAST(strftime(\'%s\', CURRENT_TIMESTAMP) AS INTEGER)')); + @override + List<GeneratedColumn> get $columns => [messageId, contactId, type, actionAt]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'message_actions'; + @override + Set<GeneratedColumn> get $primaryKey => {messageId, contactId, type}; + @override + MessageActionsData map(Map<String, dynamic> data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MessageActionsData( + messageId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}message_id'])!, + contactId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}contact_id'])!, + type: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}type'])!, + actionAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}action_at'])!, + ); + } + + @override + MessageActions createAlias(String alias) { + return MessageActions(attachedDatabase, alias); + } +} + +class MessageActionsData extends DataClass + implements Insertable<MessageActionsData> { + final String messageId; + final int contactId; + final String type; + final DateTime actionAt; + const MessageActionsData( + {required this.messageId, + required this.contactId, + required this.type, + required this.actionAt}); + @override + Map<String, Expression> toColumns(bool nullToAbsent) { + final map = <String, Expression>{}; + map['message_id'] = Variable<String>(messageId); + map['contact_id'] = Variable<int>(contactId); + map['type'] = Variable<String>(type); + map['action_at'] = Variable<DateTime>(actionAt); + return map; + } + + MessageActionsCompanion toCompanion(bool nullToAbsent) { + return MessageActionsCompanion( + messageId: Value(messageId), + contactId: Value(contactId), + type: Value(type), + actionAt: Value(actionAt), + ); + } + + factory MessageActionsData.fromJson(Map<String, dynamic> json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MessageActionsData( + messageId: serializer.fromJson<String>(json['messageId']), + contactId: serializer.fromJson<int>(json['contactId']), + type: serializer.fromJson<String>(json['type']), + actionAt: serializer.fromJson<DateTime>(json['actionAt']), + ); + } + @override + Map<String, dynamic> toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return <String, dynamic>{ + 'messageId': serializer.toJson<String>(messageId), + 'contactId': serializer.toJson<int>(contactId), + 'type': serializer.toJson<String>(type), + 'actionAt': serializer.toJson<DateTime>(actionAt), + }; + } + + MessageActionsData copyWith( + {String? messageId, + int? contactId, + String? type, + DateTime? actionAt}) => + MessageActionsData( + messageId: messageId ?? this.messageId, + contactId: contactId ?? this.contactId, + type: type ?? this.type, + actionAt: actionAt ?? this.actionAt, + ); + MessageActionsData copyWithCompanion(MessageActionsCompanion data) { + return MessageActionsData( + messageId: data.messageId.present ? data.messageId.value : this.messageId, + contactId: data.contactId.present ? data.contactId.value : this.contactId, + type: data.type.present ? data.type.value : this.type, + actionAt: data.actionAt.present ? data.actionAt.value : this.actionAt, + ); + } + + @override + String toString() { + return (StringBuffer('MessageActionsData(') + ..write('messageId: $messageId, ') + ..write('contactId: $contactId, ') + ..write('type: $type, ') + ..write('actionAt: $actionAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(messageId, contactId, type, actionAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MessageActionsData && + other.messageId == this.messageId && + other.contactId == this.contactId && + other.type == this.type && + other.actionAt == this.actionAt); +} + +class MessageActionsCompanion extends UpdateCompanion<MessageActionsData> { + final Value<String> messageId; + final Value<int> contactId; + final Value<String> type; + final Value<DateTime> actionAt; + final Value<int> rowid; + const MessageActionsCompanion({ + this.messageId = const Value.absent(), + this.contactId = const Value.absent(), + this.type = const Value.absent(), + this.actionAt = const Value.absent(), + this.rowid = const Value.absent(), + }); + MessageActionsCompanion.insert({ + required String messageId, + required int contactId, + required String type, + this.actionAt = const Value.absent(), + this.rowid = const Value.absent(), + }) : messageId = Value(messageId), + contactId = Value(contactId), + type = Value(type); + static Insertable<MessageActionsData> custom({ + Expression<String>? messageId, + Expression<int>? contactId, + Expression<String>? type, + Expression<DateTime>? actionAt, + Expression<int>? rowid, + }) { + return RawValuesInsertable({ + if (messageId != null) 'message_id': messageId, + if (contactId != null) 'contact_id': contactId, + if (type != null) 'type': type, + if (actionAt != null) 'action_at': actionAt, + if (rowid != null) 'rowid': rowid, + }); + } + + MessageActionsCompanion copyWith( + {Value<String>? messageId, + Value<int>? contactId, + Value<String>? type, + Value<DateTime>? actionAt, + Value<int>? rowid}) { + return MessageActionsCompanion( + messageId: messageId ?? this.messageId, + contactId: contactId ?? this.contactId, + type: type ?? this.type, + actionAt: actionAt ?? this.actionAt, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map<String, Expression> toColumns(bool nullToAbsent) { + final map = <String, Expression>{}; + if (messageId.present) { + map['message_id'] = Variable<String>(messageId.value); + } + if (contactId.present) { + map['contact_id'] = Variable<int>(contactId.value); + } + if (type.present) { + map['type'] = Variable<String>(type.value); + } + if (actionAt.present) { + map['action_at'] = Variable<DateTime>(actionAt.value); + } + if (rowid.present) { + map['rowid'] = Variable<int>(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MessageActionsCompanion(') + ..write('messageId: $messageId, ') + ..write('contactId: $contactId, ') + ..write('type: $type, ') + ..write('actionAt: $actionAt, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class GroupHistories extends Table + with TableInfo<GroupHistories, GroupHistoriesData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + GroupHistories(this.attachedDatabase, [this._alias]); + late final GeneratedColumn<String> groupHistoryId = GeneratedColumn<String>( + 'group_history_id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn<String> groupId = GeneratedColumn<String>( + 'group_id', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES "groups" (group_id) ON DELETE CASCADE')); + late final GeneratedColumn<int> contactId = GeneratedColumn<int>( + 'contact_id', aliasedName, true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('REFERENCES contacts (user_id)')); + late final GeneratedColumn<int> affectedContactId = GeneratedColumn<int>( + 'affected_contact_id', aliasedName, true, + type: DriftSqlType.int, requiredDuringInsert: false); + late final GeneratedColumn<String> oldGroupName = GeneratedColumn<String>( + 'old_group_name', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + late final GeneratedColumn<String> newGroupName = GeneratedColumn<String>( + 'new_group_name', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + late final GeneratedColumn<int> newDeleteMessagesAfterMilliseconds = + GeneratedColumn<int>( + 'new_delete_messages_after_milliseconds', aliasedName, true, + type: DriftSqlType.int, requiredDuringInsert: false); + late final GeneratedColumn<String> type = GeneratedColumn<String>( + 'type', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn<DateTime> actionAt = GeneratedColumn<DateTime>( + 'action_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression( + 'CAST(strftime(\'%s\', CURRENT_TIMESTAMP) AS INTEGER)')); + @override + List<GeneratedColumn> get $columns => [ + groupHistoryId, + groupId, + contactId, + affectedContactId, + oldGroupName, + newGroupName, + newDeleteMessagesAfterMilliseconds, + type, + actionAt + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'group_histories'; + @override + Set<GeneratedColumn> get $primaryKey => {groupHistoryId}; + @override + GroupHistoriesData map(Map<String, dynamic> data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return GroupHistoriesData( + groupHistoryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}group_history_id'])!, + groupId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}group_id'])!, + contactId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}contact_id']), + affectedContactId: attachedDatabase.typeMapping.read( + DriftSqlType.int, data['${effectivePrefix}affected_contact_id']), + oldGroupName: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}old_group_name']), + newGroupName: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}new_group_name']), + newDeleteMessagesAfterMilliseconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}new_delete_messages_after_milliseconds']), + type: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}type'])!, + actionAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}action_at'])!, + ); + } + + @override + GroupHistories createAlias(String alias) { + return GroupHistories(attachedDatabase, alias); + } +} + +class GroupHistoriesData extends DataClass + implements Insertable<GroupHistoriesData> { + final String groupHistoryId; + final String groupId; + final int? contactId; + final int? affectedContactId; + final String? oldGroupName; + final String? newGroupName; + final int? newDeleteMessagesAfterMilliseconds; + final String type; + final DateTime actionAt; + const GroupHistoriesData( + {required this.groupHistoryId, + required this.groupId, + this.contactId, + this.affectedContactId, + this.oldGroupName, + this.newGroupName, + this.newDeleteMessagesAfterMilliseconds, + required this.type, + required this.actionAt}); + @override + Map<String, Expression> toColumns(bool nullToAbsent) { + final map = <String, Expression>{}; + map['group_history_id'] = Variable<String>(groupHistoryId); + map['group_id'] = Variable<String>(groupId); + if (!nullToAbsent || contactId != null) { + map['contact_id'] = Variable<int>(contactId); + } + if (!nullToAbsent || affectedContactId != null) { + map['affected_contact_id'] = Variable<int>(affectedContactId); + } + if (!nullToAbsent || oldGroupName != null) { + map['old_group_name'] = Variable<String>(oldGroupName); + } + if (!nullToAbsent || newGroupName != null) { + map['new_group_name'] = Variable<String>(newGroupName); + } + if (!nullToAbsent || newDeleteMessagesAfterMilliseconds != null) { + map['new_delete_messages_after_milliseconds'] = + Variable<int>(newDeleteMessagesAfterMilliseconds); + } + map['type'] = Variable<String>(type); + map['action_at'] = Variable<DateTime>(actionAt); + return map; + } + + GroupHistoriesCompanion toCompanion(bool nullToAbsent) { + return GroupHistoriesCompanion( + groupHistoryId: Value(groupHistoryId), + groupId: Value(groupId), + contactId: contactId == null && nullToAbsent + ? const Value.absent() + : Value(contactId), + affectedContactId: affectedContactId == null && nullToAbsent + ? const Value.absent() + : Value(affectedContactId), + oldGroupName: oldGroupName == null && nullToAbsent + ? const Value.absent() + : Value(oldGroupName), + newGroupName: newGroupName == null && nullToAbsent + ? const Value.absent() + : Value(newGroupName), + newDeleteMessagesAfterMilliseconds: + newDeleteMessagesAfterMilliseconds == null && nullToAbsent + ? const Value.absent() + : Value(newDeleteMessagesAfterMilliseconds), + type: Value(type), + actionAt: Value(actionAt), + ); + } + + factory GroupHistoriesData.fromJson(Map<String, dynamic> json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return GroupHistoriesData( + groupHistoryId: serializer.fromJson<String>(json['groupHistoryId']), + groupId: serializer.fromJson<String>(json['groupId']), + contactId: serializer.fromJson<int?>(json['contactId']), + affectedContactId: serializer.fromJson<int?>(json['affectedContactId']), + oldGroupName: serializer.fromJson<String?>(json['oldGroupName']), + newGroupName: serializer.fromJson<String?>(json['newGroupName']), + newDeleteMessagesAfterMilliseconds: + serializer.fromJson<int?>(json['newDeleteMessagesAfterMilliseconds']), + type: serializer.fromJson<String>(json['type']), + actionAt: serializer.fromJson<DateTime>(json['actionAt']), + ); + } + @override + Map<String, dynamic> toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return <String, dynamic>{ + 'groupHistoryId': serializer.toJson<String>(groupHistoryId), + 'groupId': serializer.toJson<String>(groupId), + 'contactId': serializer.toJson<int?>(contactId), + 'affectedContactId': serializer.toJson<int?>(affectedContactId), + 'oldGroupName': serializer.toJson<String?>(oldGroupName), + 'newGroupName': serializer.toJson<String?>(newGroupName), + 'newDeleteMessagesAfterMilliseconds': + serializer.toJson<int?>(newDeleteMessagesAfterMilliseconds), + 'type': serializer.toJson<String>(type), + 'actionAt': serializer.toJson<DateTime>(actionAt), + }; + } + + GroupHistoriesData copyWith( + {String? groupHistoryId, + String? groupId, + Value<int?> contactId = const Value.absent(), + Value<int?> affectedContactId = const Value.absent(), + Value<String?> oldGroupName = const Value.absent(), + Value<String?> newGroupName = const Value.absent(), + Value<int?> newDeleteMessagesAfterMilliseconds = const Value.absent(), + String? type, + DateTime? actionAt}) => + GroupHistoriesData( + groupHistoryId: groupHistoryId ?? this.groupHistoryId, + groupId: groupId ?? this.groupId, + contactId: contactId.present ? contactId.value : this.contactId, + affectedContactId: affectedContactId.present + ? affectedContactId.value + : this.affectedContactId, + oldGroupName: + oldGroupName.present ? oldGroupName.value : this.oldGroupName, + newGroupName: + newGroupName.present ? newGroupName.value : this.newGroupName, + newDeleteMessagesAfterMilliseconds: + newDeleteMessagesAfterMilliseconds.present + ? newDeleteMessagesAfterMilliseconds.value + : this.newDeleteMessagesAfterMilliseconds, + type: type ?? this.type, + actionAt: actionAt ?? this.actionAt, + ); + GroupHistoriesData copyWithCompanion(GroupHistoriesCompanion data) { + return GroupHistoriesData( + groupHistoryId: data.groupHistoryId.present + ? data.groupHistoryId.value + : this.groupHistoryId, + groupId: data.groupId.present ? data.groupId.value : this.groupId, + contactId: data.contactId.present ? data.contactId.value : this.contactId, + affectedContactId: data.affectedContactId.present + ? data.affectedContactId.value + : this.affectedContactId, + oldGroupName: data.oldGroupName.present + ? data.oldGroupName.value + : this.oldGroupName, + newGroupName: data.newGroupName.present + ? data.newGroupName.value + : this.newGroupName, + newDeleteMessagesAfterMilliseconds: + data.newDeleteMessagesAfterMilliseconds.present + ? data.newDeleteMessagesAfterMilliseconds.value + : this.newDeleteMessagesAfterMilliseconds, + type: data.type.present ? data.type.value : this.type, + actionAt: data.actionAt.present ? data.actionAt.value : this.actionAt, + ); + } + + @override + String toString() { + return (StringBuffer('GroupHistoriesData(') + ..write('groupHistoryId: $groupHistoryId, ') + ..write('groupId: $groupId, ') + ..write('contactId: $contactId, ') + ..write('affectedContactId: $affectedContactId, ') + ..write('oldGroupName: $oldGroupName, ') + ..write('newGroupName: $newGroupName, ') + ..write( + 'newDeleteMessagesAfterMilliseconds: $newDeleteMessagesAfterMilliseconds, ') + ..write('type: $type, ') + ..write('actionAt: $actionAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + groupHistoryId, + groupId, + contactId, + affectedContactId, + oldGroupName, + newGroupName, + newDeleteMessagesAfterMilliseconds, + type, + actionAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is GroupHistoriesData && + other.groupHistoryId == this.groupHistoryId && + other.groupId == this.groupId && + other.contactId == this.contactId && + other.affectedContactId == this.affectedContactId && + other.oldGroupName == this.oldGroupName && + other.newGroupName == this.newGroupName && + other.newDeleteMessagesAfterMilliseconds == + this.newDeleteMessagesAfterMilliseconds && + other.type == this.type && + other.actionAt == this.actionAt); +} + +class GroupHistoriesCompanion extends UpdateCompanion<GroupHistoriesData> { + final Value<String> groupHistoryId; + final Value<String> groupId; + final Value<int?> contactId; + final Value<int?> affectedContactId; + final Value<String?> oldGroupName; + final Value<String?> newGroupName; + final Value<int?> newDeleteMessagesAfterMilliseconds; + final Value<String> type; + final Value<DateTime> actionAt; + final Value<int> rowid; + const GroupHistoriesCompanion({ + this.groupHistoryId = const Value.absent(), + this.groupId = const Value.absent(), + this.contactId = const Value.absent(), + this.affectedContactId = const Value.absent(), + this.oldGroupName = const Value.absent(), + this.newGroupName = const Value.absent(), + this.newDeleteMessagesAfterMilliseconds = const Value.absent(), + this.type = const Value.absent(), + this.actionAt = const Value.absent(), + this.rowid = const Value.absent(), + }); + GroupHistoriesCompanion.insert({ + required String groupHistoryId, + required String groupId, + this.contactId = const Value.absent(), + this.affectedContactId = const Value.absent(), + this.oldGroupName = const Value.absent(), + this.newGroupName = const Value.absent(), + this.newDeleteMessagesAfterMilliseconds = const Value.absent(), + required String type, + this.actionAt = const Value.absent(), + this.rowid = const Value.absent(), + }) : groupHistoryId = Value(groupHistoryId), + groupId = Value(groupId), + type = Value(type); + static Insertable<GroupHistoriesData> custom({ + Expression<String>? groupHistoryId, + Expression<String>? groupId, + Expression<int>? contactId, + Expression<int>? affectedContactId, + Expression<String>? oldGroupName, + Expression<String>? newGroupName, + Expression<int>? newDeleteMessagesAfterMilliseconds, + Expression<String>? type, + Expression<DateTime>? actionAt, + Expression<int>? rowid, + }) { + return RawValuesInsertable({ + if (groupHistoryId != null) 'group_history_id': groupHistoryId, + if (groupId != null) 'group_id': groupId, + if (contactId != null) 'contact_id': contactId, + if (affectedContactId != null) 'affected_contact_id': affectedContactId, + if (oldGroupName != null) 'old_group_name': oldGroupName, + if (newGroupName != null) 'new_group_name': newGroupName, + if (newDeleteMessagesAfterMilliseconds != null) + 'new_delete_messages_after_milliseconds': + newDeleteMessagesAfterMilliseconds, + if (type != null) 'type': type, + if (actionAt != null) 'action_at': actionAt, + if (rowid != null) 'rowid': rowid, + }); + } + + GroupHistoriesCompanion copyWith( + {Value<String>? groupHistoryId, + Value<String>? groupId, + Value<int?>? contactId, + Value<int?>? affectedContactId, + Value<String?>? oldGroupName, + Value<String?>? newGroupName, + Value<int?>? newDeleteMessagesAfterMilliseconds, + Value<String>? type, + Value<DateTime>? actionAt, + Value<int>? rowid}) { + return GroupHistoriesCompanion( + groupHistoryId: groupHistoryId ?? this.groupHistoryId, + groupId: groupId ?? this.groupId, + contactId: contactId ?? this.contactId, + affectedContactId: affectedContactId ?? this.affectedContactId, + oldGroupName: oldGroupName ?? this.oldGroupName, + newGroupName: newGroupName ?? this.newGroupName, + newDeleteMessagesAfterMilliseconds: newDeleteMessagesAfterMilliseconds ?? + this.newDeleteMessagesAfterMilliseconds, + type: type ?? this.type, + actionAt: actionAt ?? this.actionAt, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map<String, Expression> toColumns(bool nullToAbsent) { + final map = <String, Expression>{}; + if (groupHistoryId.present) { + map['group_history_id'] = Variable<String>(groupHistoryId.value); + } + if (groupId.present) { + map['group_id'] = Variable<String>(groupId.value); + } + if (contactId.present) { + map['contact_id'] = Variable<int>(contactId.value); + } + if (affectedContactId.present) { + map['affected_contact_id'] = Variable<int>(affectedContactId.value); + } + if (oldGroupName.present) { + map['old_group_name'] = Variable<String>(oldGroupName.value); + } + if (newGroupName.present) { + map['new_group_name'] = Variable<String>(newGroupName.value); + } + if (newDeleteMessagesAfterMilliseconds.present) { + map['new_delete_messages_after_milliseconds'] = + Variable<int>(newDeleteMessagesAfterMilliseconds.value); + } + if (type.present) { + map['type'] = Variable<String>(type.value); + } + if (actionAt.present) { + map['action_at'] = Variable<DateTime>(actionAt.value); + } + if (rowid.present) { + map['rowid'] = Variable<int>(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('GroupHistoriesCompanion(') + ..write('groupHistoryId: $groupHistoryId, ') + ..write('groupId: $groupId, ') + ..write('contactId: $contactId, ') + ..write('affectedContactId: $affectedContactId, ') + ..write('oldGroupName: $oldGroupName, ') + ..write('newGroupName: $newGroupName, ') + ..write( + 'newDeleteMessagesAfterMilliseconds: $newDeleteMessagesAfterMilliseconds, ') + ..write('type: $type, ') + ..write('actionAt: $actionAt, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV7 extends GeneratedDatabase { + DatabaseAtV7(QueryExecutor e) : super(e); + late final Contacts contacts = Contacts(this); + late final Groups groups = Groups(this); + late final MediaFiles mediaFiles = MediaFiles(this); + late final Messages messages = Messages(this); + late final MessageHistories messageHistories = MessageHistories(this); + late final Reactions reactions = Reactions(this); + late final GroupMembers groupMembers = GroupMembers(this); + late final Receipts receipts = Receipts(this); + late final ReceivedReceipts receivedReceipts = ReceivedReceipts(this); + late final SignalIdentityKeyStores signalIdentityKeyStores = + SignalIdentityKeyStores(this); + late final SignalPreKeyStores signalPreKeyStores = SignalPreKeyStores(this); + late final SignalSenderKeyStores signalSenderKeyStores = + SignalSenderKeyStores(this); + late final SignalSessionStores signalSessionStores = + SignalSessionStores(this); + late final SignalContactPreKeys signalContactPreKeys = + SignalContactPreKeys(this); + late final SignalContactSignedPreKeys signalContactSignedPreKeys = + SignalContactSignedPreKeys(this); + late final MessageActions messageActions = MessageActions(this); + late final GroupHistories groupHistories = GroupHistories(this); + @override + Iterable<TableInfo<Table, Object?>> get allTables => + allSchemaEntities.whereType<TableInfo<Table, Object?>>(); + @override + List<DatabaseSchemaEntity> get allSchemaEntities => [ + contacts, + groups, + mediaFiles, + messages, + messageHistories, + reactions, + groupMembers, + receipts, + receivedReceipts, + signalIdentityKeyStores, + signalPreKeyStores, + signalSenderKeyStores, + signalSessionStores, + signalContactPreKeys, + signalContactSignedPreKeys, + messageActions, + groupHistories + ]; + @override + int get schemaVersion => 7; +} diff --git a/test/features/link_parser_test.dart b/test/features/link_parser_test.dart new file mode 100644 index 0000000..ea637c7 --- /dev/null +++ b/test/features/link_parser_test.dart @@ -0,0 +1,131 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parse_link.dart'; +import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/base.dart'; + +class LinkParserTest { + LinkParserTest({ + required this.title, + required this.url, + this.desc, + this.image, + this.siteName, + this.vendor, + this.publishDate, + this.likeAction, + this.shareAction, + }); + String title; + String? desc; + String? image; + String url; + String? siteName; + + Vendor? vendor; + + DateTime? publishDate; + int? likeAction; // https://schema.org/LikeAction + int? shareAction; +} + +void main() { + test('testing different urls', () async { + final testCases = [ + LinkParserTest( + url: 'https://mastodon.social/@islieb/115883317936171927', + title: 'islieb? (@islieb@mastodon.social)', + siteName: 'Mastodon', + desc: 'Attached: 1 image', + image: + 'https://files.mastodon.social/media_attachments/files/115/883/317/526/523/824/original/6fa7ef90ec68f1f1.jpg', + vendor: Vendor.mastodonSocialMediaPosting, + shareAction: 90, + likeAction: 290, + ), + LinkParserTest( + url: 'https://chaos.social/@netzpolitik_feed/115921534467938262', + title: 'netzpolitik.org (@netzpolitik_feed@chaos.social)', + siteName: 'chaos.social', + desc: + 'Die EU-Kommission erkennt Open Source als entscheidend für die digitale Souveränität an und wünscht sich mehr Kommerzialisierung. Bis April will Brüssel eine neue Strategie veröffentlichen. In einer laufenden Konsultation bekräftigen Stimmen aus ganz Europa, welche Vorteile sie in offenem Quellcode sehen.\n' + '\n' + 'https://netzpolitik.org/2026/konsultation-eu-kommission-arbeitet-an-neuer-open-source-strategie/', + vendor: Vendor.mastodonSocialMediaPosting, + shareAction: 70, + likeAction: 90, + ), + LinkParserTest( + title: 'Kuketz-Blog 🛡 (@kuketzblog@social.tchncs.de)', + url: 'https://social.tchncs.de/@kuketzblog/115898752560771936', + siteName: 'Mastodon', + desc: + 'AWS verspricht jetzt »Souveränität« mit einem »europäischen« Cloud-Angebot – Standort Deutschland, großes Vertrauens-Theater.\n' + '\n' + 'Nur: Souveränität ist keine Postleitzahl. Wenn der Anbieter Amazon heißt, bleibt es dasselbe Märchen mit neuem Umschlag: Der Cloud Act, FISA etc. gilt trotzdem. US-Recht schlägt Geografie. Das Gerede von »Souveränität« ist kein Konzept, sondern Marketing.\n' + '\n' + 'https://www.heise.de/news/AWS-verspricht-Souveraenitaet-mit-europaeischem-Cloudangebot-11141800.html', + vendor: Vendor.mastodonSocialMediaPosting, + shareAction: 15, + likeAction: 190, + ), + LinkParserTest( + title: + 'David Kriesel: Traue keinem Scan, den du nicht selbst gefälscht hast', + url: 'https://www.youtube.com/watch?v=7FeqF1-Z1g0', + siteName: 'YouTube', + vendor: Vendor.youtubeVideo, + image: 'https://i.ytimg.com/vi/7FeqF1-Z1g0/hqdefault.jpg', + ), + LinkParserTest( + title: 'netzpolitik.org (@netzpolitik_org) on X', + url: 'https://x.com/netzpolitik_org/status/1782791019412529665', + siteName: 'X (formerly Twitter)', + desc: + 'Jetzt ist wirklich Schluss: Wir verlassen als Redaktion das zur Plattform für Rechtsradikale verkommene Twitter – und freuen uns, wenn ihr uns woanders folgt.\n' + '\n' + 'https://t.co/8W0hGly5bL', + vendor: Vendor.twitterPosting, + ), + LinkParserTest( + title: 'netzpolitik.org (@netzpolitik_org) on X', + url: 'https://x.com/netzpolitik_org/status/1162346968124968960', + siteName: 'X (formerly Twitter)', + desc: + 'Weil unsere Datenanalyse zum Twitter-Account von Maaßen rechte Millieus und ihre Verbindungen offengelegt hat, haben wir einen rechten Shitstorm an der Backe. Klar ist: Wir lassen uns nicht einschüchtern und freuen uns auf Unterstützung! \n' + '\n' + 'https://t.co/MQZ7ulHakF', + image: 'https://pbs.twimg.com/media/ECF8Z5KWwAIBZ6o.jpg:large', + vendor: Vendor.twitterPosting, + ), + LinkParserTest( + title: 'twonly Public Launch', + desc: + 'After about a year of development, twonly is finally ready for its public launch.', + url: 'https://twonly.eu/en/blog/2026-public-launch.html', + image: 'https://twonly.eu/assets/blog/2026-public-launch.webp', + ), + ]; + + for (final testCase in testCases) { + final metadata = (await getMetadata(testCase.url))!; + expect(metadata.title, testCase.title); + expect(metadata.siteName, testCase.siteName); + expect(metadata.desc, testCase.desc); + expect(metadata.url, testCase.url); + expect(metadata.image, testCase.image); + expect(metadata.vendor, testCase.vendor, reason: metadata.url); + if (testCase.shareAction != null) { + expect( + metadata.shareAction, + greaterThanOrEqualTo(testCase.shareAction!), + ); + } + if (testCase.shareAction != null) { + expect( + metadata.likeAction, + greaterThanOrEqualTo(testCase.likeAction!), + ); + } + expect(metadata.publishDate, testCase.publishDate); + } + }); +} diff --git a/test/unit_test.dart b/test/unit_test.dart index c8eb739..80a4db4 100644 --- a/test/unit_test.dart +++ b/test/unit_test.dart @@ -8,10 +8,10 @@ import 'package:twonly/src/views/components/animate_icon.dart'; void main() { group('testing utils', () { test('test isEmoji function', () { - expect(isEmoji('Hallo'), false); - expect(isEmoji('😂'), true); - expect(isEmoji('😂😂'), false); - expect(isEmoji('Hallo 😂'), false); + expect(isOneEmoji('Hallo'), false); + expect(isOneEmoji('😂'), true); + expect(isOneEmoji('😂😂'), false); + expect(isOneEmoji('Hallo 😂'), false); }); test('test proof-of-work simple', () async {