mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-03-05 11:56:47 +00:00
Merge pull request #388 from twonlyapp/dev
- 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
This commit is contained in:
commit
e8b98761a7
112 changed files with 11350 additions and 1550 deletions
10
CHANGELOG.md
10
CHANGELOG.md
|
|
@ -1,5 +1,15 @@
|
||||||
# Changelog
|
# 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
|
## 0.0.86
|
||||||
|
|
||||||
- Allows to reopen send images (if send without time limit or enabled auth)
|
- Allows to reopen send images (if send without time limit or enabled auth)
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,21 @@
|
||||||
<data android:scheme="http" android:host="me.twonly.eu" />
|
<data android:scheme="http" android:host="me.twonly.eu" />
|
||||||
<data android:scheme="https" />
|
<data android:scheme="https" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<!-- Allow other apps to share links -->
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="http" />
|
||||||
|
<data android:scheme="https" />
|
||||||
|
<data android:host="*" />
|
||||||
|
</intent-filter>
|
||||||
|
<!-- Allow other apps to share links via plain text -->
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="text/plain" />
|
||||||
|
</intent-filter>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SEND" />
|
<action android:name="android.intent.action.SEND" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
|
|
||||||
BIN
assets/filters/beard_upper_lip.webp
Normal file
BIN
assets/filters/beard_upper_lip.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.6 KiB |
BIN
assets/filters/dog_brown_ear.webp
Normal file
BIN
assets/filters/dog_brown_ear.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.4 KiB |
BIN
assets/filters/dog_brown_nose.webp
Normal file
BIN
assets/filters/dog_brown_nose.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.8 KiB |
|
|
@ -109,10 +109,10 @@ func getPushNotificationData(pushData: String) -> (
|
||||||
} else if pushUser != nil {
|
} else if pushUser != nil {
|
||||||
return (
|
return (
|
||||||
pushUser!.displayName,
|
pushUser!.displayName,
|
||||||
getPushNotificationText(pushNotification: pushNotification).0, pushUser!.userID
|
getPushNotificationText(pushNotification: pushNotification, userKnown: true).0, pushUser!.userID
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
let content = getPushNotificationText(pushNotification: pushNotification)
|
let content = getPushNotificationText(pushNotification: pushNotification, userKnown: false)
|
||||||
return (
|
return (
|
||||||
content.1, content.0, 1
|
content.1, content.0, 1
|
||||||
)
|
)
|
||||||
|
|
@ -205,15 +205,20 @@ func readFromKeychain(key: String) -> String? {
|
||||||
return nil
|
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
|
let systemLanguage = Locale.current.language.languageCode?.identifier ?? "en" // Get the current system language
|
||||||
|
|
||||||
var pushNotificationText: [PushKind: String] = [:]
|
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
|
// Define the messages based on the system language
|
||||||
if systemLanguage.contains("de") { // German
|
if systemLanguage.contains("de") { // German
|
||||||
title = "[Unbekannt]"
|
title = "Du"
|
||||||
|
noTranslationFoundTitle = "Du hast eine neue Nachricht."
|
||||||
|
noTranslationFoundBody = "Öffne twonly um mehr zu erfahren."
|
||||||
|
if (userKnown) {
|
||||||
pushNotificationText = [
|
pushNotificationText = [
|
||||||
.text: "hat eine Nachricht{inGroup} gesendet.",
|
.text: "hat eine Nachricht{inGroup} gesendet.",
|
||||||
.twonly: "hat ein twonly{inGroup} gesendet.",
|
.twonly: "hat ein twonly{inGroup} gesendet.",
|
||||||
|
|
@ -233,7 +238,13 @@ func getPushNotificationText(pushNotification: PushNotification) -> (String, Str
|
||||||
.response: "hat dir{inGroup} geantwortet.",
|
.response: "hat dir{inGroup} geantwortet.",
|
||||||
.addedToGroup: "hat dich zu \"{{content}}\" hinzugefügt.",
|
.addedToGroup: "hat dich zu \"{{content}}\" hinzugefügt.",
|
||||||
]
|
]
|
||||||
} else { // Default to English
|
} else {
|
||||||
|
pushNotificationText = [
|
||||||
|
.contactRequest: "hast eine neue Kontaktanfrage erhalten.",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (userKnown) {
|
||||||
pushNotificationText = [
|
pushNotificationText = [
|
||||||
.text: "sent a message{inGroup}.",
|
.text: "sent a message{inGroup}.",
|
||||||
.twonly: "sent a twonly{inGroup}.",
|
.twonly: "sent a twonly{inGroup}.",
|
||||||
|
|
@ -253,9 +264,18 @@ func getPushNotificationText(pushNotification: PushNotification) -> (String, Str
|
||||||
.response: "has responded{inGroup}.",
|
.response: "has responded{inGroup}.",
|
||||||
.addedToGroup: "has added you to \"{{content}}\"",
|
.addedToGroup: "has added you to \"{{content}}\"",
|
||||||
]
|
]
|
||||||
|
} else {
|
||||||
|
pushNotificationText = [
|
||||||
|
.contactRequest: "have received a new contact request.",
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var content = pushNotificationText[pushNotification.kind] ?? ""
|
var content = pushNotificationText[pushNotification.kind] ?? ""
|
||||||
|
if (content == "") {
|
||||||
|
title = noTranslationFoundTitle
|
||||||
|
content = noTranslationFoundBody
|
||||||
|
}
|
||||||
|
|
||||||
if pushNotification.hasAdditionalContent {
|
if pushNotification.hasAdditionalContent {
|
||||||
content.replace("{{content}}", with: pushNotification.additionalContent)
|
content.replace("{{content}}", with: pushNotification.additionalContent)
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,10 @@ PODS:
|
||||||
- google_mlkit_commons (0.11.0):
|
- google_mlkit_commons (0.11.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- MLKitVision
|
- MLKitVision
|
||||||
|
- google_mlkit_face_detection (0.13.1):
|
||||||
|
- Flutter
|
||||||
|
- google_mlkit_commons
|
||||||
|
- GoogleMLKit/FaceDetection (~> 7.0.0)
|
||||||
- GoogleAdsOnDeviceConversion (3.2.0):
|
- GoogleAdsOnDeviceConversion (3.2.0):
|
||||||
- GoogleUtilities/Environment (~> 8.1)
|
- GoogleUtilities/Environment (~> 8.1)
|
||||||
- GoogleUtilities/Logger (~> 8.1)
|
- GoogleUtilities/Logger (~> 8.1)
|
||||||
|
|
@ -169,6 +173,9 @@ PODS:
|
||||||
- GoogleMLKit/BarcodeScanning (7.0.0):
|
- GoogleMLKit/BarcodeScanning (7.0.0):
|
||||||
- GoogleMLKit/MLKitCore
|
- GoogleMLKit/MLKitCore
|
||||||
- MLKitBarcodeScanning (~> 6.0.0)
|
- MLKitBarcodeScanning (~> 6.0.0)
|
||||||
|
- GoogleMLKit/FaceDetection (7.0.0):
|
||||||
|
- GoogleMLKit/MLKitCore
|
||||||
|
- MLKitFaceDetection (~> 6.0.0)
|
||||||
- GoogleMLKit/MLKitCore (7.0.0):
|
- GoogleMLKit/MLKitCore (7.0.0):
|
||||||
- MLKitCommon (~> 12.0.0)
|
- MLKitCommon (~> 12.0.0)
|
||||||
- GoogleToolboxForMac/Defines (4.2.1)
|
- GoogleToolboxForMac/Defines (4.2.1)
|
||||||
|
|
@ -251,6 +258,9 @@ PODS:
|
||||||
- GoogleUtilities/Logger (~> 8.0)
|
- GoogleUtilities/Logger (~> 8.0)
|
||||||
- GoogleUtilities/UserDefaults (~> 8.0)
|
- GoogleUtilities/UserDefaults (~> 8.0)
|
||||||
- GTMSessionFetcher/Core (< 4.0, >= 3.3.2)
|
- GTMSessionFetcher/Core (< 4.0, >= 3.3.2)
|
||||||
|
- MLKitFaceDetection (6.0.0):
|
||||||
|
- MLKitCommon (~> 12.0)
|
||||||
|
- MLKitVision (~> 8.0)
|
||||||
- MLKitVision (8.0.0):
|
- MLKitVision (8.0.0):
|
||||||
- GoogleToolboxForMac/Logger (< 5.0, >= 4.2.1)
|
- GoogleToolboxForMac/Logger (< 5.0, >= 4.2.1)
|
||||||
- "GoogleToolboxForMac/NSData+zlib (< 5.0, >= 4.2.1)"
|
- "GoogleToolboxForMac/NSData+zlib (< 5.0, >= 4.2.1)"
|
||||||
|
|
@ -357,6 +367,7 @@ DEPENDENCIES:
|
||||||
- gal (from `.symlinks/plugins/gal/darwin`)
|
- gal (from `.symlinks/plugins/gal/darwin`)
|
||||||
- google_mlkit_barcode_scanning (from `.symlinks/plugins/google_mlkit_barcode_scanning/ios`)
|
- google_mlkit_barcode_scanning (from `.symlinks/plugins/google_mlkit_barcode_scanning/ios`)
|
||||||
- google_mlkit_commons (from `.symlinks/plugins/google_mlkit_commons/ios`)
|
- google_mlkit_commons (from `.symlinks/plugins/google_mlkit_commons/ios`)
|
||||||
|
- google_mlkit_face_detection (from `.symlinks/plugins/google_mlkit_face_detection/ios`)
|
||||||
- GoogleUtilities
|
- GoogleUtilities
|
||||||
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||||
- in_app_purchase_storekit (from `.symlinks/plugins/in_app_purchase_storekit/darwin`)
|
- in_app_purchase_storekit (from `.symlinks/plugins/in_app_purchase_storekit/darwin`)
|
||||||
|
|
@ -398,6 +409,7 @@ SPEC REPOS:
|
||||||
- MLImage
|
- MLImage
|
||||||
- MLKitBarcodeScanning
|
- MLKitBarcodeScanning
|
||||||
- MLKitCommon
|
- MLKitCommon
|
||||||
|
- MLKitFaceDetection
|
||||||
- MLKitVision
|
- MLKitVision
|
||||||
- nanopb
|
- nanopb
|
||||||
- PromisesObjC
|
- PromisesObjC
|
||||||
|
|
@ -454,6 +466,8 @@ EXTERNAL SOURCES:
|
||||||
:path: ".symlinks/plugins/google_mlkit_barcode_scanning/ios"
|
:path: ".symlinks/plugins/google_mlkit_barcode_scanning/ios"
|
||||||
google_mlkit_commons:
|
google_mlkit_commons:
|
||||||
:path: ".symlinks/plugins/google_mlkit_commons/ios"
|
:path: ".symlinks/plugins/google_mlkit_commons/ios"
|
||||||
|
google_mlkit_face_detection:
|
||||||
|
:path: ".symlinks/plugins/google_mlkit_face_detection/ios"
|
||||||
image_picker_ios:
|
image_picker_ios:
|
||||||
:path: ".symlinks/plugins/image_picker_ios/ios"
|
:path: ".symlinks/plugins/image_picker_ios/ios"
|
||||||
in_app_purchase_storekit:
|
in_app_purchase_storekit:
|
||||||
|
|
@ -518,6 +532,7 @@ SPEC CHECKSUMS:
|
||||||
gal: baecd024ebfd13c441269ca7404792a7152fde89
|
gal: baecd024ebfd13c441269ca7404792a7152fde89
|
||||||
google_mlkit_barcode_scanning: 8f5987f244a43fe1167689c548342a5174108159
|
google_mlkit_barcode_scanning: 8f5987f244a43fe1167689c548342a5174108159
|
||||||
google_mlkit_commons: 2abe6a70e1824e431d16a51085cb475b672c8aab
|
google_mlkit_commons: 2abe6a70e1824e431d16a51085cb475b672c8aab
|
||||||
|
google_mlkit_face_detection: 754da2113a1952f063c7c5dc347ac6ae8934fb77
|
||||||
GoogleAdsOnDeviceConversion: d68c69dd9581a0f5da02617b6f377e5be483970f
|
GoogleAdsOnDeviceConversion: d68c69dd9581a0f5da02617b6f377e5be483970f
|
||||||
GoogleAppMeasurement: 3bf40aff49a601af5da1c3345702fcb4991d35ee
|
GoogleAppMeasurement: 3bf40aff49a601af5da1c3345702fcb4991d35ee
|
||||||
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
||||||
|
|
@ -533,6 +548,7 @@ SPEC CHECKSUMS:
|
||||||
MLImage: 0ad1c5f50edd027672d8b26b0fee78a8b4a0fc56
|
MLImage: 0ad1c5f50edd027672d8b26b0fee78a8b4a0fc56
|
||||||
MLKitBarcodeScanning: 0a3064da0a7f49ac24ceb3cb46a5bc67496facd2
|
MLKitBarcodeScanning: 0a3064da0a7f49ac24ceb3cb46a5bc67496facd2
|
||||||
MLKitCommon: 07c2c33ae5640e5380beaaa6e4b9c249a205542d
|
MLKitCommon: 07c2c33ae5640e5380beaaa6e4b9c249a205542d
|
||||||
|
MLKitFaceDetection: 2a593db4837db503ad3426b565e7aab045cefea5
|
||||||
MLKitVision: 45e79d68845a2de77e2dd4d7f07947f0ed157b0e
|
MLKitVision: 45e79d68845a2de77e2dd4d7f07947f0ed157b0e
|
||||||
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
||||||
no_screenshot: 89e778ede9f1e39cc3fb9404d782a42712f2a0b2
|
no_screenshot: 89e778ede9f1e39cc3fb9404d782a42712f2a0b2
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@
|
||||||
|
|
||||||
<key>NSExtensionActivationRule</key>
|
<key>NSExtensionActivationRule</key>
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
|
||||||
|
<integer>1</integer>
|
||||||
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
|
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
|
||||||
<integer>1</integer>
|
<integer>1</integer>
|
||||||
<key>NSExtensionActivationSupportsImageWithMaxCount</key>
|
<key>NSExtensionActivationSupportsImageWithMaxCount</key>
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ class ReactionsDao extends DatabaseAccessor<TwonlyDB> with _$ReactionsDaoMixin {
|
||||||
String emoji,
|
String emoji,
|
||||||
bool remove,
|
bool remove,
|
||||||
) async {
|
) async {
|
||||||
if (!isEmoji(emoji)) {
|
if (!isOneEmoji(emoji)) {
|
||||||
Log.error('Did not update reaction as it is not an emoji!');
|
Log.error('Did not update reaction as it is not an emoji!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -59,7 +59,7 @@ class ReactionsDao extends DatabaseAccessor<TwonlyDB> with _$ReactionsDaoMixin {
|
||||||
String emoji,
|
String emoji,
|
||||||
bool remove,
|
bool remove,
|
||||||
) async {
|
) async {
|
||||||
if (!isEmoji(emoji)) {
|
if (!isOneEmoji(emoji)) {
|
||||||
Log.error('Did not update reaction as it is not an emoji!');
|
Log.error('Did not update reaction as it is not an emoji!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1
lib/src/database/schemas/twonly_db/drift_schema_v7.json
Normal file
1
lib/src/database/schemas/twonly_db/drift_schema_v7.json
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -22,6 +22,8 @@ class Messages extends Table {
|
||||||
.nullable()
|
.nullable()
|
||||||
.references(MediaFiles, #mediaId, onDelete: KeyAction.setNull)();
|
.references(MediaFiles, #mediaId, onDelete: KeyAction.setNull)();
|
||||||
|
|
||||||
|
BlobColumn get additionalMessageData => blob().nullable()();
|
||||||
|
|
||||||
BoolColumn get mediaStored => boolean().withDefault(const Constant(false))();
|
BoolColumn get mediaStored => boolean().withDefault(const Constant(false))();
|
||||||
BoolColumn get mediaReopened =>
|
BoolColumn get mediaReopened =>
|
||||||
boolean().withDefault(const Constant(false))();
|
boolean().withDefault(const Constant(false))();
|
||||||
|
|
@ -56,7 +58,7 @@ class MessageActions extends Table {
|
||||||
text().references(Messages, #messageId, onDelete: KeyAction.cascade)();
|
text().references(Messages, #messageId, onDelete: KeyAction.cascade)();
|
||||||
|
|
||||||
IntColumn get contactId =>
|
IntColumn get contactId =>
|
||||||
integer().references(Contacts, #contactId, onDelete: KeyAction.cascade)();
|
integer().references(Contacts, #userId, onDelete: KeyAction.cascade)();
|
||||||
|
|
||||||
TextColumn get type => textEnum<MessageActionType>()();
|
TextColumn get type => textEnum<MessageActionType>()();
|
||||||
|
|
||||||
|
|
@ -75,7 +77,7 @@ class MessageHistories extends Table {
|
||||||
|
|
||||||
IntColumn get contactId => integer()
|
IntColumn get contactId => integer()
|
||||||
.nullable()
|
.nullable()
|
||||||
.references(Contacts, #contactId, onDelete: KeyAction.cascade)();
|
.references(Contacts, #userId, onDelete: KeyAction.cascade)();
|
||||||
|
|
||||||
TextColumn get content => text().nullable()();
|
TextColumn get content => text().nullable()();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ class TwonlyDB extends _$TwonlyDB {
|
||||||
TwonlyDB.forTesting(DatabaseConnection super.connection);
|
TwonlyDB.forTesting(DatabaseConnection super.connection);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get schemaVersion => 6;
|
int get schemaVersion => 7;
|
||||||
|
|
||||||
static QueryExecutor _openConnection() {
|
static QueryExecutor _openConnection() {
|
||||||
return driftDatabase(
|
return driftDatabase(
|
||||||
|
|
@ -85,7 +85,10 @@ class TwonlyDB extends _$TwonlyDB {
|
||||||
beforeOpen: (details) async {
|
beforeOpen: (details) async {
|
||||||
await customStatement('PRAGMA foreign_keys = ON');
|
await customStatement('PRAGMA foreign_keys = ON');
|
||||||
},
|
},
|
||||||
onUpgrade: stepByStep(
|
onUpgrade: (m, from, to) async {
|
||||||
|
// disable foreign_keys before migrations
|
||||||
|
await customStatement('PRAGMA foreign_keys = OFF');
|
||||||
|
return stepByStep(
|
||||||
from1To2: (m, schema) async {
|
from1To2: (m, schema) async {
|
||||||
await m.addColumn(schema.messages, schema.messages.mediaReopened);
|
await m.addColumn(schema.messages, schema.messages.mediaReopened);
|
||||||
await m.dropColumn(schema.mediaFiles, 'reopen_by_contact');
|
await m.dropColumn(schema.mediaFiles, 'reopen_by_contact');
|
||||||
|
|
@ -117,7 +120,14 @@ class TwonlyDB extends _$TwonlyDB {
|
||||||
schema.receipts.markForRetryAfterAccepted,
|
schema.receipts.markForRetryAfterAccepted,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
from6To7: (m, schema) async {
|
||||||
|
await m.addColumn(
|
||||||
|
schema.messages,
|
||||||
|
schema.messages.additionalMessageData,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)(m, from, to);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2796,6 +2796,12 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> {
|
||||||
requiredDuringInsert: false,
|
requiredDuringInsert: false,
|
||||||
defaultConstraints: GeneratedColumn.constraintIsAlways(
|
defaultConstraints: GeneratedColumn.constraintIsAlways(
|
||||||
'REFERENCES media_files (media_id) ON DELETE SET NULL'));
|
'REFERENCES media_files (media_id) ON DELETE SET NULL'));
|
||||||
|
static const VerificationMeta _additionalMessageDataMeta =
|
||||||
|
const VerificationMeta('additionalMessageData');
|
||||||
|
@override
|
||||||
|
late final GeneratedColumn<Uint8List> additionalMessageData =
|
||||||
|
GeneratedColumn<Uint8List>('additional_message_data', aliasedName, true,
|
||||||
|
type: DriftSqlType.blob, requiredDuringInsert: false);
|
||||||
static const VerificationMeta _mediaStoredMeta =
|
static const VerificationMeta _mediaStoredMeta =
|
||||||
const VerificationMeta('mediaStored');
|
const VerificationMeta('mediaStored');
|
||||||
@override
|
@override
|
||||||
|
|
@ -2884,6 +2890,7 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> {
|
||||||
type,
|
type,
|
||||||
content,
|
content,
|
||||||
mediaId,
|
mediaId,
|
||||||
|
additionalMessageData,
|
||||||
mediaStored,
|
mediaStored,
|
||||||
mediaReopened,
|
mediaReopened,
|
||||||
downloadToken,
|
downloadToken,
|
||||||
|
|
@ -2930,6 +2937,12 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> {
|
||||||
context.handle(_mediaIdMeta,
|
context.handle(_mediaIdMeta,
|
||||||
mediaId.isAcceptableOrUnknown(data['media_id']!, _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')) {
|
if (data.containsKey('media_stored')) {
|
||||||
context.handle(
|
context.handle(
|
||||||
_mediaStoredMeta,
|
_mediaStoredMeta,
|
||||||
|
|
@ -3013,6 +3026,8 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> {
|
||||||
.read(DriftSqlType.string, data['${effectivePrefix}content']),
|
.read(DriftSqlType.string, data['${effectivePrefix}content']),
|
||||||
mediaId: attachedDatabase.typeMapping
|
mediaId: attachedDatabase.typeMapping
|
||||||
.read(DriftSqlType.string, data['${effectivePrefix}media_id']),
|
.read(DriftSqlType.string, data['${effectivePrefix}media_id']),
|
||||||
|
additionalMessageData: attachedDatabase.typeMapping.read(
|
||||||
|
DriftSqlType.blob, data['${effectivePrefix}additional_message_data']),
|
||||||
mediaStored: attachedDatabase.typeMapping
|
mediaStored: attachedDatabase.typeMapping
|
||||||
.read(DriftSqlType.bool, data['${effectivePrefix}media_stored'])!,
|
.read(DriftSqlType.bool, data['${effectivePrefix}media_stored'])!,
|
||||||
mediaReopened: attachedDatabase.typeMapping
|
mediaReopened: attachedDatabase.typeMapping
|
||||||
|
|
@ -3054,6 +3069,7 @@ class Message extends DataClass implements Insertable<Message> {
|
||||||
final MessageType type;
|
final MessageType type;
|
||||||
final String? content;
|
final String? content;
|
||||||
final String? mediaId;
|
final String? mediaId;
|
||||||
|
final Uint8List? additionalMessageData;
|
||||||
final bool mediaStored;
|
final bool mediaStored;
|
||||||
final bool mediaReopened;
|
final bool mediaReopened;
|
||||||
final Uint8List? downloadToken;
|
final Uint8List? downloadToken;
|
||||||
|
|
@ -3072,6 +3088,7 @@ class Message extends DataClass implements Insertable<Message> {
|
||||||
required this.type,
|
required this.type,
|
||||||
this.content,
|
this.content,
|
||||||
this.mediaId,
|
this.mediaId,
|
||||||
|
this.additionalMessageData,
|
||||||
required this.mediaStored,
|
required this.mediaStored,
|
||||||
required this.mediaReopened,
|
required this.mediaReopened,
|
||||||
this.downloadToken,
|
this.downloadToken,
|
||||||
|
|
@ -3100,6 +3117,10 @@ class Message extends DataClass implements Insertable<Message> {
|
||||||
if (!nullToAbsent || mediaId != null) {
|
if (!nullToAbsent || mediaId != null) {
|
||||||
map['media_id'] = Variable<String>(mediaId);
|
map['media_id'] = Variable<String>(mediaId);
|
||||||
}
|
}
|
||||||
|
if (!nullToAbsent || additionalMessageData != null) {
|
||||||
|
map['additional_message_data'] =
|
||||||
|
Variable<Uint8List>(additionalMessageData);
|
||||||
|
}
|
||||||
map['media_stored'] = Variable<bool>(mediaStored);
|
map['media_stored'] = Variable<bool>(mediaStored);
|
||||||
map['media_reopened'] = Variable<bool>(mediaReopened);
|
map['media_reopened'] = Variable<bool>(mediaReopened);
|
||||||
if (!nullToAbsent || downloadToken != null) {
|
if (!nullToAbsent || downloadToken != null) {
|
||||||
|
|
@ -3142,6 +3163,9 @@ class Message extends DataClass implements Insertable<Message> {
|
||||||
mediaId: mediaId == null && nullToAbsent
|
mediaId: mediaId == null && nullToAbsent
|
||||||
? const Value.absent()
|
? const Value.absent()
|
||||||
: Value(mediaId),
|
: Value(mediaId),
|
||||||
|
additionalMessageData: additionalMessageData == null && nullToAbsent
|
||||||
|
? const Value.absent()
|
||||||
|
: Value(additionalMessageData),
|
||||||
mediaStored: Value(mediaStored),
|
mediaStored: Value(mediaStored),
|
||||||
mediaReopened: Value(mediaReopened),
|
mediaReopened: Value(mediaReopened),
|
||||||
downloadToken: downloadToken == null && nullToAbsent
|
downloadToken: downloadToken == null && nullToAbsent
|
||||||
|
|
@ -3181,6 +3205,8 @@ class Message extends DataClass implements Insertable<Message> {
|
||||||
.fromJson(serializer.fromJson<String>(json['type'])),
|
.fromJson(serializer.fromJson<String>(json['type'])),
|
||||||
content: serializer.fromJson<String?>(json['content']),
|
content: serializer.fromJson<String?>(json['content']),
|
||||||
mediaId: serializer.fromJson<String?>(json['mediaId']),
|
mediaId: serializer.fromJson<String?>(json['mediaId']),
|
||||||
|
additionalMessageData:
|
||||||
|
serializer.fromJson<Uint8List?>(json['additionalMessageData']),
|
||||||
mediaStored: serializer.fromJson<bool>(json['mediaStored']),
|
mediaStored: serializer.fromJson<bool>(json['mediaStored']),
|
||||||
mediaReopened: serializer.fromJson<bool>(json['mediaReopened']),
|
mediaReopened: serializer.fromJson<bool>(json['mediaReopened']),
|
||||||
downloadToken: serializer.fromJson<Uint8List?>(json['downloadToken']),
|
downloadToken: serializer.fromJson<Uint8List?>(json['downloadToken']),
|
||||||
|
|
@ -3206,6 +3232,8 @@ class Message extends DataClass implements Insertable<Message> {
|
||||||
serializer.toJson<String>($MessagesTable.$convertertype.toJson(type)),
|
serializer.toJson<String>($MessagesTable.$convertertype.toJson(type)),
|
||||||
'content': serializer.toJson<String?>(content),
|
'content': serializer.toJson<String?>(content),
|
||||||
'mediaId': serializer.toJson<String?>(mediaId),
|
'mediaId': serializer.toJson<String?>(mediaId),
|
||||||
|
'additionalMessageData':
|
||||||
|
serializer.toJson<Uint8List?>(additionalMessageData),
|
||||||
'mediaStored': serializer.toJson<bool>(mediaStored),
|
'mediaStored': serializer.toJson<bool>(mediaStored),
|
||||||
'mediaReopened': serializer.toJson<bool>(mediaReopened),
|
'mediaReopened': serializer.toJson<bool>(mediaReopened),
|
||||||
'downloadToken': serializer.toJson<Uint8List?>(downloadToken),
|
'downloadToken': serializer.toJson<Uint8List?>(downloadToken),
|
||||||
|
|
@ -3227,6 +3255,7 @@ class Message extends DataClass implements Insertable<Message> {
|
||||||
MessageType? type,
|
MessageType? type,
|
||||||
Value<String?> content = const Value.absent(),
|
Value<String?> content = const Value.absent(),
|
||||||
Value<String?> mediaId = const Value.absent(),
|
Value<String?> mediaId = const Value.absent(),
|
||||||
|
Value<Uint8List?> additionalMessageData = const Value.absent(),
|
||||||
bool? mediaStored,
|
bool? mediaStored,
|
||||||
bool? mediaReopened,
|
bool? mediaReopened,
|
||||||
Value<Uint8List?> downloadToken = const Value.absent(),
|
Value<Uint8List?> downloadToken = const Value.absent(),
|
||||||
|
|
@ -3245,6 +3274,9 @@ class Message extends DataClass implements Insertable<Message> {
|
||||||
type: type ?? this.type,
|
type: type ?? this.type,
|
||||||
content: content.present ? content.value : this.content,
|
content: content.present ? content.value : this.content,
|
||||||
mediaId: mediaId.present ? mediaId.value : this.mediaId,
|
mediaId: mediaId.present ? mediaId.value : this.mediaId,
|
||||||
|
additionalMessageData: additionalMessageData.present
|
||||||
|
? additionalMessageData.value
|
||||||
|
: this.additionalMessageData,
|
||||||
mediaStored: mediaStored ?? this.mediaStored,
|
mediaStored: mediaStored ?? this.mediaStored,
|
||||||
mediaReopened: mediaReopened ?? this.mediaReopened,
|
mediaReopened: mediaReopened ?? this.mediaReopened,
|
||||||
downloadToken:
|
downloadToken:
|
||||||
|
|
@ -3268,6 +3300,9 @@ class Message extends DataClass implements Insertable<Message> {
|
||||||
type: data.type.present ? data.type.value : this.type,
|
type: data.type.present ? data.type.value : this.type,
|
||||||
content: data.content.present ? data.content.value : this.content,
|
content: data.content.present ? data.content.value : this.content,
|
||||||
mediaId: data.mediaId.present ? data.mediaId.value : this.mediaId,
|
mediaId: data.mediaId.present ? data.mediaId.value : this.mediaId,
|
||||||
|
additionalMessageData: data.additionalMessageData.present
|
||||||
|
? data.additionalMessageData.value
|
||||||
|
: this.additionalMessageData,
|
||||||
mediaStored:
|
mediaStored:
|
||||||
data.mediaStored.present ? data.mediaStored.value : this.mediaStored,
|
data.mediaStored.present ? data.mediaStored.value : this.mediaStored,
|
||||||
mediaReopened: data.mediaReopened.present
|
mediaReopened: data.mediaReopened.present
|
||||||
|
|
@ -3303,6 +3338,7 @@ class Message extends DataClass implements Insertable<Message> {
|
||||||
..write('type: $type, ')
|
..write('type: $type, ')
|
||||||
..write('content: $content, ')
|
..write('content: $content, ')
|
||||||
..write('mediaId: $mediaId, ')
|
..write('mediaId: $mediaId, ')
|
||||||
|
..write('additionalMessageData: $additionalMessageData, ')
|
||||||
..write('mediaStored: $mediaStored, ')
|
..write('mediaStored: $mediaStored, ')
|
||||||
..write('mediaReopened: $mediaReopened, ')
|
..write('mediaReopened: $mediaReopened, ')
|
||||||
..write('downloadToken: $downloadToken, ')
|
..write('downloadToken: $downloadToken, ')
|
||||||
|
|
@ -3326,6 +3362,7 @@ class Message extends DataClass implements Insertable<Message> {
|
||||||
type,
|
type,
|
||||||
content,
|
content,
|
||||||
mediaId,
|
mediaId,
|
||||||
|
$driftBlobEquality.hash(additionalMessageData),
|
||||||
mediaStored,
|
mediaStored,
|
||||||
mediaReopened,
|
mediaReopened,
|
||||||
$driftBlobEquality.hash(downloadToken),
|
$driftBlobEquality.hash(downloadToken),
|
||||||
|
|
@ -3347,6 +3384,8 @@ class Message extends DataClass implements Insertable<Message> {
|
||||||
other.type == this.type &&
|
other.type == this.type &&
|
||||||
other.content == this.content &&
|
other.content == this.content &&
|
||||||
other.mediaId == this.mediaId &&
|
other.mediaId == this.mediaId &&
|
||||||
|
$driftBlobEquality.equals(
|
||||||
|
other.additionalMessageData, this.additionalMessageData) &&
|
||||||
other.mediaStored == this.mediaStored &&
|
other.mediaStored == this.mediaStored &&
|
||||||
other.mediaReopened == this.mediaReopened &&
|
other.mediaReopened == this.mediaReopened &&
|
||||||
$driftBlobEquality.equals(other.downloadToken, this.downloadToken) &&
|
$driftBlobEquality.equals(other.downloadToken, this.downloadToken) &&
|
||||||
|
|
@ -3367,6 +3406,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
||||||
final Value<MessageType> type;
|
final Value<MessageType> type;
|
||||||
final Value<String?> content;
|
final Value<String?> content;
|
||||||
final Value<String?> mediaId;
|
final Value<String?> mediaId;
|
||||||
|
final Value<Uint8List?> additionalMessageData;
|
||||||
final Value<bool> mediaStored;
|
final Value<bool> mediaStored;
|
||||||
final Value<bool> mediaReopened;
|
final Value<bool> mediaReopened;
|
||||||
final Value<Uint8List?> downloadToken;
|
final Value<Uint8List?> downloadToken;
|
||||||
|
|
@ -3386,6 +3426,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
||||||
this.type = const Value.absent(),
|
this.type = const Value.absent(),
|
||||||
this.content = const Value.absent(),
|
this.content = const Value.absent(),
|
||||||
this.mediaId = const Value.absent(),
|
this.mediaId = const Value.absent(),
|
||||||
|
this.additionalMessageData = const Value.absent(),
|
||||||
this.mediaStored = const Value.absent(),
|
this.mediaStored = const Value.absent(),
|
||||||
this.mediaReopened = const Value.absent(),
|
this.mediaReopened = const Value.absent(),
|
||||||
this.downloadToken = const Value.absent(),
|
this.downloadToken = const Value.absent(),
|
||||||
|
|
@ -3406,6 +3447,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
||||||
required MessageType type,
|
required MessageType type,
|
||||||
this.content = const Value.absent(),
|
this.content = const Value.absent(),
|
||||||
this.mediaId = const Value.absent(),
|
this.mediaId = const Value.absent(),
|
||||||
|
this.additionalMessageData = const Value.absent(),
|
||||||
this.mediaStored = const Value.absent(),
|
this.mediaStored = const Value.absent(),
|
||||||
this.mediaReopened = const Value.absent(),
|
this.mediaReopened = const Value.absent(),
|
||||||
this.downloadToken = const Value.absent(),
|
this.downloadToken = const Value.absent(),
|
||||||
|
|
@ -3428,6 +3470,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
||||||
Expression<String>? type,
|
Expression<String>? type,
|
||||||
Expression<String>? content,
|
Expression<String>? content,
|
||||||
Expression<String>? mediaId,
|
Expression<String>? mediaId,
|
||||||
|
Expression<Uint8List>? additionalMessageData,
|
||||||
Expression<bool>? mediaStored,
|
Expression<bool>? mediaStored,
|
||||||
Expression<bool>? mediaReopened,
|
Expression<bool>? mediaReopened,
|
||||||
Expression<Uint8List>? downloadToken,
|
Expression<Uint8List>? downloadToken,
|
||||||
|
|
@ -3448,6 +3491,8 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
||||||
if (type != null) 'type': type,
|
if (type != null) 'type': type,
|
||||||
if (content != null) 'content': content,
|
if (content != null) 'content': content,
|
||||||
if (mediaId != null) 'media_id': mediaId,
|
if (mediaId != null) 'media_id': mediaId,
|
||||||
|
if (additionalMessageData != null)
|
||||||
|
'additional_message_data': additionalMessageData,
|
||||||
if (mediaStored != null) 'media_stored': mediaStored,
|
if (mediaStored != null) 'media_stored': mediaStored,
|
||||||
if (mediaReopened != null) 'media_reopened': mediaReopened,
|
if (mediaReopened != null) 'media_reopened': mediaReopened,
|
||||||
if (downloadToken != null) 'download_token': downloadToken,
|
if (downloadToken != null) 'download_token': downloadToken,
|
||||||
|
|
@ -3471,6 +3516,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
||||||
Value<MessageType>? type,
|
Value<MessageType>? type,
|
||||||
Value<String?>? content,
|
Value<String?>? content,
|
||||||
Value<String?>? mediaId,
|
Value<String?>? mediaId,
|
||||||
|
Value<Uint8List?>? additionalMessageData,
|
||||||
Value<bool>? mediaStored,
|
Value<bool>? mediaStored,
|
||||||
Value<bool>? mediaReopened,
|
Value<bool>? mediaReopened,
|
||||||
Value<Uint8List?>? downloadToken,
|
Value<Uint8List?>? downloadToken,
|
||||||
|
|
@ -3490,6 +3536,8 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
||||||
type: type ?? this.type,
|
type: type ?? this.type,
|
||||||
content: content ?? this.content,
|
content: content ?? this.content,
|
||||||
mediaId: mediaId ?? this.mediaId,
|
mediaId: mediaId ?? this.mediaId,
|
||||||
|
additionalMessageData:
|
||||||
|
additionalMessageData ?? this.additionalMessageData,
|
||||||
mediaStored: mediaStored ?? this.mediaStored,
|
mediaStored: mediaStored ?? this.mediaStored,
|
||||||
mediaReopened: mediaReopened ?? this.mediaReopened,
|
mediaReopened: mediaReopened ?? this.mediaReopened,
|
||||||
downloadToken: downloadToken ?? this.downloadToken,
|
downloadToken: downloadToken ?? this.downloadToken,
|
||||||
|
|
@ -3527,6 +3575,10 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
||||||
if (mediaId.present) {
|
if (mediaId.present) {
|
||||||
map['media_id'] = Variable<String>(mediaId.value);
|
map['media_id'] = Variable<String>(mediaId.value);
|
||||||
}
|
}
|
||||||
|
if (additionalMessageData.present) {
|
||||||
|
map['additional_message_data'] =
|
||||||
|
Variable<Uint8List>(additionalMessageData.value);
|
||||||
|
}
|
||||||
if (mediaStored.present) {
|
if (mediaStored.present) {
|
||||||
map['media_stored'] = Variable<bool>(mediaStored.value);
|
map['media_stored'] = Variable<bool>(mediaStored.value);
|
||||||
}
|
}
|
||||||
|
|
@ -3575,6 +3627,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
||||||
..write('type: $type, ')
|
..write('type: $type, ')
|
||||||
..write('content: $content, ')
|
..write('content: $content, ')
|
||||||
..write('mediaId: $mediaId, ')
|
..write('mediaId: $mediaId, ')
|
||||||
|
..write('additionalMessageData: $additionalMessageData, ')
|
||||||
..write('mediaStored: $mediaStored, ')
|
..write('mediaStored: $mediaStored, ')
|
||||||
..write('mediaReopened: $mediaReopened, ')
|
..write('mediaReopened: $mediaReopened, ')
|
||||||
..write('downloadToken: $downloadToken, ')
|
..write('downloadToken: $downloadToken, ')
|
||||||
|
|
@ -3621,7 +3674,10 @@ class $MessageHistoriesTable extends MessageHistories
|
||||||
@override
|
@override
|
||||||
late final GeneratedColumn<int> contactId = GeneratedColumn<int>(
|
late final GeneratedColumn<int> contactId = GeneratedColumn<int>(
|
||||||
'contact_id', aliasedName, true,
|
'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 =
|
static const VerificationMeta _contentMeta =
|
||||||
const VerificationMeta('content');
|
const VerificationMeta('content');
|
||||||
@override
|
@override
|
||||||
|
|
@ -6964,7 +7020,10 @@ class $MessageActionsTable extends MessageActions
|
||||||
@override
|
@override
|
||||||
late final GeneratedColumn<int> contactId = GeneratedColumn<int>(
|
late final GeneratedColumn<int> contactId = GeneratedColumn<int>(
|
||||||
'contact_id', aliasedName, false,
|
'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
|
@override
|
||||||
late final GeneratedColumnWithTypeConverter<MessageActionType, String> type =
|
late final GeneratedColumnWithTypeConverter<MessageActionType, String> type =
|
||||||
GeneratedColumn<String>('type', aliasedName, false,
|
GeneratedColumn<String>('type', aliasedName, false,
|
||||||
|
|
@ -7837,6 +7896,13 @@ abstract class _$TwonlyDB extends GeneratedDatabase {
|
||||||
TableUpdate('message_histories', kind: UpdateKind.delete),
|
TableUpdate('message_histories', kind: UpdateKind.delete),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
WritePropagation(
|
||||||
|
on: TableUpdateQuery.onTableName('contacts',
|
||||||
|
limitUpdateKind: UpdateKind.delete),
|
||||||
|
result: [
|
||||||
|
TableUpdate('message_histories', kind: UpdateKind.delete),
|
||||||
|
],
|
||||||
|
),
|
||||||
WritePropagation(
|
WritePropagation(
|
||||||
on: TableUpdateQuery.onTableName('messages',
|
on: TableUpdateQuery.onTableName('messages',
|
||||||
limitUpdateKind: UpdateKind.delete),
|
limitUpdateKind: UpdateKind.delete),
|
||||||
|
|
@ -7894,6 +7960,13 @@ abstract class _$TwonlyDB extends GeneratedDatabase {
|
||||||
TableUpdate('message_actions', kind: UpdateKind.delete),
|
TableUpdate('message_actions', kind: UpdateKind.delete),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
WritePropagation(
|
||||||
|
on: TableUpdateQuery.onTableName('contacts',
|
||||||
|
limitUpdateKind: UpdateKind.delete),
|
||||||
|
result: [
|
||||||
|
TableUpdate('message_actions', kind: UpdateKind.delete),
|
||||||
|
],
|
||||||
|
),
|
||||||
WritePropagation(
|
WritePropagation(
|
||||||
on: TableUpdateQuery.onTableName('groups',
|
on: TableUpdateQuery.onTableName('groups',
|
||||||
limitUpdateKind: UpdateKind.delete),
|
limitUpdateKind: UpdateKind.delete),
|
||||||
|
|
@ -7955,6 +8028,23 @@ final class $$ContactsTableReferences
|
||||||
manager.$state.copyWith(prefetchedData: cache));
|
manager.$state.copyWith(prefetchedData: cache));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static MultiTypedResultKey<$MessageHistoriesTable, List<MessageHistory>>
|
||||||
|
_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<int>('user_id')!));
|
||||||
|
|
||||||
|
final cache =
|
||||||
|
$_typedResult.readTableOrNull(_messageHistoriesRefsTable($_db));
|
||||||
|
return ProcessedTableManager(
|
||||||
|
manager.$state.copyWith(prefetchedData: cache));
|
||||||
|
}
|
||||||
|
|
||||||
static MultiTypedResultKey<$ReactionsTable, List<Reaction>>
|
static MultiTypedResultKey<$ReactionsTable, List<Reaction>>
|
||||||
_reactionsRefsTable(_$TwonlyDB db) => MultiTypedResultKey.fromTable(
|
_reactionsRefsTable(_$TwonlyDB db) => MultiTypedResultKey.fromTable(
|
||||||
db.reactions,
|
db.reactions,
|
||||||
|
|
@ -8041,6 +8131,22 @@ final class $$ContactsTableReferences
|
||||||
manager.$state.copyWith(prefetchedData: cache));
|
manager.$state.copyWith(prefetchedData: cache));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static MultiTypedResultKey<$MessageActionsTable, List<MessageAction>>
|
||||||
|
_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<int>('user_id')!));
|
||||||
|
|
||||||
|
final cache = $_typedResult.readTableOrNull(_messageActionsRefsTable($_db));
|
||||||
|
return ProcessedTableManager(
|
||||||
|
manager.$state.copyWith(prefetchedData: cache));
|
||||||
|
}
|
||||||
|
|
||||||
static MultiTypedResultKey<$GroupHistoriesTable, List<GroupHistory>>
|
static MultiTypedResultKey<$GroupHistoriesTable, List<GroupHistory>>
|
||||||
_groupHistoriesRefsTable(_$TwonlyDB db) =>
|
_groupHistoriesRefsTable(_$TwonlyDB db) =>
|
||||||
MultiTypedResultKey.fromTable(db.groupHistories,
|
MultiTypedResultKey.fromTable(db.groupHistories,
|
||||||
|
|
@ -8130,6 +8236,27 @@ class $$ContactsTableFilterComposer
|
||||||
return f(composer);
|
return f(composer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Expression<bool> messageHistoriesRefs(
|
||||||
|
Expression<bool> 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<bool> reactionsRefs(
|
Expression<bool> reactionsRefs(
|
||||||
Expression<bool> Function($$ReactionsTableFilterComposer f) f) {
|
Expression<bool> Function($$ReactionsTableFilterComposer f) f) {
|
||||||
final $$ReactionsTableFilterComposer composer = $composerBuilder(
|
final $$ReactionsTableFilterComposer composer = $composerBuilder(
|
||||||
|
|
@ -8239,6 +8366,27 @@ class $$ContactsTableFilterComposer
|
||||||
return f(composer);
|
return f(composer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Expression<bool> messageActionsRefs(
|
||||||
|
Expression<bool> 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<bool> groupHistoriesRefs(
|
Expression<bool> groupHistoriesRefs(
|
||||||
Expression<bool> Function($$GroupHistoriesTableFilterComposer f) f) {
|
Expression<bool> Function($$GroupHistoriesTableFilterComposer f) f) {
|
||||||
final $$GroupHistoriesTableFilterComposer composer = $composerBuilder(
|
final $$GroupHistoriesTableFilterComposer composer = $composerBuilder(
|
||||||
|
|
@ -8383,6 +8531,27 @@ class $$ContactsTableAnnotationComposer
|
||||||
return f(composer);
|
return f(composer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Expression<T> messageHistoriesRefs<T extends Object>(
|
||||||
|
Expression<T> 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<T> reactionsRefs<T extends Object>(
|
Expression<T> reactionsRefs<T extends Object>(
|
||||||
Expression<T> Function($$ReactionsTableAnnotationComposer a) f) {
|
Expression<T> Function($$ReactionsTableAnnotationComposer a) f) {
|
||||||
final $$ReactionsTableAnnotationComposer composer = $composerBuilder(
|
final $$ReactionsTableAnnotationComposer composer = $composerBuilder(
|
||||||
|
|
@ -8493,6 +8662,27 @@ class $$ContactsTableAnnotationComposer
|
||||||
return f(composer);
|
return f(composer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Expression<T> messageActionsRefs<T extends Object>(
|
||||||
|
Expression<T> 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<T> groupHistoriesRefs<T extends Object>(
|
Expression<T> groupHistoriesRefs<T extends Object>(
|
||||||
Expression<T> Function($$GroupHistoriesTableAnnotationComposer a) f) {
|
Expression<T> Function($$GroupHistoriesTableAnnotationComposer a) f) {
|
||||||
final $$GroupHistoriesTableAnnotationComposer composer = $composerBuilder(
|
final $$GroupHistoriesTableAnnotationComposer composer = $composerBuilder(
|
||||||
|
|
@ -8528,11 +8718,13 @@ class $$ContactsTableTableManager extends RootTableManager<
|
||||||
Contact,
|
Contact,
|
||||||
PrefetchHooks Function(
|
PrefetchHooks Function(
|
||||||
{bool messagesRefs,
|
{bool messagesRefs,
|
||||||
|
bool messageHistoriesRefs,
|
||||||
bool reactionsRefs,
|
bool reactionsRefs,
|
||||||
bool groupMembersRefs,
|
bool groupMembersRefs,
|
||||||
bool receiptsRefs,
|
bool receiptsRefs,
|
||||||
bool signalContactPreKeysRefs,
|
bool signalContactPreKeysRefs,
|
||||||
bool signalContactSignedPreKeysRefs,
|
bool signalContactSignedPreKeysRefs,
|
||||||
|
bool messageActionsRefs,
|
||||||
bool groupHistoriesRefs})> {
|
bool groupHistoriesRefs})> {
|
||||||
$$ContactsTableTableManager(_$TwonlyDB db, $ContactsTable table)
|
$$ContactsTableTableManager(_$TwonlyDB db, $ContactsTable table)
|
||||||
: super(TableManagerState(
|
: super(TableManagerState(
|
||||||
|
|
@ -8610,22 +8802,26 @@ class $$ContactsTableTableManager extends RootTableManager<
|
||||||
.toList(),
|
.toList(),
|
||||||
prefetchHooksCallback: (
|
prefetchHooksCallback: (
|
||||||
{messagesRefs = false,
|
{messagesRefs = false,
|
||||||
|
messageHistoriesRefs = false,
|
||||||
reactionsRefs = false,
|
reactionsRefs = false,
|
||||||
groupMembersRefs = false,
|
groupMembersRefs = false,
|
||||||
receiptsRefs = false,
|
receiptsRefs = false,
|
||||||
signalContactPreKeysRefs = false,
|
signalContactPreKeysRefs = false,
|
||||||
signalContactSignedPreKeysRefs = false,
|
signalContactSignedPreKeysRefs = false,
|
||||||
|
messageActionsRefs = false,
|
||||||
groupHistoriesRefs = false}) {
|
groupHistoriesRefs = false}) {
|
||||||
return PrefetchHooks(
|
return PrefetchHooks(
|
||||||
db: db,
|
db: db,
|
||||||
explicitlyWatchedTables: [
|
explicitlyWatchedTables: [
|
||||||
if (messagesRefs) db.messages,
|
if (messagesRefs) db.messages,
|
||||||
|
if (messageHistoriesRefs) db.messageHistories,
|
||||||
if (reactionsRefs) db.reactions,
|
if (reactionsRefs) db.reactions,
|
||||||
if (groupMembersRefs) db.groupMembers,
|
if (groupMembersRefs) db.groupMembers,
|
||||||
if (receiptsRefs) db.receipts,
|
if (receiptsRefs) db.receipts,
|
||||||
if (signalContactPreKeysRefs) db.signalContactPreKeys,
|
if (signalContactPreKeysRefs) db.signalContactPreKeys,
|
||||||
if (signalContactSignedPreKeysRefs)
|
if (signalContactSignedPreKeysRefs)
|
||||||
db.signalContactSignedPreKeys,
|
db.signalContactSignedPreKeys,
|
||||||
|
if (messageActionsRefs) db.messageActions,
|
||||||
if (groupHistoriesRefs) db.groupHistories
|
if (groupHistoriesRefs) db.groupHistories
|
||||||
],
|
],
|
||||||
addJoins: null,
|
addJoins: null,
|
||||||
|
|
@ -8643,6 +8839,19 @@ class $$ContactsTableTableManager extends RootTableManager<
|
||||||
(item, referencedItems) => referencedItems
|
(item, referencedItems) => referencedItems
|
||||||
.where((e) => e.senderId == item.userId),
|
.where((e) => e.senderId == item.userId),
|
||||||
typedResults: items),
|
typedResults: items),
|
||||||
|
if (messageHistoriesRefs)
|
||||||
|
await $_getPrefetchedData<Contact, $ContactsTable,
|
||||||
|
MessageHistory>(
|
||||||
|
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)
|
if (reactionsRefs)
|
||||||
await $_getPrefetchedData<Contact, $ContactsTable,
|
await $_getPrefetchedData<Contact, $ContactsTable,
|
||||||
Reaction>(
|
Reaction>(
|
||||||
|
|
@ -8707,6 +8916,19 @@ class $$ContactsTableTableManager extends RootTableManager<
|
||||||
(item, referencedItems) => referencedItems
|
(item, referencedItems) => referencedItems
|
||||||
.where((e) => e.contactId == item.userId),
|
.where((e) => e.contactId == item.userId),
|
||||||
typedResults: items),
|
typedResults: items),
|
||||||
|
if (messageActionsRefs)
|
||||||
|
await $_getPrefetchedData<Contact, $ContactsTable,
|
||||||
|
MessageAction>(
|
||||||
|
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)
|
if (groupHistoriesRefs)
|
||||||
await $_getPrefetchedData<Contact, $ContactsTable,
|
await $_getPrefetchedData<Contact, $ContactsTable,
|
||||||
GroupHistory>(
|
GroupHistory>(
|
||||||
|
|
@ -8740,11 +8962,13 @@ typedef $$ContactsTableProcessedTableManager = ProcessedTableManager<
|
||||||
Contact,
|
Contact,
|
||||||
PrefetchHooks Function(
|
PrefetchHooks Function(
|
||||||
{bool messagesRefs,
|
{bool messagesRefs,
|
||||||
|
bool messageHistoriesRefs,
|
||||||
bool reactionsRefs,
|
bool reactionsRefs,
|
||||||
bool groupMembersRefs,
|
bool groupMembersRefs,
|
||||||
bool receiptsRefs,
|
bool receiptsRefs,
|
||||||
bool signalContactPreKeysRefs,
|
bool signalContactPreKeysRefs,
|
||||||
bool signalContactSignedPreKeysRefs,
|
bool signalContactSignedPreKeysRefs,
|
||||||
|
bool messageActionsRefs,
|
||||||
bool groupHistoriesRefs})>;
|
bool groupHistoriesRefs})>;
|
||||||
typedef $$GroupsTableCreateCompanionBuilder = GroupsCompanion Function({
|
typedef $$GroupsTableCreateCompanionBuilder = GroupsCompanion Function({
|
||||||
required String groupId,
|
required String groupId,
|
||||||
|
|
@ -9927,6 +10151,7 @@ typedef $$MessagesTableCreateCompanionBuilder = MessagesCompanion Function({
|
||||||
required MessageType type,
|
required MessageType type,
|
||||||
Value<String?> content,
|
Value<String?> content,
|
||||||
Value<String?> mediaId,
|
Value<String?> mediaId,
|
||||||
|
Value<Uint8List?> additionalMessageData,
|
||||||
Value<bool> mediaStored,
|
Value<bool> mediaStored,
|
||||||
Value<bool> mediaReopened,
|
Value<bool> mediaReopened,
|
||||||
Value<Uint8List?> downloadToken,
|
Value<Uint8List?> downloadToken,
|
||||||
|
|
@ -9947,6 +10172,7 @@ typedef $$MessagesTableUpdateCompanionBuilder = MessagesCompanion Function({
|
||||||
Value<MessageType> type,
|
Value<MessageType> type,
|
||||||
Value<String?> content,
|
Value<String?> content,
|
||||||
Value<String?> mediaId,
|
Value<String?> mediaId,
|
||||||
|
Value<Uint8List?> additionalMessageData,
|
||||||
Value<bool> mediaStored,
|
Value<bool> mediaStored,
|
||||||
Value<bool> mediaReopened,
|
Value<bool> mediaReopened,
|
||||||
Value<Uint8List?> downloadToken,
|
Value<Uint8List?> downloadToken,
|
||||||
|
|
@ -10096,6 +10322,10 @@ class $$MessagesTableFilterComposer
|
||||||
ColumnFilters<String> get content => $composableBuilder(
|
ColumnFilters<String> get content => $composableBuilder(
|
||||||
column: $table.content, builder: (column) => ColumnFilters(column));
|
column: $table.content, builder: (column) => ColumnFilters(column));
|
||||||
|
|
||||||
|
ColumnFilters<Uint8List> get additionalMessageData => $composableBuilder(
|
||||||
|
column: $table.additionalMessageData,
|
||||||
|
builder: (column) => ColumnFilters(column));
|
||||||
|
|
||||||
ColumnFilters<bool> get mediaStored => $composableBuilder(
|
ColumnFilters<bool> get mediaStored => $composableBuilder(
|
||||||
column: $table.mediaStored, builder: (column) => ColumnFilters(column));
|
column: $table.mediaStored, builder: (column) => ColumnFilters(column));
|
||||||
|
|
||||||
|
|
@ -10294,6 +10524,10 @@ class $$MessagesTableOrderingComposer
|
||||||
ColumnOrderings<String> get content => $composableBuilder(
|
ColumnOrderings<String> get content => $composableBuilder(
|
||||||
column: $table.content, builder: (column) => ColumnOrderings(column));
|
column: $table.content, builder: (column) => ColumnOrderings(column));
|
||||||
|
|
||||||
|
ColumnOrderings<Uint8List> get additionalMessageData => $composableBuilder(
|
||||||
|
column: $table.additionalMessageData,
|
||||||
|
builder: (column) => ColumnOrderings(column));
|
||||||
|
|
||||||
ColumnOrderings<bool> get mediaStored => $composableBuilder(
|
ColumnOrderings<bool> get mediaStored => $composableBuilder(
|
||||||
column: $table.mediaStored, builder: (column) => ColumnOrderings(column));
|
column: $table.mediaStored, builder: (column) => ColumnOrderings(column));
|
||||||
|
|
||||||
|
|
@ -10410,6 +10644,9 @@ class $$MessagesTableAnnotationComposer
|
||||||
GeneratedColumn<String> get content =>
|
GeneratedColumn<String> get content =>
|
||||||
$composableBuilder(column: $table.content, builder: (column) => column);
|
$composableBuilder(column: $table.content, builder: (column) => column);
|
||||||
|
|
||||||
|
GeneratedColumn<Uint8List> get additionalMessageData => $composableBuilder(
|
||||||
|
column: $table.additionalMessageData, builder: (column) => column);
|
||||||
|
|
||||||
GeneratedColumn<bool> get mediaStored => $composableBuilder(
|
GeneratedColumn<bool> get mediaStored => $composableBuilder(
|
||||||
column: $table.mediaStored, builder: (column) => column);
|
column: $table.mediaStored, builder: (column) => column);
|
||||||
|
|
||||||
|
|
@ -10624,6 +10861,7 @@ class $$MessagesTableTableManager extends RootTableManager<
|
||||||
Value<MessageType> type = const Value.absent(),
|
Value<MessageType> type = const Value.absent(),
|
||||||
Value<String?> content = const Value.absent(),
|
Value<String?> content = const Value.absent(),
|
||||||
Value<String?> mediaId = const Value.absent(),
|
Value<String?> mediaId = const Value.absent(),
|
||||||
|
Value<Uint8List?> additionalMessageData = const Value.absent(),
|
||||||
Value<bool> mediaStored = const Value.absent(),
|
Value<bool> mediaStored = const Value.absent(),
|
||||||
Value<bool> mediaReopened = const Value.absent(),
|
Value<bool> mediaReopened = const Value.absent(),
|
||||||
Value<Uint8List?> downloadToken = const Value.absent(),
|
Value<Uint8List?> downloadToken = const Value.absent(),
|
||||||
|
|
@ -10644,6 +10882,7 @@ class $$MessagesTableTableManager extends RootTableManager<
|
||||||
type: type,
|
type: type,
|
||||||
content: content,
|
content: content,
|
||||||
mediaId: mediaId,
|
mediaId: mediaId,
|
||||||
|
additionalMessageData: additionalMessageData,
|
||||||
mediaStored: mediaStored,
|
mediaStored: mediaStored,
|
||||||
mediaReopened: mediaReopened,
|
mediaReopened: mediaReopened,
|
||||||
downloadToken: downloadToken,
|
downloadToken: downloadToken,
|
||||||
|
|
@ -10664,6 +10903,7 @@ class $$MessagesTableTableManager extends RootTableManager<
|
||||||
required MessageType type,
|
required MessageType type,
|
||||||
Value<String?> content = const Value.absent(),
|
Value<String?> content = const Value.absent(),
|
||||||
Value<String?> mediaId = const Value.absent(),
|
Value<String?> mediaId = const Value.absent(),
|
||||||
|
Value<Uint8List?> additionalMessageData = const Value.absent(),
|
||||||
Value<bool> mediaStored = const Value.absent(),
|
Value<bool> mediaStored = const Value.absent(),
|
||||||
Value<bool> mediaReopened = const Value.absent(),
|
Value<bool> mediaReopened = const Value.absent(),
|
||||||
Value<Uint8List?> downloadToken = const Value.absent(),
|
Value<Uint8List?> downloadToken = const Value.absent(),
|
||||||
|
|
@ -10684,6 +10924,7 @@ class $$MessagesTableTableManager extends RootTableManager<
|
||||||
type: type,
|
type: type,
|
||||||
content: content,
|
content: content,
|
||||||
mediaId: mediaId,
|
mediaId: mediaId,
|
||||||
|
additionalMessageData: additionalMessageData,
|
||||||
mediaStored: mediaStored,
|
mediaStored: mediaStored,
|
||||||
mediaReopened: mediaReopened,
|
mediaReopened: mediaReopened,
|
||||||
downloadToken: downloadToken,
|
downloadToken: downloadToken,
|
||||||
|
|
@ -10878,6 +11119,21 @@ final class $$MessageHistoriesTableReferences
|
||||||
return ProcessedTableManager(
|
return ProcessedTableManager(
|
||||||
manager.$state.copyWith(prefetchedData: [item]));
|
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<int>('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
|
class $$MessageHistoriesTableFilterComposer
|
||||||
|
|
@ -10892,9 +11148,6 @@ class $$MessageHistoriesTableFilterComposer
|
||||||
ColumnFilters<int> get id => $composableBuilder(
|
ColumnFilters<int> get id => $composableBuilder(
|
||||||
column: $table.id, builder: (column) => ColumnFilters(column));
|
column: $table.id, builder: (column) => ColumnFilters(column));
|
||||||
|
|
||||||
ColumnFilters<int> get contactId => $composableBuilder(
|
|
||||||
column: $table.contactId, builder: (column) => ColumnFilters(column));
|
|
||||||
|
|
||||||
ColumnFilters<String> get content => $composableBuilder(
|
ColumnFilters<String> get content => $composableBuilder(
|
||||||
column: $table.content, builder: (column) => ColumnFilters(column));
|
column: $table.content, builder: (column) => ColumnFilters(column));
|
||||||
|
|
||||||
|
|
@ -10920,6 +11173,26 @@ class $$MessageHistoriesTableFilterComposer
|
||||||
));
|
));
|
||||||
return composer;
|
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
|
class $$MessageHistoriesTableOrderingComposer
|
||||||
|
|
@ -10934,9 +11207,6 @@ class $$MessageHistoriesTableOrderingComposer
|
||||||
ColumnOrderings<int> get id => $composableBuilder(
|
ColumnOrderings<int> get id => $composableBuilder(
|
||||||
column: $table.id, builder: (column) => ColumnOrderings(column));
|
column: $table.id, builder: (column) => ColumnOrderings(column));
|
||||||
|
|
||||||
ColumnOrderings<int> get contactId => $composableBuilder(
|
|
||||||
column: $table.contactId, builder: (column) => ColumnOrderings(column));
|
|
||||||
|
|
||||||
ColumnOrderings<String> get content => $composableBuilder(
|
ColumnOrderings<String> get content => $composableBuilder(
|
||||||
column: $table.content, builder: (column) => ColumnOrderings(column));
|
column: $table.content, builder: (column) => ColumnOrderings(column));
|
||||||
|
|
||||||
|
|
@ -10962,6 +11232,26 @@ class $$MessageHistoriesTableOrderingComposer
|
||||||
));
|
));
|
||||||
return composer;
|
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
|
class $$MessageHistoriesTableAnnotationComposer
|
||||||
|
|
@ -10976,9 +11266,6 @@ class $$MessageHistoriesTableAnnotationComposer
|
||||||
GeneratedColumn<int> get id =>
|
GeneratedColumn<int> get id =>
|
||||||
$composableBuilder(column: $table.id, builder: (column) => column);
|
$composableBuilder(column: $table.id, builder: (column) => column);
|
||||||
|
|
||||||
GeneratedColumn<int> get contactId =>
|
|
||||||
$composableBuilder(column: $table.contactId, builder: (column) => column);
|
|
||||||
|
|
||||||
GeneratedColumn<String> get content =>
|
GeneratedColumn<String> get content =>
|
||||||
$composableBuilder(column: $table.content, builder: (column) => column);
|
$composableBuilder(column: $table.content, builder: (column) => column);
|
||||||
|
|
||||||
|
|
@ -11004,6 +11291,26 @@ class $$MessageHistoriesTableAnnotationComposer
|
||||||
));
|
));
|
||||||
return composer;
|
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<
|
class $$MessageHistoriesTableTableManager extends RootTableManager<
|
||||||
|
|
@ -11017,7 +11324,7 @@ class $$MessageHistoriesTableTableManager extends RootTableManager<
|
||||||
$$MessageHistoriesTableUpdateCompanionBuilder,
|
$$MessageHistoriesTableUpdateCompanionBuilder,
|
||||||
(MessageHistory, $$MessageHistoriesTableReferences),
|
(MessageHistory, $$MessageHistoriesTableReferences),
|
||||||
MessageHistory,
|
MessageHistory,
|
||||||
PrefetchHooks Function({bool messageId})> {
|
PrefetchHooks Function({bool messageId, bool contactId})> {
|
||||||
$$MessageHistoriesTableTableManager(
|
$$MessageHistoriesTableTableManager(
|
||||||
_$TwonlyDB db, $MessageHistoriesTable table)
|
_$TwonlyDB db, $MessageHistoriesTable table)
|
||||||
: super(TableManagerState(
|
: super(TableManagerState(
|
||||||
|
|
@ -11063,7 +11370,7 @@ class $$MessageHistoriesTableTableManager extends RootTableManager<
|
||||||
$$MessageHistoriesTableReferences(db, table, e)
|
$$MessageHistoriesTableReferences(db, table, e)
|
||||||
))
|
))
|
||||||
.toList(),
|
.toList(),
|
||||||
prefetchHooksCallback: ({messageId = false}) {
|
prefetchHooksCallback: ({messageId = false, contactId = false}) {
|
||||||
return PrefetchHooks(
|
return PrefetchHooks(
|
||||||
db: db,
|
db: db,
|
||||||
explicitlyWatchedTables: [],
|
explicitlyWatchedTables: [],
|
||||||
|
|
@ -11091,6 +11398,17 @@ class $$MessageHistoriesTableTableManager extends RootTableManager<
|
||||||
.messageId,
|
.messageId,
|
||||||
) as T;
|
) 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;
|
return state;
|
||||||
},
|
},
|
||||||
|
|
@ -11113,7 +11431,7 @@ typedef $$MessageHistoriesTableProcessedTableManager = ProcessedTableManager<
|
||||||
$$MessageHistoriesTableUpdateCompanionBuilder,
|
$$MessageHistoriesTableUpdateCompanionBuilder,
|
||||||
(MessageHistory, $$MessageHistoriesTableReferences),
|
(MessageHistory, $$MessageHistoriesTableReferences),
|
||||||
MessageHistory,
|
MessageHistory,
|
||||||
PrefetchHooks Function({bool messageId})>;
|
PrefetchHooks Function({bool messageId, bool contactId})>;
|
||||||
typedef $$ReactionsTableCreateCompanionBuilder = ReactionsCompanion Function({
|
typedef $$ReactionsTableCreateCompanionBuilder = ReactionsCompanion Function({
|
||||||
required String messageId,
|
required String messageId,
|
||||||
required String emoji,
|
required String emoji,
|
||||||
|
|
@ -13581,6 +13899,21 @@ final class $$MessageActionsTableReferences
|
||||||
return ProcessedTableManager(
|
return ProcessedTableManager(
|
||||||
manager.$state.copyWith(prefetchedData: [item]));
|
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<int>('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
|
class $$MessageActionsTableFilterComposer
|
||||||
|
|
@ -13592,9 +13925,6 @@ class $$MessageActionsTableFilterComposer
|
||||||
super.$addJoinBuilderToRootComposer,
|
super.$addJoinBuilderToRootComposer,
|
||||||
super.$removeJoinBuilderFromRootComposer,
|
super.$removeJoinBuilderFromRootComposer,
|
||||||
});
|
});
|
||||||
ColumnFilters<int> get contactId => $composableBuilder(
|
|
||||||
column: $table.contactId, builder: (column) => ColumnFilters(column));
|
|
||||||
|
|
||||||
ColumnWithTypeConverterFilters<MessageActionType, MessageActionType, String>
|
ColumnWithTypeConverterFilters<MessageActionType, MessageActionType, String>
|
||||||
get type => $composableBuilder(
|
get type => $composableBuilder(
|
||||||
column: $table.type,
|
column: $table.type,
|
||||||
|
|
@ -13622,6 +13952,26 @@ class $$MessageActionsTableFilterComposer
|
||||||
));
|
));
|
||||||
return composer;
|
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
|
class $$MessageActionsTableOrderingComposer
|
||||||
|
|
@ -13633,9 +13983,6 @@ class $$MessageActionsTableOrderingComposer
|
||||||
super.$addJoinBuilderToRootComposer,
|
super.$addJoinBuilderToRootComposer,
|
||||||
super.$removeJoinBuilderFromRootComposer,
|
super.$removeJoinBuilderFromRootComposer,
|
||||||
});
|
});
|
||||||
ColumnOrderings<int> get contactId => $composableBuilder(
|
|
||||||
column: $table.contactId, builder: (column) => ColumnOrderings(column));
|
|
||||||
|
|
||||||
ColumnOrderings<String> get type => $composableBuilder(
|
ColumnOrderings<String> get type => $composableBuilder(
|
||||||
column: $table.type, builder: (column) => ColumnOrderings(column));
|
column: $table.type, builder: (column) => ColumnOrderings(column));
|
||||||
|
|
||||||
|
|
@ -13661,6 +14008,26 @@ class $$MessageActionsTableOrderingComposer
|
||||||
));
|
));
|
||||||
return composer;
|
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
|
class $$MessageActionsTableAnnotationComposer
|
||||||
|
|
@ -13672,9 +14039,6 @@ class $$MessageActionsTableAnnotationComposer
|
||||||
super.$addJoinBuilderToRootComposer,
|
super.$addJoinBuilderToRootComposer,
|
||||||
super.$removeJoinBuilderFromRootComposer,
|
super.$removeJoinBuilderFromRootComposer,
|
||||||
});
|
});
|
||||||
GeneratedColumn<int> get contactId =>
|
|
||||||
$composableBuilder(column: $table.contactId, builder: (column) => column);
|
|
||||||
|
|
||||||
GeneratedColumnWithTypeConverter<MessageActionType, String> get type =>
|
GeneratedColumnWithTypeConverter<MessageActionType, String> get type =>
|
||||||
$composableBuilder(column: $table.type, builder: (column) => column);
|
$composableBuilder(column: $table.type, builder: (column) => column);
|
||||||
|
|
||||||
|
|
@ -13700,6 +14064,26 @@ class $$MessageActionsTableAnnotationComposer
|
||||||
));
|
));
|
||||||
return composer;
|
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<
|
class $$MessageActionsTableTableManager extends RootTableManager<
|
||||||
|
|
@ -13713,7 +14097,7 @@ class $$MessageActionsTableTableManager extends RootTableManager<
|
||||||
$$MessageActionsTableUpdateCompanionBuilder,
|
$$MessageActionsTableUpdateCompanionBuilder,
|
||||||
(MessageAction, $$MessageActionsTableReferences),
|
(MessageAction, $$MessageActionsTableReferences),
|
||||||
MessageAction,
|
MessageAction,
|
||||||
PrefetchHooks Function({bool messageId})> {
|
PrefetchHooks Function({bool messageId, bool contactId})> {
|
||||||
$$MessageActionsTableTableManager(_$TwonlyDB db, $MessageActionsTable table)
|
$$MessageActionsTableTableManager(_$TwonlyDB db, $MessageActionsTable table)
|
||||||
: super(TableManagerState(
|
: super(TableManagerState(
|
||||||
db: db,
|
db: db,
|
||||||
|
|
@ -13758,7 +14142,7 @@ class $$MessageActionsTableTableManager extends RootTableManager<
|
||||||
$$MessageActionsTableReferences(db, table, e)
|
$$MessageActionsTableReferences(db, table, e)
|
||||||
))
|
))
|
||||||
.toList(),
|
.toList(),
|
||||||
prefetchHooksCallback: ({messageId = false}) {
|
prefetchHooksCallback: ({messageId = false, contactId = false}) {
|
||||||
return PrefetchHooks(
|
return PrefetchHooks(
|
||||||
db: db,
|
db: db,
|
||||||
explicitlyWatchedTables: [],
|
explicitlyWatchedTables: [],
|
||||||
|
|
@ -13786,6 +14170,17 @@ class $$MessageActionsTableTableManager extends RootTableManager<
|
||||||
.messageId,
|
.messageId,
|
||||||
) as T;
|
) 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;
|
return state;
|
||||||
},
|
},
|
||||||
|
|
@ -13808,7 +14203,7 @@ typedef $$MessageActionsTableProcessedTableManager = ProcessedTableManager<
|
||||||
$$MessageActionsTableUpdateCompanionBuilder,
|
$$MessageActionsTableUpdateCompanionBuilder,
|
||||||
(MessageAction, $$MessageActionsTableReferences),
|
(MessageAction, $$MessageActionsTableReferences),
|
||||||
MessageAction,
|
MessageAction,
|
||||||
PrefetchHooks Function({bool messageId})>;
|
PrefetchHooks Function({bool messageId, bool contactId})>;
|
||||||
typedef $$GroupHistoriesTableCreateCompanionBuilder = GroupHistoriesCompanion
|
typedef $$GroupHistoriesTableCreateCompanionBuilder = GroupHistoriesCompanion
|
||||||
Function({
|
Function({
|
||||||
required String groupHistoryId,
|
required String groupHistoryId,
|
||||||
|
|
|
||||||
|
|
@ -2804,12 +2804,439 @@ i1.GeneratedColumn<DateTime> _column_104(String aliasedName) =>
|
||||||
i1.GeneratedColumn<DateTime>(
|
i1.GeneratedColumn<DateTime>(
|
||||||
'mark_for_retry_after_accepted', aliasedName, true,
|
'mark_for_retry_after_accepted', aliasedName, true,
|
||||||
type: i1.DriftSqlType.dateTime);
|
type: i1.DriftSqlType.dateTime);
|
||||||
|
|
||||||
|
final class Schema7 extends i0.VersionedSchema {
|
||||||
|
Schema7({required super.database}) : super(version: 7);
|
||||||
|
@override
|
||||||
|
late final List<i1.DatabaseSchemaEntity> 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<String> get groupId =>
|
||||||
|
columnsByName['group_id']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get messageId =>
|
||||||
|
columnsByName['message_id']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<int> get senderId =>
|
||||||
|
columnsByName['sender_id']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<String> get type =>
|
||||||
|
columnsByName['type']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get content =>
|
||||||
|
columnsByName['content']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get mediaId =>
|
||||||
|
columnsByName['media_id']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<i2.Uint8List> get additionalMessageData =>
|
||||||
|
columnsByName['additional_message_data']!
|
||||||
|
as i1.GeneratedColumn<i2.Uint8List>;
|
||||||
|
i1.GeneratedColumn<bool> get mediaStored =>
|
||||||
|
columnsByName['media_stored']! as i1.GeneratedColumn<bool>;
|
||||||
|
i1.GeneratedColumn<bool> get mediaReopened =>
|
||||||
|
columnsByName['media_reopened']! as i1.GeneratedColumn<bool>;
|
||||||
|
i1.GeneratedColumn<i2.Uint8List> get downloadToken =>
|
||||||
|
columnsByName['download_token']! as i1.GeneratedColumn<i2.Uint8List>;
|
||||||
|
i1.GeneratedColumn<String> get quotesMessageId =>
|
||||||
|
columnsByName['quotes_message_id']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<bool> get isDeletedFromSender =>
|
||||||
|
columnsByName['is_deleted_from_sender']! as i1.GeneratedColumn<bool>;
|
||||||
|
i1.GeneratedColumn<DateTime> get openedAt =>
|
||||||
|
columnsByName['opened_at']! as i1.GeneratedColumn<DateTime>;
|
||||||
|
i1.GeneratedColumn<DateTime> get openedByAll =>
|
||||||
|
columnsByName['opened_by_all']! as i1.GeneratedColumn<DateTime>;
|
||||||
|
i1.GeneratedColumn<DateTime> get createdAt =>
|
||||||
|
columnsByName['created_at']! as i1.GeneratedColumn<DateTime>;
|
||||||
|
i1.GeneratedColumn<DateTime> get modifiedAt =>
|
||||||
|
columnsByName['modified_at']! as i1.GeneratedColumn<DateTime>;
|
||||||
|
i1.GeneratedColumn<DateTime> get ackByUser =>
|
||||||
|
columnsByName['ack_by_user']! as i1.GeneratedColumn<DateTime>;
|
||||||
|
i1.GeneratedColumn<DateTime> get ackByServer =>
|
||||||
|
columnsByName['ack_by_server']! as i1.GeneratedColumn<DateTime>;
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.GeneratedColumn<i2.Uint8List> _column_105(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<i2.Uint8List>(
|
||||||
|
'additional_message_data', aliasedName, true,
|
||||||
|
type: i1.DriftSqlType.blob);
|
||||||
i0.MigrationStepWithVersion migrationSteps({
|
i0.MigrationStepWithVersion migrationSteps({
|
||||||
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
||||||
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
||||||
required Future<void> Function(i1.Migrator m, Schema4 schema) from3To4,
|
required Future<void> Function(i1.Migrator m, Schema4 schema) from3To4,
|
||||||
required Future<void> Function(i1.Migrator m, Schema5 schema) from4To5,
|
required Future<void> Function(i1.Migrator m, Schema5 schema) from4To5,
|
||||||
required Future<void> Function(i1.Migrator m, Schema6 schema) from5To6,
|
required Future<void> Function(i1.Migrator m, Schema6 schema) from5To6,
|
||||||
|
required Future<void> Function(i1.Migrator m, Schema7 schema) from6To7,
|
||||||
}) {
|
}) {
|
||||||
return (currentVersion, database) async {
|
return (currentVersion, database) async {
|
||||||
switch (currentVersion) {
|
switch (currentVersion) {
|
||||||
|
|
@ -2838,6 +3265,11 @@ i0.MigrationStepWithVersion migrationSteps({
|
||||||
final migrator = i1.Migrator(database, schema);
|
final migrator = i1.Migrator(database, schema);
|
||||||
await from5To6(migrator, schema);
|
await from5To6(migrator, schema);
|
||||||
return 6;
|
return 6;
|
||||||
|
case 6:
|
||||||
|
final schema = Schema7(database: database);
|
||||||
|
final migrator = i1.Migrator(database, schema);
|
||||||
|
await from6To7(migrator, schema);
|
||||||
|
return 7;
|
||||||
default:
|
default:
|
||||||
throw ArgumentError.value('Unknown migration from $currentVersion');
|
throw ArgumentError.value('Unknown migration from $currentVersion');
|
||||||
}
|
}
|
||||||
|
|
@ -2850,6 +3282,7 @@ i1.OnUpgrade stepByStep({
|
||||||
required Future<void> Function(i1.Migrator m, Schema4 schema) from3To4,
|
required Future<void> Function(i1.Migrator m, Schema4 schema) from3To4,
|
||||||
required Future<void> Function(i1.Migrator m, Schema5 schema) from4To5,
|
required Future<void> Function(i1.Migrator m, Schema5 schema) from4To5,
|
||||||
required Future<void> Function(i1.Migrator m, Schema6 schema) from5To6,
|
required Future<void> Function(i1.Migrator m, Schema6 schema) from5To6,
|
||||||
|
required Future<void> Function(i1.Migrator m, Schema7 schema) from6To7,
|
||||||
}) =>
|
}) =>
|
||||||
i0.VersionedSchema.stepByStepHelper(
|
i0.VersionedSchema.stepByStepHelper(
|
||||||
step: migrationSteps(
|
step: migrationSteps(
|
||||||
|
|
@ -2858,4 +3291,5 @@ i1.OnUpgrade stepByStep({
|
||||||
from3To4: from3To4,
|
from3To4: from3To4,
|
||||||
from4To5: from4To5,
|
from4To5: from4To5,
|
||||||
from5To6: from5To6,
|
from5To6: from5To6,
|
||||||
|
from6To7: from6To7,
|
||||||
));
|
));
|
||||||
|
|
|
||||||
|
|
@ -2518,6 +2518,12 @@ abstract class AppLocalizations {
|
||||||
/// **'wants to connect with you.'**
|
/// **'wants to connect with you.'**
|
||||||
String get notificationContactRequest;
|
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.
|
/// No description provided for @notificationAcceptRequest.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|
@ -2572,11 +2578,17 @@ abstract class AppLocalizations {
|
||||||
/// **'has responded{inGroup}.'**
|
/// **'has responded{inGroup}.'**
|
||||||
String notificationResponse(Object inGroup);
|
String notificationResponse(Object inGroup);
|
||||||
|
|
||||||
/// No description provided for @notificationTitleUnknownUser.
|
/// No description provided for @notificationTitleUnknown.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'[Unknown]'**
|
/// **'You have a new message.'**
|
||||||
String get notificationTitleUnknownUser;
|
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.
|
/// No description provided for @notificationCategoryMessageTitle.
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -1380,6 +1380,10 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get notificationContactRequest => 'möchte sich mit dir vernetzen.';
|
String get notificationContactRequest => 'möchte sich mit dir vernetzen.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notificationContactRequestUnknownUser =>
|
||||||
|
'hast eine neue Kontaktanfrage erhalten.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get notificationAcceptRequest => 'ist jetzt mit dir vernetzt.';
|
String get notificationAcceptRequest => 'ist jetzt mit dir vernetzt.';
|
||||||
|
|
||||||
|
|
@ -1418,7 +1422,10 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get notificationTitleUnknownUser => '[Unbekannt]';
|
String get notificationTitleUnknown => 'Du hast eine neue Nachricht.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notificationBodyUnknown => 'Öffne twonly um mehr zu erfahren.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get notificationCategoryMessageTitle => 'Nachrichten';
|
String get notificationCategoryMessageTitle => 'Nachrichten';
|
||||||
|
|
|
||||||
|
|
@ -1372,6 +1372,10 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get notificationContactRequest => 'wants to connect with you.';
|
String get notificationContactRequest => 'wants to connect with you.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notificationContactRequestUnknownUser =>
|
||||||
|
'have received a new contact request.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get notificationAcceptRequest => 'is now connected with you.';
|
String get notificationAcceptRequest => 'is now connected with you.';
|
||||||
|
|
||||||
|
|
@ -1410,7 +1414,10 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get notificationTitleUnknownUser => '[Unknown]';
|
String get notificationTitleUnknown => 'You have a new message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notificationBodyUnknown => 'Open twonly to learn more.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get notificationCategoryMessageTitle => 'Messages';
|
String get notificationCategoryMessageTitle => 'Messages';
|
||||||
|
|
|
||||||
|
|
@ -1372,6 +1372,10 @@ class AppLocalizationsSv extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get notificationContactRequest => 'wants to connect with you.';
|
String get notificationContactRequest => 'wants to connect with you.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notificationContactRequestUnknownUser =>
|
||||||
|
'have received a new contact request.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get notificationAcceptRequest => 'is now connected with you.';
|
String get notificationAcceptRequest => 'is now connected with you.';
|
||||||
|
|
||||||
|
|
@ -1410,7 +1414,10 @@ class AppLocalizationsSv extends AppLocalizations {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get notificationTitleUnknownUser => '[Unknown]';
|
String get notificationTitleUnknown => 'You have a new message.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get notificationBodyUnknown => 'Open twonly to learn more.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get notificationCategoryMessageTitle => 'Messages';
|
String get notificationCategoryMessageTitle => 'Messages';
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 20f3c2f0a49e4c9be452ecbc84d98054c92974e1
|
Subproject commit 9d04e9e1d0cdba8f1be4b0cbba341706c3cffac9
|
||||||
|
|
@ -108,6 +108,19 @@ class UserData {
|
||||||
DateTime? nextTimeToShowBackupNotice;
|
DateTime? nextTimeToShowBackupNotice;
|
||||||
BackupServer? backupServer;
|
BackupServer? backupServer;
|
||||||
TwonlySafeBackup? twonlySafeBackup;
|
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<String, dynamic> toJson() => _$UserDataToJson(this);
|
Map<String, dynamic> toJson() => _$UserDataToJson(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,14 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) => UserData(
|
||||||
..twonlySafeBackup = json['twonlySafeBackup'] == null
|
..twonlySafeBackup = json['twonlySafeBackup'] == null
|
||||||
? null
|
? null
|
||||||
: TwonlySafeBackup.fromJson(
|
: TwonlySafeBackup.fromJson(
|
||||||
json['twonlySafeBackup'] as Map<String, dynamic>);
|
json['twonlySafeBackup'] as Map<String, dynamic>)
|
||||||
|
..askedForUserStudyPermission =
|
||||||
|
json['askedForUserStudyPermission'] as bool? ?? false
|
||||||
|
..userStudyParticipantsToken =
|
||||||
|
json['userStudyParticipantsToken'] as String?
|
||||||
|
..lastUserStudyDataUpload = json['lastUserStudyDataUpload'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['lastUserStudyDataUpload'] as String);
|
||||||
|
|
||||||
Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
||||||
'userId': instance.userId,
|
'userId': instance.userId,
|
||||||
|
|
@ -122,6 +129,10 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
||||||
instance.nextTimeToShowBackupNotice?.toIso8601String(),
|
instance.nextTimeToShowBackupNotice?.toIso8601String(),
|
||||||
'backupServer': instance.backupServer,
|
'backupServer': instance.backupServer,
|
||||||
'twonlySafeBackup': instance.twonlySafeBackup,
|
'twonlySafeBackup': instance.twonlySafeBackup,
|
||||||
|
'askedForUserStudyPermission': instance.askedForUserStudyPermission,
|
||||||
|
'userStudyParticipantsToken': instance.userStudyParticipantsToken,
|
||||||
|
'lastUserStudyDataUpload':
|
||||||
|
instance.lastUserStudyDataUpload?.toIso8601String(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const _$ThemeModeEnumMap = {
|
const _$ThemeModeEnumMap = {
|
||||||
|
|
|
||||||
11
lib/src/model/protobuf/client/data.proto
Normal file
11
lib/src/model/protobuf/client/data.proto
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
|
||||||
|
message AdditionalMessageData {
|
||||||
|
enum Type {
|
||||||
|
LINK = 0;
|
||||||
|
}
|
||||||
|
Type type = 1;
|
||||||
|
|
||||||
|
optional string link = 2;
|
||||||
|
}
|
||||||
99
lib/src/model/protobuf/client/generated/data.pb.dart
Normal file
99
lib/src/model/protobuf/client/generated/data.pb.dart
Normal file
|
|
@ -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<AdditionalMessageData_Type>(
|
||||||
|
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<AdditionalMessageData> createRepeated() =>
|
||||||
|
$pb.PbList<AdditionalMessageData>();
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static AdditionalMessageData getDefault() => _defaultInstance ??=
|
||||||
|
$pb.GeneratedMessage.$_defaultFor<AdditionalMessageData>(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');
|
||||||
35
lib/src/model/protobuf/client/generated/data.pbenum.dart
Normal file
35
lib/src/model/protobuf/client/generated/data.pbenum.dart
Normal file
|
|
@ -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<AdditionalMessageData_Type> values =
|
||||||
|
<AdditionalMessageData_Type>[
|
||||||
|
LINK,
|
||||||
|
];
|
||||||
|
|
||||||
|
static final $core.List<AdditionalMessageData_Type?> _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');
|
||||||
49
lib/src/model/protobuf/client/generated/data.pbjson.dart
Normal file
49
lib/src/model/protobuf/client/generated/data.pbjson.dart
Normal file
|
|
@ -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==');
|
||||||
|
|
@ -969,6 +969,7 @@ class EncryptedContent_Media extends $pb.GeneratedMessage {
|
||||||
$core.List<$core.int>? encryptionKey,
|
$core.List<$core.int>? encryptionKey,
|
||||||
$core.List<$core.int>? encryptionMac,
|
$core.List<$core.int>? encryptionMac,
|
||||||
$core.List<$core.int>? encryptionNonce,
|
$core.List<$core.int>? encryptionNonce,
|
||||||
|
$core.List<$core.int>? additionalMessageData,
|
||||||
}) {
|
}) {
|
||||||
final result = create();
|
final result = create();
|
||||||
if (senderMessageId != null) result.senderMessageId = senderMessageId;
|
if (senderMessageId != null) result.senderMessageId = senderMessageId;
|
||||||
|
|
@ -983,6 +984,8 @@ class EncryptedContent_Media extends $pb.GeneratedMessage {
|
||||||
if (encryptionKey != null) result.encryptionKey = encryptionKey;
|
if (encryptionKey != null) result.encryptionKey = encryptionKey;
|
||||||
if (encryptionMac != null) result.encryptionMac = encryptionMac;
|
if (encryptionMac != null) result.encryptionMac = encryptionMac;
|
||||||
if (encryptionNonce != null) result.encryptionNonce = encryptionNonce;
|
if (encryptionNonce != null) result.encryptionNonce = encryptionNonce;
|
||||||
|
if (additionalMessageData != null)
|
||||||
|
result.additionalMessageData = additionalMessageData;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1024,6 +1027,8 @@ class EncryptedContent_Media extends $pb.GeneratedMessage {
|
||||||
..a<$core.List<$core.int>>(
|
..a<$core.List<$core.int>>(
|
||||||
10, _omitFieldNames ? '' : 'encryptionNonce', $pb.PbFieldType.OY,
|
10, _omitFieldNames ? '' : 'encryptionNonce', $pb.PbFieldType.OY,
|
||||||
protoName: 'encryptionNonce')
|
protoName: 'encryptionNonce')
|
||||||
|
..a<$core.List<$core.int>>(
|
||||||
|
11, _omitFieldNames ? '' : 'additionalMessageData', $pb.PbFieldType.OY)
|
||||||
..hasRequiredFields = false;
|
..hasRequiredFields = false;
|
||||||
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
@$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);
|
$core.bool hasEncryptionNonce() => $_has(9);
|
||||||
@$pb.TagNumber(10)
|
@$pb.TagNumber(10)
|
||||||
void clearEncryptionNonce() => $_clearField(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 {
|
class EncryptedContent_MediaUpdate extends $pb.GeneratedMessage {
|
||||||
|
|
|
||||||
|
|
@ -603,6 +603,15 @@ const EncryptedContent_Media$json = {
|
||||||
'10': 'encryptionNonce',
|
'10': 'encryptionNonce',
|
||||||
'17': true
|
'17': true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'1': 'additional_message_data',
|
||||||
|
'3': 11,
|
||||||
|
'4': 1,
|
||||||
|
'5': 12,
|
||||||
|
'9': 6,
|
||||||
|
'10': 'additionalMessageData',
|
||||||
|
'17': true
|
||||||
|
},
|
||||||
],
|
],
|
||||||
'4': [EncryptedContent_Media_Type$json],
|
'4': [EncryptedContent_Media_Type$json],
|
||||||
'8': [
|
'8': [
|
||||||
|
|
@ -612,6 +621,7 @@ const EncryptedContent_Media$json = {
|
||||||
{'1': '_encryptionKey'},
|
{'1': '_encryptionKey'},
|
||||||
{'1': '_encryptionMac'},
|
{'1': '_encryptionMac'},
|
||||||
{'1': '_encryptionNonce'},
|
{'1': '_encryptionNonce'},
|
||||||
|
{'1': '_additional_message_data'},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -840,7 +850,7 @@ final $typed_data.Uint8List encryptedContentDescriptor = $convert.base64Decode(
|
||||||
'EjoKGG11bHRpcGxlVGFyZ2V0TWVzc2FnZUlkcxgDIAMoCVIYbXVsdGlwbGVUYXJnZXRNZXNzYW'
|
'EjoKGG11bHRpcGxlVGFyZ2V0TWVzc2FnZUlkcxgDIAMoCVIYbXVsdGlwbGVUYXJnZXRNZXNzYW'
|
||||||
'dlSWRzEhcKBHRleHQYBCABKAlIAVIEdGV4dIgBARIcCgl0aW1lc3RhbXAYBSABKANSCXRpbWVz'
|
'dlSWRzEhcKBHRleHQYBCABKAlIAVIEdGV4dIgBARIcCgl0aW1lc3RhbXAYBSABKANSCXRpbWVz'
|
||||||
'dGFtcCItCgRUeXBlEgoKBkRFTEVURRAAEg0KCUVESVRfVEVYVBABEgoKBk9QRU5FRBACQhIKEF'
|
'dGFtcCItCgRUeXBlEgoKBkRFTEVURRAAEg0KCUVESVRfVEVYVBABEgoKBk9QRU5FRBACQhIKEF'
|
||||||
'9zZW5kZXJNZXNzYWdlSWRCBwoFX3RleHQalwUKBU1lZGlhEigKD3NlbmRlck1lc3NhZ2VJZBgB'
|
'9zZW5kZXJNZXNzYWdlSWRCBwoFX3RleHQa8AUKBU1lZGlhEigKD3NlbmRlck1lc3NhZ2VJZBgB'
|
||||||
'IAEoCVIPc2VuZGVyTWVzc2FnZUlkEjAKBHR5cGUYAiABKA4yHC5FbmNyeXB0ZWRDb250ZW50Lk'
|
'IAEoCVIPc2VuZGVyTWVzc2FnZUlkEjAKBHR5cGUYAiABKA4yHC5FbmNyeXB0ZWRDb250ZW50Lk'
|
||||||
'1lZGlhLlR5cGVSBHR5cGUSQwoaZGlzcGxheUxpbWl0SW5NaWxsaXNlY29uZHMYAyABKANIAFIa'
|
'1lZGlhLlR5cGVSBHR5cGUSQwoaZGlzcGxheUxpbWl0SW5NaWxsaXNlY29uZHMYAyABKANIAFIa'
|
||||||
'ZGlzcGxheUxpbWl0SW5NaWxsaXNlY29uZHOIAQESNgoWcmVxdWlyZXNBdXRoZW50aWNhdGlvbh'
|
'ZGlzcGxheUxpbWl0SW5NaWxsaXNlY29uZHOIAQESNgoWcmVxdWlyZXNBdXRoZW50aWNhdGlvbh'
|
||||||
|
|
@ -849,29 +859,31 @@ final $typed_data.Uint8List encryptedContentDescriptor = $convert.base64Decode(
|
||||||
'dubG9hZFRva2VuGAcgASgMSAJSDWRvd25sb2FkVG9rZW6IAQESKQoNZW5jcnlwdGlvbktleRgI'
|
'dubG9hZFRva2VuGAcgASgMSAJSDWRvd25sb2FkVG9rZW6IAQESKQoNZW5jcnlwdGlvbktleRgI'
|
||||||
'IAEoDEgDUg1lbmNyeXB0aW9uS2V5iAEBEikKDWVuY3J5cHRpb25NYWMYCSABKAxIBFINZW5jcn'
|
'IAEoDEgDUg1lbmNyeXB0aW9uS2V5iAEBEikKDWVuY3J5cHRpb25NYWMYCSABKAxIBFINZW5jcn'
|
||||||
'lwdGlvbk1hY4gBARItCg9lbmNyeXB0aW9uTm9uY2UYCiABKAxIBVIPZW5jcnlwdGlvbk5vbmNl'
|
'lwdGlvbk1hY4gBARItCg9lbmNyeXB0aW9uTm9uY2UYCiABKAxIBVIPZW5jcnlwdGlvbk5vbmNl'
|
||||||
'iAEBIj4KBFR5cGUSDAoIUkVVUExPQUQQABIJCgVJTUFHRRABEgkKBVZJREVPEAISBwoDR0lGEA'
|
'iAEBEjsKF2FkZGl0aW9uYWxfbWVzc2FnZV9kYXRhGAsgASgMSAZSFWFkZGl0aW9uYWxNZXNzYW'
|
||||||
'MSCQoFQVVESU8QBEIdChtfZGlzcGxheUxpbWl0SW5NaWxsaXNlY29uZHNCEQoPX3F1b3RlTWVz'
|
'dlRGF0YYgBASI+CgRUeXBlEgwKCFJFVVBMT0FEEAASCQoFSU1BR0UQARIJCgVWSURFTxACEgcK'
|
||||||
'c2FnZUlkQhAKDl9kb3dubG9hZFRva2VuQhAKDl9lbmNyeXB0aW9uS2V5QhAKDl9lbmNyeXB0aW'
|
'A0dJRhADEgkKBUFVRElPEARCHQobX2Rpc3BsYXlMaW1pdEluTWlsbGlzZWNvbmRzQhEKD19xdW'
|
||||||
'9uTWFjQhIKEF9lbmNyeXB0aW9uTm9uY2UapwEKC01lZGlhVXBkYXRlEjYKBHR5cGUYASABKA4y'
|
'90ZU1lc3NhZ2VJZEIQCg5fZG93bmxvYWRUb2tlbkIQCg5fZW5jcnlwdGlvbktleUIQCg5fZW5j'
|
||||||
'Ii5FbmNyeXB0ZWRDb250ZW50Lk1lZGlhVXBkYXRlLlR5cGVSBHR5cGUSKAoPdGFyZ2V0TWVzc2'
|
'cnlwdGlvbk1hY0ISChBfZW5jcnlwdGlvbk5vbmNlQhoKGF9hZGRpdGlvbmFsX21lc3NhZ2VfZG'
|
||||||
'FnZUlkGAIgASgJUg90YXJnZXRNZXNzYWdlSWQiNgoEVHlwZRIMCghSRU9QRU5FRBAAEgoKBlNU'
|
'F0YRqnAQoLTWVkaWFVcGRhdGUSNgoEdHlwZRgBIAEoDjIiLkVuY3J5cHRlZENvbnRlbnQuTWVk'
|
||||||
'T1JFRBABEhQKEERFQ1JZUFRJT05fRVJST1IQAhp4Cg5Db250YWN0UmVxdWVzdBI5CgR0eXBlGA'
|
'aWFVcGRhdGUuVHlwZVIEdHlwZRIoCg90YXJnZXRNZXNzYWdlSWQYAiABKAlSD3RhcmdldE1lc3'
|
||||||
'EgASgOMiUuRW5jcnlwdGVkQ29udGVudC5Db250YWN0UmVxdWVzdC5UeXBlUgR0eXBlIisKBFR5'
|
'NhZ2VJZCI2CgRUeXBlEgwKCFJFT1BFTkVEEAASCgoGU1RPUkVEEAESFAoQREVDUllQVElPTl9F'
|
||||||
'cGUSCwoHUkVRVUVTVBAAEgoKBlJFSkVDVBABEgoKBkFDQ0VQVBACGp4CCg1Db250YWN0VXBkYX'
|
'UlJPUhACGngKDkNvbnRhY3RSZXF1ZXN0EjkKBHR5cGUYASABKA4yJS5FbmNyeXB0ZWRDb250ZW'
|
||||||
'RlEjgKBHR5cGUYASABKA4yJC5FbmNyeXB0ZWRDb250ZW50LkNvbnRhY3RVcGRhdGUuVHlwZVIE'
|
'50LkNvbnRhY3RSZXF1ZXN0LlR5cGVSBHR5cGUiKwoEVHlwZRILCgdSRVFVRVNUEAASCgoGUkVK'
|
||||||
'dHlwZRI1ChNhdmF0YXJTdmdDb21wcmVzc2VkGAIgASgMSABSE2F2YXRhclN2Z0NvbXByZXNzZW'
|
'RUNUEAESCgoGQUNDRVBUEAIangIKDUNvbnRhY3RVcGRhdGUSOAoEdHlwZRgBIAEoDjIkLkVuY3'
|
||||||
'SIAQESHwoIdXNlcm5hbWUYAyABKAlIAVIIdXNlcm5hbWWIAQESJQoLZGlzcGxheU5hbWUYBCAB'
|
'J5cHRlZENvbnRlbnQuQ29udGFjdFVwZGF0ZS5UeXBlUgR0eXBlEjUKE2F2YXRhclN2Z0NvbXBy'
|
||||||
'KAlIAlILZGlzcGxheU5hbWWIAQEiHwoEVHlwZRILCgdSRVFVRVNUEAASCgoGVVBEQVRFEAFCFg'
|
'ZXNzZWQYAiABKAxIAFITYXZhdGFyU3ZnQ29tcHJlc3NlZIgBARIfCgh1c2VybmFtZRgDIAEoCU'
|
||||||
'oUX2F2YXRhclN2Z0NvbXByZXNzZWRCCwoJX3VzZXJuYW1lQg4KDF9kaXNwbGF5TmFtZRrVAQoI'
|
'gBUgh1c2VybmFtZYgBARIlCgtkaXNwbGF5TmFtZRgEIAEoCUgCUgtkaXNwbGF5TmFtZYgBASIf'
|
||||||
'UHVzaEtleXMSMwoEdHlwZRgBIAEoDjIfLkVuY3J5cHRlZENvbnRlbnQuUHVzaEtleXMuVHlwZV'
|
'CgRUeXBlEgsKB1JFUVVFU1QQABIKCgZVUERBVEUQAUIWChRfYXZhdGFyU3ZnQ29tcHJlc3NlZE'
|
||||||
'IEdHlwZRIZCgVrZXlJZBgCIAEoA0gAUgVrZXlJZIgBARIVCgNrZXkYAyABKAxIAVIDa2V5iAEB'
|
'ILCglfdXNlcm5hbWVCDgoMX2Rpc3BsYXlOYW1lGtUBCghQdXNoS2V5cxIzCgR0eXBlGAEgASgO'
|
||||||
'EiEKCWNyZWF0ZWRBdBgEIAEoA0gCUgljcmVhdGVkQXSIAQEiHwoEVHlwZRILCgdSRVFVRVNUEA'
|
'Mh8uRW5jcnlwdGVkQ29udGVudC5QdXNoS2V5cy5UeXBlUgR0eXBlEhkKBWtleUlkGAIgASgDSA'
|
||||||
'ASCgoGVVBEQVRFEAFCCAoGX2tleUlkQgYKBF9rZXlCDAoKX2NyZWF0ZWRBdBqpAQoJRmxhbWVT'
|
'BSBWtleUlkiAEBEhUKA2tleRgDIAEoDEgBUgNrZXmIAQESIQoJY3JlYXRlZEF0GAQgASgDSAJS'
|
||||||
'eW5jEiIKDGZsYW1lQ291bnRlchgBIAEoA1IMZmxhbWVDb3VudGVyEjYKFmxhc3RGbGFtZUNvdW'
|
'CWNyZWF0ZWRBdIgBASIfCgRUeXBlEgsKB1JFUVVFU1QQABIKCgZVUERBVEUQAUIICgZfa2V5SW'
|
||||||
'50ZXJDaGFuZ2UYAiABKANSFmxhc3RGbGFtZUNvdW50ZXJDaGFuZ2USHgoKYmVzdEZyaWVuZBgD'
|
'RCBgoEX2tleUIMCgpfY3JlYXRlZEF0GqkBCglGbGFtZVN5bmMSIgoMZmxhbWVDb3VudGVyGAEg'
|
||||||
'IAEoCFIKYmVzdEZyaWVuZBIgCgtmb3JjZVVwZGF0ZRgEIAEoCFILZm9yY2VVcGRhdGVCCgoIX2'
|
'ASgDUgxmbGFtZUNvdW50ZXISNgoWbGFzdEZsYW1lQ291bnRlckNoYW5nZRgCIAEoA1IWbGFzdE'
|
||||||
'dyb3VwSWRCDwoNX2lzRGlyZWN0Q2hhdEIXChVfc2VuZGVyUHJvZmlsZUNvdW50ZXJCEAoOX21l'
|
'ZsYW1lQ291bnRlckNoYW5nZRIeCgpiZXN0RnJpZW5kGAMgASgIUgpiZXN0RnJpZW5kEiAKC2Zv'
|
||||||
'c3NhZ2VVcGRhdGVCCAoGX21lZGlhQg4KDF9tZWRpYVVwZGF0ZUIQCg5fY29udGFjdFVwZGF0ZU'
|
'cmNlVXBkYXRlGAQgASgIUgtmb3JjZVVwZGF0ZUIKCghfZ3JvdXBJZEIPCg1faXNEaXJlY3RDaG'
|
||||||
'IRCg9fY29udGFjdFJlcXVlc3RCDAoKX2ZsYW1lU3luY0ILCglfcHVzaEtleXNCCwoJX3JlYWN0'
|
'F0QhcKFV9zZW5kZXJQcm9maWxlQ291bnRlckIQCg5fbWVzc2FnZVVwZGF0ZUIICgZfbWVkaWFC'
|
||||||
'aW9uQg4KDF90ZXh0TWVzc2FnZUIOCgxfZ3JvdXBDcmVhdGVCDAoKX2dyb3VwSm9pbkIOCgxfZ3'
|
'DgoMX21lZGlhVXBkYXRlQhAKDl9jb250YWN0VXBkYXRlQhEKD19jb250YWN0UmVxdWVzdEIMCg'
|
||||||
'JvdXBVcGRhdGVCFwoVX3Jlc2VuZEdyb3VwUHVibGljS2V5QhEKD19lcnJvcl9tZXNzYWdlcw==');
|
'pfZmxhbWVTeW5jQgsKCV9wdXNoS2V5c0ILCglfcmVhY3Rpb25CDgoMX3RleHRNZXNzYWdlQg4K'
|
||||||
|
'DF9ncm91cENyZWF0ZUIMCgpfZ3JvdXBKb2luQg4KDF9ncm91cFVwZGF0ZUIXChVfcmVzZW5kR3'
|
||||||
|
'JvdXBQdWJsaWNLZXlCEQoPX2Vycm9yX21lc3NhZ2Vz');
|
||||||
|
|
|
||||||
|
|
@ -132,6 +132,8 @@ message EncryptedContent {
|
||||||
optional bytes encryptionKey = 8;
|
optional bytes encryptionKey = 8;
|
||||||
optional bytes encryptionMac = 9;
|
optional bytes encryptionMac = 9;
|
||||||
optional bytes encryptionNonce = 10;
|
optional bytes encryptionNonce = 10;
|
||||||
|
|
||||||
|
optional bytes additional_message_data = 11;
|
||||||
}
|
}
|
||||||
|
|
||||||
message MediaUpdate {
|
message MediaUpdate {
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart
|
||||||
as server;
|
as server;
|
||||||
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pbserver.dart';
|
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/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/messages.dart';
|
||||||
import 'package:twonly/src/services/api/server_messages.dart';
|
import 'package:twonly/src/services/api/server_messages.dart';
|
||||||
import 'package:twonly/src/services/api/utils.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/log.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/utils/storage.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';
|
import 'package:web_socket_channel/io.dart';
|
||||||
|
|
||||||
final lockConnecting = Mutex();
|
final lockConnecting = Mutex();
|
||||||
|
|
@ -100,6 +102,11 @@ class ApiService {
|
||||||
unawaited(fetchGroupStatesForUnjoinedGroups());
|
unawaited(fetchGroupStatesForUnjoinedGroups());
|
||||||
unawaited(fetchMissingGroupPublicKey());
|
unawaited(fetchMissingGroupPublicKey());
|
||||||
unawaited(checkForDeletedUsernames());
|
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;
|
return user;
|
||||||
});
|
});
|
||||||
globalCallbackUpdatePlan(planFromString(authenticated.plan));
|
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) {
|
if (res.isError) {
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,11 @@ Future<void> handleMedia(
|
||||||
groupId: Value(groupId),
|
groupId: Value(groupId),
|
||||||
mediaId: Value(mediaFile.mediaId),
|
mediaId: Value(mediaFile.mediaId),
|
||||||
type: const Value(MessageType.media),
|
type: const Value(MessageType.media),
|
||||||
|
additionalMessageData: Value.absentIfNull(
|
||||||
|
media.hasAdditionalMessageData()
|
||||||
|
? Uint8List.fromList(media.additionalMessageData)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
quotesMessageId: Value(
|
quotesMessageId: Value(
|
||||||
media.hasQuoteMessageId() ? media.quoteMessageId : null,
|
media.hasQuoteMessageId() ? media.quoteMessageId : null,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -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/tables/messages.table.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.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/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/model/protobuf/client/generated/messages.pb.dart';
|
||||||
import 'package:twonly/src/services/api/mediafiles/media_background.service.dart';
|
import 'package:twonly/src/services/api/mediafiles/media_background.service.dart';
|
||||||
import 'package:twonly/src/services/api/messages.dart';
|
import 'package:twonly/src/services/api/messages.dart';
|
||||||
|
|
@ -88,8 +89,9 @@ Future<MediaFileService?> initializeMediaUpload(
|
||||||
|
|
||||||
Future<void> insertMediaFileInMessagesTable(
|
Future<void> insertMediaFileInMessagesTable(
|
||||||
MediaFileService mediaService,
|
MediaFileService mediaService,
|
||||||
List<String> groupIds,
|
List<String> groupIds, {
|
||||||
) async {
|
AdditionalMessageData? additionalData,
|
||||||
|
}) async {
|
||||||
await twonlyDB.mediaFilesDao.updateAllMediaFiles(
|
await twonlyDB.mediaFilesDao.updateAllMediaFiles(
|
||||||
const MediaFilesCompanion(
|
const MediaFilesCompanion(
|
||||||
isDraftMedia: Value(false),
|
isDraftMedia: Value(false),
|
||||||
|
|
@ -101,6 +103,8 @@ Future<void> insertMediaFileInMessagesTable(
|
||||||
groupId: Value(groupId),
|
groupId: Value(groupId),
|
||||||
mediaId: Value(mediaService.mediaFile.mediaId),
|
mediaId: Value(mediaService.mediaFile.mediaId),
|
||||||
type: const Value(MessageType.media),
|
type: const Value(MessageType.media),
|
||||||
|
additionalMessageData:
|
||||||
|
Value.absentIfNull(additionalData?.writeToBuffer()),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
await twonlyDB.groupsDao.increaseLastMessageExchange(groupId, clock.now());
|
await twonlyDB.groupsDao.increaseLastMessageExchange(groupId, clock.now());
|
||||||
|
|
@ -245,6 +249,7 @@ Future<void> _createUploadRequest(MediaFileService media) async {
|
||||||
encryptionKey: media.mediaFile.encryptionKey,
|
encryptionKey: media.mediaFile.encryptionKey,
|
||||||
encryptionNonce: media.mediaFile.encryptionNonce,
|
encryptionNonce: media.mediaFile.encryptionNonce,
|
||||||
encryptionMac: media.mediaFile.encryptionMac,
|
encryptionMac: media.mediaFile.encryptionMac,
|
||||||
|
additionalMessageData: message.additionalMessageData,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@ Future<(Uint8List, Uint8List?)?> tryToSendCompleteMessage({
|
||||||
String? receiptId,
|
String? receiptId,
|
||||||
Receipt? receipt,
|
Receipt? receipt,
|
||||||
bool onlyReturnEncryptedData = false,
|
bool onlyReturnEncryptedData = false,
|
||||||
|
bool blocking = true,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
if (receiptId == null && receipt == null) return null;
|
if (receiptId == null && receipt == null) return null;
|
||||||
|
|
@ -238,12 +239,11 @@ Future<void> sendCipherTextToGroup(
|
||||||
encryptedContent.groupId = groupId;
|
encryptedContent.groupId = groupId;
|
||||||
|
|
||||||
for (final groupMember in groupMembers) {
|
for (final groupMember in groupMembers) {
|
||||||
unawaited(
|
await sendCipherText(
|
||||||
sendCipherText(
|
|
||||||
groupMember.contactId,
|
groupMember.contactId,
|
||||||
encryptedContent,
|
encryptedContent,
|
||||||
messageId: messageId,
|
messageId: messageId,
|
||||||
),
|
blocking: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -252,6 +252,7 @@ Future<(Uint8List, Uint8List?)?> sendCipherText(
|
||||||
int contactId,
|
int contactId,
|
||||||
pb.EncryptedContent encryptedContent, {
|
pb.EncryptedContent encryptedContent, {
|
||||||
bool onlyReturnEncryptedData = false,
|
bool onlyReturnEncryptedData = false,
|
||||||
|
bool blocking = true,
|
||||||
String? messageId,
|
String? messageId,
|
||||||
}) async {
|
}) async {
|
||||||
encryptedContent.senderProfileCounter = Int64(gUser.avatarCounter);
|
encryptedContent.senderProfileCounter = Int64(gUser.avatarCounter);
|
||||||
|
|
@ -270,10 +271,15 @@ Future<(Uint8List, Uint8List?)?> sendCipherText(
|
||||||
);
|
);
|
||||||
|
|
||||||
if (receipt != null) {
|
if (receipt != null) {
|
||||||
return tryToSendCompleteMessage(
|
final tmp = tryToSendCompleteMessage(
|
||||||
receipt: receipt,
|
receipt: receipt,
|
||||||
onlyReturnEncryptedData: onlyReturnEncryptedData,
|
onlyReturnEncryptedData: onlyReturnEncryptedData,
|
||||||
|
blocking: blocking,
|
||||||
);
|
);
|
||||||
|
if (!blocking) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return tmp;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -302,6 +308,7 @@ Future<void> notifyContactAboutOpeningMessage(
|
||||||
timestamp: Int64(actionAt.millisecondsSinceEpoch),
|
timestamp: Int64(actionAt.millisecondsSinceEpoch),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
blocking: false,
|
||||||
);
|
);
|
||||||
for (final messageId in messageOtherIds) {
|
for (final messageId in messageOtherIds) {
|
||||||
await twonlyDB.messagesDao.updateMessageId(
|
await twonlyDB.messagesDao.updateMessageId(
|
||||||
|
|
|
||||||
|
|
@ -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/services/signal/session.signal.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/misc.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/chats/add_new_user.view.dart';
|
||||||
import 'package:twonly/src/views/components/alert_dialog.dart';
|
import 'package:twonly/src/views/components/alert_dialog.dart';
|
||||||
import 'package:twonly/src/views/contact/contact.view.dart';
|
import 'package:twonly/src/views/contact/contact.view.dart';
|
||||||
|
|
@ -151,6 +151,7 @@ Future<void> handleIntentMediaFile(
|
||||||
Future<void> handleIntentSharedFile(
|
Future<void> handleIntentSharedFile(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
List<SharedFile> files,
|
List<SharedFile> files,
|
||||||
|
void Function(Uri) onUrlCallBack,
|
||||||
) async {
|
) async {
|
||||||
for (final file in files) {
|
for (final file in files) {
|
||||||
if (file.value == null) {
|
if (file.value == null) {
|
||||||
|
|
@ -163,7 +164,9 @@ Future<void> handleIntentSharedFile(
|
||||||
|
|
||||||
switch (file.type) {
|
switch (file.type) {
|
||||||
case SharedMediaType.URL:
|
case SharedMediaType.URL:
|
||||||
// await handleIntentUrl(context, Uri.parse(file.value!));
|
if (file.value?.startsWith('http') ?? false) {
|
||||||
|
onUrlCallBack(Uri.parse(file.value!));
|
||||||
|
}
|
||||||
case SharedMediaType.IMAGE:
|
case SharedMediaType.IMAGE:
|
||||||
var type = MediaType.image;
|
var type = MediaType.image;
|
||||||
if (file.value!.endsWith('.gif')) {
|
if (file.value!.endsWith('.gif')) {
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ class MediaFileService {
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> purgeTempFolder() async {
|
static Future<void> purgeTempFolder() async {
|
||||||
|
try {
|
||||||
final tempDirectory = MediaFileService.buildDirectoryPath(
|
final tempDirectory = MediaFileService.buildDirectoryPath(
|
||||||
'tmp',
|
'tmp',
|
||||||
globalApplicationSupportDirectory,
|
globalApplicationSupportDirectory,
|
||||||
|
|
@ -70,7 +71,8 @@ class MediaFileService {
|
||||||
} else {
|
} 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.
|
// 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.
|
// This also allows to reopen this image for two days.
|
||||||
final group = await twonlyDB.groupsDao.getGroup(message.groupId);
|
final group =
|
||||||
|
await twonlyDB.groupsDao.getGroup(message.groupId);
|
||||||
if (group != null && !group.isDirectChat) {
|
if (group != null && !group.isDirectChat) {
|
||||||
delete = false;
|
delete = false;
|
||||||
}
|
}
|
||||||
|
|
@ -85,6 +87,9 @@ class MediaFileService {
|
||||||
file.deleteSync();
|
file.deleteSync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
Log.error(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateFromDB() async {
|
Future<void> updateFromDB() async {
|
||||||
|
|
|
||||||
|
|
@ -99,9 +99,10 @@ Future<void> handlePushData(String pushDataB64) async {
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Log.error(e);
|
Log.error(e);
|
||||||
|
final lang = getLocalizations();
|
||||||
await customLocalPushNotification(
|
await customLocalPushNotification(
|
||||||
'Du hast eine neue Nachricht.',
|
lang.notificationTitleUnknown,
|
||||||
'Öffne twonly um mehr zu erfahren.',
|
lang.notificationBodyUnknown,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -186,16 +187,14 @@ Future<void> showLocalPushNotification(
|
||||||
Future<void> showLocalPushNotificationWithoutUserId(
|
Future<void> showLocalPushNotificationWithoutUserId(
|
||||||
PushNotification pushNotification,
|
PushNotification pushNotification,
|
||||||
) async {
|
) async {
|
||||||
String? body;
|
|
||||||
|
|
||||||
body = getPushNotificationText(pushNotification);
|
|
||||||
|
|
||||||
final lang = getLocalizations();
|
final lang = getLocalizations();
|
||||||
|
|
||||||
final title = lang.notificationTitleUnknownUser;
|
var title = lang.notificationTitleUnknown;
|
||||||
|
var body = lang.notificationBodyUnknown;
|
||||||
|
|
||||||
if (body == '') {
|
if (pushNotification.kind == PushKind.contactRequest) {
|
||||||
Log.error('No push notification type defined!');
|
title = lang.you;
|
||||||
|
body = lang.notificationContactRequestUnknownUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
final androidNotificationDetails = AndroidNotificationDetails(
|
final androidNotificationDetails = AndroidNotificationDetails(
|
||||||
|
|
|
||||||
|
|
@ -4,38 +4,41 @@ import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
class KeyValueStore {
|
class KeyValueStore {
|
||||||
static Future<String> _getFilePath(String key) async {
|
static Future<File> _getFilePath(String key) async {
|
||||||
final directory = await getApplicationSupportDirectory();
|
final directory = await getApplicationSupportDirectory();
|
||||||
return '${directory.path}/keyvalue/$key.json';
|
return File('${directory.path}/keyvalue/$key.json');
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> 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<Map<String, dynamic>?> get(String key) async {
|
static Future<Map<String, dynamic>?> get(String key) async {
|
||||||
try {
|
try {
|
||||||
final filePath = await _getFilePath(key);
|
final file = await _getFilePath(key);
|
||||||
final file = File(filePath);
|
|
||||||
|
|
||||||
// Check if the file exists
|
|
||||||
if (file.existsSync()) {
|
if (file.existsSync()) {
|
||||||
final contents = await file.readAsString();
|
final contents = await file.readAsString();
|
||||||
return jsonDecode(contents) as Map<String, dynamic>;
|
return jsonDecode(contents) as Map<String, dynamic>;
|
||||||
} else {
|
} else {
|
||||||
return null; // File does not exist
|
return null;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Log.error('Error reading file: $e');
|
Log.warn('Error reading file: $e');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> put(String key, Map<String, dynamic> value) async {
|
static Future<void> put(String key, Map<String, dynamic> value) async {
|
||||||
try {
|
try {
|
||||||
final filePath = await _getFilePath(key);
|
final file = await _getFilePath(key);
|
||||||
final file = File(filePath);
|
|
||||||
|
|
||||||
// Create the directory if it doesn't exist
|
|
||||||
await file.parent.create(recursive: true);
|
await file.parent.create(recursive: true);
|
||||||
|
|
||||||
// Write the JSON data to the file
|
|
||||||
await file.writeAsString(jsonEncode(value));
|
await file.writeAsString(jsonEncode(value));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Log.error('Error writing file: $e');
|
Log.error('Error writing file: $e');
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,9 @@ class MainCameraPreview extends StatelessWidget {
|
||||||
requiredHeight: 0,
|
requiredHeight: 0,
|
||||||
additionalPadding: 59,
|
additionalPadding: 59,
|
||||||
bottomNavigation: Container(),
|
bottomNavigation: Container(),
|
||||||
child: Screenshot(
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Screenshot(
|
||||||
controller: mainCameraController.screenshotController,
|
controller: mainCameraController.screenshotController,
|
||||||
child: AspectRatio(
|
child: AspectRatio(
|
||||||
aspectRatio: 9 / 16,
|
aspectRatio: 9 / 16,
|
||||||
|
|
@ -36,8 +38,19 @@ class MainCameraPreview extends StatelessWidget {
|
||||||
height: mainCameraController
|
height: mainCameraController
|
||||||
.cameraController!.value.previewSize!.width,
|
.cameraController!.value.previewSize!.width,
|
||||||
child: CameraPreview(
|
child: CameraPreview(
|
||||||
|
key: mainCameraController.cameraPreviewKey,
|
||||||
mainCameraController.cameraController!,
|
mainCameraController.cameraController!,
|
||||||
child: mainCameraController.customPaint,
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
if (mainCameraController.customPaint != null)
|
||||||
|
Positioned.fill(
|
||||||
|
child: mainCameraController.customPaint!,
|
||||||
|
),
|
||||||
|
if (mainCameraController.facePaint != null)
|
||||||
|
Positioned.fill(
|
||||||
|
child: mainCameraController.facePaint!,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -45,6 +58,45 @@ class MainCameraPreview extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:camera/camera.dart';
|
import 'package:camera/camera.dart';
|
||||||
import 'package:clock/clock.dart';
|
import 'package:clock/clock.dart';
|
||||||
import 'package:device_info_plus/device_info_plus.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/qr.dart';
|
||||||
import 'package:twonly/src/utils/screenshot.dart';
|
import 'package:twonly/src/utils/screenshot.dart';
|
||||||
import 'package:twonly/src/utils/storage.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/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/permissions_view.dart';
|
||||||
import 'package:twonly/src/views/camera/camera_preview_components/send_to.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/video_recording_time.dart';
|
||||||
import 'package:twonly/src/views/camera/camera_preview_components/zoom_selector.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/avatar_icon.component.dart';
|
||||||
import 'package:twonly/src/views/components/loader.dart';
|
import 'package:twonly/src/views/components/loader.dart';
|
||||||
import 'package:twonly/src/views/components/media_view_sizing.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;
|
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 {
|
class SelectedCameraDetails {
|
||||||
double maxAvailableZoom = 1;
|
double maxAvailableZoom = 1;
|
||||||
double minAvailableZoom = 1;
|
double minAvailableZoom = 1;
|
||||||
|
|
@ -156,12 +109,10 @@ class CameraPreviewView extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CameraPreviewViewState extends State<CameraPreviewView> {
|
class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
bool _sharePreviewIsShown = false;
|
|
||||||
bool _galleryLoadedImageIsShown = false;
|
bool _galleryLoadedImageIsShown = false;
|
||||||
bool _showSelfieFlash = false;
|
bool _showSelfieFlash = false;
|
||||||
double _basePanY = 0;
|
double _basePanY = 0;
|
||||||
double _baseScaleFactor = 0;
|
double _baseScaleFactor = 0;
|
||||||
bool _isVideoRecording = false;
|
|
||||||
bool _hasAudioPermission = true;
|
bool _hasAudioPermission = true;
|
||||||
DateTime? _videoRecordingStarted;
|
DateTime? _videoRecordingStarted;
|
||||||
Timer? _videoRecordingTimer;
|
Timer? _videoRecordingTimer;
|
||||||
|
|
@ -317,10 +268,10 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> takePicture() async {
|
Future<void> takePicture() async {
|
||||||
if (_sharePreviewIsShown || _isVideoRecording) return;
|
if (mc.isSharePreviewIsShown || mc.isVideoRecording) return;
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_sharePreviewIsShown = true;
|
mc.isSharePreviewIsShown = true;
|
||||||
});
|
});
|
||||||
if (mc.selectedCameraDetails.isFlashOn) {
|
if (mc.selectedCameraDetails.isFlashOn) {
|
||||||
if (isFront) {
|
if (isFront) {
|
||||||
|
|
@ -353,12 +304,12 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
_sharePreviewIsShown = false;
|
mc.isSharePreviewIsShown = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> pushMediaEditor(
|
Future<bool> pushMediaEditor(
|
||||||
ScreenshotImage? imageBytes,
|
ScreenshotImage? screenshotImage,
|
||||||
File? videoFilePath, {
|
File? videoFilePath, {
|
||||||
bool sharedFromGallery = false,
|
bool sharedFromGallery = false,
|
||||||
MediaType? mediaType,
|
MediaType? mediaType,
|
||||||
|
|
@ -394,11 +345,12 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
PageRouteBuilder(
|
PageRouteBuilder(
|
||||||
opaque: false,
|
opaque: false,
|
||||||
pageBuilder: (context, a1, a2) => ShareImageEditorView(
|
pageBuilder: (context, a1, a2) => ShareImageEditorView(
|
||||||
imageBytesFuture: imageBytes,
|
screenshotImage: screenshotImage,
|
||||||
sharedFromGallery: sharedFromGallery,
|
sharedFromGallery: sharedFromGallery,
|
||||||
sendToGroup: widget.sendToGroup,
|
sendToGroup: widget.sendToGroup,
|
||||||
mediaFileService: mediaFileService,
|
mediaFileService: mediaFileService,
|
||||||
mainCameraController: mc,
|
mainCameraController: mc,
|
||||||
|
previewLink: mc.sharedLinkForPreview,
|
||||||
),
|
),
|
||||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||||
return child;
|
return child;
|
||||||
|
|
@ -409,7 +361,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
) as bool?;
|
) as bool?;
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_sharePreviewIsShown = false;
|
mc.isSharePreviewIsShown = false;
|
||||||
_showSelfieFlash = false;
|
_showSelfieFlash = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -459,7 +411,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
Future<void> pickImageFromGallery() async {
|
Future<void> pickImageFromGallery() async {
|
||||||
setState(() {
|
setState(() {
|
||||||
_galleryLoadedImageIsShown = true;
|
_galleryLoadedImageIsShown = true;
|
||||||
_sharePreviewIsShown = true;
|
mc.isSharePreviewIsShown = true;
|
||||||
});
|
});
|
||||||
final picker = ImagePicker();
|
final picker = ImagePicker();
|
||||||
final pickedFile = await picker.pickMedia();
|
final pickedFile = await picker.pickMedia();
|
||||||
|
|
@ -502,17 +454,47 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
}
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
_galleryLoadedImageIsShown = false;
|
_galleryLoadedImageIsShown = false;
|
||||||
_sharePreviewIsShown = false;
|
mc.isSharePreviewIsShown = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> 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<void> 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<void> startVideoRecording() async {
|
Future<void> startVideoRecording() async {
|
||||||
if (mc.cameraController != null &&
|
if (mc.cameraController != null &&
|
||||||
mc.cameraController!.value.isRecordingVideo) {
|
mc.cameraController!.value.isRecordingVideo) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
_isVideoRecording = true;
|
mc.isVideoRecording = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -532,11 +514,11 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
});
|
});
|
||||||
setState(() {
|
setState(() {
|
||||||
_videoRecordingStarted = clock.now();
|
_videoRecordingStarted = clock.now();
|
||||||
_isVideoRecording = true;
|
mc.isVideoRecording = true;
|
||||||
});
|
});
|
||||||
} on CameraException catch (e) {
|
} on CameraException catch (e) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isVideoRecording = false;
|
mc.isVideoRecording = false;
|
||||||
});
|
});
|
||||||
_showCameraException(e);
|
_showCameraException(e);
|
||||||
return;
|
return;
|
||||||
|
|
@ -551,7 +533,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_videoRecordingStarted = null;
|
_videoRecordingStarted = null;
|
||||||
_isVideoRecording = false;
|
mc.isVideoRecording = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (mc.cameraController == null ||
|
if (mc.cameraController == null ||
|
||||||
|
|
@ -560,7 +542,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_sharePreviewIsShown = true;
|
mc.isSharePreviewIsShown = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -646,12 +628,23 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (!_sharePreviewIsShown &&
|
if (!mc.isSharePreviewIsShown &&
|
||||||
widget.sendToGroup != null &&
|
widget.sendToGroup != null &&
|
||||||
!_isVideoRecording)
|
!mc.isVideoRecording)
|
||||||
SendToWidget(sendTo: widget.sendToGroup!.groupName),
|
ShowTitleText(
|
||||||
if (!_sharePreviewIsShown &&
|
title: widget.sendToGroup!.groupName,
|
||||||
!_isVideoRecording &&
|
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)
|
!widget.hideControllers)
|
||||||
Positioned(
|
Positioned(
|
||||||
right: 5,
|
right: 5,
|
||||||
|
|
@ -707,7 +700,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (!_sharePreviewIsShown && !widget.hideControllers)
|
if (!mc.isSharePreviewIsShown && !widget.hideControllers)
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: 30,
|
bottom: 30,
|
||||||
left: 0,
|
left: 0,
|
||||||
|
|
@ -718,7 +711,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
children: [
|
children: [
|
||||||
if (mc.cameraController!.value.isInitialized &&
|
if (mc.cameraController!.value.isInitialized &&
|
||||||
mc.selectedCameraDetails.isZoomAble &&
|
mc.selectedCameraDetails.isZoomAble &&
|
||||||
!_isVideoRecording)
|
!mc.isVideoRecording)
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 120,
|
width: 120,
|
||||||
child: CameraZoomButtons(
|
child: CameraZoomButtons(
|
||||||
|
|
@ -734,17 +727,21 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
if (!_isVideoRecording)
|
if (!mc.isVideoRecording)
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: pickImageFromGallery,
|
onTap: pressSideButtonLeft,
|
||||||
child: Align(
|
child: Align(
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 50,
|
height: 50,
|
||||||
width: 80,
|
width: 80,
|
||||||
padding: const EdgeInsets.all(2),
|
padding: const EdgeInsets.all(2),
|
||||||
child: const Center(
|
child: Center(
|
||||||
child: FaIcon(
|
child: FaIcon(
|
||||||
FontAwesomeIcons.photoFilm,
|
mc.isSelectingFaceFilters
|
||||||
|
? mc.currentFilterType.index == 1
|
||||||
|
? FontAwesomeIcons.xmark
|
||||||
|
: FontAwesomeIcons.arrowLeft
|
||||||
|
: FontAwesomeIcons.photoFilm,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
size: 25,
|
size: 25,
|
||||||
),
|
),
|
||||||
|
|
@ -766,15 +763,44 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
width: 7,
|
width: 7,
|
||||||
color: _isVideoRecording
|
color: mc.isVideoRecording
|
||||||
? Colors.red
|
? Colors.red
|
||||||
: Colors.white,
|
: 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<CameraPreviewView> {
|
||||||
videoRecordingStarted: _videoRecordingStarted,
|
videoRecordingStarted: _videoRecordingStarted,
|
||||||
maxVideoRecordingTime: maxVideoRecordingTime,
|
maxVideoRecordingTime: maxVideoRecordingTime,
|
||||||
),
|
),
|
||||||
if (!_sharePreviewIsShown && widget.sendToGroup != null ||
|
if (!mc.isSharePreviewIsShown && widget.sendToGroup != null ||
|
||||||
widget.hideControllers)
|
widget.hideControllers)
|
||||||
Positioned(
|
Positioned(
|
||||||
left: 5,
|
left: 5,
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:camera/camera.dart';
|
import 'package:camera/camera.dart';
|
||||||
import 'package:collection/collection.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/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:google_mlkit_barcode_scanning/google_mlkit_barcode_scanning.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/globals.dart';
|
||||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.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/qr.dart';
|
||||||
import 'package:twonly/src/utils/screenshot.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/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 {
|
class ScannedVerifiedContact {
|
||||||
ScannedVerifiedContact({
|
ScannedVerifiedContact({
|
||||||
|
|
@ -45,6 +52,34 @@ class MainCameraController {
|
||||||
Map<int, ScannedNewProfile> scannedNewProfiles = {};
|
Map<int, ScannedNewProfile> scannedNewProfiles = {};
|
||||||
String? scannedUrl;
|
String? scannedUrl;
|
||||||
GlobalKey zoomButtonKey = GlobalKey();
|
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<void> closeCamera() async {
|
Future<void> closeCamera() async {
|
||||||
contactsVerified = {};
|
contactsVerified = {};
|
||||||
|
|
@ -57,51 +92,131 @@ class MainCameraController {
|
||||||
}
|
}
|
||||||
final cameraControllerTemp = cameraController;
|
final cameraControllerTemp = cameraController;
|
||||||
cameraController = null;
|
cameraController = null;
|
||||||
|
// prevents: CameraException(Disposed CameraController, buildPreview() was called on a disposed CameraController.)
|
||||||
|
Future.delayed(const Duration(milliseconds: 100), () async {
|
||||||
await cameraControllerTemp?.dispose();
|
await cameraControllerTemp?.dispose();
|
||||||
|
});
|
||||||
initCameraStarted = false;
|
initCameraStarted = false;
|
||||||
selectedCameraDetails = SelectedCameraDetails();
|
selectedCameraDetails = SelectedCameraDetails();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<CameraController?> selectCamera(int sCameraId, bool init) async {
|
Future<void> selectCamera(int sCameraId, bool init) async {
|
||||||
initCameraStarted = true;
|
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<void> toggleSelectedCamera() async {
|
var cameraId = sCameraId;
|
||||||
if (cameraController == null) return;
|
if (cameraId >= gCameras.length) {
|
||||||
// do not allow switching camera when recording
|
Log.warn(
|
||||||
if (cameraController!.value.isRecordingVideo) {
|
'Trying to select a non existing camera $cameraId >= ${gCameras.length}',
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
await cameraController!.stopImageStream();
|
if (init) {
|
||||||
} catch (e) {
|
for (; cameraId < gCameras.length; cameraId++) {
|
||||||
// Log.warn(e);
|
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<void> onDoubleTap() async {
|
||||||
await selectCamera((selectedCameraDetails.cameraId + 1) % 2, false);
|
await selectCamera((selectedCameraDetails.cameraId + 1) % 2, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
final BarcodeScanner _barcodeScanner = BarcodeScanner();
|
Future<void> onTapDown(TapDownDetails details) async {
|
||||||
bool _isBusy = false;
|
final box =
|
||||||
CustomPaint? customPaint;
|
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<DeviceOrientation, int> _orientations = {
|
final Map<DeviceOrientation, int> _orientations = {
|
||||||
DeviceOrientation.portraitUp: 0,
|
DeviceOrientation.portraitUp: 0,
|
||||||
|
|
@ -111,9 +226,21 @@ class MainCameraController {
|
||||||
};
|
};
|
||||||
|
|
||||||
void _processCameraImage(CameraImage image) {
|
void _processCameraImage(CameraImage image) {
|
||||||
|
if (isVideoRecording || isSharePreviewIsShown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
final inputImage = _inputImageFromCameraImage(image);
|
final inputImage = _inputImageFromCameraImage(image);
|
||||||
if (inputImage == null) return;
|
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) {
|
InputImage? _inputImageFromCameraImage(CameraImage image) {
|
||||||
|
|
@ -175,7 +302,7 @@ class MainCameraController {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _processImage(InputImage inputImage) async {
|
Future<void> _processBarcode(InputImage inputImage) async {
|
||||||
if (_isBusy) return;
|
if (_isBusy) return;
|
||||||
_isBusy = true;
|
_isBusy = true;
|
||||||
final barcodes = await _barcodeScanner.processImage(inputImage);
|
final barcodes = await _barcodeScanner.processImage(inputImage);
|
||||||
|
|
@ -255,4 +382,48 @@ class MainCameraController {
|
||||||
_isBusy = false;
|
_isBusy = false;
|
||||||
setState();
|
setState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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<void> _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<ui.Image> _loadImage(String assetPath) async {
|
||||||
|
final data = await rootBundle.load(assetPath);
|
||||||
|
final list = Uint8List.view(data.buffer);
|
||||||
|
final completer = Completer<ui.Image>();
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<void> _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<ui.Image> _loadImage(String assetPath) async {
|
||||||
|
final data = await rootBundle.load(assetPath);
|
||||||
|
final list = Uint8List.view(data.buffer);
|
||||||
|
final completer = Completer<ui.Image>();
|
||||||
|
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<int>? leftMost;
|
||||||
|
Point<int>? rightMost;
|
||||||
|
Point<int>? 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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<Face> 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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'package:clock/clock.dart';
|
import 'package:clock/clock.dart';
|
||||||
import 'package:drift/drift.dart' show Value;
|
import 'package:drift/drift.dart' show Value;
|
||||||
import 'package:flutter/material.dart';
|
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/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
import 'package:twonly/src/utils/screenshot.dart';
|
||||||
|
|
||||||
class SaveToGalleryButton extends StatefulWidget {
|
class SaveToGalleryButton extends StatefulWidget {
|
||||||
const SaveToGalleryButton({
|
const SaveToGalleryButton({
|
||||||
|
|
@ -17,7 +17,7 @@ class SaveToGalleryButton extends StatefulWidget {
|
||||||
this.storeImageAsOriginal,
|
this.storeImageAsOriginal,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
final Future<Uint8List?> Function()? storeImageAsOriginal;
|
final Future<ScreenshotImage?> Function()? storeImageAsOriginal;
|
||||||
final bool displayButtonLabel;
|
final bool displayButtonLabel;
|
||||||
final MediaFileService mediaService;
|
final MediaFileService mediaService;
|
||||||
final bool isLoading;
|
final bool isLoading;
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,25 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
|
||||||
|
|
||||||
class SendToWidget extends StatelessWidget {
|
class ShowTitleText extends StatelessWidget {
|
||||||
const SendToWidget({
|
const ShowTitleText({
|
||||||
required this.sendTo,
|
required this.desc,
|
||||||
|
required this.title,
|
||||||
|
this.isLink = false,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
final String sendTo;
|
final String title;
|
||||||
|
final String desc;
|
||||||
|
final bool isLink;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
const textStyle = TextStyle(
|
final textStyle = TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 24,
|
fontSize: isLink ? 14 : 24,
|
||||||
decoration: TextDecoration.none,
|
decoration: TextDecoration.none,
|
||||||
shadows: [
|
shadows: const [
|
||||||
Shadow(
|
Shadow(
|
||||||
color: Color.fromARGB(122, 0, 0, 0),
|
color: Color.fromARGB(122, 0, 0, 0),
|
||||||
blurRadius: 5,
|
blurRadius: 5,
|
||||||
|
|
@ -26,7 +29,7 @@ class SendToWidget extends StatelessWidget {
|
||||||
|
|
||||||
final boldTextStyle = textStyle.copyWith(
|
final boldTextStyle = textStyle.copyWith(
|
||||||
fontWeight: FontWeight.normal,
|
fontWeight: FontWeight.normal,
|
||||||
fontSize: 28,
|
fontSize: isLink ? 17 : 28,
|
||||||
);
|
);
|
||||||
|
|
||||||
return Positioned(
|
return Positioned(
|
||||||
|
|
@ -36,12 +39,12 @@ class SendToWidget extends StatelessWidget {
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
context.lang.cameraPreviewSendTo,
|
desc,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: textStyle,
|
style: textStyle,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
substringBy(sendTo, 20),
|
substringBy(title, isLink ? 30 : 20),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: boldTextStyle, // Use the bold text style here
|
style: boldTextStyle, // Use the bold text style here
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,8 @@ class QrCodeScannerState extends State<QrCodeScanner> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: GestureDetector(
|
body: GestureDetector(
|
||||||
onDoubleTap: _mainCameraController.toggleSelectedCamera,
|
onDoubleTap: _mainCameraController.onDoubleTap,
|
||||||
|
onTapDown: _mainCameraController.onTapDown,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
MainCameraPreview(
|
MainCameraPreview(
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,8 @@ class CameraSendToViewState extends State<CameraSendToView> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: GestureDetector(
|
body: GestureDetector(
|
||||||
onDoubleTap: _mainCameraController.toggleSelectedCamera,
|
onDoubleTap: _mainCameraController.onDoubleTap,
|
||||||
|
onTapDown: _mainCameraController.onTapDown,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
MainCameraPreview(
|
MainCameraPreview(
|
||||||
|
|
@ -1,710 +0,0 @@
|
||||||
Map<String, String> emojiWeights = {};
|
|
||||||
|
|
||||||
List<String> emojis = [
|
|
||||||
'😀',
|
|
||||||
'😁',
|
|
||||||
'😂',
|
|
||||||
'🤣',
|
|
||||||
'😃',
|
|
||||||
'😄',
|
|
||||||
'😅',
|
|
||||||
'😆',
|
|
||||||
'😉',
|
|
||||||
'😊',
|
|
||||||
'😋',
|
|
||||||
'😎',
|
|
||||||
'😍',
|
|
||||||
'😘',
|
|
||||||
'🥰',
|
|
||||||
'😗',
|
|
||||||
'😙',
|
|
||||||
'😚',
|
|
||||||
'🙂️',
|
|
||||||
'🤗',
|
|
||||||
'🤩',
|
|
||||||
'🤔',
|
|
||||||
'🤔',
|
|
||||||
'🤨',
|
|
||||||
'😐',
|
|
||||||
'😑',
|
|
||||||
'😶',
|
|
||||||
'🙄',
|
|
||||||
'😏',
|
|
||||||
'😣',
|
|
||||||
'😥',
|
|
||||||
'😮',
|
|
||||||
'🤐',
|
|
||||||
'😯',
|
|
||||||
'😪',
|
|
||||||
'😫',
|
|
||||||
'😴',
|
|
||||||
'😌',
|
|
||||||
'😛',
|
|
||||||
'😜',
|
|
||||||
'😝',
|
|
||||||
'🤤',
|
|
||||||
'😒',
|
|
||||||
'😓',
|
|
||||||
'😔',
|
|
||||||
'😕',
|
|
||||||
'🙃',
|
|
||||||
'🤑',
|
|
||||||
'😲',
|
|
||||||
'🙁',
|
|
||||||
'😖',
|
|
||||||
'😞',
|
|
||||||
'😟',
|
|
||||||
'😤',
|
|
||||||
'😢',
|
|
||||||
'😭',
|
|
||||||
'😦',
|
|
||||||
'😧',
|
|
||||||
'😨',
|
|
||||||
'😩',
|
|
||||||
'🤯',
|
|
||||||
'😬',
|
|
||||||
'😰',
|
|
||||||
'😱',
|
|
||||||
'🥵',
|
|
||||||
'🥶',
|
|
||||||
'😳',
|
|
||||||
'🤪',
|
|
||||||
'😵',
|
|
||||||
'😡',
|
|
||||||
'😠',
|
|
||||||
'🤬',
|
|
||||||
'😷',
|
|
||||||
'🤒',
|
|
||||||
'🤕',
|
|
||||||
'🤢',
|
|
||||||
'🤮',
|
|
||||||
'🤧',
|
|
||||||
'😇',
|
|
||||||
'🤠',
|
|
||||||
'🤡',
|
|
||||||
'🥳',
|
|
||||||
'🥴',
|
|
||||||
'🤥',
|
|
||||||
'🤫',
|
|
||||||
'🤭',
|
|
||||||
'🤭',
|
|
||||||
'🧐',
|
|
||||||
'🤓',
|
|
||||||
'😈',
|
|
||||||
'👿',
|
|
||||||
'👹',
|
|
||||||
'👺',
|
|
||||||
'💀',
|
|
||||||
'👻',
|
|
||||||
'👽',
|
|
||||||
'🤖',
|
|
||||||
'💩',
|
|
||||||
'😺',
|
|
||||||
'😸',
|
|
||||||
'😹',
|
|
||||||
'😻',
|
|
||||||
'😼',
|
|
||||||
'😽',
|
|
||||||
'🙀',
|
|
||||||
'😿',
|
|
||||||
'😾',
|
|
||||||
'😾',
|
|
||||||
|
|
||||||
/// People and Fantasy
|
|
||||||
'👶',
|
|
||||||
'👧',
|
|
||||||
'🧒',
|
|
||||||
'👩',
|
|
||||||
'🧑',
|
|
||||||
'👨',
|
|
||||||
'👵',
|
|
||||||
'👴',
|
|
||||||
'👲',
|
|
||||||
'👳♀️️',
|
|
||||||
'👳♂️️️',
|
|
||||||
'🧕️️️',
|
|
||||||
'🧔',
|
|
||||||
'👱♂️️',
|
|
||||||
'👱♀️️️',
|
|
||||||
'👨🦰️️️',
|
|
||||||
'👩🦰',
|
|
||||||
'👨🦱',
|
|
||||||
'👨🦲',
|
|
||||||
'👩🦲',
|
|
||||||
'👨🦳',
|
|
||||||
'👩🦳',
|
|
||||||
'🦸♀️',
|
|
||||||
'🦸♂️️',
|
|
||||||
'🦹♀️️️',
|
|
||||||
'🦹♂️️️️',
|
|
||||||
'👮♀️',
|
|
||||||
'👮♂️️',
|
|
||||||
'👷♀️️️',
|
|
||||||
'👷♂️️️️',
|
|
||||||
'💂♀️️️️️',
|
|
||||||
'💂♂️️️️️️',
|
|
||||||
'🕵️♀️️️️️️️',
|
|
||||||
'🕵️♂️️️️️️️️',
|
|
||||||
'👩⚕️️️️️️️️️',
|
|
||||||
'👨⚕️️️️️️️️️️',
|
|
||||||
'👩🌾️️️️️️️️️️',
|
|
||||||
'👨🌾',
|
|
||||||
'👩🍳',
|
|
||||||
'👨🍳',
|
|
||||||
'👩🎓',
|
|
||||||
'👨🎓',
|
|
||||||
'👩🎤',
|
|
||||||
'👨🎤',
|
|
||||||
'👩🏫',
|
|
||||||
'👨🏫',
|
|
||||||
'👩🏭',
|
|
||||||
'👨🏭',
|
|
||||||
'👩💻',
|
|
||||||
'👨💻',
|
|
||||||
'👩💼',
|
|
||||||
'👨💼',
|
|
||||||
'👩🔧',
|
|
||||||
'👨🔧',
|
|
||||||
'👩🔬',
|
|
||||||
'👨🔬',
|
|
||||||
'👩🎨',
|
|
||||||
'👨🎨',
|
|
||||||
'👩🚒',
|
|
||||||
'👨🚒',
|
|
||||||
'👩✈️',
|
|
||||||
'👨✈️️',
|
|
||||||
'👩🚀',
|
|
||||||
'👨🚀',
|
|
||||||
'👩⚖️',
|
|
||||||
'👨⚖️️',
|
|
||||||
'👰',
|
|
||||||
'🤵',
|
|
||||||
'👸',
|
|
||||||
'🤴',
|
|
||||||
'🤶',
|
|
||||||
'🎅',
|
|
||||||
'🧙♀️',
|
|
||||||
'🧙♂️️',
|
|
||||||
'🧝♀️️️',
|
|
||||||
'🧝♂️',
|
|
||||||
'🧛♀️️',
|
|
||||||
'🧛♂️️️',
|
|
||||||
'🧟♀️️️️',
|
|
||||||
'🧟♂️️️️️',
|
|
||||||
'🧞♀️️️️️️',
|
|
||||||
'🧞♂️️️️️️️',
|
|
||||||
'🧜♀️️️️️️️️',
|
|
||||||
'🧜♂️️️️️️️️️',
|
|
||||||
'🧚♀️️️️️️️️️️',
|
|
||||||
'🧚♂️️️️️️️️️️️',
|
|
||||||
'👼️️️️️️️️️️️',
|
|
||||||
'🤰',
|
|
||||||
'🤱',
|
|
||||||
'🙇♀️',
|
|
||||||
'🙇♂️',
|
|
||||||
'💁♀️️',
|
|
||||||
'💁♂️️️',
|
|
||||||
'🙅♀️️️️',
|
|
||||||
'🙅♂️',
|
|
||||||
'🙆♀️️',
|
|
||||||
'🙆♂️️️',
|
|
||||||
'🙋♀️️️️',
|
|
||||||
'🙋♂️',
|
|
||||||
'🤦♀️️',
|
|
||||||
'🤦♂️️️',
|
|
||||||
'🤷♀️️️️',
|
|
||||||
'🤷♂️️️️️',
|
|
||||||
'🙎♀️️️️️️',
|
|
||||||
'🙎♂️️️️️️️',
|
|
||||||
'🙍♀️️️️️️️️',
|
|
||||||
'🙍♂️️️️️️️️️',
|
|
||||||
'💇♀️️️️️️️️️️',
|
|
||||||
'💇♂️️️️️️️️️️️',
|
|
||||||
'💆♀️️️️️️️️️️️️',
|
|
||||||
'💆♂️️️️️️️️️️️️️',
|
|
||||||
'🧖♀️️️️️️️️️️️️️️',
|
|
||||||
'🧖♂️️️️️️️️️️️️️️️',
|
|
||||||
'💅️️️️️️️️️️️️️️️',
|
|
||||||
'🤳️️️️️️️️️️️️️️',
|
|
||||||
'💃️️️️️️️️️️️️️',
|
|
||||||
'🕺️️️️️️️️️️️️',
|
|
||||||
'👯♀️',
|
|
||||||
'👯♂️️',
|
|
||||||
'🕴️️',
|
|
||||||
'🚶♀️️',
|
|
||||||
'🚶♂️️️',
|
|
||||||
'🏃♀️️️️',
|
|
||||||
'🏃♂️',
|
|
||||||
'👫️',
|
|
||||||
'👭',
|
|
||||||
'👬',
|
|
||||||
'💑',
|
|
||||||
'👩❤️👩',
|
|
||||||
'👨❤️👨',
|
|
||||||
'💏',
|
|
||||||
'👩❤️💋👩',
|
|
||||||
'👨❤️💋👨',
|
|
||||||
'👪',
|
|
||||||
'👨👩👧',
|
|
||||||
'👨👩👧👦',
|
|
||||||
'👨👩👦👦',
|
|
||||||
'👨👩👧👧',
|
|
||||||
'👩👩👦',
|
|
||||||
'👩👩👧',
|
|
||||||
'👩👩👧👦',
|
|
||||||
'👩👩👦👦',
|
|
||||||
'👩👩👧👧',
|
|
||||||
'👨👨👦',
|
|
||||||
'👨👨👧',
|
|
||||||
'👨👨👧👦',
|
|
||||||
'👨👨👦👦',
|
|
||||||
'👨👨👧👧',
|
|
||||||
'👩👦',
|
|
||||||
'👩👧',
|
|
||||||
'👩👧👦',
|
|
||||||
'👩👦👦',
|
|
||||||
'👩👧👧',
|
|
||||||
'👨👦',
|
|
||||||
'👨👧',
|
|
||||||
'👨👧👦',
|
|
||||||
'👨👦👦',
|
|
||||||
'👨👧👧',
|
|
||||||
'🤲',
|
|
||||||
'👐',
|
|
||||||
'🙌',
|
|
||||||
'👏',
|
|
||||||
'🤝',
|
|
||||||
'👍',
|
|
||||||
'👎',
|
|
||||||
'👊',
|
|
||||||
'✊',
|
|
||||||
'🤛',
|
|
||||||
'🤜',
|
|
||||||
'🤞',
|
|
||||||
'✌️',
|
|
||||||
'🤟️',
|
|
||||||
'🤘',
|
|
||||||
'👌',
|
|
||||||
'👈',
|
|
||||||
'👉',
|
|
||||||
'👆',
|
|
||||||
'👇',
|
|
||||||
'☝️',
|
|
||||||
'✋️',
|
|
||||||
'🤚️',
|
|
||||||
'🤚️',
|
|
||||||
'🖐',
|
|
||||||
'🖖',
|
|
||||||
'👋',
|
|
||||||
'🤙',
|
|
||||||
'💪',
|
|
||||||
'🦵',
|
|
||||||
'🦶',
|
|
||||||
'🖕',
|
|
||||||
'✍️',
|
|
||||||
'🙏️',
|
|
||||||
'💍',
|
|
||||||
'💄',
|
|
||||||
'💋',
|
|
||||||
'👄',
|
|
||||||
'👅',
|
|
||||||
'👂',
|
|
||||||
'👃',
|
|
||||||
'👣',
|
|
||||||
'👁',
|
|
||||||
'👀',
|
|
||||||
'🧠',
|
|
||||||
'🦴',
|
|
||||||
'🦷',
|
|
||||||
'🗣',
|
|
||||||
'👤',
|
|
||||||
'👥',
|
|
||||||
'🧥',
|
|
||||||
'👚',
|
|
||||||
'👕',
|
|
||||||
'👖',
|
|
||||||
'👔',
|
|
||||||
'👗',
|
|
||||||
'👙',
|
|
||||||
'👘',
|
|
||||||
'👠',
|
|
||||||
'👡',
|
|
||||||
'👢',
|
|
||||||
'👞',
|
|
||||||
'👟',
|
|
||||||
'🥾',
|
|
||||||
'🥿',
|
|
||||||
'🧦',
|
|
||||||
'🧤',
|
|
||||||
'🧣',
|
|
||||||
'🎩',
|
|
||||||
'🧢',
|
|
||||||
'👒',
|
|
||||||
'🎓',
|
|
||||||
'⛑',
|
|
||||||
'👑',
|
|
||||||
'👝',
|
|
||||||
'👛',
|
|
||||||
'👜',
|
|
||||||
'💼',
|
|
||||||
'🎒',
|
|
||||||
'👓',
|
|
||||||
'🕶',
|
|
||||||
'🥽',
|
|
||||||
'🥼',
|
|
||||||
'🌂',
|
|
||||||
'🧵',
|
|
||||||
'🧶',
|
|
||||||
|
|
||||||
/// Animals
|
|
||||||
'🐶',
|
|
||||||
'🐱',
|
|
||||||
'🐭',
|
|
||||||
'🐰',
|
|
||||||
'🦊',
|
|
||||||
'🦝',
|
|
||||||
'🐻',
|
|
||||||
'🦘',
|
|
||||||
'🦡',
|
|
||||||
'🐨',
|
|
||||||
'🐯',
|
|
||||||
'🦁',
|
|
||||||
'🐼',
|
|
||||||
'🐼',
|
|
||||||
'🐮',
|
|
||||||
'🐷',
|
|
||||||
'🐽',
|
|
||||||
'🐸',
|
|
||||||
'🐵',
|
|
||||||
'🙈',
|
|
||||||
'🙉',
|
|
||||||
'🙊',
|
|
||||||
'🐒',
|
|
||||||
'🐔',
|
|
||||||
'🐧',
|
|
||||||
'🐦',
|
|
||||||
'🐤',
|
|
||||||
'🐣',
|
|
||||||
'🐥',
|
|
||||||
'🦆',
|
|
||||||
'🦢',
|
|
||||||
'🦅',
|
|
||||||
'🦉',
|
|
||||||
'🦚',
|
|
||||||
'🦜',
|
|
||||||
'🦇',
|
|
||||||
'🐺',
|
|
||||||
'🐗',
|
|
||||||
'🐴',
|
|
||||||
'🦄',
|
|
||||||
'🐝',
|
|
||||||
'🐛',
|
|
||||||
'🦋',
|
|
||||||
'🐌',
|
|
||||||
'🐚',
|
|
||||||
'🐞',
|
|
||||||
'🐜',
|
|
||||||
'🦗',
|
|
||||||
'🕷',
|
|
||||||
'🕸',
|
|
||||||
'🦂',
|
|
||||||
'🦟',
|
|
||||||
'🦠',
|
|
||||||
'🐢',
|
|
||||||
'🐍',
|
|
||||||
'🦎',
|
|
||||||
'🦖',
|
|
||||||
'🦕',
|
|
||||||
'🐙',
|
|
||||||
'🦑',
|
|
||||||
'🦐',
|
|
||||||
'🦀',
|
|
||||||
'🐡',
|
|
||||||
'🐠',
|
|
||||||
'🐟',
|
|
||||||
'🐬',
|
|
||||||
'🐳',
|
|
||||||
'🐋',
|
|
||||||
'🦈',
|
|
||||||
'🐊',
|
|
||||||
'🐅',
|
|
||||||
'🐆',
|
|
||||||
'🦓',
|
|
||||||
'🦍',
|
|
||||||
'🐘',
|
|
||||||
'🦏',
|
|
||||||
'🦛',
|
|
||||||
'🐪',
|
|
||||||
'🐫',
|
|
||||||
'🦙',
|
|
||||||
'🦒',
|
|
||||||
'🐃',
|
|
||||||
'🐂',
|
|
||||||
'🐄',
|
|
||||||
'🐎',
|
|
||||||
'🐖',
|
|
||||||
'🐏',
|
|
||||||
'🐐',
|
|
||||||
'🦌',
|
|
||||||
'🐕',
|
|
||||||
'🐩',
|
|
||||||
'🐈',
|
|
||||||
'🐓',
|
|
||||||
'🦃',
|
|
||||||
'🕊',
|
|
||||||
'🐇',
|
|
||||||
'🐁',
|
|
||||||
'🐀',
|
|
||||||
'🐿',
|
|
||||||
'🦔',
|
|
||||||
'🐾',
|
|
||||||
'🐉',
|
|
||||||
'🐲',
|
|
||||||
'🌵',
|
|
||||||
'🎄',
|
|
||||||
'🌲',
|
|
||||||
'🌳',
|
|
||||||
'🌴',
|
|
||||||
'🌱',
|
|
||||||
'🌿',
|
|
||||||
'☘️',
|
|
||||||
'🎍️',
|
|
||||||
'🎋️',
|
|
||||||
'🍃',
|
|
||||||
'🍂',
|
|
||||||
'🍁',
|
|
||||||
'🍄',
|
|
||||||
'🌾️',
|
|
||||||
'💐️',
|
|
||||||
'🌷️',
|
|
||||||
'🌹',
|
|
||||||
'🥀',
|
|
||||||
'🌺',
|
|
||||||
'🌸',
|
|
||||||
'🌼',
|
|
||||||
'🌻️',
|
|
||||||
'🌞',
|
|
||||||
'🌝',
|
|
||||||
'🌛',
|
|
||||||
'🌜',
|
|
||||||
'🌚',
|
|
||||||
'🌕',
|
|
||||||
'🌖',
|
|
||||||
'🌗',
|
|
||||||
'🌘',
|
|
||||||
'🌑',
|
|
||||||
'🌒',
|
|
||||||
'🌔',
|
|
||||||
'🌙',
|
|
||||||
'🌎',
|
|
||||||
'🌍',
|
|
||||||
'🌏',
|
|
||||||
'💫',
|
|
||||||
'⭐️',
|
|
||||||
'🌟️',
|
|
||||||
'✨️',
|
|
||||||
'⚡️️',
|
|
||||||
'☄️️️',
|
|
||||||
'💥️️️',
|
|
||||||
'🔥',
|
|
||||||
'🌪',
|
|
||||||
'🌈',
|
|
||||||
'☀️',
|
|
||||||
'🌤️',
|
|
||||||
'⛅️️',
|
|
||||||
'🌥️️',
|
|
||||||
'☁️️',
|
|
||||||
'🌦️️',
|
|
||||||
'🌧️',
|
|
||||||
'⛈',
|
|
||||||
'🌩',
|
|
||||||
'🌨',
|
|
||||||
'❄️',
|
|
||||||
'☃️️',
|
|
||||||
'⛄️️️',
|
|
||||||
'🌬️️️',
|
|
||||||
'💨️️️',
|
|
||||||
'💧️️️',
|
|
||||||
'💦️️️',
|
|
||||||
'☔️️️️',
|
|
||||||
'☂️️️️️',
|
|
||||||
'🌊️️️️️',
|
|
||||||
'🌫️️️️',
|
|
||||||
|
|
||||||
/// Foods
|
|
||||||
'🍏',
|
|
||||||
'🍎',
|
|
||||||
'🍐',
|
|
||||||
'🍊',
|
|
||||||
'🍋',
|
|
||||||
'🍌',
|
|
||||||
'🍉',
|
|
||||||
'🍇',
|
|
||||||
'🍓',
|
|
||||||
'🍈',
|
|
||||||
'🍒',
|
|
||||||
'🍑',
|
|
||||||
'🍍',
|
|
||||||
'🥭',
|
|
||||||
'🥥',
|
|
||||||
'🥝',
|
|
||||||
'🍅',
|
|
||||||
'🍆',
|
|
||||||
'🥑',
|
|
||||||
'🥦',
|
|
||||||
'🥒',
|
|
||||||
'🥬',
|
|
||||||
'🌶',
|
|
||||||
'🌽',
|
|
||||||
'🥕',
|
|
||||||
'🥔',
|
|
||||||
'🍠',
|
|
||||||
'🥐',
|
|
||||||
'🍞',
|
|
||||||
'🥖',
|
|
||||||
'🥨',
|
|
||||||
'🥯',
|
|
||||||
'🧀',
|
|
||||||
'🥚',
|
|
||||||
'🍳',
|
|
||||||
'🥞',
|
|
||||||
'🥓',
|
|
||||||
'🥩',
|
|
||||||
'🍗',
|
|
||||||
'🍖',
|
|
||||||
'🌭',
|
|
||||||
'🍔',
|
|
||||||
'🍟',
|
|
||||||
'🍕',
|
|
||||||
'🥪',
|
|
||||||
'🥙',
|
|
||||||
'🌮',
|
|
||||||
'🌯',
|
|
||||||
'🥗',
|
|
||||||
'🥘',
|
|
||||||
'🥫',
|
|
||||||
'🍝',
|
|
||||||
'🍜',
|
|
||||||
'🍲',
|
|
||||||
'🍛',
|
|
||||||
'🍣',
|
|
||||||
'🍱',
|
|
||||||
'🥟',
|
|
||||||
'🍤',
|
|
||||||
'🍙',
|
|
||||||
'🍚',
|
|
||||||
'🍘',
|
|
||||||
'🍥',
|
|
||||||
'🥮',
|
|
||||||
'🥠',
|
|
||||||
'🍢',
|
|
||||||
'🍧',
|
|
||||||
'🍨',
|
|
||||||
'🍦',
|
|
||||||
'🥧',
|
|
||||||
'🍰',
|
|
||||||
'🎂',
|
|
||||||
'🍮',
|
|
||||||
'🍭',
|
|
||||||
'🍬',
|
|
||||||
'🍫',
|
|
||||||
'🍿',
|
|
||||||
'🧂',
|
|
||||||
'🍩',
|
|
||||||
'🍪',
|
|
||||||
'🌰',
|
|
||||||
'🥜',
|
|
||||||
'🍯',
|
|
||||||
'🥛',
|
|
||||||
'🍼',
|
|
||||||
'☕️',
|
|
||||||
'🍵️',
|
|
||||||
'🥤️',
|
|
||||||
'🍶',
|
|
||||||
'🍺',
|
|
||||||
'🍻',
|
|
||||||
'🥂',
|
|
||||||
'🍷',
|
|
||||||
'🍸',
|
|
||||||
'🍹',
|
|
||||||
'🍾',
|
|
||||||
'🥄',
|
|
||||||
'🍴',
|
|
||||||
'🍽',
|
|
||||||
'🥣',
|
|
||||||
'🥡',
|
|
||||||
'🥢',
|
|
||||||
|
|
||||||
/// Activity and Sports
|
|
||||||
'⚽️',
|
|
||||||
'🏀️',
|
|
||||||
'🏈',
|
|
||||||
'⚾️',
|
|
||||||
'🥎️',
|
|
||||||
'🏐️',
|
|
||||||
'🏉',
|
|
||||||
'🎾',
|
|
||||||
'🥏',
|
|
||||||
'🎱',
|
|
||||||
'🏓',
|
|
||||||
'🏸',
|
|
||||||
'🥅',
|
|
||||||
'🏒',
|
|
||||||
'🏑',
|
|
||||||
'🥍',
|
|
||||||
'🏏',
|
|
||||||
'⛳️',
|
|
||||||
'🏹️',
|
|
||||||
'🎣️',
|
|
||||||
'🥊',
|
|
||||||
'🥋',
|
|
||||||
'🎽',
|
|
||||||
'⛸',
|
|
||||||
'🥌',
|
|
||||||
'🛷',
|
|
||||||
'🛹',
|
|
||||||
'🎿',
|
|
||||||
'⛷',
|
|
||||||
'🏂',
|
|
||||||
'🏋️♀️',
|
|
||||||
'🏋🏼♀️',
|
|
||||||
'🏋🏽♀️️',
|
|
||||||
'🏋🏾♀️️️',
|
|
||||||
'🏋🏿♀️️️️',
|
|
||||||
'🏋️♂️️️️',
|
|
||||||
'🏋🏻♂️️️️',
|
|
||||||
'🏋🏼♂️️️️',
|
|
||||||
'🏋🏽♂️️️️',
|
|
||||||
'🏋🏾♂️️️️',
|
|
||||||
'🏋🏿♂️️️️',
|
|
||||||
'🤼♀️️️️',
|
|
||||||
'🤼♂️️️️',
|
|
||||||
'🤸♀️️️️',
|
|
||||||
'🤸🏻♀️️️️',
|
|
||||||
'🤸🏼♀️️️️',
|
|
||||||
'🤸🏽♀️️️️',
|
|
||||||
'🤸🏿♀️️️️️',
|
|
||||||
'🤸♂️️️️',
|
|
||||||
'🤸🏻♂️️️️',
|
|
||||||
'🤸🏼♂️️️️️',
|
|
||||||
'🤸🏽♂️️️️️️',
|
|
||||||
'🤸🏾♂️️️️️️',
|
|
||||||
'🤸🏿♂️️️️️️',
|
|
||||||
'⛹️♀️️️️️️',
|
|
||||||
'⛹🏻♀️️️️️️️',
|
|
||||||
'⛹🏼♀️️️️️️️️',
|
|
||||||
'⛹🏽♀️️️️️️️️️',
|
|
||||||
'⛹🏾♀️️️️️️️️️️',
|
|
||||||
'⛹🏿♀️️️️️️️️️️️',
|
|
||||||
'⛹️♂️️️️️️️️️️️️',
|
|
||||||
'⛹🏻♂️️️️️️️️️️️️️',
|
|
||||||
'⛹🏼♂️️️️️️️️️️️️️️',
|
|
||||||
'⛹🏽♂️️️️️️️️️️️️️️️',
|
|
||||||
'⛹🏾♂️️️️️️️️️️️️️️️️',
|
|
||||||
'⛹🏿♂️',
|
|
||||||
'🤺️',
|
|
||||||
'🤾♀️',
|
|
||||||
'🤾🏻♀️️',
|
|
||||||
'🤾🏼♀️️️',
|
|
||||||
'🤾🏾♀️️️️',
|
|
||||||
];
|
|
||||||
|
|
@ -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<bool> loader = Completer<bool>();
|
|
||||||
|
|
||||||
Future<void> load(dynamic image) async {
|
|
||||||
loader = Completer<bool>();
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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<BackgroundLayer> createState() => _BackgroundLayerState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _BackgroundLayerState extends State<BackgroundLayer> {
|
|
||||||
@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;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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<EmojiLayer> createState() => _EmojiLayerState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _EmojiLayerState extends State<EmojiLayer> {
|
|
||||||
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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,18 +2,20 @@
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.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/api/mediafiles/upload.service.dart';
|
||||||
import 'package:twonly/src/services/flame.service.dart';
|
import 'package:twonly/src/services/flame.service.dart';
|
||||||
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||||
import 'package:twonly/src/utils/misc.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/avatar_icon.component.dart';
|
||||||
import 'package:twonly/src/views/components/flame.dart';
|
import 'package:twonly/src/views/components/flame.dart';
|
||||||
import 'package:twonly/src/views/components/headline.dart';
|
import 'package:twonly/src/views/components/headline.dart';
|
||||||
|
|
@ -24,12 +26,14 @@ class ShareImageView extends StatefulWidget {
|
||||||
required this.updateSelectedGroupIds,
|
required this.updateSelectedGroupIds,
|
||||||
required this.mediaStoreFuture,
|
required this.mediaStoreFuture,
|
||||||
required this.mediaFileService,
|
required this.mediaFileService,
|
||||||
|
required this.additionalData,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
final HashSet<String> selectedGroupIds;
|
final HashSet<String> selectedGroupIds;
|
||||||
final void Function(String, bool) updateSelectedGroupIds;
|
final void Function(String, bool) updateSelectedGroupIds;
|
||||||
final Future<Uint8List?>? mediaStoreFuture;
|
final Future<ScreenshotImage?>? mediaStoreFuture;
|
||||||
final MediaFileService mediaFileService;
|
final MediaFileService mediaFileService;
|
||||||
|
final AdditionalMessageData? additionalData;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ShareImageView> createState() => _ShareImageView();
|
State<ShareImageView> createState() => _ShareImageView();
|
||||||
|
|
@ -43,7 +47,7 @@ class _ShareImageView extends State<ShareImageView> {
|
||||||
|
|
||||||
bool sendingImage = false;
|
bool sendingImage = false;
|
||||||
bool mediaStoreFutureReady = false;
|
bool mediaStoreFutureReady = false;
|
||||||
Uint8List? _imageBytes;
|
ScreenshotImage? _screenshotImage;
|
||||||
bool hideArchivedUsers = true;
|
bool hideArchivedUsers = true;
|
||||||
final TextEditingController searchUserName = TextEditingController();
|
final TextEditingController searchUserName = TextEditingController();
|
||||||
late StreamSubscription<List<Group>> allGroupSub;
|
late StreamSubscription<List<Group>> allGroupSub;
|
||||||
|
|
@ -66,7 +70,7 @@ class _ShareImageView extends State<ShareImageView> {
|
||||||
|
|
||||||
Future<void> initAsync() async {
|
Future<void> initAsync() async {
|
||||||
if (widget.mediaStoreFuture != null) {
|
if (widget.mediaStoreFuture != null) {
|
||||||
_imageBytes = await widget.mediaStoreFuture;
|
_screenshotImage = await widget.mediaStoreFuture;
|
||||||
}
|
}
|
||||||
mediaStoreFutureReady = true;
|
mediaStoreFutureReady = true;
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
@ -244,10 +248,11 @@ class _ShareImageView extends State<ShareImageView> {
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
if (widget.mediaFileService.mediaFile.type == MediaType.image &&
|
if (widget.mediaFileService.mediaFile.type == MediaType.image &&
|
||||||
_imageBytes != null &&
|
_screenshotImage?.image != null &&
|
||||||
gUser.showShowImagePreviewWhenSending)
|
gUser.showShowImagePreviewWhenSending)
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 100,
|
height: 100,
|
||||||
|
width: 100 * 9 / 16,
|
||||||
child: Container(
|
child: Container(
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
|
@ -258,7 +263,9 @@ class _ShareImageView extends State<ShareImageView> {
|
||||||
),
|
),
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
child: Image.memory(_imageBytes!),
|
child: CustomPaint(
|
||||||
|
painter: UiImagePainter(_screenshotImage!.image!),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -283,19 +290,15 @@ class _ShareImageView extends State<ShareImageView> {
|
||||||
sendingImage = true;
|
sendingImage = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// in case mediaStoreFutureReady is ready, the image is stored in the originalPath
|
||||||
await insertMediaFileInMessagesTable(
|
await insertMediaFileInMessagesTable(
|
||||||
widget.mediaFileService,
|
widget.mediaFileService,
|
||||||
widget.selectedGroupIds.toList(),
|
widget.selectedGroupIds.toList(),
|
||||||
|
additionalData: widget.additionalData,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
Navigator.pop(context, true);
|
Navigator.pop(context, true);
|
||||||
// if (widget.preselectedUser != null) {
|
|
||||||
// Navigator.pop(context, true);
|
|
||||||
// } else {
|
|
||||||
// Navigator.popUntil(context, (route) => route.isFirst, true);
|
|
||||||
// globalUpdateOfHomeViewPageIndex(1);
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
|
|
@ -2,14 +2,15 @@
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
|
|
||||||
import 'package:drift/drift.dart' show Value;
|
import 'package:drift/drift.dart' show Value;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.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/api/mediafiles/upload.service.dart';
|
||||||
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||||
import 'package:twonly/src/utils/log.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/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/main_camera_controller.dart';
|
||||||
import 'package:twonly/src/views/camera/camera_preview_components/save_to_gallery.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/share_image_contact_selection.view.dart';
|
||||||
import 'package:twonly/src/views/camera/image_editor/data/image_item.dart';
|
import 'package:twonly/src/views/camera/share_image_contact_selection/select_show_time.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/image_editor/layers_viewer.dart';
|
import 'package:twonly/src/views/camera/share_image_editor/image_item.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/camera/share_image_components/select_show_time.dart';
|
import 'package:twonly/src/views/camera/share_image_editor/layers_viewer.dart';
|
||||||
import 'package:twonly/src/views/camera/share_image_view.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/media_view_sizing.dart';
|
||||||
import 'package:twonly/src/views/components/notification_badge.dart';
|
import 'package:twonly/src/views/components/notification_badge.dart';
|
||||||
import 'package:video_player/video_player.dart';
|
import 'package:video_player/video_player.dart';
|
||||||
|
|
@ -37,16 +38,18 @@ class ShareImageEditorView extends StatefulWidget {
|
||||||
const ShareImageEditorView({
|
const ShareImageEditorView({
|
||||||
required this.sharedFromGallery,
|
required this.sharedFromGallery,
|
||||||
required this.mediaFileService,
|
required this.mediaFileService,
|
||||||
|
this.screenshotImage,
|
||||||
|
this.previewLink,
|
||||||
super.key,
|
super.key,
|
||||||
this.imageBytesFuture,
|
|
||||||
this.sendToGroup,
|
this.sendToGroup,
|
||||||
this.mainCameraController,
|
this.mainCameraController,
|
||||||
});
|
});
|
||||||
final ScreenshotImage? imageBytesFuture;
|
final ScreenshotImage? screenshotImage;
|
||||||
final Group? sendToGroup;
|
final Group? sendToGroup;
|
||||||
final bool sharedFromGallery;
|
final bool sharedFromGallery;
|
||||||
final MediaFileService mediaFileService;
|
final MediaFileService mediaFileService;
|
||||||
final MainCameraController? mainCameraController;
|
final MainCameraController? mainCameraController;
|
||||||
|
final Uri? previewLink;
|
||||||
@override
|
@override
|
||||||
State<ShareImageEditorView> createState() => _ShareImageEditorView();
|
State<ShareImageEditorView> createState() => _ShareImageEditorView();
|
||||||
}
|
}
|
||||||
|
|
@ -60,7 +63,6 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
double widthRatio = 1;
|
double widthRatio = 1;
|
||||||
double heightRatio = 1;
|
double heightRatio = 1;
|
||||||
double pixelRatio = 1;
|
double pixelRatio = 1;
|
||||||
Uint8List? imageBytes;
|
|
||||||
VideoPlayerController? videoController;
|
VideoPlayerController? videoController;
|
||||||
ImageItem currentImage = ImageItem();
|
ImageItem currentImage = ImageItem();
|
||||||
ScreenshotController screenshotController = ScreenshotController();
|
ScreenshotController screenshotController = ScreenshotController();
|
||||||
|
|
@ -77,14 +79,20 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
layers.add(FilterLayerData(key: GlobalKey()));
|
layers.add(FilterLayerData(key: GlobalKey()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (widget.previewLink != null) {
|
||||||
|
layers.add(
|
||||||
|
LinkPreviewLayerData(key: GlobalKey(), link: widget.previewLink!),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (widget.sendToGroup != null) {
|
if (widget.sendToGroup != null) {
|
||||||
selectedGroupIds.add(widget.sendToGroup!.groupId);
|
selectedGroupIds.add(widget.sendToGroup!.groupId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (widget.mediaFileService.mediaFile.type == MediaType.image ||
|
if (widget.mediaFileService.mediaFile.type == MediaType.image ||
|
||||||
widget.mediaFileService.mediaFile.type == MediaType.gif) {
|
widget.mediaFileService.mediaFile.type == MediaType.gif) {
|
||||||
if (widget.imageBytesFuture != null) {
|
if (widget.screenshotImage != null) {
|
||||||
loadImage(widget.imageBytesFuture!);
|
loadImage(widget.screenshotImage!);
|
||||||
} else {
|
} else {
|
||||||
if (widget.mediaFileService.tempPath.existsSync()) {
|
if (widget.mediaFileService.tempPath.existsSync()) {
|
||||||
loadImage(ScreenshotImage(file: widget.mediaFileService.tempPath));
|
loadImage(ScreenshotImage(file: widget.mediaFileService.tempPath));
|
||||||
|
|
@ -411,6 +419,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
updateSelectedGroupIds: updateSelectedGroupIds,
|
updateSelectedGroupIds: updateSelectedGroupIds,
|
||||||
mediaStoreFuture: mediaStoreFuture,
|
mediaStoreFuture: mediaStoreFuture,
|
||||||
mediaFileService: mediaService,
|
mediaFileService: mediaService,
|
||||||
|
additionalData: getAdditionalData(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
) as bool?;
|
) as bool?;
|
||||||
|
|
@ -424,8 +433,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
Future<ScreenshotImage?> getEditedImageBytes() async {
|
Future<ScreenshotImage?> getEditedImageBytes() async {
|
||||||
if (layers.length == 1) {
|
if (layers.length == 1) {
|
||||||
if (layers.first is BackgroundLayerData) {
|
if (layers.first is BackgroundLayerData) {
|
||||||
final image = (layers.first as BackgroundLayerData).image.bytes;
|
return (layers.first as BackgroundLayerData).image.image;
|
||||||
return ScreenshotImage(imageBytes: image);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -454,7 +462,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Uint8List?> storeImageAsOriginal() async {
|
Future<ScreenshotImage?> storeImageAsOriginal() async {
|
||||||
if (mediaService.overlayImagePath.existsSync()) {
|
if (mediaService.overlayImagePath.existsSync()) {
|
||||||
mediaService.overlayImagePath.deleteSync();
|
mediaService.overlayImagePath.deleteSync();
|
||||||
}
|
}
|
||||||
|
|
@ -466,11 +474,16 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
mediaService.originalPath.deleteSync();
|
mediaService.originalPath.deleteSync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var bytes = imageBytes;
|
ScreenshotImage? image;
|
||||||
|
var bytes = await widget.screenshotImage?.getBytes();
|
||||||
if (media.type == MediaType.gif) {
|
if (media.type == MediaType.gif) {
|
||||||
mediaService.originalPath.writeAsBytesSync(imageBytes!.toList());
|
if (bytes != null) {
|
||||||
|
mediaService.originalPath.writeAsBytesSync(bytes.toList());
|
||||||
} else {
|
} else {
|
||||||
final image = await getEditedImageBytes();
|
Log.error('Could not load image bytes for gif!');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
image = await getEditedImageBytes();
|
||||||
if (image == null) return null;
|
if (image == null) return null;
|
||||||
bytes = await image.getBytes();
|
bytes = await image.getBytes();
|
||||||
if (bytes == null) {
|
if (bytes == null) {
|
||||||
|
|
@ -485,16 +498,38 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
Log.error('MediaType not supported: ${media.type}');
|
Log.error('MediaType not supported: ${media.type}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return image;
|
||||||
return bytes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadImage(ScreenshotImage imageBytesFuture) async {
|
Future<void> storeIoImageAsDraft(ScreenshotImage screenshotImage) async {
|
||||||
imageBytes = await imageBytesFuture.getBytes();
|
final imageBytes = await screenshotImage.getBytes();
|
||||||
// store this image so it can be used as a draft in case the app is restarted
|
|
||||||
mediaService.originalPath.writeAsBytesSync(imageBytes!.toList());
|
mediaService.originalPath.writeAsBytesSync(imageBytes!.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> 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 (isDisposed) return;
|
||||||
|
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
|
|
@ -536,6 +571,18 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AdditionalMessageData? getAdditionalData() {
|
||||||
|
AdditionalMessageData? additionalData;
|
||||||
|
|
||||||
|
if (widget.previewLink != null) {
|
||||||
|
additionalData = AdditionalMessageData(
|
||||||
|
type: AdditionalMessageData_Type.LINK,
|
||||||
|
link: widget.previewLink.toString(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return additionalData;
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> sendImageToSinglePerson() async {
|
Future<void> sendImageToSinglePerson() async {
|
||||||
if (sendingOrLoadingImage) return;
|
if (sendingOrLoadingImage) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
@ -551,6 +598,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
await insertMediaFileInMessagesTable(
|
await insertMediaFileInMessagesTable(
|
||||||
mediaService,
|
mediaService,
|
||||||
[widget.sendToGroup!.groupId],
|
[widget.sendToGroup!.groupId],
|
||||||
|
additionalData: getAdditionalData(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
18
lib/src/views/camera/share_image_editor/image_item.dart
Executable file
18
lib/src/views/camera/share_image_editor/image_item.dart
Executable file
|
|
@ -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<bool> loader = Completer<bool>();
|
||||||
|
|
||||||
|
void load(ScreenshotImage img) {
|
||||||
|
image = img;
|
||||||
|
if (image?.image != null) {
|
||||||
|
height = image!.image!.height;
|
||||||
|
width = image!.image!.width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
// ignore_for_file: comment_references
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hand_signature/signature.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
|
/// Layer class with some common properties
|
||||||
class Layer {
|
class Layer {
|
||||||
|
|
@ -28,7 +27,6 @@ class Layer {
|
||||||
bool showCustomButtons;
|
bool showCustomButtons;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attributes used by [BackgroundLayer]
|
|
||||||
class BackgroundLayerData extends Layer {
|
class BackgroundLayerData extends Layer {
|
||||||
BackgroundLayerData({
|
BackgroundLayerData({
|
||||||
required super.key,
|
required super.key,
|
||||||
|
|
@ -38,6 +36,16 @@ class BackgroundLayerData extends Layer {
|
||||||
bool imageLoaded = false;
|
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 {
|
class FilterLayerData extends Layer {
|
||||||
FilterLayerData({
|
FilterLayerData({
|
||||||
required super.key,
|
required super.key,
|
||||||
|
|
@ -46,12 +54,11 @@ class FilterLayerData extends Layer {
|
||||||
int page = 1;
|
int page = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attributes used by [EmojiLayer]
|
|
||||||
class EmojiLayerData extends Layer {
|
class EmojiLayerData extends Layer {
|
||||||
EmojiLayerData({
|
EmojiLayerData({
|
||||||
required super.key,
|
required super.key,
|
||||||
this.text = '',
|
this.text = '',
|
||||||
this.size = 64,
|
this.size = 94,
|
||||||
super.offset,
|
super.offset,
|
||||||
super.opacity,
|
super.opacity,
|
||||||
super.rotation,
|
super.rotation,
|
||||||
|
|
@ -62,7 +69,6 @@ class EmojiLayerData extends Layer {
|
||||||
double size;
|
double size;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attributes used by [TextLayer]
|
|
||||||
class TextLayerData extends Layer {
|
class TextLayerData extends Layer {
|
||||||
TextLayerData({
|
TextLayerData({
|
||||||
required super.key,
|
required super.key,
|
||||||
|
|
@ -78,9 +84,7 @@ class TextLayerData extends Layer {
|
||||||
int textLayersBefore;
|
int textLayersBefore;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attributes used by [DrawLayer]
|
|
||||||
class DrawLayerData extends Layer {
|
class DrawLayerData extends Layer {
|
||||||
// String text;
|
|
||||||
DrawLayerData({
|
DrawLayerData({
|
||||||
required super.key,
|
required super.key,
|
||||||
super.offset,
|
super.offset,
|
||||||
62
lib/src/views/camera/share_image_editor/layers/background.layer.dart
Executable file
62
lib/src/views/camera/share_image_editor/layers/background.layer.dart
Executable file
|
|
@ -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<BackgroundLayer> createState() => _BackgroundLayerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BackgroundLayerState extends State<BackgroundLayer> {
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:hand_signature/signature.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/misc.dart';
|
||||||
import 'package:twonly/src/utils/screenshot.dart';
|
import 'package:twonly/src/views/camera/share_image_editor/action_button.dart';
|
||||||
import 'package:twonly/src/views/camera/image_editor/action_button.dart';
|
import 'package:twonly/src/views/camera/share_image_editor/layer_data.dart';
|
||||||
import 'package:twonly/src/views/camera/image_editor/data/layer.dart';
|
import 'package:twonly/src/views/camera/share_image_editor/layers/draw/custom_hand_signature.dart';
|
||||||
|
|
||||||
class DrawLayer extends StatefulWidget {
|
class DrawLayer extends StatefulWidget {
|
||||||
const DrawLayer({
|
const DrawLayer({
|
||||||
|
|
@ -23,8 +22,6 @@ class DrawLayer extends StatefulWidget {
|
||||||
class _DrawLayerState extends State<DrawLayer> {
|
class _DrawLayerState extends State<DrawLayer> {
|
||||||
Color currentColor = Colors.red;
|
Color currentColor = Colors.red;
|
||||||
|
|
||||||
ScreenshotController screenshotController = ScreenshotController();
|
|
||||||
|
|
||||||
List<CubicPath> undoList = [];
|
List<CubicPath> undoList = [];
|
||||||
bool skipNextEvent = false;
|
bool skipNextEvent = false;
|
||||||
bool showMagnifyingGlass = false;
|
bool showMagnifyingGlass = false;
|
||||||
|
|
@ -85,17 +82,11 @@ class _DrawLayerState extends State<DrawLayer> {
|
||||||
fit: StackFit.expand,
|
fit: StackFit.expand,
|
||||||
children: [
|
children: [
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: Container(
|
child: CustomHandSignature(
|
||||||
decoration: const BoxDecoration(
|
|
||||||
color: Colors.transparent,
|
|
||||||
),
|
|
||||||
child: Screenshot(
|
|
||||||
controller: screenshotController,
|
|
||||||
child: HandSignature(
|
|
||||||
control: widget.layerData.control,
|
control: widget.layerData.control,
|
||||||
drawer: CustomSignatureDrawer(color: currentColor, width: 7),
|
isModificationEnabled: widget.layerData.isEditing,
|
||||||
),
|
currentColor: currentColor,
|
||||||
),
|
width: 7,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (widget.layerData.isEditing && widget.layerData.showCustomButtons)
|
if (widget.layerData.isEditing && widget.layerData.showCustomButtons)
|
||||||
|
|
@ -211,12 +202,12 @@ class _DrawLayerState extends State<DrawLayer> {
|
||||||
top: 50 + (185 * _sliderValue),
|
top: 50 + (185 * _sliderValue),
|
||||||
child: MagnifyingGlass(color: currentColor),
|
child: MagnifyingGlass(color: currentColor),
|
||||||
),
|
),
|
||||||
if (!widget.layerData.isEditing)
|
// if (!widget.layerData.isEditing)
|
||||||
Positioned.fill(
|
// Positioned.fill(
|
||||||
child: Container(
|
// child: Container(
|
||||||
color: Colors.transparent,
|
// 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<CubicPath> 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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<CubicPath> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
248
lib/src/views/camera/share_image_editor/layers/emoji.layer.dart
Executable file
248
lib/src/views/camera/share_image_editor/layers/emoji.layer.dart
Executable file
|
|
@ -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<EmojiLayer> createState() => _EmojiLayerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _EmojiLayerState extends State<EmojiLayer> {
|
||||||
|
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<ScreenshotEmoji> createState() => _ScreenshotEmojiState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ScreenshotEmojiState extends State<ScreenshotEmoji> {
|
||||||
|
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<void> _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),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,10 +2,10 @@ import 'dart:async';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/src/views/camera/image_editor/data/layer.dart';
|
import 'package:twonly/src/views/camera/share_image_editor/layer_data.dart';
|
||||||
import 'package:twonly/src/views/camera/image_editor/layers/filters/datetime_filter.dart';
|
import 'package:twonly/src/views/camera/share_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/share_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/layers/filters/location_filter.dart';
|
||||||
|
|
||||||
/// Main layer
|
/// Main layer
|
||||||
class FilterLayer extends StatefulWidget {
|
class FilterLayer extends StatefulWidget {
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:clock/clock.dart';
|
import 'package:clock/clock.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.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 {
|
class DateTimeFilter extends StatelessWidget {
|
||||||
const DateTimeFilter({super.key, this.color = Colors.white});
|
const DateTimeFilter({super.key, this.color = Colors.white});
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/material.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 {
|
class ImageFilter extends StatelessWidget {
|
||||||
const ImageFilter({required this.imagePath, super.key});
|
const ImageFilter({required this.imagePath, super.key});
|
||||||
|
|
@ -11,8 +11,8 @@ import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.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/utils/log.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';
|
||||||
import 'package:twonly/src/views/camera/image_editor/layers/filters/datetime_filter.dart';
|
import 'package:twonly/src/views/camera/share_image_editor/layers/filters/datetime_filter.dart';
|
||||||
|
|
||||||
class LocationFilter extends StatefulWidget {
|
class LocationFilter extends StatefulWidget {
|
||||||
const LocationFilter({super.key});
|
const LocationFilter({super.key});
|
||||||
|
|
@ -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<LinkPreviewLayer> createState() => _LinkPreviewLayerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LinkPreviewLayerState extends State<LinkPreviewLayer> {
|
||||||
|
Metadata? metadata;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
initAsync();
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> 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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<Metadata?> 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<Metadata?> 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;
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
import 'package:html/dom.dart';
|
||||||
|
|
||||||
|
import 'base.dart';
|
||||||
|
import 'util.dart';
|
||||||
|
|
||||||
|
/// Parses [Metadata] from `<meta>`, `<title>`, 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');
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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');
|
||||||
|
}
|
||||||
|
|
@ -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');
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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_;
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
@ -5,8 +5,8 @@ import 'package:flutter/services.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:twonly/src/providers/image_editor.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/share_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/layer_data.dart';
|
||||||
|
|
||||||
/// Text layer
|
/// Text layer
|
||||||
class TextLayer extends StatefulWidget {
|
class TextLayer extends StatefulWidget {
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/src/views/camera/image_editor/data/layer.dart';
|
import 'package:twonly/src/views/camera/share_image_editor/layer_data.dart';
|
||||||
import 'package:twonly/src/views/camera/image_editor/layers/background_layer.dart';
|
import 'package:twonly/src/views/camera/share_image_editor/layers/background.layer.dart';
|
||||||
import 'package:twonly/src/views/camera/image_editor/layers/draw_layer.dart';
|
import 'package:twonly/src/views/camera/share_image_editor/layers/draw.layer.dart';
|
||||||
import 'package:twonly/src/views/camera/image_editor/layers/emoji_layer.dart';
|
import 'package:twonly/src/views/camera/share_image_editor/layers/emoji.layer.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';
|
||||||
import 'package:twonly/src/views/camera/image_editor/layers/text_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)
|
/// View stacked layers (unbounded height, width)
|
||||||
class LayersViewer extends StatelessWidget {
|
class LayersViewer extends StatelessWidget {
|
||||||
|
|
@ -37,7 +38,10 @@ class LayersViewer extends StatelessWidget {
|
||||||
...layers
|
...layers
|
||||||
.where(
|
.where(
|
||||||
(layerItem) =>
|
(layerItem) =>
|
||||||
layerItem is EmojiLayerData || layerItem is DrawLayerData,
|
layerItem is EmojiLayerData ||
|
||||||
|
layerItem is DrawLayerData ||
|
||||||
|
layerItem is LinkPreviewLayerData ||
|
||||||
|
layerItem is TextLayerData,
|
||||||
)
|
)
|
||||||
.map((layerItem) {
|
.map((layerItem) {
|
||||||
if (layerItem is EmojiLayerData) {
|
if (layerItem is EmojiLayerData) {
|
||||||
|
|
@ -52,15 +56,20 @@ class LayersViewer extends StatelessWidget {
|
||||||
layerData: layerItem,
|
layerData: layerItem,
|
||||||
onUpdate: onUpdate,
|
onUpdate: onUpdate,
|
||||||
);
|
);
|
||||||
}
|
} else if (layerItem is TextLayerData) {
|
||||||
return Container();
|
|
||||||
}),
|
|
||||||
...layers.whereType<TextLayerData>().map((layerItem) {
|
|
||||||
return TextLayer(
|
return TextLayer(
|
||||||
key: layerItem.key,
|
key: layerItem.key,
|
||||||
layerData: layerItem,
|
layerData: layerItem,
|
||||||
onUpdate: onUpdate,
|
onUpdate: onUpdate,
|
||||||
);
|
);
|
||||||
|
} else if (layerItem is LinkPreviewLayerData) {
|
||||||
|
return LinkPreviewLayer(
|
||||||
|
key: layerItem.key,
|
||||||
|
layerData: layerItem,
|
||||||
|
onUpdate: onUpdate,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Container();
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
@ -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/profile/profile.view.dart';
|
||||||
import 'package:twonly/src/views/settings/settings_main.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/settings/subscription/subscription.view.dart';
|
||||||
|
import 'package:twonly/src/views/user_study/user_study_welcome.view.dart';
|
||||||
|
|
||||||
class ChatListView extends StatefulWidget {
|
class ChatListView extends StatefulWidget {
|
||||||
const ChatListView({super.key});
|
const ChatListView({super.key});
|
||||||
|
|
@ -58,6 +59,23 @@ class _ChatListViewState extends State<ChatListView> {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 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 const UserStudyWelcomeView(
|
||||||
|
wasOpenedAutomatic: true,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
final changeLog = await rootBundle.loadString('CHANGELOG.md');
|
final changeLog = await rootBundle.loadString('CHANGELOG.md');
|
||||||
final changeLogHash =
|
final changeLogHash =
|
||||||
(await compute(Sha256().hash, changeLog.codeUnits)).bytes;
|
(await compute(Sha256().hash, changeLog.codeUnits)).bytes;
|
||||||
|
|
@ -83,6 +101,7 @@ class _ChatListViewState extends State<ChatListView> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -295,9 +314,13 @@ class _ChatListViewState extends State<ChatListView> {
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
FloatingActionButton.small(
|
Material(
|
||||||
backgroundColor: context.color.primary,
|
elevation: 3,
|
||||||
onPressed: () {
|
shape: const CircleBorder(),
|
||||||
|
color: context.color.primary,
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
onTap: () {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
|
|
@ -307,11 +330,18 @@ class _ChatListViewState extends State<ChatListView> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
child: SizedBox(
|
||||||
|
width: 45,
|
||||||
|
height: 45,
|
||||||
|
child: Center(
|
||||||
child: FaIcon(
|
child: FaIcon(
|
||||||
FontAwesomeIcons.qrcode,
|
FontAwesomeIcons.qrcode,
|
||||||
color: isDarkMode(context) ? Colors.black : Colors.white,
|
color: isDarkMode(context) ? Colors.black : Colors.white,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
FloatingActionButton(
|
FloatingActionButton(
|
||||||
backgroundColor: context.color.primary,
|
backgroundColor: context.color.primary,
|
||||||
|
|
|
||||||
|
|
@ -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/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/services/api/mediafiles/download.service.dart';
|
import 'package:twonly/src/services/api/mediafiles/download.service.dart';
|
||||||
import 'package:twonly/src/utils/misc.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_list_components/last_message_time.dart';
|
||||||
import 'package:twonly/src/views/chats/chat_messages.view.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';
|
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();
|
_previewMessages.where((x) => x.type == MessageType.media).toList();
|
||||||
if (msgs.isNotEmpty &&
|
if (msgs.isNotEmpty &&
|
||||||
msgs.first.type == MessageType.media &&
|
msgs.first.type == MessageType.media &&
|
||||||
|
!msgs.first.isDeletedFromSender &&
|
||||||
msgs.first.senderId != null &&
|
msgs.first.senderId != null &&
|
||||||
msgs.first.openedAt == null) {
|
msgs.first.openedAt == null) {
|
||||||
_hasNonOpenedMediaFile = true;
|
_hasNonOpenedMediaFile = true;
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ class FriendlyMessageTime extends StatelessWidget {
|
||||||
padding: const EdgeInsets.only(left: 6),
|
padding: const EdgeInsets.only(left: 6),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
if (message.modifiedAt != null)
|
if (message.modifiedAt != null && !message.isDeletedFromSender)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(right: 5),
|
padding: const EdgeInsets.only(right: 5),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
|
|
|
||||||
|
|
@ -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/api/messages.dart';
|
||||||
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||||
import 'package:twonly/src/utils/misc.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';
|
||||||
import 'package:twonly/src/views/camera/image_editor/modules/all_emojis.dart';
|
|
||||||
import 'package:twonly/src/views/chats/message_info.view.dart';
|
import 'package:twonly/src/views/chats/message_info.view.dart';
|
||||||
import 'package:twonly/src/views/components/alert_dialog.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/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';
|
import 'package:twonly/src/views/memories/memories_photo_slider.view.dart';
|
||||||
|
|
||||||
class MessageContextMenu extends StatelessWidget {
|
class MessageContextMenu extends StatelessWidget {
|
||||||
|
|
|
||||||
|
|
@ -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/mediafiles/upload.service.dart';
|
||||||
import 'package:twonly/src/services/api/messages.dart';
|
import 'package:twonly/src/services/api/messages.dart';
|
||||||
import 'package:twonly/src/utils/misc.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';
|
import 'package:twonly/src/views/chats/chat_messages_components/entries/chat_audio_entry.dart';
|
||||||
|
|
||||||
class MessageInput extends StatefulWidget {
|
class MessageInput extends StatefulWidget {
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,7 @@ class _MessageSendStateIconState extends State<MessageSendStateIcon> {
|
||||||
case MessageSendState.receivedOpened:
|
case MessageSendState.receivedOpened:
|
||||||
icon = Icon(Icons.crop_square, size: 14, color: color);
|
icon = Icon(Icons.crop_square, size: 14, color: color);
|
||||||
if (message.content != null) {
|
if (message.content != null) {
|
||||||
if (isEmoji(message.content!)) {
|
if (isOneEmoji(message.content!)) {
|
||||||
icon = Text(
|
icon = Text(
|
||||||
message.content!,
|
message.content!,
|
||||||
style: const TextStyle(fontSize: 12),
|
style: const TextStyle(fontSize: 12),
|
||||||
|
|
|
||||||
|
|
@ -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/services/notifications/background.notifications.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/misc.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/chats/media_viewer_components/reaction_buttons.component.dart';
|
||||||
import 'package:twonly/src/views/components/animate_icon.dart';
|
import 'package:twonly/src/views/components/animate_icon.dart';
|
||||||
import 'package:twonly/src/views/components/loader.dart';
|
import 'package:twonly/src/views/components/loader.dart';
|
||||||
|
|
@ -91,8 +92,9 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
||||||
_noScreenshot.screenshotOn();
|
_noScreenshot.screenshotOn();
|
||||||
_subscription.cancel();
|
_subscription.cancel();
|
||||||
downloadStateListener?.cancel();
|
downloadStateListener?.cancel();
|
||||||
videoController?.dispose();
|
final tmp = videoController;
|
||||||
videoController = null;
|
videoController = null;
|
||||||
|
tmp?.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -493,15 +495,8 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
Widget _loader() {
|
||||||
Widget build(BuildContext context) {
|
return Center(
|
||||||
return Scaffold(
|
|
||||||
body: SafeArea(
|
|
||||||
child: Stack(
|
|
||||||
fit: StackFit.expand,
|
|
||||||
children: [
|
|
||||||
if (_showDownloadingLoader)
|
|
||||||
Center(
|
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 60,
|
height: 60,
|
||||||
width: 60,
|
width: 60,
|
||||||
|
|
@ -510,7 +505,17 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
||||||
color: context.color.primary,
|
color: context.color.primary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: SafeArea(
|
||||||
|
child: Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: [
|
||||||
|
if (_showDownloadingLoader) _loader(),
|
||||||
if ((currentMedia != null || videoController != null) &&
|
if ((currentMedia != null || videoController != null) &&
|
||||||
(canBeSeenUntil == null || progress >= 0))
|
(canBeSeenUntil == null || progress >= 0))
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
|
|
@ -549,9 +554,7 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
||||||
if (displayTwonlyPresent)
|
if (displayTwonlyPresent)
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () {
|
onTap: () => loadCurrentMediaFile(showTwonly: true),
|
||||||
loadCurrentMediaFile(showTwonly: true);
|
|
||||||
},
|
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
|
|
@ -575,26 +578,14 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.close, size: 30),
|
icon: const Icon(Icons.close, size: 30),
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
onPressed: () async {
|
onPressed: () => Navigator.pop(context),
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (currentMedia != null &&
|
if (currentMedia != null &&
|
||||||
currentMedia?.mediaFile.downloadState != DownloadState.ready)
|
currentMedia?.mediaFile.downloadState != DownloadState.ready)
|
||||||
const Positioned.fill(
|
Positioned.fill(child: _loader()),
|
||||||
child: Center(
|
|
||||||
child: SizedBox(
|
|
||||||
height: 60,
|
|
||||||
width: 60,
|
|
||||||
child: CircularProgressIndicator(
|
|
||||||
strokeWidth: 6,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (canBeSeenUntil != null || progress >= 0)
|
if (canBeSeenUntil != null || progress >= 0)
|
||||||
Positioned(
|
Positioned(
|
||||||
right: 20,
|
right: 20,
|
||||||
|
|
@ -700,6 +691,8 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (currentMessage != null)
|
||||||
|
AdditionalMessageContent(currentMessage!),
|
||||||
if (currentMedia != null)
|
if (currentMedia != null)
|
||||||
ReactionButtons(
|
ReactionButtons(
|
||||||
show: showShortReactions,
|
show: showShortReactions,
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/utils/misc.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';
|
||||||
import 'package:twonly/src/views/camera/image_editor/modules/all_emojis.dart';
|
|
||||||
import 'package:twonly/src/views/chats/media_viewer_components/emoji_reactions_row.component.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/animate_icon.dart';
|
||||||
|
import 'package:twonly/src/views/components/emoji_picker.bottom.dart';
|
||||||
|
|
||||||
class ReactionButtons extends StatefulWidget {
|
class ReactionButtons extends StatefulWidget {
|
||||||
const ReactionButtons({
|
const ReactionButtons({
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -6,8 +6,8 @@ class BlinkWidget extends StatefulWidget {
|
||||||
required this.child,
|
required this.child,
|
||||||
required this.enabled,
|
required this.enabled,
|
||||||
super.key,
|
super.key,
|
||||||
this.blinkDuration = const Duration(milliseconds: 2500),
|
this.blinkDuration = const Duration(milliseconds: 2000),
|
||||||
this.interval = const Duration(milliseconds: 100),
|
this.interval = const Duration(milliseconds: 300),
|
||||||
this.visibleOpacity = 1.0,
|
this.visibleOpacity = 1.0,
|
||||||
this.hiddenOpacity = 0.05,
|
this.hiddenOpacity = 0.05,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import 'dart:io';
|
||||||
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart';
|
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/src/utils/misc.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 {
|
class EmojiPickerBottom extends StatelessWidget {
|
||||||
const EmojiPickerBottom({super.key});
|
const EmojiPickerBottom({super.key});
|
||||||
|
|
@ -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.dart';
|
||||||
import 'package:twonly/src/views/camera/camera_preview_components/camera_preview_controller_view.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/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/chats/chat_list.view.dart';
|
||||||
import 'package:twonly/src/views/memories/memories.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(
|
_intentStreamSub = FlutterSharingIntent.instance.getMediaStream().listen(
|
||||||
(f) {
|
(f) {
|
||||||
if (mounted) handleIntentSharedFile(context, f);
|
if (mounted) {
|
||||||
|
handleIntentSharedFile(
|
||||||
|
context,
|
||||||
|
f,
|
||||||
|
_mainCameraController.setSharedLinkForPreview,
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
// ignore: inference_failure_on_untyped_parameter
|
// ignore: inference_failure_on_untyped_parameter
|
||||||
onError: (err) {
|
onError: (err) {
|
||||||
|
|
@ -129,7 +135,13 @@ class HomeViewState extends State<HomeView> {
|
||||||
);
|
);
|
||||||
|
|
||||||
FlutterSharingIntent.instance.getInitialSharing().then((f) {
|
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) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: GestureDetector(
|
body: GestureDetector(
|
||||||
onDoubleTap: offsetRatio == 0
|
onDoubleTap:
|
||||||
? _mainCameraController.toggleSelectedCamera
|
offsetRatio == 0 ? _mainCameraController.onDoubleTap : null,
|
||||||
: null,
|
onTapDown: offsetRatio == 0 ? _mainCameraController.onTapDown : null,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
MainCameraPreview(mainCameraController: _mainCameraController),
|
MainCameraPreview(mainCameraController: _mainCameraController),
|
||||||
|
|
|
||||||
|
|
@ -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/log.dart';
|
||||||
import 'package:twonly/src/utils/misc.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/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/alert_dialog.dart';
|
||||||
import 'package:twonly/src/views/components/media_view_sizing.dart';
|
import 'package:twonly/src/views/components/media_view_sizing.dart';
|
||||||
import 'package:twonly/src/views/components/video_player_wrapper.dart';
|
import 'package:twonly/src/views/components/video_player_wrapper.dart';
|
||||||
|
|
@ -105,7 +105,11 @@ class _MemoriesPhotoSliderViewState extends State<MemoriesPhotoSliderView> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (orgMediaService.storedPath.existsSync()) {
|
||||||
orgMediaService.storedPath.copySync(newMediaService.originalPath.path);
|
orgMediaService.storedPath.copySync(newMediaService.originalPath.path);
|
||||||
|
} else if (orgMediaService.tempPath.existsSync()) {
|
||||||
|
orgMediaService.tempPath.copySync(newMediaService.originalPath.path);
|
||||||
|
}
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue