mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 15:48:41 +00:00
Compare commits
39 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f8bdaa32d | ||
|
|
d067a3c931 | ||
|
|
b3c25dd160 | ||
|
|
3899c8e6e4 | ||
|
|
87187843fa | ||
|
|
ebf53a5ab4 | ||
|
|
6a104e9468 | ||
|
|
ea68dcaf1c | ||
|
|
4b5a4387d1 | ||
|
|
85d6bdfcc9 | ||
|
|
41dfd54e81 | ||
|
|
20a2d61751 | ||
|
|
57c73a86ac | ||
|
|
230809290a | ||
|
|
0984eaf347 | ||
|
|
b093a7acdb | ||
|
|
6dc9aa10bc | ||
|
|
11aa4c4202 | ||
|
|
987a55dc65 | ||
|
|
910f5f79fa | ||
|
|
27483bccd6 | ||
|
|
abd689f1fa | ||
|
|
027871290d | ||
|
|
82f4c9af9f | ||
|
|
7007e7b063 | ||
|
|
333f033993 | ||
|
|
e17e39ef41 | ||
|
|
b9bb074ba6 | ||
|
|
2713f092eb | ||
|
|
c91c53dd8b | ||
|
|
049507cd25 | ||
|
|
fa953b8928 | ||
|
|
074ead8b4f | ||
|
|
9fe55ab62d | ||
|
|
2db1775d1f | ||
|
|
189ed26dd7 | ||
|
|
39d406d156 | ||
|
|
6b2cc85e81 | ||
|
|
0ed264f152 |
109 changed files with 18173 additions and 1296 deletions
20
CHANGELOG.md
20
CHANGELOG.md
|
|
@ -1,5 +1,25 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.0.82
|
||||||
|
|
||||||
|
- Added an option in the settings to automatically save all sent images
|
||||||
|
- Hides duplicate images in the memory
|
||||||
|
- Fixes a bug where messages were not being received
|
||||||
|
- Several other minor improvements
|
||||||
|
|
||||||
|
## 0.0.81
|
||||||
|
|
||||||
|
- Fixes the issue where black/blank images were sometimes received
|
||||||
|
- Fixes an issue in the image editor
|
||||||
|
|
||||||
|
## 0.0.80
|
||||||
|
|
||||||
|
- Share images/videos directly from other applications
|
||||||
|
- More customization options in the appearance settings
|
||||||
|
- Improved UI for changing the display time of images
|
||||||
|
- Several minor UI improvements
|
||||||
|
- Several bug fixes
|
||||||
|
|
||||||
## 0.0.74
|
## 0.0.74
|
||||||
|
|
||||||
- Improving uploading speed
|
- Improving uploading speed
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,16 @@
|
||||||
<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>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="image/*" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="video/*" />
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<!-- Don't delete the meta-data below.
|
<!-- Don't delete the meta-data below.
|
||||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
|
|
@ -46,19 +56,11 @@
|
||||||
android:name="com.google.android.datatransport.runtime.scheduling.jobscheduling.JobInfoSchedulerService"
|
android:name="com.google.android.datatransport.runtime.scheduling.jobscheduling.JobInfoSchedulerService"
|
||||||
tools:node="remove">
|
tools:node="remove">
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<!-- <service
|
|
||||||
android:name="com.pravera.flutter_foreground_task.service.ForegroundService"
|
|
||||||
android:foregroundServiceType="dataSync|remoteMessaging"
|
|
||||||
android:exported="false" /> -->
|
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="eu.twonly.service.TWONLY_LOGO"
|
android:name="eu.twonly.service.TWONLY_LOGO"
|
||||||
android:resource="@drawable/ic_launcher_foreground" />
|
android:resource="@drawable/ic_launcher_foreground" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
|
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
|
||||||
<uses-permission android:name="android.permission.CAMERA"/>
|
<uses-permission android:name="android.permission.CAMERA"/>
|
||||||
|
|
@ -66,6 +68,7 @@
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
|
<uses-permission android:name="com.android.vending.BILLING" />
|
||||||
|
|
||||||
<!-- Required to query activities that can process text, see:
|
<!-- Required to query activities that can process text, see:
|
||||||
https://developer.android.com/training/package-visibility and
|
https://developer.android.com/training/package-visibility and
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit fb66274bf729cde6f7184ec6f7f9ea89f12450fd
|
Subproject commit 7930d9727019344238297d810661bc3e8f724c37
|
||||||
|
|
@ -209,11 +209,11 @@ func getPushNotificationText(pushNotification: PushNotification) -> (String, Str
|
||||||
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 = "Someone"
|
var title = "[Unknown]"
|
||||||
|
|
||||||
// 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 = "Jemand"
|
title = "[Unbekannt]"
|
||||||
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.",
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,13 @@ target 'Runner' do
|
||||||
target 'RunnerTests' do
|
target 'RunnerTests' do
|
||||||
inherit! :search_paths
|
inherit! :search_paths
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Share Extension is name of Extension which you created which is in this case 'Share Extension'
|
||||||
|
target 'ShareExtension' do
|
||||||
|
inherit! :search_paths
|
||||||
|
# flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
post_install do |installer|
|
post_install do |installer|
|
||||||
|
|
|
||||||
129
ios/Podfile.lock
129
ios/Podfile.lock
|
|
@ -54,55 +54,55 @@ PODS:
|
||||||
- file_picker (0.0.1):
|
- file_picker (0.0.1):
|
||||||
- DKImagePickerController/PhotoGallery
|
- DKImagePickerController/PhotoGallery
|
||||||
- Flutter
|
- Flutter
|
||||||
- Firebase (12.4.0):
|
- Firebase (12.6.0):
|
||||||
- Firebase/Core (= 12.4.0)
|
- Firebase/Core (= 12.6.0)
|
||||||
- Firebase/Core (12.4.0):
|
- Firebase/Core (12.6.0):
|
||||||
- Firebase/CoreOnly
|
- Firebase/CoreOnly
|
||||||
- FirebaseAnalytics (~> 12.4.0)
|
- FirebaseAnalytics (~> 12.6.0)
|
||||||
- Firebase/CoreOnly (12.4.0):
|
- Firebase/CoreOnly (12.6.0):
|
||||||
- FirebaseCore (~> 12.4.0)
|
- FirebaseCore (~> 12.6.0)
|
||||||
- Firebase/Messaging (12.4.0):
|
- Firebase/Messaging (12.6.0):
|
||||||
- Firebase/CoreOnly
|
- Firebase/CoreOnly
|
||||||
- FirebaseMessaging (~> 12.4.0)
|
- FirebaseMessaging (~> 12.6.0)
|
||||||
- firebase_core (4.2.1):
|
- firebase_core (4.3.0):
|
||||||
- Firebase/CoreOnly (= 12.4.0)
|
- Firebase/CoreOnly (= 12.6.0)
|
||||||
- Flutter
|
- Flutter
|
||||||
- firebase_messaging (16.0.4):
|
- firebase_messaging (16.1.0):
|
||||||
- Firebase/Messaging (= 12.4.0)
|
- Firebase/Messaging (= 12.6.0)
|
||||||
- firebase_core
|
- firebase_core
|
||||||
- Flutter
|
- Flutter
|
||||||
- FirebaseAnalytics (12.4.0):
|
- FirebaseAnalytics (12.6.0):
|
||||||
- FirebaseAnalytics/Default (= 12.4.0)
|
- FirebaseAnalytics/Default (= 12.6.0)
|
||||||
- FirebaseCore (~> 12.4.0)
|
- FirebaseCore (~> 12.6.0)
|
||||||
- FirebaseInstallations (~> 12.4.0)
|
- FirebaseInstallations (~> 12.6.0)
|
||||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||||
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
||||||
- GoogleUtilities/Network (~> 8.1)
|
- GoogleUtilities/Network (~> 8.1)
|
||||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||||
- nanopb (~> 3.30910.0)
|
- nanopb (~> 3.30910.0)
|
||||||
- FirebaseAnalytics/Default (12.4.0):
|
- FirebaseAnalytics/Default (12.6.0):
|
||||||
- FirebaseCore (~> 12.4.0)
|
- FirebaseCore (~> 12.6.0)
|
||||||
- FirebaseInstallations (~> 12.4.0)
|
- FirebaseInstallations (~> 12.6.0)
|
||||||
- GoogleAppMeasurement/Default (= 12.4.0)
|
- GoogleAppMeasurement/Default (= 12.6.0)
|
||||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||||
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
||||||
- GoogleUtilities/Network (~> 8.1)
|
- GoogleUtilities/Network (~> 8.1)
|
||||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||||
- nanopb (~> 3.30910.0)
|
- nanopb (~> 3.30910.0)
|
||||||
- FirebaseCore (12.4.0):
|
- FirebaseCore (12.6.0):
|
||||||
- FirebaseCoreInternal (~> 12.4.0)
|
- FirebaseCoreInternal (~> 12.6.0)
|
||||||
- GoogleUtilities/Environment (~> 8.1)
|
- GoogleUtilities/Environment (~> 8.1)
|
||||||
- GoogleUtilities/Logger (~> 8.1)
|
- GoogleUtilities/Logger (~> 8.1)
|
||||||
- FirebaseCoreInternal (12.4.0):
|
- FirebaseCoreInternal (12.6.0):
|
||||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||||
- FirebaseInstallations (12.4.0):
|
- FirebaseInstallations (12.6.0):
|
||||||
- FirebaseCore (~> 12.4.0)
|
- FirebaseCore (~> 12.6.0)
|
||||||
- GoogleUtilities/Environment (~> 8.1)
|
- GoogleUtilities/Environment (~> 8.1)
|
||||||
- GoogleUtilities/UserDefaults (~> 8.1)
|
- GoogleUtilities/UserDefaults (~> 8.1)
|
||||||
- PromisesObjC (~> 2.4)
|
- PromisesObjC (~> 2.4)
|
||||||
- FirebaseMessaging (12.4.0):
|
- FirebaseMessaging (12.6.0):
|
||||||
- FirebaseCore (~> 12.4.0)
|
- FirebaseCore (~> 12.6.0)
|
||||||
- FirebaseInstallations (~> 12.4.0)
|
- FirebaseInstallations (~> 12.6.0)
|
||||||
- GoogleDataTransport (~> 10.1)
|
- GoogleDataTransport (~> 10.1)
|
||||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||||
- GoogleUtilities/Environment (~> 8.1)
|
- GoogleUtilities/Environment (~> 8.1)
|
||||||
|
|
@ -122,6 +122,8 @@ PODS:
|
||||||
- flutter_secure_storage_darwin (10.0.0):
|
- flutter_secure_storage_darwin (10.0.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
|
- flutter_sharing_intent (1.0.1):
|
||||||
|
- Flutter
|
||||||
- flutter_volume_controller (0.0.1):
|
- flutter_volume_controller (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- gal (1.0.0):
|
- gal (1.0.0):
|
||||||
|
|
@ -134,28 +136,28 @@ PODS:
|
||||||
- google_mlkit_commons (0.11.0):
|
- google_mlkit_commons (0.11.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- MLKitVision
|
- MLKitVision
|
||||||
- GoogleAdsOnDeviceConversion (3.1.0):
|
- GoogleAdsOnDeviceConversion (3.2.0):
|
||||||
- GoogleUtilities/Environment (~> 8.1)
|
- GoogleUtilities/Environment (~> 8.1)
|
||||||
- GoogleUtilities/Logger (~> 8.1)
|
- GoogleUtilities/Logger (~> 8.1)
|
||||||
- GoogleUtilities/Network (~> 8.1)
|
- GoogleUtilities/Network (~> 8.1)
|
||||||
- nanopb (~> 3.30910.0)
|
- nanopb (~> 3.30910.0)
|
||||||
- GoogleAppMeasurement/Core (12.4.0):
|
- GoogleAppMeasurement/Core (12.6.0):
|
||||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||||
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
||||||
- GoogleUtilities/Network (~> 8.1)
|
- GoogleUtilities/Network (~> 8.1)
|
||||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||||
- nanopb (~> 3.30910.0)
|
- nanopb (~> 3.30910.0)
|
||||||
- GoogleAppMeasurement/Default (12.4.0):
|
- GoogleAppMeasurement/Default (12.6.0):
|
||||||
- GoogleAdsOnDeviceConversion (~> 3.1.0)
|
- GoogleAdsOnDeviceConversion (~> 3.2.0)
|
||||||
- GoogleAppMeasurement/Core (= 12.4.0)
|
- GoogleAppMeasurement/Core (= 12.6.0)
|
||||||
- GoogleAppMeasurement/IdentitySupport (= 12.4.0)
|
- GoogleAppMeasurement/IdentitySupport (= 12.6.0)
|
||||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||||
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
||||||
- GoogleUtilities/Network (~> 8.1)
|
- GoogleUtilities/Network (~> 8.1)
|
||||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||||
- nanopb (~> 3.30910.0)
|
- nanopb (~> 3.30910.0)
|
||||||
- GoogleAppMeasurement/IdentitySupport (12.4.0):
|
- GoogleAppMeasurement/IdentitySupport (12.6.0):
|
||||||
- GoogleAppMeasurement/Core (= 12.4.0)
|
- GoogleAppMeasurement/Core (= 12.6.0)
|
||||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||||
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
- GoogleUtilities/MethodSwizzler (~> 8.1)
|
||||||
- GoogleUtilities/Network (~> 8.1)
|
- GoogleUtilities/Network (~> 8.1)
|
||||||
|
|
@ -217,6 +219,9 @@ PODS:
|
||||||
- GTMSessionFetcher/Core (3.5.0)
|
- GTMSessionFetcher/Core (3.5.0)
|
||||||
- image_picker_ios (0.0.1):
|
- image_picker_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- in_app_purchase_storekit (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
- libwebp (1.5.0):
|
- libwebp (1.5.0):
|
||||||
- libwebp/demux (= 1.5.0)
|
- libwebp/demux (= 1.5.0)
|
||||||
- libwebp/mux (= 1.5.0)
|
- libwebp/mux (= 1.5.0)
|
||||||
|
|
@ -257,7 +262,7 @@ PODS:
|
||||||
- nanopb/encode (= 3.30910.0)
|
- nanopb/encode (= 3.30910.0)
|
||||||
- nanopb/decode (3.30910.0)
|
- nanopb/decode (3.30910.0)
|
||||||
- nanopb/encode (3.30910.0)
|
- nanopb/encode (3.30910.0)
|
||||||
- "no_screenshot (0.0.1+4)":
|
- no_screenshot (0.3.2-beta.3):
|
||||||
- Flutter
|
- Flutter
|
||||||
- ScreenProtectorKit (~> 1.3.1)
|
- ScreenProtectorKit (~> 1.3.1)
|
||||||
- objective_c (0.0.1):
|
- objective_c (0.0.1):
|
||||||
|
|
@ -273,10 +278,10 @@ PODS:
|
||||||
- restart_app (0.0.1):
|
- restart_app (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- ScreenProtectorKit (1.3.1)
|
- ScreenProtectorKit (1.3.1)
|
||||||
- SDWebImage (5.21.3):
|
- SDWebImage (5.21.5):
|
||||||
- SDWebImage/Core (= 5.21.3)
|
- SDWebImage/Core (= 5.21.5)
|
||||||
- SDWebImage/Core (5.21.3)
|
- SDWebImage/Core (5.21.5)
|
||||||
- SDWebImageWebPCoder (0.14.6):
|
- SDWebImageWebPCoder (0.15.0):
|
||||||
- libwebp (~> 1.0)
|
- libwebp (~> 1.0)
|
||||||
- SDWebImage/Core (~> 5.17)
|
- SDWebImage/Core (~> 5.17)
|
||||||
- Sentry/HybridSDK (8.56.2)
|
- Sentry/HybridSDK (8.56.2)
|
||||||
|
|
@ -317,7 +322,7 @@ PODS:
|
||||||
- sqlite3/perf-threadsafe
|
- sqlite3/perf-threadsafe
|
||||||
- sqlite3/rtree
|
- sqlite3/rtree
|
||||||
- sqlite3/session
|
- sqlite3/session
|
||||||
- SwiftProtobuf (1.33.1)
|
- SwiftProtobuf (1.33.3)
|
||||||
- SwiftyGif (5.4.5)
|
- SwiftyGif (5.4.5)
|
||||||
- url_launcher_ios (0.0.1):
|
- url_launcher_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
|
@ -347,12 +352,14 @@ DEPENDENCIES:
|
||||||
- flutter_keyboard_visibility_temp_fork (from `.symlinks/plugins/flutter_keyboard_visibility_temp_fork/ios`)
|
- flutter_keyboard_visibility_temp_fork (from `.symlinks/plugins/flutter_keyboard_visibility_temp_fork/ios`)
|
||||||
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
|
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
|
||||||
- flutter_secure_storage_darwin (from `.symlinks/plugins/flutter_secure_storage_darwin/darwin`)
|
- flutter_secure_storage_darwin (from `.symlinks/plugins/flutter_secure_storage_darwin/darwin`)
|
||||||
|
- flutter_sharing_intent (from `.symlinks/plugins/flutter_sharing_intent/ios`)
|
||||||
- flutter_volume_controller (from `.symlinks/plugins/flutter_volume_controller/ios`)
|
- flutter_volume_controller (from `.symlinks/plugins/flutter_volume_controller/ios`)
|
||||||
- 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`)
|
||||||
- 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`)
|
||||||
- local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`)
|
- local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`)
|
||||||
- no_screenshot (from `.symlinks/plugins/no_screenshot/ios`)
|
- no_screenshot (from `.symlinks/plugins/no_screenshot/ios`)
|
||||||
- objective_c (from `.symlinks/plugins/objective_c/ios`)
|
- objective_c (from `.symlinks/plugins/objective_c/ios`)
|
||||||
|
|
@ -437,6 +444,8 @@ EXTERNAL SOURCES:
|
||||||
:path: ".symlinks/plugins/flutter_local_notifications/ios"
|
:path: ".symlinks/plugins/flutter_local_notifications/ios"
|
||||||
flutter_secure_storage_darwin:
|
flutter_secure_storage_darwin:
|
||||||
:path: ".symlinks/plugins/flutter_secure_storage_darwin/darwin"
|
:path: ".symlinks/plugins/flutter_secure_storage_darwin/darwin"
|
||||||
|
flutter_sharing_intent:
|
||||||
|
:path: ".symlinks/plugins/flutter_sharing_intent/ios"
|
||||||
flutter_volume_controller:
|
flutter_volume_controller:
|
||||||
:path: ".symlinks/plugins/flutter_volume_controller/ios"
|
:path: ".symlinks/plugins/flutter_volume_controller/ios"
|
||||||
gal:
|
gal:
|
||||||
|
|
@ -447,6 +456,8 @@ EXTERNAL SOURCES:
|
||||||
:path: ".symlinks/plugins/google_mlkit_commons/ios"
|
:path: ".symlinks/plugins/google_mlkit_commons/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:
|
||||||
|
:path: ".symlinks/plugins/in_app_purchase_storekit/darwin"
|
||||||
local_auth_darwin:
|
local_auth_darwin:
|
||||||
:path: ".symlinks/plugins/local_auth_darwin/darwin"
|
:path: ".symlinks/plugins/local_auth_darwin/darwin"
|
||||||
no_screenshot:
|
no_screenshot:
|
||||||
|
|
@ -489,31 +500,33 @@ SPEC CHECKSUMS:
|
||||||
emoji_picker_flutter: ece213fc274bdddefb77d502d33080dc54e616cc
|
emoji_picker_flutter: ece213fc274bdddefb77d502d33080dc54e616cc
|
||||||
ffmpeg_kit_flutter_new: 12426a19f10ac81186c67c6ebc4717f8f4364b7f
|
ffmpeg_kit_flutter_new: 12426a19f10ac81186c67c6ebc4717f8f4364b7f
|
||||||
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
|
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
|
||||||
Firebase: f07b15ae5a6ec0f93713e30b923d9970d144af3e
|
Firebase: a451a7b61536298fd5cbfe3a746fd40443a50679
|
||||||
firebase_core: f1aafb21c14f497e5498f7ffc4dc63cbb52b2594
|
firebase_core: ba00a168e719694f38960502ceb560285603d073
|
||||||
firebase_messaging: c17a29984eafce4b2997fe078bb0a9e0b06f5dde
|
firebase_messaging: bf0e29321927edc02a563c984dbfa5b063864b15
|
||||||
FirebaseAnalytics: 0fc2b20091f0ddd21bf73397cf8f0eb5346dc24f
|
FirebaseAnalytics: d0a97a0db6425e5a5d966340b87f92ca7b13a557
|
||||||
FirebaseCore: bb595f3114953664e3c1dc032f008a244147cfd3
|
FirebaseCore: 0e38ad5d62d980a47a64b8e9301ffa311457be04
|
||||||
FirebaseCoreInternal: d7f5a043c2cd01a08103ab586587c1468047bca6
|
FirebaseCoreInternal: 69bf1306a05b8ac43004f6cc1f804bb7b05b229e
|
||||||
FirebaseInstallations: ae9f4902cb5bf1d0c5eaa31ec1f4e5495a0714e2
|
FirebaseInstallations: 631b38da2e11a83daa4bfb482f79d286a5dfa7ad
|
||||||
FirebaseMessaging: d33971b7bb252745ea6cd31ab190d1a1df4b8ed5
|
FirebaseMessaging: a61bc42dcab3f7a346d94bbb54dab2c9435b18b2
|
||||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||||
flutter_image_compress_common: 1697a328fd72bfb335507c6bca1a65fa5ad87df1
|
flutter_image_compress_common: 1697a328fd72bfb335507c6bca1a65fa5ad87df1
|
||||||
flutter_keyboard_visibility_temp_fork: 95b2d534bacf6ac62e7fcbe5c2a9e2c2a17ce06f
|
flutter_keyboard_visibility_temp_fork: 95b2d534bacf6ac62e7fcbe5c2a9e2c2a17ce06f
|
||||||
flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb
|
flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb
|
||||||
flutter_secure_storage_darwin: ce237a8775b39723566dc72571190a3769d70468
|
flutter_secure_storage_darwin: acdb3f316ed05a3e68f856e0353b133eec373a23
|
||||||
|
flutter_sharing_intent: 0c1e53949f09fa8df8ac2268505687bde8ff264c
|
||||||
flutter_volume_controller: c2be490cb0487e8b88d0d9fc2b7e1c139a4ebccb
|
flutter_volume_controller: c2be490cb0487e8b88d0d9fc2b7e1c139a4ebccb
|
||||||
gal: baecd024ebfd13c441269ca7404792a7152fde89
|
gal: baecd024ebfd13c441269ca7404792a7152fde89
|
||||||
google_mlkit_barcode_scanning: 8f5987f244a43fe1167689c548342a5174108159
|
google_mlkit_barcode_scanning: 8f5987f244a43fe1167689c548342a5174108159
|
||||||
google_mlkit_commons: 2abe6a70e1824e431d16a51085cb475b672c8aab
|
google_mlkit_commons: 2abe6a70e1824e431d16a51085cb475b672c8aab
|
||||||
GoogleAdsOnDeviceConversion: e03a386840803ea7eef3fd22a061930142c039c1
|
GoogleAdsOnDeviceConversion: d68c69dd9581a0f5da02617b6f377e5be483970f
|
||||||
GoogleAppMeasurement: 1e718274b7e015cefd846ac1fcf7820c70dc017d
|
GoogleAppMeasurement: 3bf40aff49a601af5da1c3345702fcb4991d35ee
|
||||||
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
||||||
GoogleMLKit: eff9e23ec1d90ea4157a1ee2e32a4f610c5b3318
|
GoogleMLKit: eff9e23ec1d90ea4157a1ee2e32a4f610c5b3318
|
||||||
GoogleToolboxForMac: d1a2cbf009c453f4d6ded37c105e2f67a32206d8
|
GoogleToolboxForMac: d1a2cbf009c453f4d6ded37c105e2f67a32206d8
|
||||||
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
||||||
GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6
|
GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6
|
||||||
image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
|
image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
|
||||||
|
in_app_purchase_storekit: 22cca7d08eebca9babdf4d07d0baccb73325d3c8
|
||||||
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
|
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
|
||||||
local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb
|
local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb
|
||||||
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
|
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
|
||||||
|
|
@ -522,7 +535,7 @@ SPEC CHECKSUMS:
|
||||||
MLKitCommon: 07c2c33ae5640e5380beaaa6e4b9c249a205542d
|
MLKitCommon: 07c2c33ae5640e5380beaaa6e4b9c249a205542d
|
||||||
MLKitVision: 45e79d68845a2de77e2dd4d7f07947f0ed157b0e
|
MLKitVision: 45e79d68845a2de77e2dd4d7f07947f0ed157b0e
|
||||||
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
||||||
no_screenshot: 6d183496405a3ab709a67a54e5cd0f639e94729e
|
no_screenshot: 89e778ede9f1e39cc3fb9404d782a42712f2a0b2
|
||||||
objective_c: 89e720c30d716b036faf9c9684022048eee1eee2
|
objective_c: 89e720c30d716b036faf9c9684022048eee1eee2
|
||||||
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
||||||
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
|
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
|
||||||
|
|
@ -530,8 +543,8 @@ SPEC CHECKSUMS:
|
||||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||||
restart_app: 9cda5378aacc5000e3f66ee76a9201534e7d3ecf
|
restart_app: 9cda5378aacc5000e3f66ee76a9201534e7d3ecf
|
||||||
ScreenProtectorKit: 83a6281b02c7a5902ee6eac4f5045f674e902ae4
|
ScreenProtectorKit: 83a6281b02c7a5902ee6eac4f5045f674e902ae4
|
||||||
SDWebImage: 16309af6d214ba3f77a7c6f6fdda888cb313a50a
|
SDWebImage: e9c98383c7572d713c1a0d7dd2783b10599b9838
|
||||||
SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380
|
SDWebImageWebPCoder: 0e06e365080397465cc73a7a9b472d8a3bd0f377
|
||||||
Sentry: b53951377b78e21a734f5dc8318e333dbfc682d7
|
Sentry: b53951377b78e21a734f5dc8318e333dbfc682d7
|
||||||
sentry_flutter: 4c33648b7e83310aa1fdb1b10c5491027d9643f0
|
sentry_flutter: 4c33648b7e83310aa1fdb1b10c5491027d9643f0
|
||||||
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
|
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
|
||||||
|
|
@ -539,11 +552,11 @@ SPEC CHECKSUMS:
|
||||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||||
sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b
|
sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b
|
||||||
sqlite3_flutter_libs: 83f8e9f5b6554077f1d93119fe20ebaa5f3a9ef1
|
sqlite3_flutter_libs: 83f8e9f5b6554077f1d93119fe20ebaa5f3a9ef1
|
||||||
SwiftProtobuf: 533a18409c3ca3a6156b2b1e46afd0f69e751aba
|
SwiftProtobuf: e1b437c8e31a4c5577b643249a0bb62ed4f02153
|
||||||
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
||||||
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
|
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
|
||||||
video_player_avfoundation: dd410b52df6d2466a42d28550e33e4146928280a
|
video_player_avfoundation: dd410b52df6d2466a42d28550e33e4146928280a
|
||||||
|
|
||||||
PODFILE CHECKSUM: c0c524475498435108850efecde62ba98e081c25
|
PODFILE CHECKSUM: ae041999f13ba7b2285ff9ad9bc688ed647bbcb7
|
||||||
|
|
||||||
COCOAPODS: 1.16.2
|
COCOAPODS: 1.16.2
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
05CF222065FC24670B05B6D0 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC1EE71614E1B4F84D6FDC2D /* Pods_RunnerTests.framework */; };
|
05CF222065FC24670B05B6D0 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC1EE71614E1B4F84D6FDC2D /* Pods_RunnerTests.framework */; };
|
||||||
06AA21445BEAF2C45DC9DCDF /* Pods_NotificationService.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A198C9B5D90584C4F96206B2 /* Pods_NotificationService.framework */; };
|
06AA21445BEAF2C45DC9DCDF /* Pods_NotificationService.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A198C9B5D90584C4F96206B2 /* Pods_NotificationService.framework */; };
|
||||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||||
|
30EBDD0F93DC44E774F3B785 /* Pods_ShareExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E190E82D9973B318A389650B /* Pods_ShareExtension.framework */; };
|
||||||
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
||||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||||
|
|
@ -18,6 +19,8 @@
|
||||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||||
CA4FDF5DD8F229C30DE512AF /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE2CCFEE4ABECF33852F7735 /* Pods_Runner.framework */; };
|
CA4FDF5DD8F229C30DE512AF /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE2CCFEE4ABECF33852F7735 /* Pods_Runner.framework */; };
|
||||||
D21FCEAB2D9F2B750088701D /* NotificationService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = D21FCEA42D9F2B750088701D /* NotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
D21FCEAB2D9F2B750088701D /* NotificationService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = D21FCEA42D9F2B750088701D /* NotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
|
D25D4D1E2EF626E30029F805 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D25D4D1D2EF626E30029F805 /* StoreKit.framework */; };
|
||||||
|
D25D4D7A2EFF41DB0029F805 /* ShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = D25D4D702EFF41DB0029F805 /* ShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
F3C66D726A2EB28484DF0B10 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 16FBC6F5B58E1C6646F5D447 /* GoogleService-Info.plist */; };
|
F3C66D726A2EB28484DF0B10 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 16FBC6F5B58E1C6646F5D447 /* GoogleService-Info.plist */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
|
|
@ -36,6 +39,13 @@
|
||||||
remoteGlobalIDString = D21FCEA32D9F2B750088701D;
|
remoteGlobalIDString = D21FCEA32D9F2B750088701D;
|
||||||
remoteInfo = NotificationService;
|
remoteInfo = NotificationService;
|
||||||
};
|
};
|
||||||
|
D25D4D782EFF41DB0029F805 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = D25D4D6F2EFF41DB0029F805;
|
||||||
|
remoteInfo = ShareExtension;
|
||||||
|
};
|
||||||
/* End PBXContainerItemProxy section */
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
/* Begin PBXCopyFilesBuildPhase section */
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
|
|
@ -55,6 +65,7 @@
|
||||||
dstPath = "";
|
dstPath = "";
|
||||||
dstSubfolderSpec = 13;
|
dstSubfolderSpec = 13;
|
||||||
files = (
|
files = (
|
||||||
|
D25D4D7A2EFF41DB0029F805 /* ShareExtension.appex in Embed Foundation Extensions */,
|
||||||
D21FCEAB2D9F2B750088701D /* NotificationService.appex in Embed Foundation Extensions */,
|
D21FCEAB2D9F2B750088701D /* NotificationService.appex in Embed Foundation Extensions */,
|
||||||
);
|
);
|
||||||
name = "Embed Foundation Extensions";
|
name = "Embed Foundation Extensions";
|
||||||
|
|
@ -66,10 +77,12 @@
|
||||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||||
1581CC44342D555EFB889768 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
|
1581CC44342D555EFB889768 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
15CEF849B61A620CFB2DC5F1 /* Pods-ShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareExtension.debug.xcconfig"; path = "Target Support Files/Pods-ShareExtension/Pods-ShareExtension.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
16FBC6F5B58E1C6646F5D447 /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = "<group>"; };
|
16FBC6F5B58E1C6646F5D447 /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = "<group>"; };
|
||||||
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
35366FD433E0EFC6EF19A452 /* Pods-NotificationService.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationService.release.xcconfig"; path = "Target Support Files/Pods-NotificationService/Pods-NotificationService.release.xcconfig"; sourceTree = "<group>"; };
|
35366FD433E0EFC6EF19A452 /* Pods-NotificationService.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationService.release.xcconfig"; path = "Target Support Files/Pods-NotificationService/Pods-NotificationService.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
39FB86A38393489D58A01B0B /* Pods-ShareExtension.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareExtension.profile.xcconfig"; path = "Target Support Files/Pods-ShareExtension/Pods-ShareExtension.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||||
4D78471482626812FE2468E9 /* Pods-NotificationService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationService.debug.xcconfig"; path = "Target Support Files/Pods-NotificationService/Pods-NotificationService.debug.xcconfig"; sourceTree = "<group>"; };
|
4D78471482626812FE2468E9 /* Pods-NotificationService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationService.debug.xcconfig"; path = "Target Support Files/Pods-NotificationService/Pods-NotificationService.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
6EB462F87F0A23758713308F /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
6EB462F87F0A23758713308F /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
|
@ -86,10 +99,15 @@
|
||||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
A198C9B5D90584C4F96206B2 /* Pods_NotificationService.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NotificationService.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
A198C9B5D90584C4F96206B2 /* Pods_NotificationService.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NotificationService.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
A22BD564F16069E5FCB60767 /* Pods-ShareExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareExtension.release.xcconfig"; path = "Target Support Files/Pods-ShareExtension/Pods-ShareExtension.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
B3B27B7FBEEA31DB7793A0C2 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
B3B27B7FBEEA31DB7793A0C2 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
D21FCEA42D9F2B750088701D /* NotificationService.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NotificationService.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
D21FCEA42D9F2B750088701D /* NotificationService.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NotificationService.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
D2265DD42D920142000D99BB /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
|
D2265DD42D920142000D99BB /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
|
||||||
|
D25D4D1D2EF626E30029F805 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; };
|
||||||
|
D25D4D702EFF41DB0029F805 /* ShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
D25D4D802EFF437F0029F805 /* RunnerDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerDebug.entitlements; sourceTree = "<group>"; };
|
||||||
DC1EE71614E1B4F84D6FDC2D /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
DC1EE71614E1B4F84D6FDC2D /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
E190E82D9973B318A389650B /* Pods_ShareExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ShareExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
E96A5ACA32A7118204F050A5 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
|
E96A5ACA32A7118204F050A5 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
EE2CCFEE4ABECF33852F7735 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
EE2CCFEE4ABECF33852F7735 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
F02F7A1D63544AA9F23A1085 /* Pods-NotificationService.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationService.profile.xcconfig"; path = "Target Support Files/Pods-NotificationService/Pods-NotificationService.profile.xcconfig"; sourceTree = "<group>"; };
|
F02F7A1D63544AA9F23A1085 /* Pods-NotificationService.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationService.profile.xcconfig"; path = "Target Support Files/Pods-NotificationService/Pods-NotificationService.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
|
@ -103,6 +121,13 @@
|
||||||
);
|
);
|
||||||
target = D21FCEA32D9F2B750088701D /* NotificationService */;
|
target = D21FCEA32D9F2B750088701D /* NotificationService */;
|
||||||
};
|
};
|
||||||
|
D25D4D7E2EFF41DB0029F805 /* Exceptions for "ShareExtension" folder in "ShareExtension" target */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||||
|
membershipExceptions = (
|
||||||
|
Info.plist,
|
||||||
|
);
|
||||||
|
target = D25D4D6F2EFF41DB0029F805 /* ShareExtension */;
|
||||||
|
};
|
||||||
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||||
|
|
||||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||||
|
|
@ -114,6 +139,14 @@
|
||||||
path = NotificationService;
|
path = NotificationService;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
D25D4D712EFF41DB0029F805 /* ShareExtension */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
|
exceptions = (
|
||||||
|
D25D4D7E2EFF41DB0029F805 /* Exceptions for "ShareExtension" folder in "ShareExtension" target */,
|
||||||
|
);
|
||||||
|
path = ShareExtension;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
/* End PBXFileSystemSynchronizedRootGroup section */
|
/* End PBXFileSystemSynchronizedRootGroup section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
|
@ -130,6 +163,7 @@
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
CA4FDF5DD8F229C30DE512AF /* Pods_Runner.framework in Frameworks */,
|
CA4FDF5DD8F229C30DE512AF /* Pods_Runner.framework in Frameworks */,
|
||||||
|
D25D4D1E2EF626E30029F805 /* StoreKit.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|
@ -141,6 +175,14 @@
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
D25D4D6D2EFF41DB0029F805 /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
30EBDD0F93DC44E774F3B785 /* Pods_ShareExtension.framework in Frameworks */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
|
|
@ -169,6 +211,7 @@
|
||||||
9740EEB11CF90186004384FC /* Flutter */,
|
9740EEB11CF90186004384FC /* Flutter */,
|
||||||
97C146F01CF9000F007C117D /* Runner */,
|
97C146F01CF9000F007C117D /* Runner */,
|
||||||
D21FCEA52D9F2B750088701D /* NotificationService */,
|
D21FCEA52D9F2B750088701D /* NotificationService */,
|
||||||
|
D25D4D712EFF41DB0029F805 /* ShareExtension */,
|
||||||
97C146EF1CF9000F007C117D /* Products */,
|
97C146EF1CF9000F007C117D /* Products */,
|
||||||
331C8082294A63A400263BE5 /* RunnerTests */,
|
331C8082294A63A400263BE5 /* RunnerTests */,
|
||||||
16FBC6F5B58E1C6646F5D447 /* GoogleService-Info.plist */,
|
16FBC6F5B58E1C6646F5D447 /* GoogleService-Info.plist */,
|
||||||
|
|
@ -183,6 +226,7 @@
|
||||||
97C146EE1CF9000F007C117D /* Runner.app */,
|
97C146EE1CF9000F007C117D /* Runner.app */,
|
||||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
|
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
|
||||||
D21FCEA42D9F2B750088701D /* NotificationService.appex */,
|
D21FCEA42D9F2B750088701D /* NotificationService.appex */,
|
||||||
|
D25D4D702EFF41DB0029F805 /* ShareExtension.appex */,
|
||||||
);
|
);
|
||||||
name = Products;
|
name = Products;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -190,6 +234,7 @@
|
||||||
97C146F01CF9000F007C117D /* Runner */ = {
|
97C146F01CF9000F007C117D /* Runner */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
D25D4D802EFF437F0029F805 /* RunnerDebug.entitlements */,
|
||||||
D2265DD42D920142000D99BB /* Runner.entitlements */,
|
D2265DD42D920142000D99BB /* Runner.entitlements */,
|
||||||
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||||
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||||
|
|
@ -206,9 +251,11 @@
|
||||||
E5079CCEE4804DB65AA3F23F /* Frameworks */ = {
|
E5079CCEE4804DB65AA3F23F /* Frameworks */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
D25D4D1D2EF626E30029F805 /* StoreKit.framework */,
|
||||||
A198C9B5D90584C4F96206B2 /* Pods_NotificationService.framework */,
|
A198C9B5D90584C4F96206B2 /* Pods_NotificationService.framework */,
|
||||||
EE2CCFEE4ABECF33852F7735 /* Pods_Runner.framework */,
|
EE2CCFEE4ABECF33852F7735 /* Pods_Runner.framework */,
|
||||||
DC1EE71614E1B4F84D6FDC2D /* Pods_RunnerTests.framework */,
|
DC1EE71614E1B4F84D6FDC2D /* Pods_RunnerTests.framework */,
|
||||||
|
E190E82D9973B318A389650B /* Pods_ShareExtension.framework */,
|
||||||
);
|
);
|
||||||
name = Frameworks;
|
name = Frameworks;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -225,6 +272,9 @@
|
||||||
4D78471482626812FE2468E9 /* Pods-NotificationService.debug.xcconfig */,
|
4D78471482626812FE2468E9 /* Pods-NotificationService.debug.xcconfig */,
|
||||||
35366FD433E0EFC6EF19A452 /* Pods-NotificationService.release.xcconfig */,
|
35366FD433E0EFC6EF19A452 /* Pods-NotificationService.release.xcconfig */,
|
||||||
F02F7A1D63544AA9F23A1085 /* Pods-NotificationService.profile.xcconfig */,
|
F02F7A1D63544AA9F23A1085 /* Pods-NotificationService.profile.xcconfig */,
|
||||||
|
15CEF849B61A620CFB2DC5F1 /* Pods-ShareExtension.debug.xcconfig */,
|
||||||
|
A22BD564F16069E5FCB60767 /* Pods-ShareExtension.release.xcconfig */,
|
||||||
|
39FB86A38393489D58A01B0B /* Pods-ShareExtension.profile.xcconfig */,
|
||||||
);
|
);
|
||||||
path = Pods;
|
path = Pods;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -270,6 +320,7 @@
|
||||||
);
|
);
|
||||||
dependencies = (
|
dependencies = (
|
||||||
D21FCEAA2D9F2B750088701D /* PBXTargetDependency */,
|
D21FCEAA2D9F2B750088701D /* PBXTargetDependency */,
|
||||||
|
D25D4D792EFF41DB0029F805 /* PBXTargetDependency */,
|
||||||
);
|
);
|
||||||
name = Runner;
|
name = Runner;
|
||||||
productName = Runner;
|
productName = Runner;
|
||||||
|
|
@ -297,6 +348,27 @@
|
||||||
productReference = D21FCEA42D9F2B750088701D /* NotificationService.appex */;
|
productReference = D21FCEA42D9F2B750088701D /* NotificationService.appex */;
|
||||||
productType = "com.apple.product-type.app-extension";
|
productType = "com.apple.product-type.app-extension";
|
||||||
};
|
};
|
||||||
|
D25D4D6F2EFF41DB0029F805 /* ShareExtension */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = D25D4D7F2EFF41DB0029F805 /* Build configuration list for PBXNativeTarget "ShareExtension" */;
|
||||||
|
buildPhases = (
|
||||||
|
627F39EA1643E08048D23996 /* [CP] Check Pods Manifest.lock */,
|
||||||
|
D25D4D6C2EFF41DB0029F805 /* Sources */,
|
||||||
|
D25D4D6D2EFF41DB0029F805 /* Frameworks */,
|
||||||
|
D25D4D6E2EFF41DB0029F805 /* Resources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
fileSystemSynchronizedGroups = (
|
||||||
|
D25D4D712EFF41DB0029F805 /* ShareExtension */,
|
||||||
|
);
|
||||||
|
name = ShareExtension;
|
||||||
|
productName = ShareExtension;
|
||||||
|
productReference = D25D4D702EFF41DB0029F805 /* ShareExtension.appex */;
|
||||||
|
productType = "com.apple.product-type.app-extension";
|
||||||
|
};
|
||||||
/* End PBXNativeTarget section */
|
/* End PBXNativeTarget section */
|
||||||
|
|
||||||
/* Begin PBXProject section */
|
/* Begin PBXProject section */
|
||||||
|
|
@ -304,7 +376,7 @@
|
||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
BuildIndependentTargetsInParallel = YES;
|
BuildIndependentTargetsInParallel = YES;
|
||||||
LastSwiftUpdateCheck = 1620;
|
LastSwiftUpdateCheck = 2610;
|
||||||
LastUpgradeCheck = 1510;
|
LastUpgradeCheck = 1510;
|
||||||
ORGANIZATIONNAME = "";
|
ORGANIZATIONNAME = "";
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
|
|
@ -319,6 +391,9 @@
|
||||||
D21FCEA32D9F2B750088701D = {
|
D21FCEA32D9F2B750088701D = {
|
||||||
CreatedOnToolsVersion = 16.2;
|
CreatedOnToolsVersion = 16.2;
|
||||||
};
|
};
|
||||||
|
D25D4D6F2EFF41DB0029F805 = {
|
||||||
|
CreatedOnToolsVersion = 26.1.1;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
||||||
|
|
@ -337,6 +412,7 @@
|
||||||
97C146ED1CF9000F007C117D /* Runner */,
|
97C146ED1CF9000F007C117D /* Runner */,
|
||||||
331C8080294A63A400263BE5 /* RunnerTests */,
|
331C8080294A63A400263BE5 /* RunnerTests */,
|
||||||
D21FCEA32D9F2B750088701D /* NotificationService */,
|
D21FCEA32D9F2B750088701D /* NotificationService */,
|
||||||
|
D25D4D6F2EFF41DB0029F805 /* ShareExtension */,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
/* End PBXProject section */
|
/* End PBXProject section */
|
||||||
|
|
@ -368,6 +444,13 @@
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
D25D4D6E2EFF41DB0029F805 /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
/* End PBXResourcesBuildPhase section */
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXShellScriptBuildPhase section */
|
/* Begin PBXShellScriptBuildPhase section */
|
||||||
|
|
@ -448,6 +531,28 @@
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin\n";
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin\n";
|
||||||
};
|
};
|
||||||
|
627F39EA1643E08048D23996 /* [CP] Check Pods Manifest.lock */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||||
|
"${PODS_ROOT}/Manifest.lock",
|
||||||
|
);
|
||||||
|
name = "[CP] Check Pods Manifest.lock";
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
"$(DERIVED_FILE_DIR)/Pods-ShareExtension-checkManifestLockResult.txt",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
alwaysOutOfDate = 1;
|
alwaysOutOfDate = 1;
|
||||||
|
|
@ -529,6 +634,13 @@
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
D25D4D6C2EFF41DB0029F805 /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
/* End PBXSourcesBuildPhase section */
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXTargetDependency section */
|
/* Begin PBXTargetDependency section */
|
||||||
|
|
@ -542,6 +654,11 @@
|
||||||
target = D21FCEA32D9F2B750088701D /* NotificationService */;
|
target = D21FCEA32D9F2B750088701D /* NotificationService */;
|
||||||
targetProxy = D21FCEA92D9F2B750088701D /* PBXContainerItemProxy */;
|
targetProxy = D21FCEA92D9F2B750088701D /* PBXContainerItemProxy */;
|
||||||
};
|
};
|
||||||
|
D25D4D792EFF41DB0029F805 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = D25D4D6F2EFF41DB0029F805 /* ShareExtension */;
|
||||||
|
targetProxy = D25D4D782EFF41DB0029F805 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
/* End PBXTargetDependency section */
|
/* End PBXTargetDependency section */
|
||||||
|
|
||||||
/* Begin PBXVariantGroup section */
|
/* Begin PBXVariantGroup section */
|
||||||
|
|
@ -627,6 +744,7 @@
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
CUSTOM_GROUP_ID = group.eu.twonly.shareIntent;
|
||||||
DEVELOPMENT_TEAM = CN332ZUGRP;
|
DEVELOPMENT_TEAM = CN332ZUGRP;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
|
@ -823,8 +941,9 @@
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES;
|
CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
CUSTOM_GROUP_ID = group.eu.twonly.shareIntent;
|
||||||
DEVELOPMENT_TEAM = CN332ZUGRP;
|
DEVELOPMENT_TEAM = CN332ZUGRP;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
|
@ -859,6 +978,7 @@
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
CUSTOM_GROUP_ID = group.eu.twonly.shareIntent;
|
||||||
DEVELOPMENT_TEAM = CN332ZUGRP;
|
DEVELOPMENT_TEAM = CN332ZUGRP;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
|
@ -1000,6 +1120,133 @@
|
||||||
};
|
};
|
||||||
name = Profile;
|
name = Profile;
|
||||||
};
|
};
|
||||||
|
D25D4D7B2EFF41DB0029F805 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 15CEF849B61A620CFB2DC5F1 /* Pods-ShareExtension.debug.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtensionDebug.entitlements;
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
CUSTOM_GROUP_ID = group.eu.twonly.shareIntent;
|
||||||
|
DEVELOPMENT_TEAM = CN332ZUGRP;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_FILE = ShareExtension/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
|
||||||
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 26.1;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
"@executable_path/../../Frameworks",
|
||||||
|
);
|
||||||
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||||
|
MTL_FAST_MATH = YES;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = eu.twonly.ShareExtension;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||||
|
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
D25D4D7C2EFF41DB0029F805 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = A22BD564F16069E5FCB60767 /* Pods-ShareExtension.release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
CUSTOM_GROUP_ID = group.eu.twonly.shareIntent;
|
||||||
|
DEVELOPMENT_TEAM = CN332ZUGRP;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_FILE = ShareExtension/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
|
||||||
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 26.1;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
"@executable_path/../../Frameworks",
|
||||||
|
);
|
||||||
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
MTL_FAST_MATH = YES;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = eu.twonly.ShareExtension;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
|
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
D25D4D7D2EFF41DB0029F805 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 39FB86A38393489D58A01B0B /* Pods-ShareExtension.profile.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
CUSTOM_GROUP_ID = group.eu.twonly.shareIntent;
|
||||||
|
DEVELOPMENT_TEAM = CN332ZUGRP;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_FILE = ShareExtension/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
|
||||||
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 26.1;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
"@executable_path/../../Frameworks",
|
||||||
|
);
|
||||||
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
MTL_FAST_MATH = YES;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = eu.twonly.ShareExtension;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
|
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
/* End XCBuildConfiguration section */
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
/* Begin XCConfigurationList section */
|
/* Begin XCConfigurationList section */
|
||||||
|
|
@ -1043,6 +1290,16 @@
|
||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
|
D25D4D7F2EFF41DB0029F805 /* Build configuration list for PBXNativeTarget "ShareExtension" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
D25D4D7B2EFF41DB0029F805 /* Debug */,
|
||||||
|
D25D4D7C2EFF41DB0029F805 /* Release */,
|
||||||
|
D25D4D7D2EFF41DB0029F805 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
};
|
};
|
||||||
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import Flutter
|
||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
import UserNotifications
|
import UserNotifications
|
||||||
|
import flutter_sharing_intent
|
||||||
|
|
||||||
@main
|
@main
|
||||||
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
|
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
|
||||||
|
|
@ -14,6 +15,17 @@ import UserNotifications
|
||||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
|
||||||
|
|
||||||
|
let sharingIntent = SwiftFlutterSharingIntentPlugin.instance
|
||||||
|
if sharingIntent.hasSameSchemePrefix(url: url) {
|
||||||
|
return sharingIntent.application(app, open: url, options: options)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proceed url handling for other Flutter libraries like app_links
|
||||||
|
return super.application(app, open: url, options:options)
|
||||||
|
}
|
||||||
|
|
||||||
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
|
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
|
||||||
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
|
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -87,9 +87,21 @@
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
</array>
|
||||||
<!--Disable Firebase Telemetry-->
|
|
||||||
<key>firebase_performance_collection_deactivated</key>
|
<key>firebase_performance_collection_deactivated</key>
|
||||||
<true/>
|
<true/>
|
||||||
<!--...-->
|
|
||||||
|
<key>AppGroupId</key>
|
||||||
|
<string>$(CUSTOM_GROUP_ID)</string>
|
||||||
|
<key>CFBundleURLTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Editor</string>
|
||||||
|
<key>CFBundleURLSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>SharingMedia-$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
|
||||||
20
ios/Runner/RunnerDebug.entitlements
Normal file
20
ios/Runner/RunnerDebug.entitlements
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>aps-environment</key>
|
||||||
|
<string>development</string>
|
||||||
|
<key>com.apple.developer.associated-domains</key>
|
||||||
|
<array>
|
||||||
|
<string>applinks:me.twonly.eu</string>
|
||||||
|
</array>
|
||||||
|
<key>com.apple.security.application-groups</key>
|
||||||
|
<array>
|
||||||
|
<string>group.eu.twonly.shareIntent</string>
|
||||||
|
</array>
|
||||||
|
<key>keychain-access-groups</key>
|
||||||
|
<array>
|
||||||
|
<string>$(AppIdentifierPrefix)eu.twonly.shared</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
24
ios/ShareExtension/Base.lproj/MainInterface.storyboard
Normal file
24
ios/ShareExtension/Base.lproj/MainInterface.storyboard
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="j1y-V4-xli">
|
||||||
|
<dependencies>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
|
||||||
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes>
|
||||||
|
<!--Share View Controller-->
|
||||||
|
<scene sceneID="ceB-am-kn3">
|
||||||
|
<objects>
|
||||||
|
<viewController id="j1y-V4-xli" customClass="ShareViewController" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
|
<view key="view" opaque="NO" contentMode="scaleToFill" id="wbc-yd-nQP">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<viewLayoutGuide key="safeArea" id="1Xd-am-t49"/>
|
||||||
|
</view>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="CEy-Cv-SGf" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
</scene>
|
||||||
|
</scenes>
|
||||||
|
</document>
|
||||||
553
ios/ShareExtension/FSIShareViewController.swift
Normal file
553
ios/ShareExtension/FSIShareViewController.swift
Normal file
|
|
@ -0,0 +1,553 @@
|
||||||
|
// SOURCE: https://github.com/bhagat-techind/flutter_sharing_intent/blob/main/example/ios/Share%20Extension/FSIShareViewController.swift
|
||||||
|
|
||||||
|
// FSIShareViewController.swift
|
||||||
|
// Merged, optimized controller: uses RSI architecture with all FSI features preserved
|
||||||
|
// Uses model name `SharingFile` (same fields as SharedMediaFile) where `value` = path
|
||||||
|
|
||||||
|
import AVFoundation
|
||||||
|
import MobileCoreServices
|
||||||
|
import Social
|
||||||
|
import UIKit
|
||||||
|
import UniformTypeIdentifiers
|
||||||
|
|
||||||
|
public let kSchemePrefix = "SharingMedia"
|
||||||
|
public let kUserDefaultsKey = "SharingKey"
|
||||||
|
public let kUserDefaultsMessageKey = "SharingMessageKey"
|
||||||
|
public let kAppGroupIdKey = "AppGroupId"
|
||||||
|
public let kAppChannel = "flutter_sharing_intent"
|
||||||
|
|
||||||
|
@available(swift, introduced: 5.0)
|
||||||
|
open class FSIShareViewController: SLComposeServiceViewController {
|
||||||
|
// MARK: - Config
|
||||||
|
private(set) var hostAppBundleIdentifier: String = ""
|
||||||
|
private(set) var appGroupId: String = ""
|
||||||
|
|
||||||
|
// Results
|
||||||
|
private var sharedMedia: [SharingFile] = []
|
||||||
|
|
||||||
|
// Debug
|
||||||
|
private let debugLogs = false
|
||||||
|
|
||||||
|
// MARK: - Lifecycle
|
||||||
|
open override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
loadIds()
|
||||||
|
}
|
||||||
|
|
||||||
|
open override func isContentValid() -> Bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
open override func didSelectPost() {
|
||||||
|
if self.sharedMedia.isEmpty {
|
||||||
|
if let text = self.contentText, !text.isEmpty {
|
||||||
|
self.sharedMedia.append(
|
||||||
|
SharingFile(value: text, thumbnail: nil, duration: nil, type: .text)
|
||||||
|
)
|
||||||
|
self.saveAndRedirect(message: text)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.completeAndExit()
|
||||||
|
} else {
|
||||||
|
self.saveAndRedirect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open override func viewDidAppear(_ animated: Bool) {
|
||||||
|
super.viewDidAppear(animated)
|
||||||
|
// Process attachments automatically on appear like original FSI
|
||||||
|
processAttachments()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Load Ids
|
||||||
|
private func loadIds() {
|
||||||
|
let shareExtId = Bundle.main.bundleIdentifier ?? ""
|
||||||
|
if let idx = shareExtId.lastIndex(of: ".") {
|
||||||
|
hostAppBundleIdentifier = String(shareExtId[..<idx])
|
||||||
|
} else {
|
||||||
|
hostAppBundleIdentifier = shareExtId
|
||||||
|
}
|
||||||
|
let custom = Bundle.main.object(forInfoDictionaryKey: kAppGroupIdKey) as? String
|
||||||
|
appGroupId = custom ?? "group.\(hostAppBundleIdentifier)"
|
||||||
|
log("loaded host=\(hostAppBundleIdentifier) group=\(appGroupId)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Attachment processing (clean RSI style, preserve FSI features)
|
||||||
|
private func processAttachments() {
|
||||||
|
guard let content = extensionContext?.inputItems.first as? NSExtensionItem else {
|
||||||
|
completeAndExit()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let attachments = content.attachments, !attachments.isEmpty else {
|
||||||
|
completeAndExit()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use DispatchGroup to wait for async loads
|
||||||
|
let group = DispatchGroup()
|
||||||
|
for (index, provider) in attachments.enumerated() {
|
||||||
|
group.enter()
|
||||||
|
// Try all SharedMediaType options similar to RSI but preserve explicit FSI order
|
||||||
|
if provider.isImage {
|
||||||
|
provider.loadItem(forTypeIdentifier: UType.image, options: nil) { [weak self] data, error in
|
||||||
|
defer { group.leave() }
|
||||||
|
guard let self = self, error == nil else { self?.dismissWithError(); return }
|
||||||
|
self.handleImageItem(data: data, index: index, total: attachments.count)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if provider.isMovie {
|
||||||
|
provider.loadItem(forTypeIdentifier: UType.movie, options: nil) { [weak self] data, error in
|
||||||
|
defer { group.leave() }
|
||||||
|
guard let self = self, error == nil else { self?.dismissWithError(); return }
|
||||||
|
self.handleVideoItem(data: data, index: index, total: attachments.count)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if provider.isFile {
|
||||||
|
provider.loadItem(forTypeIdentifier: UType.fileURL, options: nil) { [weak self] data, error in
|
||||||
|
defer { group.leave() }
|
||||||
|
guard let self = self, error == nil else { self?.dismissWithError(); return }
|
||||||
|
self.handleFileItem(data: data, index: index, total: attachments.count)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if provider.isURL {
|
||||||
|
provider.loadItem(forTypeIdentifier: UType.url, options: nil) { [weak self] data, error in
|
||||||
|
defer { group.leave() }
|
||||||
|
guard let self = self, error == nil else { self?.dismissWithError(); return }
|
||||||
|
self.handleUrlItem(data: data, index: index, total: attachments.count)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if provider.isText {
|
||||||
|
let id = provider.hasItemConformingToTypeIdentifier(UType.plainText)
|
||||||
|
? UType.plainText
|
||||||
|
: UType.text
|
||||||
|
provider.loadItem(forTypeIdentifier: id, options: nil) { [weak self] data, error in
|
||||||
|
defer { group.leave() }
|
||||||
|
guard let self = self, error == nil else { self?.dismissWithError(); return }
|
||||||
|
self.handleTextItem(data: data, index: index, total: attachments.count)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if provider.isData {
|
||||||
|
provider.loadItem(forTypeIdentifier: UType.data, options: nil) { [weak self] data, error in
|
||||||
|
defer { group.leave() }
|
||||||
|
guard let self = self, error == nil else { self?.dismissWithError(); return }
|
||||||
|
self.handleFileItem(data: data, index: index, total: attachments.count)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if provider.isItem {
|
||||||
|
provider.loadItem(forTypeIdentifier: UType.item, options: nil) { [weak self] data, error in
|
||||||
|
defer { group.leave() }
|
||||||
|
guard let self = self, error == nil else { self?.dismissWithError(); return }
|
||||||
|
self.handleFileItem(data: data, index: index, total: attachments.count)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
log("Unknown provider type: \(provider.registeredTypeIdentifiers)")
|
||||||
|
|
||||||
|
// Unknown type: just leave
|
||||||
|
group.leave()
|
||||||
|
}
|
||||||
|
|
||||||
|
group.notify(queue: .main) { [weak self] in
|
||||||
|
guard let self = self else { return }
|
||||||
|
// if we have media -> media, else fallback to complete
|
||||||
|
if !self.sharedMedia.isEmpty {
|
||||||
|
self.saveAndRedirect()
|
||||||
|
} else {
|
||||||
|
print("FSIShare: No shared media → stopping.")
|
||||||
|
self.completeAndExit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Individual handlers (preserve FSI behavior)
|
||||||
|
private func handleTextItem(data: NSSecureCoding?, index: Int, total: Int) {
|
||||||
|
if let s = data as? String {
|
||||||
|
sharedMedia.append(SharingFile(value: s, thumbnail: nil, duration: nil, type: .text))
|
||||||
|
} else if let url = data as? URL {
|
||||||
|
sharedMedia.append(SharingFile(value: url.absoluteString, thumbnail: nil, duration: nil, type: .url))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private func handleUrlItem(data: NSSecureCoding?, index: Int, total: Int) {
|
||||||
|
if let url = data as? URL {
|
||||||
|
sharedMedia.append(SharingFile(value: url.absoluteString, thumbnail: nil, duration: nil, type: .url))
|
||||||
|
} else if let s = data as? String {
|
||||||
|
sharedMedia.append(SharingFile(value: s, thumbnail: nil, duration: nil, type: .text))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private func handleImageItem(data: NSSecureCoding?, index: Int, total: Int) {
|
||||||
|
// data can be URL, UIImage, or Data
|
||||||
|
if let url = data as? URL {
|
||||||
|
let filename = getFileName(from: url, type: .image)
|
||||||
|
if let dst = containerURL()?.appendingPathComponent(filename) {
|
||||||
|
if copyFile(at: url, to: dst) {
|
||||||
|
sharedMedia.append(SharingFile(value: dst.absoluteString, mimeType: url.mimeType(), thumbnail: nil, duration: nil, type: .image))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let img = data as? UIImage {
|
||||||
|
if let saved = writeTempImage(img) {
|
||||||
|
sharedMedia.append(saved)
|
||||||
|
}
|
||||||
|
} else if let raw = data as? Data, let img = UIImage(data: raw) {
|
||||||
|
if let saved = writeTempImage(img) {
|
||||||
|
sharedMedia.append(saved)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private func handleVideoItem(data: NSSecureCoding?, index: Int, total: Int) {
|
||||||
|
if let url = data as? URL {
|
||||||
|
let filename = getFileName(from: url, type: .video)
|
||||||
|
if let dst = containerURL()?.appendingPathComponent(filename) {
|
||||||
|
if copyFile(at: url, to: dst) {
|
||||||
|
if let m = getSharedMediaFile(forVideo: dst) {
|
||||||
|
sharedMedia.append(m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private func handleFileItem(data: NSSecureCoding?, index: Int, total: Int) {
|
||||||
|
if let url = data as? URL {
|
||||||
|
let filename = getFileName(from: url, type: .file)
|
||||||
|
if let dst = containerURL()?.appendingPathComponent(filename) {
|
||||||
|
if copyFile(at: url, to: dst) {
|
||||||
|
sharedMedia.append(SharingFile(value: dst.absoluteString, mimeType: url.mimeType(), thumbnail: nil, duration: nil, type: .file))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if let raw = data as? Data {
|
||||||
|
let filename = "File_\(UUID().uuidString)"
|
||||||
|
if let dst = containerURL()?.appendingPathComponent(filename) {
|
||||||
|
do {
|
||||||
|
try raw.write(to: dst)
|
||||||
|
sharedMedia.append(SharingFile(value: dst.absoluteString, mimeType: "application/octet-stream", thumbnail: nil, duration: nil, type: .file))
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Helpers: write temp image
|
||||||
|
private func writeTempImage(_ image: UIImage) -> SharingFile? {
|
||||||
|
guard let container = containerURL() else { return nil }
|
||||||
|
let tempName = "TempImage_\(UUID().uuidString).png"
|
||||||
|
let dst = container.appendingPathComponent(tempName)
|
||||||
|
do {
|
||||||
|
if let d = image.pngData() {
|
||||||
|
try d.write(to: dst)
|
||||||
|
let decoded = dst.absoluteString.removingPercentEncoding ?? dst.absoluteString
|
||||||
|
return SharingFile(value: decoded, mimeType: "image/png", thumbnail: nil, duration: nil, type: .image)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
log("writeTempImage error: \(error)")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private func saveAndRedirect(message: String? = nil) {
|
||||||
|
let ud = UserDefaults(suiteName: appGroupId)
|
||||||
|
if !sharedMedia.isEmpty {
|
||||||
|
if let data = try? JSONEncoder().encode(sharedMedia) {
|
||||||
|
ud?.set(data, forKey: kUserDefaultsKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ud?.set(message, forKey: kUserDefaultsMessageKey)
|
||||||
|
ud?.synchronize()
|
||||||
|
redirectToHostApp()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private func redirectToHostApp() {
|
||||||
|
// kept for compatibility (RSI style)
|
||||||
|
loadIds()
|
||||||
|
// let raw = "\(kSchemePrefix)-\(hostAppBundleIdentifier):share"
|
||||||
|
let raw = "\(kSchemePrefix)-\(hostAppBundleIdentifier)://dataUrl=\(kUserDefaultsKey)"
|
||||||
|
guard let url = URL(string: raw.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? raw) else { completeAndExit(); return }
|
||||||
|
|
||||||
|
var responder: UIResponder? = self
|
||||||
|
if #available(iOS 18.0, *) {
|
||||||
|
while responder != nil {
|
||||||
|
if let app = responder as? UIApplication { app.open(url, options: [:], completionHandler: nil) }
|
||||||
|
responder = responder?.next
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let sel = sel_registerName("openURL:")
|
||||||
|
while responder != nil {
|
||||||
|
if responder?.responds(to: sel) ?? false { _ = responder?.perform(sel, with: url) }
|
||||||
|
responder = responder?.next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - File / thumbnail / metadata helpers
|
||||||
|
func getExtension(from url: URL, type: SharingFileType) -> String {
|
||||||
|
let parts = url.lastPathComponent.components(separatedBy: ".")
|
||||||
|
var ex: String? = nil
|
||||||
|
if parts.count > 1 { ex = parts.last }
|
||||||
|
if ex == nil {
|
||||||
|
switch type {
|
||||||
|
case .image: ex = "png"
|
||||||
|
case .video: ex = "mp4"
|
||||||
|
case .file: ex = "txt"
|
||||||
|
case .text: ex = "txt"
|
||||||
|
case .url: ex = "txt"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ex ?? "bin"
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFileName(from url: URL, type: SharingFileType) -> String {
|
||||||
|
var name = url.lastPathComponent
|
||||||
|
if name.isEmpty { name = UUID().uuidString + "." + getExtension(from: url, type: type) }
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyFile(at srcURL: URL, to dstURL: URL) -> Bool {
|
||||||
|
do {
|
||||||
|
if FileManager.default.fileExists(atPath: dstURL.path) { try FileManager.default.removeItem(at: dstURL) }
|
||||||
|
try FileManager.default.copyItem(at: srcURL, to: dstURL)
|
||||||
|
return true
|
||||||
|
} catch {
|
||||||
|
log("copyFile error: \(error)")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func getSharedMediaFile(forVideo: URL) -> SharingFile? {
|
||||||
|
let asset = AVAsset(url: forVideo)
|
||||||
|
let duration = (CMTimeGetSeconds(asset.duration) * 1000).rounded()
|
||||||
|
let thumbnailPath = getThumbnailPath(for: forVideo)
|
||||||
|
|
||||||
|
if FileManager.default.fileExists(atPath: thumbnailPath.path) {
|
||||||
|
return SharingFile(value: forVideo.absoluteString, mimeType: forVideo.mimeType(), thumbnail: thumbnailPath.absoluteString, duration: Int(duration), type: .video)
|
||||||
|
}
|
||||||
|
|
||||||
|
let gen = AVAssetImageGenerator(asset: asset)
|
||||||
|
gen.appliesPreferredTrackTransform = true
|
||||||
|
gen.maximumSize = CGSize(width: 360, height: 360)
|
||||||
|
|
||||||
|
// Use first second or zero
|
||||||
|
let time = CMTime(seconds: min(1.0, CMTimeGetSeconds(asset.duration)), preferredTimescale: 600)
|
||||||
|
do {
|
||||||
|
let cg = try gen.copyCGImage(at: time, actualTime: nil)
|
||||||
|
if let data = UIImage(cgImage: cg).jpegData(compressionQuality: 0.8) {
|
||||||
|
try data.write(to: thumbnailPath)
|
||||||
|
return SharingFile(value: forVideo.absoluteString, mimeType: forVideo.mimeType(), thumbnail: thumbnailPath.absoluteString, duration: Int(duration), type: .video)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
log("getSharedMediaFile thumbnail error: \(error)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback
|
||||||
|
return SharingFile(value: forVideo.absoluteString, mimeType: forVideo.mimeType(), thumbnail: nil, duration: Int(duration), type: .video)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func getThumbnailPath(for url: URL) -> URL {
|
||||||
|
guard let container = containerURL() else { fatalError("App group not configured or missing") }
|
||||||
|
let fileName = Data(url.lastPathComponent.utf8).base64EncodedString().replacingOccurrences(of: "=", with: "")
|
||||||
|
return container.appendingPathComponent("\(fileName).jpg")
|
||||||
|
}
|
||||||
|
|
||||||
|
private func containerURL() -> URL? {
|
||||||
|
FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func completeAndExit() {
|
||||||
|
extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func dismissWithError() {
|
||||||
|
log("[ERROR] Error loading data!")
|
||||||
|
let alert = UIAlertController(title: "Error", message: "Error loading data", preferredStyle: .alert)
|
||||||
|
alert.addAction(UIAlertAction(title: "OK", style: .cancel) { _ in self.dismiss(animated: true, completion: nil) })
|
||||||
|
present(alert, animated: true, completion: nil)
|
||||||
|
extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func writeTempFile(_ image: UIImage, to dstURL: URL) -> Bool {
|
||||||
|
do {
|
||||||
|
if FileManager.default.fileExists(atPath: dstURL.path) { try FileManager.default.removeItem(at: dstURL) }
|
||||||
|
let pngData = image.pngData()
|
||||||
|
try pngData?.write(to: dstURL)
|
||||||
|
return true
|
||||||
|
} catch (let error) {
|
||||||
|
log("writeTempFile error: \(error)")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func saveToUserDefaults(data: [SharingFile]) {
|
||||||
|
let ud = UserDefaults(suiteName: appGroupId)
|
||||||
|
if let enc = try? JSONEncoder().encode(data) { ud?.set(enc, forKey: kUserDefaultsKey); ud?.synchronize() }
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Logging
|
||||||
|
private func log(_ s: String) { if debugLogs { print("[FSIShareVC] \(s)") } }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Extensions
|
||||||
|
extension URL {
|
||||||
|
func mimeType() -> String {
|
||||||
|
if #available(iOS 14.0, *) {
|
||||||
|
if let ut = UTType(filenameExtension: self.pathExtension), let m = ut.preferredMIMEType { return m }
|
||||||
|
} else {
|
||||||
|
if let uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, self.pathExtension as NSString, nil)?.takeRetainedValue() {
|
||||||
|
if let mimetype = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType)?.takeRetainedValue() { return mimetype as String }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "application/octet-stream"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension NSItemProvider {
|
||||||
|
var isImage: Bool { return hasItemConformingToTypeIdentifier(UType.image) }
|
||||||
|
var isMovie: Bool { return hasItemConformingToTypeIdentifier(UType.movie) }
|
||||||
|
var isText: Bool {
|
||||||
|
hasItemConformingToTypeIdentifier(UType.plainText) || hasItemConformingToTypeIdentifier(UType.text)
|
||||||
|
}
|
||||||
|
var isURL: Bool { return hasItemConformingToTypeIdentifier(UType.url) }
|
||||||
|
var isFile: Bool { return hasItemConformingToTypeIdentifier(UType.fileURL) }
|
||||||
|
var isData:Bool { return hasItemConformingToTypeIdentifier(UType.data) }
|
||||||
|
var isItem: Bool { hasItemConformingToTypeIdentifier(UType.item) }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Array {
|
||||||
|
subscript(safe index: UInt) -> Element? { return Int(index) < count ? self[Int(index)] : nil }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class SharingFile: Codable {
|
||||||
|
var value: String
|
||||||
|
var mimeType: String?
|
||||||
|
var thumbnail: String?; // video thumbnail
|
||||||
|
var duration: Int?; // video duration in milliseconds
|
||||||
|
var type: SharingFileType;
|
||||||
|
var message: String? // post message
|
||||||
|
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case value
|
||||||
|
case mimeType
|
||||||
|
case thumbnail
|
||||||
|
case duration
|
||||||
|
case type
|
||||||
|
case message
|
||||||
|
}
|
||||||
|
|
||||||
|
init(value: String, mimeType: String? = nil, thumbnail: String?, duration: Int?,
|
||||||
|
type: SharingFileType, message: String?=nil) {
|
||||||
|
self.value = value
|
||||||
|
self.mimeType = mimeType
|
||||||
|
self.thumbnail = thumbnail
|
||||||
|
self.duration = duration
|
||||||
|
self.type = type
|
||||||
|
self.message = message
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug method to print out SharedMediaFile details in the console
|
||||||
|
func toString() {
|
||||||
|
print("[SharingFile] \n\tvalue: \(self.value)\n\tthumbnail: \(self.thumbnail ?? "--" )\n\tduration: \(self.duration ?? 0)\n\ttype: \(self.type)\n\tmimeType: \(String(describing: self.mimeType))\n\tmessage: \(String(describing: self.message))")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
enum SharingFileType: Int, Codable {
|
||||||
|
case text
|
||||||
|
case url
|
||||||
|
case image
|
||||||
|
case video
|
||||||
|
case file
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unified UTType → works on iOS 11–18
|
||||||
|
enum UType {
|
||||||
|
static var image: String {
|
||||||
|
if #available(iOS 14.0, *) {
|
||||||
|
return UTType.image.identifier
|
||||||
|
} else {
|
||||||
|
return kUTTypeImage as String // old API
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static var movie: String {
|
||||||
|
if #available(iOS 14.0, *) {
|
||||||
|
return UTType.movie.identifier
|
||||||
|
} else {
|
||||||
|
return kUTTypeMovie as String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static var url: String {
|
||||||
|
if #available(iOS 14.0, *) {
|
||||||
|
return UTType.url.identifier
|
||||||
|
} else {
|
||||||
|
return kUTTypeURL as String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static var fileURL: String {
|
||||||
|
if #available(iOS 14.0, *) {
|
||||||
|
return UTType.fileURL.identifier
|
||||||
|
} else {
|
||||||
|
return kUTTypeFileURL as String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static var text: String {
|
||||||
|
if #available(iOS 14.0, *) {
|
||||||
|
return UTType.text.identifier
|
||||||
|
} else {
|
||||||
|
return kUTTypeText as String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static var plainText: String {
|
||||||
|
if #available(iOS 14.0, *) {
|
||||||
|
return UTType.plainText.identifier
|
||||||
|
} else {
|
||||||
|
return kUTTypePlainText as String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static var data: String {
|
||||||
|
if #available(iOS 14.0, *) {
|
||||||
|
return UTType.data.identifier
|
||||||
|
} else {
|
||||||
|
return kUTTypeData as String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static var item: String {
|
||||||
|
if #available(iOS 14.0, *) {
|
||||||
|
return UTType.item.identifier
|
||||||
|
} else {
|
||||||
|
return kUTTypeItem as String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
35
ios/ShareExtension/Info.plist
Normal file
35
ios/ShareExtension/Info.plist
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>AppGroupId</key>
|
||||||
|
<string>$(CUSTOM_GROUP_ID)</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
|
<key>NSExtension</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSExtensionMainStoryboard</key>
|
||||||
|
<string>MainInterface</string>
|
||||||
|
<key>NSExtensionPointIdentifier</key>
|
||||||
|
<string>com.apple.share-services</string>
|
||||||
|
<key>NSExtensionAttributes</key>
|
||||||
|
<dict>
|
||||||
|
<key>PHSupportedMediaTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>Video</string>
|
||||||
|
<string>Image</string>
|
||||||
|
</array>
|
||||||
|
|
||||||
|
<key>NSExtensionActivationRule</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>NSExtensionActivationSupportsImageWithMaxCount</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>NSExtensionActivationSupportsMovieWithMaxCount</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
10
ios/ShareExtension/ShareExtensionDebug.entitlements
Normal file
10
ios/ShareExtension/ShareExtensionDebug.entitlements
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.security.application-groups</key>
|
||||||
|
<array>
|
||||||
|
<string>group.eu.twonly.shareIntent</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
3
ios/ShareExtension/ShareViewController.swift
Normal file
3
ios/ShareExtension/ShareViewController.swift
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
class ShareViewController: FSIShareViewController {
|
||||||
|
|
||||||
|
}
|
||||||
22
lib/app.dart
22
lib/app.dart
|
|
@ -1,10 +1,12 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/localization/generated/app_localizations.dart';
|
import 'package:twonly/src/localization/generated/app_localizations.dart';
|
||||||
import 'package:twonly/src/providers/connection.provider.dart';
|
import 'package:twonly/src/providers/connection.provider.dart';
|
||||||
|
import 'package:twonly/src/providers/purchases.provider.dart';
|
||||||
import 'package:twonly/src/providers/settings.provider.dart';
|
import 'package:twonly/src/providers/settings.provider.dart';
|
||||||
import 'package:twonly/src/services/subscription.service.dart';
|
import 'package:twonly/src/services/subscription.service.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
@ -39,8 +41,8 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||||
await setUserPlan();
|
await setUserPlan();
|
||||||
};
|
};
|
||||||
|
|
||||||
globalCallbackUpdatePlan = (SubscriptionPlan plan) async {
|
globalCallbackUpdatePlan = (SubscriptionPlan plan) {
|
||||||
await context.read<CustomChangeProvider>().updatePlan(plan);
|
context.read<PurchasesProvider>().updatePlan(plan);
|
||||||
};
|
};
|
||||||
|
|
||||||
unawaited(initAsync());
|
unawaited(initAsync());
|
||||||
|
|
@ -50,7 +52,7 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||||
final user = await getUser();
|
final user = await getUser();
|
||||||
if (user != null && mounted) {
|
if (user != null && mounted) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
await context.read<CustomChangeProvider>().updatePlan(
|
context.read<PurchasesProvider>().updatePlan(
|
||||||
planFromString(user.subscriptionPlan),
|
planFromString(user.subscriptionPlan),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -59,7 +61,7 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||||
|
|
||||||
Future<void> initAsync() async {
|
Future<void> initAsync() async {
|
||||||
await setUserPlan();
|
await setUserPlan();
|
||||||
await apiService.connect(force: true);
|
await apiService.connect();
|
||||||
await apiService.listenToNetworkChanges();
|
await apiService.listenToNetworkChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,7 +72,7 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||||
if (wasPaused) {
|
if (wasPaused) {
|
||||||
globalIsAppInBackground = false;
|
globalIsAppInBackground = false;
|
||||||
twonlyDB.markUpdated();
|
twonlyDB.markUpdated();
|
||||||
unawaited(apiService.connect(force: true));
|
unawaited(apiService.connect());
|
||||||
}
|
}
|
||||||
} else if (state == AppLifecycleState.paused) {
|
} else if (state == AppLifecycleState.paused) {
|
||||||
wasPaused = true;
|
wasPaused = true;
|
||||||
|
|
@ -92,6 +94,7 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||||
listenable: context.watch<SettingsChangeProvider>(),
|
listenable: context.watch<SettingsChangeProvider>(),
|
||||||
builder: (BuildContext context, Widget? child) {
|
builder: (BuildContext context, Widget? child) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
|
scaffoldMessengerKey: globalRootScaffoldMessengerKey,
|
||||||
restorationScopeId: 'app',
|
restorationScopeId: 'app',
|
||||||
localizationsDelegates: const [
|
localizationsDelegates: const [
|
||||||
AppLocalizations.delegate,
|
AppLocalizations.delegate,
|
||||||
|
|
@ -156,11 +159,13 @@ class _AppMainWidgetState extends State<AppMainWidget> {
|
||||||
bool _showOnboarding = true;
|
bool _showOnboarding = true;
|
||||||
bool _isLoaded = false;
|
bool _isLoaded = false;
|
||||||
bool _skipBackup = false;
|
bool _skipBackup = false;
|
||||||
|
int _initialPage = 0;
|
||||||
|
|
||||||
(Future<int>?, bool) _proofOfWork = (null, false);
|
(Future<int>?, bool) _proofOfWork = (null, false);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
_initialPage = widget.initialPage;
|
||||||
initAsync();
|
initAsync();
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
@ -172,6 +177,9 @@ class _AppMainWidgetState extends State<AppMainWidget> {
|
||||||
if (gUser.appVersion < 62) {
|
if (gUser.appVersion < 62) {
|
||||||
_showDatabaseMigration = true;
|
_showDatabaseMigration = true;
|
||||||
}
|
}
|
||||||
|
if (!gUser.startWithCameraOpen) {
|
||||||
|
_initialPage = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_isUserCreated && !_showDatabaseMigration) {
|
if (!_isUserCreated && !_showDatabaseMigration) {
|
||||||
|
|
@ -204,7 +212,7 @@ class _AppMainWidgetState extends State<AppMainWidget> {
|
||||||
if (_showDatabaseMigration) {
|
if (_showDatabaseMigration) {
|
||||||
child = const DatabaseMigrationView();
|
child = const DatabaseMigrationView();
|
||||||
} else if (_isUserCreated) {
|
} else if (_isUserCreated) {
|
||||||
if (gUser.twonlySafeBackup == null && !_skipBackup) {
|
if (gUser.twonlySafeBackup == null && !_skipBackup && kReleaseMode) {
|
||||||
child = TwonlyIdentityBackupView(
|
child = TwonlyIdentityBackupView(
|
||||||
callBack: () {
|
callBack: () {
|
||||||
_skipBackup = true;
|
_skipBackup = true;
|
||||||
|
|
@ -213,7 +221,7 @@ class _AppMainWidgetState extends State<AppMainWidget> {
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
child = HomeView(
|
child = HomeView(
|
||||||
initialPage: widget.initialPage,
|
initialPage: _initialPage,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (_showOnboarding) {
|
} else if (_showOnboarding) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import 'dart:ui';
|
|
||||||
|
|
||||||
import 'package:camera/camera.dart';
|
import 'package:camera/camera.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/model/json/userdata.dart';
|
import 'package:twonly/src/model/json/userdata.dart';
|
||||||
import 'package:twonly/src/services/api.service.dart';
|
import 'package:twonly/src/services/api.service.dart';
|
||||||
|
|
@ -37,3 +36,6 @@ bool globalAllowErrorTrackingViaSentry = false;
|
||||||
|
|
||||||
late String globalApplicationCacheDirectory;
|
late String globalApplicationCacheDirectory;
|
||||||
late String globalApplicationSupportDirectory;
|
late String globalApplicationSupportDirectory;
|
||||||
|
|
||||||
|
final GlobalKey<ScaffoldMessengerState> globalRootScaffoldMessengerKey =
|
||||||
|
GlobalKey<ScaffoldMessengerState>();
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,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/providers/connection.provider.dart';
|
import 'package:twonly/src/providers/connection.provider.dart';
|
||||||
import 'package:twonly/src/providers/image_editor.provider.dart';
|
import 'package:twonly/src/providers/image_editor.provider.dart';
|
||||||
|
import 'package:twonly/src/providers/purchases.provider.dart';
|
||||||
import 'package:twonly/src/providers/settings.provider.dart';
|
import 'package:twonly/src/providers/settings.provider.dart';
|
||||||
import 'package:twonly/src/services/api.service.dart';
|
import 'package:twonly/src/services/api.service.dart';
|
||||||
import 'package:twonly/src/services/api/mediafiles/media_background.service.dart';
|
import 'package:twonly/src/services/api/mediafiles/media_background.service.dart';
|
||||||
|
|
@ -25,6 +26,8 @@ import 'package:twonly/src/utils/storage.dart';
|
||||||
void main() async {
|
void main() async {
|
||||||
SentryWidgetsFlutterBinding.ensureInitialized();
|
SentryWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
await initFCMService();
|
||||||
|
|
||||||
final user = await getUser();
|
final user = await getUser();
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
gUser = user;
|
gUser = user;
|
||||||
|
|
@ -43,8 +46,6 @@ void main() async {
|
||||||
unawaited(performTwonlySafeBackup());
|
unawaited(performTwonlySafeBackup());
|
||||||
}
|
}
|
||||||
|
|
||||||
await initFCMService();
|
|
||||||
|
|
||||||
globalApplicationCacheDirectory = (await getApplicationCacheDirectory()).path;
|
globalApplicationCacheDirectory = (await getApplicationCacheDirectory()).path;
|
||||||
globalApplicationSupportDirectory =
|
globalApplicationSupportDirectory =
|
||||||
(await getApplicationSupportDirectory()).path;
|
(await getApplicationSupportDirectory()).path;
|
||||||
|
|
@ -54,9 +55,7 @@ void main() async {
|
||||||
final settingsController = SettingsChangeProvider();
|
final settingsController = SettingsChangeProvider();
|
||||||
|
|
||||||
await settingsController.loadSettings();
|
await settingsController.loadSettings();
|
||||||
await SystemChrome.setPreferredOrientations(
|
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
||||||
[DeviceOrientation.portraitUp],
|
|
||||||
);
|
|
||||||
|
|
||||||
unawaited(setupPushNotification());
|
unawaited(setupPushNotification());
|
||||||
|
|
||||||
|
|
@ -79,6 +78,7 @@ void main() async {
|
||||||
ChangeNotifierProvider(create: (_) => settingsController),
|
ChangeNotifierProvider(create: (_) => settingsController),
|
||||||
ChangeNotifierProvider(create: (_) => CustomChangeProvider()),
|
ChangeNotifierProvider(create: (_) => CustomChangeProvider()),
|
||||||
ChangeNotifierProvider(create: (_) => ImageEditorProvider()),
|
ChangeNotifierProvider(create: (_) => ImageEditorProvider()),
|
||||||
|
ChangeNotifierProvider(create: (_) => PurchasesProvider()),
|
||||||
],
|
],
|
||||||
child: const App(),
|
child: const App(),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
6
lib/src/constants/subscription.keys.dart
Normal file
6
lib/src/constants/subscription.keys.dart
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
class SubscriptionKeys {
|
||||||
|
static const String proMonthly = 'pro_monthly';
|
||||||
|
static const String proYearly = 'pro_yearly';
|
||||||
|
// static const String familyMonthly = 'family_monthly';
|
||||||
|
static const String familyYearly = 'family_yearly';
|
||||||
|
}
|
||||||
|
|
@ -42,8 +42,15 @@ class ContactsDao extends DatabaseAccessor<TwonlyDB> with _$ContactsDaoMixin {
|
||||||
.getSingleOrNull();
|
.getSingleOrNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Contact>> getContactsByUsername(String username) async {
|
Future<List<Contact>> getContactsByUsername(
|
||||||
return (select(contacts)..where((t) => t.username.equals(username))).get();
|
String username, {
|
||||||
|
String username2 = '_______',
|
||||||
|
}) async {
|
||||||
|
return (select(contacts)
|
||||||
|
..where(
|
||||||
|
(t) => t.username.equals(username) | t.username.equals(username2),
|
||||||
|
))
|
||||||
|
.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteContactByUserId(int userId) {
|
Future<void> deleteContactByUserId(int userId) {
|
||||||
|
|
@ -58,7 +65,8 @@ class ContactsDao extends DatabaseAccessor<TwonlyDB> with _$ContactsDaoMixin {
|
||||||
.write(updatedValues);
|
.write(updatedValues);
|
||||||
if (updatedValues.blocked.present ||
|
if (updatedValues.blocked.present ||
|
||||||
updatedValues.displayName.present ||
|
updatedValues.displayName.present ||
|
||||||
updatedValues.nickName.present) {
|
updatedValues.nickName.present ||
|
||||||
|
updatedValues.username.present) {
|
||||||
final contact = await getContactByUserId(userId).getSingleOrNull();
|
final contact = await getContactByUserId(userId).getSingleOrNull();
|
||||||
if (contact != null) {
|
if (contact != null) {
|
||||||
await updatePushUser(contact);
|
await updatePushUser(contact);
|
||||||
|
|
@ -118,7 +126,12 @@ class ContactsDao extends DatabaseAccessor<TwonlyDB> with _$ContactsDaoMixin {
|
||||||
|
|
||||||
Stream<List<Contact>> watchAllAcceptedContacts() {
|
Stream<List<Contact>> watchAllAcceptedContacts() {
|
||||||
return (select(contacts)
|
return (select(contacts)
|
||||||
..where((t) => t.blocked.equals(false) & t.accepted.equals(true)))
|
..where(
|
||||||
|
(t) =>
|
||||||
|
t.blocked.equals(false) &
|
||||||
|
t.accepted.equals(true) &
|
||||||
|
t.accountDeleted.equals(false),
|
||||||
|
))
|
||||||
.watch();
|
.watch();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -386,14 +386,18 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
|
||||||
|
|
||||||
int getFlameCounterFromGroup(Group? group) {
|
int getFlameCounterFromGroup(Group? group) {
|
||||||
if (group == null) return 0;
|
if (group == null) return 0;
|
||||||
if (group.lastMessageSend == null || group.lastMessageReceived == null) {
|
if (group.lastMessageSend == null ||
|
||||||
|
group.lastMessageReceived == null ||
|
||||||
|
group.lastFlameCounterChange == null) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
final startOfToday = DateTime(now.year, now.month, now.day);
|
final startOfToday = DateTime(now.year, now.month, now.day);
|
||||||
final twoDaysAgo = startOfToday.subtract(const Duration(days: 2));
|
final twoDaysAgo = startOfToday.subtract(const Duration(days: 2));
|
||||||
|
final oneDayAgo = startOfToday.subtract(const Duration(days: 1));
|
||||||
if (group.lastMessageSend!.isAfter(twoDaysAgo) &&
|
if (group.lastMessageSend!.isAfter(twoDaysAgo) &&
|
||||||
group.lastMessageReceived!.isAfter(twoDaysAgo)) {
|
group.lastMessageReceived!.isAfter(twoDaysAgo) ||
|
||||||
|
group.lastFlameCounterChange!.isAfter(oneDayAgo)) {
|
||||||
return group.flameCounter + 1;
|
return group.flameCounter + 1;
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,14 @@ class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
|
||||||
.get();
|
.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<MediaFile>> getAllNonHashedStoredMediaFiles() async {
|
||||||
|
return (select(mediaFiles)
|
||||||
|
..where(
|
||||||
|
(t) => t.stored.equals(true) & t.storedFileHash.isNull(),
|
||||||
|
))
|
||||||
|
.get();
|
||||||
|
}
|
||||||
|
|
||||||
Future<List<MediaFile>> getAllMediaFilesPendingUpload() async {
|
Future<List<MediaFile>> getAllMediaFilesPendingUpload() async {
|
||||||
return (select(mediaFiles)
|
return (select(mediaFiles)
|
||||||
..where(
|
..where(
|
||||||
|
|
@ -111,7 +119,10 @@ class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<List<MediaFile>> watchAllStoredMediaFiles() {
|
Stream<List<MediaFile>> watchAllStoredMediaFiles() {
|
||||||
return (select(mediaFiles)..where((t) => t.stored.equals(true))).watch();
|
final query = (select(mediaFiles)..where((t) => t.stored.equals(true)))
|
||||||
|
.join([])
|
||||||
|
..groupBy([mediaFiles.storedFileHash]);
|
||||||
|
return query.map((row) => row.readTable(mediaFiles)).watch();
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<List<MediaFile>> watchNewestMediaFiles() {
|
Stream<List<MediaFile>> watchNewestMediaFiles() {
|
||||||
|
|
|
||||||
|
|
@ -80,10 +80,18 @@ class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Receipt>> getReceiptsNotAckByServer() async {
|
Future<List<Receipt>> getReceiptsForRetransmission() async {
|
||||||
|
final markedRetriesTime = DateTime.now().subtract(
|
||||||
|
const Duration(
|
||||||
|
// give the server time to transmit all messages to the client
|
||||||
|
seconds: 20,
|
||||||
|
),
|
||||||
|
);
|
||||||
return (select(receipts)
|
return (select(receipts)
|
||||||
..where(
|
..where(
|
||||||
(t) => t.ackByServerAt.isNull(),
|
(t) =>
|
||||||
|
t.ackByServerAt.isNull() |
|
||||||
|
t.markForRetry.isSmallerThanValue(markedRetriesTime),
|
||||||
))
|
))
|
||||||
.get();
|
.get();
|
||||||
}
|
}
|
||||||
|
|
@ -100,6 +108,14 @@ class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
|
||||||
.write(updates);
|
.write(updates);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> markMessagesForRetry(int contactId) async {
|
||||||
|
await (update(receipts)..where((c) => c.contactId.equals(contactId))).write(
|
||||||
|
ReceiptsCompanion(
|
||||||
|
markForRetry: Value(DateTime.now()),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<bool> isDuplicated(String receiptId) async {
|
Future<bool> isDuplicated(String receiptId) async {
|
||||||
return await (select(receivedReceipts)
|
return await (select(receivedReceipts)
|
||||||
..where((t) => t.receiptId.equals(receiptId)))
|
..where((t) => t.receiptId.equals(receiptId)))
|
||||||
|
|
|
||||||
1
lib/src/database/schemas/twonly_db/drift_schema_v4.json
Normal file
1
lib/src/database/schemas/twonly_db/drift_schema_v4.json
Normal file
File diff suppressed because one or more lines are too long
1
lib/src/database/schemas/twonly_db/drift_schema_v5.json
Normal file
1
lib/src/database/schemas/twonly_db/drift_schema_v5.json
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -90,8 +90,7 @@ class GroupHistories extends Table {
|
||||||
IntColumn get contactId =>
|
IntColumn get contactId =>
|
||||||
integer().nullable().references(Contacts, #userId)();
|
integer().nullable().references(Contacts, #userId)();
|
||||||
|
|
||||||
IntColumn get affectedContactId =>
|
IntColumn get affectedContactId => integer().nullable()();
|
||||||
integer().nullable().references(Contacts, #userId)();
|
|
||||||
|
|
||||||
TextColumn get oldGroupName => text().nullable()();
|
TextColumn get oldGroupName => text().nullable()();
|
||||||
TextColumn get newGroupName => text().nullable()();
|
TextColumn get newGroupName => text().nullable()();
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,8 @@ class MediaFiles extends Table {
|
||||||
BlobColumn get encryptionMac => blob().nullable()();
|
BlobColumn get encryptionMac => blob().nullable()();
|
||||||
BlobColumn get encryptionNonce => blob().nullable()();
|
BlobColumn get encryptionNonce => blob().nullable()();
|
||||||
|
|
||||||
|
BlobColumn get storedFileHash => blob().nullable()();
|
||||||
|
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ class Receipts extends Table {
|
||||||
BoolColumn get contactWillSendsReceipt =>
|
BoolColumn get contactWillSendsReceipt =>
|
||||||
boolean().withDefault(const Constant(true))();
|
boolean().withDefault(const Constant(true))();
|
||||||
|
|
||||||
|
DateTimeColumn get markForRetry => dateTime().nullable()();
|
||||||
|
|
||||||
DateTimeColumn get ackByServerAt => dateTime().nullable()();
|
DateTimeColumn get ackByServerAt => dateTime().nullable()();
|
||||||
|
|
||||||
IntColumn get retryCount => integer().withDefault(const Constant(0))();
|
IntColumn get retryCount => integer().withDefault(const Constant(0))();
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ class TwonlyDB extends _$TwonlyDB {
|
||||||
TwonlyDB.forTesting(DatabaseConnection super.connection);
|
TwonlyDB.forTesting(DatabaseConnection super.connection);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get schemaVersion => 3;
|
int get schemaVersion => 5;
|
||||||
|
|
||||||
static QueryExecutor _openConnection() {
|
static QueryExecutor _openConnection() {
|
||||||
return driftDatabase(
|
return driftDatabase(
|
||||||
|
|
@ -92,6 +92,24 @@ class TwonlyDB extends _$TwonlyDB {
|
||||||
from2To3: (m, schema) async {
|
from2To3: (m, schema) async {
|
||||||
await m.addColumn(schema.groups, schema.groups.draftMessage);
|
await m.addColumn(schema.groups, schema.groups.draftMessage);
|
||||||
},
|
},
|
||||||
|
from3To4: (m, schema) async {
|
||||||
|
await m.alterTable(
|
||||||
|
TableMigration(
|
||||||
|
schema.groupHistories,
|
||||||
|
columnTransformer: {
|
||||||
|
schema.groupHistories.affectedContactId:
|
||||||
|
schema.groupHistories.affectedContactId,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
from4To5: (m, schema) async {
|
||||||
|
await m.addColumn(schema.receipts, schema.receipts.markForRetry);
|
||||||
|
await m.addColumn(
|
||||||
|
schema.mediaFiles,
|
||||||
|
schema.mediaFiles.storedFileHash,
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1996,6 +1996,12 @@ class $MediaFilesTable extends MediaFiles
|
||||||
late final GeneratedColumn<Uint8List> encryptionNonce =
|
late final GeneratedColumn<Uint8List> encryptionNonce =
|
||||||
GeneratedColumn<Uint8List>('encryption_nonce', aliasedName, true,
|
GeneratedColumn<Uint8List>('encryption_nonce', aliasedName, true,
|
||||||
type: DriftSqlType.blob, requiredDuringInsert: false);
|
type: DriftSqlType.blob, requiredDuringInsert: false);
|
||||||
|
static const VerificationMeta _storedFileHashMeta =
|
||||||
|
const VerificationMeta('storedFileHash');
|
||||||
|
@override
|
||||||
|
late final GeneratedColumn<Uint8List> storedFileHash =
|
||||||
|
GeneratedColumn<Uint8List>('stored_file_hash', aliasedName, true,
|
||||||
|
type: DriftSqlType.blob, requiredDuringInsert: false);
|
||||||
static const VerificationMeta _createdAtMeta =
|
static const VerificationMeta _createdAtMeta =
|
||||||
const VerificationMeta('createdAt');
|
const VerificationMeta('createdAt');
|
||||||
@override
|
@override
|
||||||
|
|
@ -2020,6 +2026,7 @@ class $MediaFilesTable extends MediaFiles
|
||||||
encryptionKey,
|
encryptionKey,
|
||||||
encryptionMac,
|
encryptionMac,
|
||||||
encryptionNonce,
|
encryptionNonce,
|
||||||
|
storedFileHash,
|
||||||
createdAt
|
createdAt
|
||||||
];
|
];
|
||||||
@override
|
@override
|
||||||
|
|
@ -2091,6 +2098,12 @@ class $MediaFilesTable extends MediaFiles
|
||||||
encryptionNonce.isAcceptableOrUnknown(
|
encryptionNonce.isAcceptableOrUnknown(
|
||||||
data['encryption_nonce']!, _encryptionNonceMeta));
|
data['encryption_nonce']!, _encryptionNonceMeta));
|
||||||
}
|
}
|
||||||
|
if (data.containsKey('stored_file_hash')) {
|
||||||
|
context.handle(
|
||||||
|
_storedFileHashMeta,
|
||||||
|
storedFileHash.isAcceptableOrUnknown(
|
||||||
|
data['stored_file_hash']!, _storedFileHashMeta));
|
||||||
|
}
|
||||||
if (data.containsKey('created_at')) {
|
if (data.containsKey('created_at')) {
|
||||||
context.handle(_createdAtMeta,
|
context.handle(_createdAtMeta,
|
||||||
createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta));
|
createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta));
|
||||||
|
|
@ -2137,6 +2150,8 @@ class $MediaFilesTable extends MediaFiles
|
||||||
.read(DriftSqlType.blob, data['${effectivePrefix}encryption_mac']),
|
.read(DriftSqlType.blob, data['${effectivePrefix}encryption_mac']),
|
||||||
encryptionNonce: attachedDatabase.typeMapping
|
encryptionNonce: attachedDatabase.typeMapping
|
||||||
.read(DriftSqlType.blob, data['${effectivePrefix}encryption_nonce']),
|
.read(DriftSqlType.blob, data['${effectivePrefix}encryption_nonce']),
|
||||||
|
storedFileHash: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.blob, data['${effectivePrefix}stored_file_hash']),
|
||||||
createdAt: attachedDatabase.typeMapping
|
createdAt: attachedDatabase.typeMapping
|
||||||
.read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!,
|
.read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!,
|
||||||
);
|
);
|
||||||
|
|
@ -2181,6 +2196,7 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
||||||
final Uint8List? encryptionKey;
|
final Uint8List? encryptionKey;
|
||||||
final Uint8List? encryptionMac;
|
final Uint8List? encryptionMac;
|
||||||
final Uint8List? encryptionNonce;
|
final Uint8List? encryptionNonce;
|
||||||
|
final Uint8List? storedFileHash;
|
||||||
final DateTime createdAt;
|
final DateTime createdAt;
|
||||||
const MediaFile(
|
const MediaFile(
|
||||||
{required this.mediaId,
|
{required this.mediaId,
|
||||||
|
|
@ -2197,6 +2213,7 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
||||||
this.encryptionKey,
|
this.encryptionKey,
|
||||||
this.encryptionMac,
|
this.encryptionMac,
|
||||||
this.encryptionNonce,
|
this.encryptionNonce,
|
||||||
|
this.storedFileHash,
|
||||||
required this.createdAt});
|
required this.createdAt});
|
||||||
@override
|
@override
|
||||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
|
|
@ -2241,6 +2258,9 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
||||||
if (!nullToAbsent || encryptionNonce != null) {
|
if (!nullToAbsent || encryptionNonce != null) {
|
||||||
map['encryption_nonce'] = Variable<Uint8List>(encryptionNonce);
|
map['encryption_nonce'] = Variable<Uint8List>(encryptionNonce);
|
||||||
}
|
}
|
||||||
|
if (!nullToAbsent || storedFileHash != null) {
|
||||||
|
map['stored_file_hash'] = Variable<Uint8List>(storedFileHash);
|
||||||
|
}
|
||||||
map['created_at'] = Variable<DateTime>(createdAt);
|
map['created_at'] = Variable<DateTime>(createdAt);
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
@ -2280,6 +2300,9 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
||||||
encryptionNonce: encryptionNonce == null && nullToAbsent
|
encryptionNonce: encryptionNonce == null && nullToAbsent
|
||||||
? const Value.absent()
|
? const Value.absent()
|
||||||
: Value(encryptionNonce),
|
: Value(encryptionNonce),
|
||||||
|
storedFileHash: storedFileHash == null && nullToAbsent
|
||||||
|
? const Value.absent()
|
||||||
|
: Value(storedFileHash),
|
||||||
createdAt: Value(createdAt),
|
createdAt: Value(createdAt),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -2308,6 +2331,7 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
||||||
encryptionKey: serializer.fromJson<Uint8List?>(json['encryptionKey']),
|
encryptionKey: serializer.fromJson<Uint8List?>(json['encryptionKey']),
|
||||||
encryptionMac: serializer.fromJson<Uint8List?>(json['encryptionMac']),
|
encryptionMac: serializer.fromJson<Uint8List?>(json['encryptionMac']),
|
||||||
encryptionNonce: serializer.fromJson<Uint8List?>(json['encryptionNonce']),
|
encryptionNonce: serializer.fromJson<Uint8List?>(json['encryptionNonce']),
|
||||||
|
storedFileHash: serializer.fromJson<Uint8List?>(json['storedFileHash']),
|
||||||
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
|
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -2333,6 +2357,7 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
||||||
'encryptionKey': serializer.toJson<Uint8List?>(encryptionKey),
|
'encryptionKey': serializer.toJson<Uint8List?>(encryptionKey),
|
||||||
'encryptionMac': serializer.toJson<Uint8List?>(encryptionMac),
|
'encryptionMac': serializer.toJson<Uint8List?>(encryptionMac),
|
||||||
'encryptionNonce': serializer.toJson<Uint8List?>(encryptionNonce),
|
'encryptionNonce': serializer.toJson<Uint8List?>(encryptionNonce),
|
||||||
|
'storedFileHash': serializer.toJson<Uint8List?>(storedFileHash),
|
||||||
'createdAt': serializer.toJson<DateTime>(createdAt),
|
'createdAt': serializer.toJson<DateTime>(createdAt),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -2352,6 +2377,7 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
||||||
Value<Uint8List?> encryptionKey = const Value.absent(),
|
Value<Uint8List?> encryptionKey = const Value.absent(),
|
||||||
Value<Uint8List?> encryptionMac = const Value.absent(),
|
Value<Uint8List?> encryptionMac = const Value.absent(),
|
||||||
Value<Uint8List?> encryptionNonce = const Value.absent(),
|
Value<Uint8List?> encryptionNonce = const Value.absent(),
|
||||||
|
Value<Uint8List?> storedFileHash = const Value.absent(),
|
||||||
DateTime? createdAt}) =>
|
DateTime? createdAt}) =>
|
||||||
MediaFile(
|
MediaFile(
|
||||||
mediaId: mediaId ?? this.mediaId,
|
mediaId: mediaId ?? this.mediaId,
|
||||||
|
|
@ -2379,6 +2405,8 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
||||||
encryptionNonce: encryptionNonce.present
|
encryptionNonce: encryptionNonce.present
|
||||||
? encryptionNonce.value
|
? encryptionNonce.value
|
||||||
: this.encryptionNonce,
|
: this.encryptionNonce,
|
||||||
|
storedFileHash:
|
||||||
|
storedFileHash.present ? storedFileHash.value : this.storedFileHash,
|
||||||
createdAt: createdAt ?? this.createdAt,
|
createdAt: createdAt ?? this.createdAt,
|
||||||
);
|
);
|
||||||
MediaFile copyWithCompanion(MediaFilesCompanion data) {
|
MediaFile copyWithCompanion(MediaFilesCompanion data) {
|
||||||
|
|
@ -2417,6 +2445,9 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
||||||
encryptionNonce: data.encryptionNonce.present
|
encryptionNonce: data.encryptionNonce.present
|
||||||
? data.encryptionNonce.value
|
? data.encryptionNonce.value
|
||||||
: this.encryptionNonce,
|
: this.encryptionNonce,
|
||||||
|
storedFileHash: data.storedFileHash.present
|
||||||
|
? data.storedFileHash.value
|
||||||
|
: this.storedFileHash,
|
||||||
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
|
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -2438,6 +2469,7 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
||||||
..write('encryptionKey: $encryptionKey, ')
|
..write('encryptionKey: $encryptionKey, ')
|
||||||
..write('encryptionMac: $encryptionMac, ')
|
..write('encryptionMac: $encryptionMac, ')
|
||||||
..write('encryptionNonce: $encryptionNonce, ')
|
..write('encryptionNonce: $encryptionNonce, ')
|
||||||
|
..write('storedFileHash: $storedFileHash, ')
|
||||||
..write('createdAt: $createdAt')
|
..write('createdAt: $createdAt')
|
||||||
..write(')'))
|
..write(')'))
|
||||||
.toString();
|
.toString();
|
||||||
|
|
@ -2459,6 +2491,7 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
||||||
$driftBlobEquality.hash(encryptionKey),
|
$driftBlobEquality.hash(encryptionKey),
|
||||||
$driftBlobEquality.hash(encryptionMac),
|
$driftBlobEquality.hash(encryptionMac),
|
||||||
$driftBlobEquality.hash(encryptionNonce),
|
$driftBlobEquality.hash(encryptionNonce),
|
||||||
|
$driftBlobEquality.hash(storedFileHash),
|
||||||
createdAt);
|
createdAt);
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
|
|
@ -2479,6 +2512,8 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
||||||
$driftBlobEquality.equals(other.encryptionMac, this.encryptionMac) &&
|
$driftBlobEquality.equals(other.encryptionMac, this.encryptionMac) &&
|
||||||
$driftBlobEquality.equals(
|
$driftBlobEquality.equals(
|
||||||
other.encryptionNonce, this.encryptionNonce) &&
|
other.encryptionNonce, this.encryptionNonce) &&
|
||||||
|
$driftBlobEquality.equals(
|
||||||
|
other.storedFileHash, this.storedFileHash) &&
|
||||||
other.createdAt == this.createdAt);
|
other.createdAt == this.createdAt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2497,6 +2532,7 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
||||||
final Value<Uint8List?> encryptionKey;
|
final Value<Uint8List?> encryptionKey;
|
||||||
final Value<Uint8List?> encryptionMac;
|
final Value<Uint8List?> encryptionMac;
|
||||||
final Value<Uint8List?> encryptionNonce;
|
final Value<Uint8List?> encryptionNonce;
|
||||||
|
final Value<Uint8List?> storedFileHash;
|
||||||
final Value<DateTime> createdAt;
|
final Value<DateTime> createdAt;
|
||||||
final Value<int> rowid;
|
final Value<int> rowid;
|
||||||
const MediaFilesCompanion({
|
const MediaFilesCompanion({
|
||||||
|
|
@ -2514,6 +2550,7 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
||||||
this.encryptionKey = const Value.absent(),
|
this.encryptionKey = const Value.absent(),
|
||||||
this.encryptionMac = const Value.absent(),
|
this.encryptionMac = const Value.absent(),
|
||||||
this.encryptionNonce = const Value.absent(),
|
this.encryptionNonce = const Value.absent(),
|
||||||
|
this.storedFileHash = const Value.absent(),
|
||||||
this.createdAt = const Value.absent(),
|
this.createdAt = const Value.absent(),
|
||||||
this.rowid = const Value.absent(),
|
this.rowid = const Value.absent(),
|
||||||
});
|
});
|
||||||
|
|
@ -2532,6 +2569,7 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
||||||
this.encryptionKey = const Value.absent(),
|
this.encryptionKey = const Value.absent(),
|
||||||
this.encryptionMac = const Value.absent(),
|
this.encryptionMac = const Value.absent(),
|
||||||
this.encryptionNonce = const Value.absent(),
|
this.encryptionNonce = const Value.absent(),
|
||||||
|
this.storedFileHash = const Value.absent(),
|
||||||
this.createdAt = const Value.absent(),
|
this.createdAt = const Value.absent(),
|
||||||
this.rowid = const Value.absent(),
|
this.rowid = const Value.absent(),
|
||||||
}) : mediaId = Value(mediaId),
|
}) : mediaId = Value(mediaId),
|
||||||
|
|
@ -2551,6 +2589,7 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
||||||
Expression<Uint8List>? encryptionKey,
|
Expression<Uint8List>? encryptionKey,
|
||||||
Expression<Uint8List>? encryptionMac,
|
Expression<Uint8List>? encryptionMac,
|
||||||
Expression<Uint8List>? encryptionNonce,
|
Expression<Uint8List>? encryptionNonce,
|
||||||
|
Expression<Uint8List>? storedFileHash,
|
||||||
Expression<DateTime>? createdAt,
|
Expression<DateTime>? createdAt,
|
||||||
Expression<int>? rowid,
|
Expression<int>? rowid,
|
||||||
}) {
|
}) {
|
||||||
|
|
@ -2572,6 +2611,7 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
||||||
if (encryptionKey != null) 'encryption_key': encryptionKey,
|
if (encryptionKey != null) 'encryption_key': encryptionKey,
|
||||||
if (encryptionMac != null) 'encryption_mac': encryptionMac,
|
if (encryptionMac != null) 'encryption_mac': encryptionMac,
|
||||||
if (encryptionNonce != null) 'encryption_nonce': encryptionNonce,
|
if (encryptionNonce != null) 'encryption_nonce': encryptionNonce,
|
||||||
|
if (storedFileHash != null) 'stored_file_hash': storedFileHash,
|
||||||
if (createdAt != null) 'created_at': createdAt,
|
if (createdAt != null) 'created_at': createdAt,
|
||||||
if (rowid != null) 'rowid': rowid,
|
if (rowid != null) 'rowid': rowid,
|
||||||
});
|
});
|
||||||
|
|
@ -2592,6 +2632,7 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
||||||
Value<Uint8List?>? encryptionKey,
|
Value<Uint8List?>? encryptionKey,
|
||||||
Value<Uint8List?>? encryptionMac,
|
Value<Uint8List?>? encryptionMac,
|
||||||
Value<Uint8List?>? encryptionNonce,
|
Value<Uint8List?>? encryptionNonce,
|
||||||
|
Value<Uint8List?>? storedFileHash,
|
||||||
Value<DateTime>? createdAt,
|
Value<DateTime>? createdAt,
|
||||||
Value<int>? rowid}) {
|
Value<int>? rowid}) {
|
||||||
return MediaFilesCompanion(
|
return MediaFilesCompanion(
|
||||||
|
|
@ -2611,6 +2652,7 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
||||||
encryptionKey: encryptionKey ?? this.encryptionKey,
|
encryptionKey: encryptionKey ?? this.encryptionKey,
|
||||||
encryptionMac: encryptionMac ?? this.encryptionMac,
|
encryptionMac: encryptionMac ?? this.encryptionMac,
|
||||||
encryptionNonce: encryptionNonce ?? this.encryptionNonce,
|
encryptionNonce: encryptionNonce ?? this.encryptionNonce,
|
||||||
|
storedFileHash: storedFileHash ?? this.storedFileHash,
|
||||||
createdAt: createdAt ?? this.createdAt,
|
createdAt: createdAt ?? this.createdAt,
|
||||||
rowid: rowid ?? this.rowid,
|
rowid: rowid ?? this.rowid,
|
||||||
);
|
);
|
||||||
|
|
@ -2668,6 +2710,9 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
||||||
if (encryptionNonce.present) {
|
if (encryptionNonce.present) {
|
||||||
map['encryption_nonce'] = Variable<Uint8List>(encryptionNonce.value);
|
map['encryption_nonce'] = Variable<Uint8List>(encryptionNonce.value);
|
||||||
}
|
}
|
||||||
|
if (storedFileHash.present) {
|
||||||
|
map['stored_file_hash'] = Variable<Uint8List>(storedFileHash.value);
|
||||||
|
}
|
||||||
if (createdAt.present) {
|
if (createdAt.present) {
|
||||||
map['created_at'] = Variable<DateTime>(createdAt.value);
|
map['created_at'] = Variable<DateTime>(createdAt.value);
|
||||||
}
|
}
|
||||||
|
|
@ -2694,6 +2739,7 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
||||||
..write('encryptionKey: $encryptionKey, ')
|
..write('encryptionKey: $encryptionKey, ')
|
||||||
..write('encryptionMac: $encryptionMac, ')
|
..write('encryptionMac: $encryptionMac, ')
|
||||||
..write('encryptionNonce: $encryptionNonce, ')
|
..write('encryptionNonce: $encryptionNonce, ')
|
||||||
|
..write('storedFileHash: $storedFileHash, ')
|
||||||
..write('createdAt: $createdAt, ')
|
..write('createdAt: $createdAt, ')
|
||||||
..write('rowid: $rowid')
|
..write('rowid: $rowid')
|
||||||
..write(')'))
|
..write(')'))
|
||||||
|
|
@ -4546,6 +4592,12 @@ class $ReceiptsTable extends Receipts with TableInfo<$ReceiptsTable, Receipt> {
|
||||||
defaultConstraints: GeneratedColumn.constraintIsAlways(
|
defaultConstraints: GeneratedColumn.constraintIsAlways(
|
||||||
'CHECK ("contact_will_sends_receipt" IN (0, 1))'),
|
'CHECK ("contact_will_sends_receipt" IN (0, 1))'),
|
||||||
defaultValue: const Constant(true));
|
defaultValue: const Constant(true));
|
||||||
|
static const VerificationMeta _markForRetryMeta =
|
||||||
|
const VerificationMeta('markForRetry');
|
||||||
|
@override
|
||||||
|
late final GeneratedColumn<DateTime> markForRetry = GeneratedColumn<DateTime>(
|
||||||
|
'mark_for_retry', aliasedName, true,
|
||||||
|
type: DriftSqlType.dateTime, requiredDuringInsert: false);
|
||||||
static const VerificationMeta _ackByServerAtMeta =
|
static const VerificationMeta _ackByServerAtMeta =
|
||||||
const VerificationMeta('ackByServerAt');
|
const VerificationMeta('ackByServerAt');
|
||||||
@override
|
@override
|
||||||
|
|
@ -4581,6 +4633,7 @@ class $ReceiptsTable extends Receipts with TableInfo<$ReceiptsTable, Receipt> {
|
||||||
messageId,
|
messageId,
|
||||||
message,
|
message,
|
||||||
contactWillSendsReceipt,
|
contactWillSendsReceipt,
|
||||||
|
markForRetry,
|
||||||
ackByServerAt,
|
ackByServerAt,
|
||||||
retryCount,
|
retryCount,
|
||||||
lastRetry,
|
lastRetry,
|
||||||
|
|
@ -4625,6 +4678,12 @@ class $ReceiptsTable extends Receipts with TableInfo<$ReceiptsTable, Receipt> {
|
||||||
data['contact_will_sends_receipt']!,
|
data['contact_will_sends_receipt']!,
|
||||||
_contactWillSendsReceiptMeta));
|
_contactWillSendsReceiptMeta));
|
||||||
}
|
}
|
||||||
|
if (data.containsKey('mark_for_retry')) {
|
||||||
|
context.handle(
|
||||||
|
_markForRetryMeta,
|
||||||
|
markForRetry.isAcceptableOrUnknown(
|
||||||
|
data['mark_for_retry']!, _markForRetryMeta));
|
||||||
|
}
|
||||||
if (data.containsKey('ack_by_server_at')) {
|
if (data.containsKey('ack_by_server_at')) {
|
||||||
context.handle(
|
context.handle(
|
||||||
_ackByServerAtMeta,
|
_ackByServerAtMeta,
|
||||||
|
|
@ -4665,6 +4724,8 @@ class $ReceiptsTable extends Receipts with TableInfo<$ReceiptsTable, Receipt> {
|
||||||
contactWillSendsReceipt: attachedDatabase.typeMapping.read(
|
contactWillSendsReceipt: attachedDatabase.typeMapping.read(
|
||||||
DriftSqlType.bool,
|
DriftSqlType.bool,
|
||||||
data['${effectivePrefix}contact_will_sends_receipt'])!,
|
data['${effectivePrefix}contact_will_sends_receipt'])!,
|
||||||
|
markForRetry: attachedDatabase.typeMapping.read(
|
||||||
|
DriftSqlType.dateTime, data['${effectivePrefix}mark_for_retry']),
|
||||||
ackByServerAt: attachedDatabase.typeMapping.read(
|
ackByServerAt: attachedDatabase.typeMapping.read(
|
||||||
DriftSqlType.dateTime, data['${effectivePrefix}ack_by_server_at']),
|
DriftSqlType.dateTime, data['${effectivePrefix}ack_by_server_at']),
|
||||||
retryCount: attachedDatabase.typeMapping
|
retryCount: attachedDatabase.typeMapping
|
||||||
|
|
@ -4690,6 +4751,7 @@ class Receipt extends DataClass implements Insertable<Receipt> {
|
||||||
/// This is the protobuf 'Message'
|
/// This is the protobuf 'Message'
|
||||||
final Uint8List message;
|
final Uint8List message;
|
||||||
final bool contactWillSendsReceipt;
|
final bool contactWillSendsReceipt;
|
||||||
|
final DateTime? markForRetry;
|
||||||
final DateTime? ackByServerAt;
|
final DateTime? ackByServerAt;
|
||||||
final int retryCount;
|
final int retryCount;
|
||||||
final DateTime? lastRetry;
|
final DateTime? lastRetry;
|
||||||
|
|
@ -4700,6 +4762,7 @@ class Receipt extends DataClass implements Insertable<Receipt> {
|
||||||
this.messageId,
|
this.messageId,
|
||||||
required this.message,
|
required this.message,
|
||||||
required this.contactWillSendsReceipt,
|
required this.contactWillSendsReceipt,
|
||||||
|
this.markForRetry,
|
||||||
this.ackByServerAt,
|
this.ackByServerAt,
|
||||||
required this.retryCount,
|
required this.retryCount,
|
||||||
this.lastRetry,
|
this.lastRetry,
|
||||||
|
|
@ -4714,6 +4777,9 @@ class Receipt extends DataClass implements Insertable<Receipt> {
|
||||||
}
|
}
|
||||||
map['message'] = Variable<Uint8List>(message);
|
map['message'] = Variable<Uint8List>(message);
|
||||||
map['contact_will_sends_receipt'] = Variable<bool>(contactWillSendsReceipt);
|
map['contact_will_sends_receipt'] = Variable<bool>(contactWillSendsReceipt);
|
||||||
|
if (!nullToAbsent || markForRetry != null) {
|
||||||
|
map['mark_for_retry'] = Variable<DateTime>(markForRetry);
|
||||||
|
}
|
||||||
if (!nullToAbsent || ackByServerAt != null) {
|
if (!nullToAbsent || ackByServerAt != null) {
|
||||||
map['ack_by_server_at'] = Variable<DateTime>(ackByServerAt);
|
map['ack_by_server_at'] = Variable<DateTime>(ackByServerAt);
|
||||||
}
|
}
|
||||||
|
|
@ -4734,6 +4800,9 @@ class Receipt extends DataClass implements Insertable<Receipt> {
|
||||||
: Value(messageId),
|
: Value(messageId),
|
||||||
message: Value(message),
|
message: Value(message),
|
||||||
contactWillSendsReceipt: Value(contactWillSendsReceipt),
|
contactWillSendsReceipt: Value(contactWillSendsReceipt),
|
||||||
|
markForRetry: markForRetry == null && nullToAbsent
|
||||||
|
? const Value.absent()
|
||||||
|
: Value(markForRetry),
|
||||||
ackByServerAt: ackByServerAt == null && nullToAbsent
|
ackByServerAt: ackByServerAt == null && nullToAbsent
|
||||||
? const Value.absent()
|
? const Value.absent()
|
||||||
: Value(ackByServerAt),
|
: Value(ackByServerAt),
|
||||||
|
|
@ -4755,6 +4824,7 @@ class Receipt extends DataClass implements Insertable<Receipt> {
|
||||||
message: serializer.fromJson<Uint8List>(json['message']),
|
message: serializer.fromJson<Uint8List>(json['message']),
|
||||||
contactWillSendsReceipt:
|
contactWillSendsReceipt:
|
||||||
serializer.fromJson<bool>(json['contactWillSendsReceipt']),
|
serializer.fromJson<bool>(json['contactWillSendsReceipt']),
|
||||||
|
markForRetry: serializer.fromJson<DateTime?>(json['markForRetry']),
|
||||||
ackByServerAt: serializer.fromJson<DateTime?>(json['ackByServerAt']),
|
ackByServerAt: serializer.fromJson<DateTime?>(json['ackByServerAt']),
|
||||||
retryCount: serializer.fromJson<int>(json['retryCount']),
|
retryCount: serializer.fromJson<int>(json['retryCount']),
|
||||||
lastRetry: serializer.fromJson<DateTime?>(json['lastRetry']),
|
lastRetry: serializer.fromJson<DateTime?>(json['lastRetry']),
|
||||||
|
|
@ -4771,6 +4841,7 @@ class Receipt extends DataClass implements Insertable<Receipt> {
|
||||||
'message': serializer.toJson<Uint8List>(message),
|
'message': serializer.toJson<Uint8List>(message),
|
||||||
'contactWillSendsReceipt':
|
'contactWillSendsReceipt':
|
||||||
serializer.toJson<bool>(contactWillSendsReceipt),
|
serializer.toJson<bool>(contactWillSendsReceipt),
|
||||||
|
'markForRetry': serializer.toJson<DateTime?>(markForRetry),
|
||||||
'ackByServerAt': serializer.toJson<DateTime?>(ackByServerAt),
|
'ackByServerAt': serializer.toJson<DateTime?>(ackByServerAt),
|
||||||
'retryCount': serializer.toJson<int>(retryCount),
|
'retryCount': serializer.toJson<int>(retryCount),
|
||||||
'lastRetry': serializer.toJson<DateTime?>(lastRetry),
|
'lastRetry': serializer.toJson<DateTime?>(lastRetry),
|
||||||
|
|
@ -4784,6 +4855,7 @@ class Receipt extends DataClass implements Insertable<Receipt> {
|
||||||
Value<String?> messageId = const Value.absent(),
|
Value<String?> messageId = const Value.absent(),
|
||||||
Uint8List? message,
|
Uint8List? message,
|
||||||
bool? contactWillSendsReceipt,
|
bool? contactWillSendsReceipt,
|
||||||
|
Value<DateTime?> markForRetry = const Value.absent(),
|
||||||
Value<DateTime?> ackByServerAt = const Value.absent(),
|
Value<DateTime?> ackByServerAt = const Value.absent(),
|
||||||
int? retryCount,
|
int? retryCount,
|
||||||
Value<DateTime?> lastRetry = const Value.absent(),
|
Value<DateTime?> lastRetry = const Value.absent(),
|
||||||
|
|
@ -4795,6 +4867,8 @@ class Receipt extends DataClass implements Insertable<Receipt> {
|
||||||
message: message ?? this.message,
|
message: message ?? this.message,
|
||||||
contactWillSendsReceipt:
|
contactWillSendsReceipt:
|
||||||
contactWillSendsReceipt ?? this.contactWillSendsReceipt,
|
contactWillSendsReceipt ?? this.contactWillSendsReceipt,
|
||||||
|
markForRetry:
|
||||||
|
markForRetry.present ? markForRetry.value : this.markForRetry,
|
||||||
ackByServerAt:
|
ackByServerAt:
|
||||||
ackByServerAt.present ? ackByServerAt.value : this.ackByServerAt,
|
ackByServerAt.present ? ackByServerAt.value : this.ackByServerAt,
|
||||||
retryCount: retryCount ?? this.retryCount,
|
retryCount: retryCount ?? this.retryCount,
|
||||||
|
|
@ -4810,6 +4884,9 @@ class Receipt extends DataClass implements Insertable<Receipt> {
|
||||||
contactWillSendsReceipt: data.contactWillSendsReceipt.present
|
contactWillSendsReceipt: data.contactWillSendsReceipt.present
|
||||||
? data.contactWillSendsReceipt.value
|
? data.contactWillSendsReceipt.value
|
||||||
: this.contactWillSendsReceipt,
|
: this.contactWillSendsReceipt,
|
||||||
|
markForRetry: data.markForRetry.present
|
||||||
|
? data.markForRetry.value
|
||||||
|
: this.markForRetry,
|
||||||
ackByServerAt: data.ackByServerAt.present
|
ackByServerAt: data.ackByServerAt.present
|
||||||
? data.ackByServerAt.value
|
? data.ackByServerAt.value
|
||||||
: this.ackByServerAt,
|
: this.ackByServerAt,
|
||||||
|
|
@ -4828,6 +4905,7 @@ class Receipt extends DataClass implements Insertable<Receipt> {
|
||||||
..write('messageId: $messageId, ')
|
..write('messageId: $messageId, ')
|
||||||
..write('message: $message, ')
|
..write('message: $message, ')
|
||||||
..write('contactWillSendsReceipt: $contactWillSendsReceipt, ')
|
..write('contactWillSendsReceipt: $contactWillSendsReceipt, ')
|
||||||
|
..write('markForRetry: $markForRetry, ')
|
||||||
..write('ackByServerAt: $ackByServerAt, ')
|
..write('ackByServerAt: $ackByServerAt, ')
|
||||||
..write('retryCount: $retryCount, ')
|
..write('retryCount: $retryCount, ')
|
||||||
..write('lastRetry: $lastRetry, ')
|
..write('lastRetry: $lastRetry, ')
|
||||||
|
|
@ -4843,6 +4921,7 @@ class Receipt extends DataClass implements Insertable<Receipt> {
|
||||||
messageId,
|
messageId,
|
||||||
$driftBlobEquality.hash(message),
|
$driftBlobEquality.hash(message),
|
||||||
contactWillSendsReceipt,
|
contactWillSendsReceipt,
|
||||||
|
markForRetry,
|
||||||
ackByServerAt,
|
ackByServerAt,
|
||||||
retryCount,
|
retryCount,
|
||||||
lastRetry,
|
lastRetry,
|
||||||
|
|
@ -4856,6 +4935,7 @@ class Receipt extends DataClass implements Insertable<Receipt> {
|
||||||
other.messageId == this.messageId &&
|
other.messageId == this.messageId &&
|
||||||
$driftBlobEquality.equals(other.message, this.message) &&
|
$driftBlobEquality.equals(other.message, this.message) &&
|
||||||
other.contactWillSendsReceipt == this.contactWillSendsReceipt &&
|
other.contactWillSendsReceipt == this.contactWillSendsReceipt &&
|
||||||
|
other.markForRetry == this.markForRetry &&
|
||||||
other.ackByServerAt == this.ackByServerAt &&
|
other.ackByServerAt == this.ackByServerAt &&
|
||||||
other.retryCount == this.retryCount &&
|
other.retryCount == this.retryCount &&
|
||||||
other.lastRetry == this.lastRetry &&
|
other.lastRetry == this.lastRetry &&
|
||||||
|
|
@ -4868,6 +4948,7 @@ class ReceiptsCompanion extends UpdateCompanion<Receipt> {
|
||||||
final Value<String?> messageId;
|
final Value<String?> messageId;
|
||||||
final Value<Uint8List> message;
|
final Value<Uint8List> message;
|
||||||
final Value<bool> contactWillSendsReceipt;
|
final Value<bool> contactWillSendsReceipt;
|
||||||
|
final Value<DateTime?> markForRetry;
|
||||||
final Value<DateTime?> ackByServerAt;
|
final Value<DateTime?> ackByServerAt;
|
||||||
final Value<int> retryCount;
|
final Value<int> retryCount;
|
||||||
final Value<DateTime?> lastRetry;
|
final Value<DateTime?> lastRetry;
|
||||||
|
|
@ -4879,6 +4960,7 @@ class ReceiptsCompanion extends UpdateCompanion<Receipt> {
|
||||||
this.messageId = const Value.absent(),
|
this.messageId = const Value.absent(),
|
||||||
this.message = const Value.absent(),
|
this.message = const Value.absent(),
|
||||||
this.contactWillSendsReceipt = const Value.absent(),
|
this.contactWillSendsReceipt = const Value.absent(),
|
||||||
|
this.markForRetry = const Value.absent(),
|
||||||
this.ackByServerAt = const Value.absent(),
|
this.ackByServerAt = const Value.absent(),
|
||||||
this.retryCount = const Value.absent(),
|
this.retryCount = const Value.absent(),
|
||||||
this.lastRetry = const Value.absent(),
|
this.lastRetry = const Value.absent(),
|
||||||
|
|
@ -4891,6 +4973,7 @@ class ReceiptsCompanion extends UpdateCompanion<Receipt> {
|
||||||
this.messageId = const Value.absent(),
|
this.messageId = const Value.absent(),
|
||||||
required Uint8List message,
|
required Uint8List message,
|
||||||
this.contactWillSendsReceipt = const Value.absent(),
|
this.contactWillSendsReceipt = const Value.absent(),
|
||||||
|
this.markForRetry = const Value.absent(),
|
||||||
this.ackByServerAt = const Value.absent(),
|
this.ackByServerAt = const Value.absent(),
|
||||||
this.retryCount = const Value.absent(),
|
this.retryCount = const Value.absent(),
|
||||||
this.lastRetry = const Value.absent(),
|
this.lastRetry = const Value.absent(),
|
||||||
|
|
@ -4905,6 +4988,7 @@ class ReceiptsCompanion extends UpdateCompanion<Receipt> {
|
||||||
Expression<String>? messageId,
|
Expression<String>? messageId,
|
||||||
Expression<Uint8List>? message,
|
Expression<Uint8List>? message,
|
||||||
Expression<bool>? contactWillSendsReceipt,
|
Expression<bool>? contactWillSendsReceipt,
|
||||||
|
Expression<DateTime>? markForRetry,
|
||||||
Expression<DateTime>? ackByServerAt,
|
Expression<DateTime>? ackByServerAt,
|
||||||
Expression<int>? retryCount,
|
Expression<int>? retryCount,
|
||||||
Expression<DateTime>? lastRetry,
|
Expression<DateTime>? lastRetry,
|
||||||
|
|
@ -4918,6 +5002,7 @@ class ReceiptsCompanion extends UpdateCompanion<Receipt> {
|
||||||
if (message != null) 'message': message,
|
if (message != null) 'message': message,
|
||||||
if (contactWillSendsReceipt != null)
|
if (contactWillSendsReceipt != null)
|
||||||
'contact_will_sends_receipt': contactWillSendsReceipt,
|
'contact_will_sends_receipt': contactWillSendsReceipt,
|
||||||
|
if (markForRetry != null) 'mark_for_retry': markForRetry,
|
||||||
if (ackByServerAt != null) 'ack_by_server_at': ackByServerAt,
|
if (ackByServerAt != null) 'ack_by_server_at': ackByServerAt,
|
||||||
if (retryCount != null) 'retry_count': retryCount,
|
if (retryCount != null) 'retry_count': retryCount,
|
||||||
if (lastRetry != null) 'last_retry': lastRetry,
|
if (lastRetry != null) 'last_retry': lastRetry,
|
||||||
|
|
@ -4932,6 +5017,7 @@ class ReceiptsCompanion extends UpdateCompanion<Receipt> {
|
||||||
Value<String?>? messageId,
|
Value<String?>? messageId,
|
||||||
Value<Uint8List>? message,
|
Value<Uint8List>? message,
|
||||||
Value<bool>? contactWillSendsReceipt,
|
Value<bool>? contactWillSendsReceipt,
|
||||||
|
Value<DateTime?>? markForRetry,
|
||||||
Value<DateTime?>? ackByServerAt,
|
Value<DateTime?>? ackByServerAt,
|
||||||
Value<int>? retryCount,
|
Value<int>? retryCount,
|
||||||
Value<DateTime?>? lastRetry,
|
Value<DateTime?>? lastRetry,
|
||||||
|
|
@ -4944,6 +5030,7 @@ class ReceiptsCompanion extends UpdateCompanion<Receipt> {
|
||||||
message: message ?? this.message,
|
message: message ?? this.message,
|
||||||
contactWillSendsReceipt:
|
contactWillSendsReceipt:
|
||||||
contactWillSendsReceipt ?? this.contactWillSendsReceipt,
|
contactWillSendsReceipt ?? this.contactWillSendsReceipt,
|
||||||
|
markForRetry: markForRetry ?? this.markForRetry,
|
||||||
ackByServerAt: ackByServerAt ?? this.ackByServerAt,
|
ackByServerAt: ackByServerAt ?? this.ackByServerAt,
|
||||||
retryCount: retryCount ?? this.retryCount,
|
retryCount: retryCount ?? this.retryCount,
|
||||||
lastRetry: lastRetry ?? this.lastRetry,
|
lastRetry: lastRetry ?? this.lastRetry,
|
||||||
|
|
@ -4971,6 +5058,9 @@ class ReceiptsCompanion extends UpdateCompanion<Receipt> {
|
||||||
map['contact_will_sends_receipt'] =
|
map['contact_will_sends_receipt'] =
|
||||||
Variable<bool>(contactWillSendsReceipt.value);
|
Variable<bool>(contactWillSendsReceipt.value);
|
||||||
}
|
}
|
||||||
|
if (markForRetry.present) {
|
||||||
|
map['mark_for_retry'] = Variable<DateTime>(markForRetry.value);
|
||||||
|
}
|
||||||
if (ackByServerAt.present) {
|
if (ackByServerAt.present) {
|
||||||
map['ack_by_server_at'] = Variable<DateTime>(ackByServerAt.value);
|
map['ack_by_server_at'] = Variable<DateTime>(ackByServerAt.value);
|
||||||
}
|
}
|
||||||
|
|
@ -4997,6 +5087,7 @@ class ReceiptsCompanion extends UpdateCompanion<Receipt> {
|
||||||
..write('messageId: $messageId, ')
|
..write('messageId: $messageId, ')
|
||||||
..write('message: $message, ')
|
..write('message: $message, ')
|
||||||
..write('contactWillSendsReceipt: $contactWillSendsReceipt, ')
|
..write('contactWillSendsReceipt: $contactWillSendsReceipt, ')
|
||||||
|
..write('markForRetry: $markForRetry, ')
|
||||||
..write('ackByServerAt: $ackByServerAt, ')
|
..write('ackByServerAt: $ackByServerAt, ')
|
||||||
..write('retryCount: $retryCount, ')
|
..write('retryCount: $retryCount, ')
|
||||||
..write('lastRetry: $lastRetry, ')
|
..write('lastRetry: $lastRetry, ')
|
||||||
|
|
@ -7111,10 +7202,7 @@ class $GroupHistoriesTable extends GroupHistories
|
||||||
@override
|
@override
|
||||||
late final GeneratedColumn<int> affectedContactId = GeneratedColumn<int>(
|
late final GeneratedColumn<int> affectedContactId = GeneratedColumn<int>(
|
||||||
'affected_contact_id', aliasedName, true,
|
'affected_contact_id', aliasedName, true,
|
||||||
type: DriftSqlType.int,
|
type: DriftSqlType.int, requiredDuringInsert: false);
|
||||||
requiredDuringInsert: false,
|
|
||||||
defaultConstraints:
|
|
||||||
GeneratedColumn.constraintIsAlways('REFERENCES contacts (user_id)'));
|
|
||||||
static const VerificationMeta _oldGroupNameMeta =
|
static const VerificationMeta _oldGroupNameMeta =
|
||||||
const VerificationMeta('oldGroupName');
|
const VerificationMeta('oldGroupName');
|
||||||
@override
|
@override
|
||||||
|
|
@ -7896,6 +7984,22 @@ final class $$ContactsTableReferences
|
||||||
return ProcessedTableManager(
|
return ProcessedTableManager(
|
||||||
manager.$state.copyWith(prefetchedData: cache));
|
manager.$state.copyWith(prefetchedData: cache));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static MultiTypedResultKey<$GroupHistoriesTable, List<GroupHistory>>
|
||||||
|
_groupHistoriesRefsTable(_$TwonlyDB db) =>
|
||||||
|
MultiTypedResultKey.fromTable(db.groupHistories,
|
||||||
|
aliasName: $_aliasNameGenerator(
|
||||||
|
db.contacts.userId, db.groupHistories.contactId));
|
||||||
|
|
||||||
|
$$GroupHistoriesTableProcessedTableManager get groupHistoriesRefs {
|
||||||
|
final manager = $$GroupHistoriesTableTableManager($_db, $_db.groupHistories)
|
||||||
|
.filter(
|
||||||
|
(f) => f.contactId.userId.sqlEquals($_itemColumn<int>('user_id')!));
|
||||||
|
|
||||||
|
final cache = $_typedResult.readTableOrNull(_groupHistoriesRefsTable($_db));
|
||||||
|
return ProcessedTableManager(
|
||||||
|
manager.$state.copyWith(prefetchedData: cache));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class $$ContactsTableFilterComposer
|
class $$ContactsTableFilterComposer
|
||||||
|
|
@ -8078,6 +8182,27 @@ class $$ContactsTableFilterComposer
|
||||||
));
|
));
|
||||||
return f(composer);
|
return f(composer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Expression<bool> groupHistoriesRefs(
|
||||||
|
Expression<bool> Function($$GroupHistoriesTableFilterComposer f) f) {
|
||||||
|
final $$GroupHistoriesTableFilterComposer composer = $composerBuilder(
|
||||||
|
composer: this,
|
||||||
|
getCurrentColumn: (t) => t.userId,
|
||||||
|
referencedTable: $db.groupHistories,
|
||||||
|
getReferencedColumn: (t) => t.contactId,
|
||||||
|
builder: (joinBuilder,
|
||||||
|
{$addJoinBuilderToRootComposer,
|
||||||
|
$removeJoinBuilderFromRootComposer}) =>
|
||||||
|
$$GroupHistoriesTableFilterComposer(
|
||||||
|
$db: $db,
|
||||||
|
$table: $db.groupHistories,
|
||||||
|
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||||
|
joinBuilder: joinBuilder,
|
||||||
|
$removeJoinBuilderFromRootComposer:
|
||||||
|
$removeJoinBuilderFromRootComposer,
|
||||||
|
));
|
||||||
|
return f(composer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class $$ContactsTableOrderingComposer
|
class $$ContactsTableOrderingComposer
|
||||||
|
|
@ -8311,6 +8436,27 @@ class $$ContactsTableAnnotationComposer
|
||||||
));
|
));
|
||||||
return f(composer);
|
return f(composer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Expression<T> groupHistoriesRefs<T extends Object>(
|
||||||
|
Expression<T> Function($$GroupHistoriesTableAnnotationComposer a) f) {
|
||||||
|
final $$GroupHistoriesTableAnnotationComposer composer = $composerBuilder(
|
||||||
|
composer: this,
|
||||||
|
getCurrentColumn: (t) => t.userId,
|
||||||
|
referencedTable: $db.groupHistories,
|
||||||
|
getReferencedColumn: (t) => t.contactId,
|
||||||
|
builder: (joinBuilder,
|
||||||
|
{$addJoinBuilderToRootComposer,
|
||||||
|
$removeJoinBuilderFromRootComposer}) =>
|
||||||
|
$$GroupHistoriesTableAnnotationComposer(
|
||||||
|
$db: $db,
|
||||||
|
$table: $db.groupHistories,
|
||||||
|
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||||
|
joinBuilder: joinBuilder,
|
||||||
|
$removeJoinBuilderFromRootComposer:
|
||||||
|
$removeJoinBuilderFromRootComposer,
|
||||||
|
));
|
||||||
|
return f(composer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class $$ContactsTableTableManager extends RootTableManager<
|
class $$ContactsTableTableManager extends RootTableManager<
|
||||||
|
|
@ -8330,7 +8476,8 @@ class $$ContactsTableTableManager extends RootTableManager<
|
||||||
bool groupMembersRefs,
|
bool groupMembersRefs,
|
||||||
bool receiptsRefs,
|
bool receiptsRefs,
|
||||||
bool signalContactPreKeysRefs,
|
bool signalContactPreKeysRefs,
|
||||||
bool signalContactSignedPreKeysRefs})> {
|
bool signalContactSignedPreKeysRefs,
|
||||||
|
bool groupHistoriesRefs})> {
|
||||||
$$ContactsTableTableManager(_$TwonlyDB db, $ContactsTable table)
|
$$ContactsTableTableManager(_$TwonlyDB db, $ContactsTable table)
|
||||||
: super(TableManagerState(
|
: super(TableManagerState(
|
||||||
db: db,
|
db: db,
|
||||||
|
|
@ -8411,7 +8558,8 @@ class $$ContactsTableTableManager extends RootTableManager<
|
||||||
groupMembersRefs = false,
|
groupMembersRefs = false,
|
||||||
receiptsRefs = false,
|
receiptsRefs = false,
|
||||||
signalContactPreKeysRefs = false,
|
signalContactPreKeysRefs = false,
|
||||||
signalContactSignedPreKeysRefs = false}) {
|
signalContactSignedPreKeysRefs = false,
|
||||||
|
groupHistoriesRefs = false}) {
|
||||||
return PrefetchHooks(
|
return PrefetchHooks(
|
||||||
db: db,
|
db: db,
|
||||||
explicitlyWatchedTables: [
|
explicitlyWatchedTables: [
|
||||||
|
|
@ -8421,7 +8569,8 @@ class $$ContactsTableTableManager extends RootTableManager<
|
||||||
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 (groupHistoriesRefs) db.groupHistories
|
||||||
],
|
],
|
||||||
addJoins: null,
|
addJoins: null,
|
||||||
getPrefetchedDataCallback: (items) async {
|
getPrefetchedDataCallback: (items) async {
|
||||||
|
|
@ -8501,6 +8650,19 @@ class $$ContactsTableTableManager extends RootTableManager<
|
||||||
referencedItemsForCurrentItem:
|
referencedItemsForCurrentItem:
|
||||||
(item, referencedItems) => referencedItems
|
(item, referencedItems) => referencedItems
|
||||||
.where((e) => e.contactId == item.userId),
|
.where((e) => e.contactId == item.userId),
|
||||||
|
typedResults: items),
|
||||||
|
if (groupHistoriesRefs)
|
||||||
|
await $_getPrefetchedData<Contact, $ContactsTable,
|
||||||
|
GroupHistory>(
|
||||||
|
currentTable: table,
|
||||||
|
referencedTable: $$ContactsTableReferences
|
||||||
|
._groupHistoriesRefsTable(db),
|
||||||
|
managerFromTypedResult: (p0) =>
|
||||||
|
$$ContactsTableReferences(db, table, p0)
|
||||||
|
.groupHistoriesRefs,
|
||||||
|
referencedItemsForCurrentItem:
|
||||||
|
(item, referencedItems) => referencedItems
|
||||||
|
.where((e) => e.contactId == item.userId),
|
||||||
typedResults: items)
|
typedResults: items)
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|
@ -8526,7 +8688,8 @@ typedef $$ContactsTableProcessedTableManager = ProcessedTableManager<
|
||||||
bool groupMembersRefs,
|
bool groupMembersRefs,
|
||||||
bool receiptsRefs,
|
bool receiptsRefs,
|
||||||
bool signalContactPreKeysRefs,
|
bool signalContactPreKeysRefs,
|
||||||
bool signalContactSignedPreKeysRefs})>;
|
bool signalContactSignedPreKeysRefs,
|
||||||
|
bool groupHistoriesRefs})>;
|
||||||
typedef $$GroupsTableCreateCompanionBuilder = GroupsCompanion Function({
|
typedef $$GroupsTableCreateCompanionBuilder = GroupsCompanion Function({
|
||||||
required String groupId,
|
required String groupId,
|
||||||
Value<bool> isGroupAdmin,
|
Value<bool> isGroupAdmin,
|
||||||
|
|
@ -9273,6 +9436,7 @@ typedef $$MediaFilesTableCreateCompanionBuilder = MediaFilesCompanion Function({
|
||||||
Value<Uint8List?> encryptionKey,
|
Value<Uint8List?> encryptionKey,
|
||||||
Value<Uint8List?> encryptionMac,
|
Value<Uint8List?> encryptionMac,
|
||||||
Value<Uint8List?> encryptionNonce,
|
Value<Uint8List?> encryptionNonce,
|
||||||
|
Value<Uint8List?> storedFileHash,
|
||||||
Value<DateTime> createdAt,
|
Value<DateTime> createdAt,
|
||||||
Value<int> rowid,
|
Value<int> rowid,
|
||||||
});
|
});
|
||||||
|
|
@ -9291,6 +9455,7 @@ typedef $$MediaFilesTableUpdateCompanionBuilder = MediaFilesCompanion Function({
|
||||||
Value<Uint8List?> encryptionKey,
|
Value<Uint8List?> encryptionKey,
|
||||||
Value<Uint8List?> encryptionMac,
|
Value<Uint8List?> encryptionMac,
|
||||||
Value<Uint8List?> encryptionNonce,
|
Value<Uint8List?> encryptionNonce,
|
||||||
|
Value<Uint8List?> storedFileHash,
|
||||||
Value<DateTime> createdAt,
|
Value<DateTime> createdAt,
|
||||||
Value<int> rowid,
|
Value<int> rowid,
|
||||||
});
|
});
|
||||||
|
|
@ -9377,6 +9542,10 @@ class $$MediaFilesTableFilterComposer
|
||||||
column: $table.encryptionNonce,
|
column: $table.encryptionNonce,
|
||||||
builder: (column) => ColumnFilters(column));
|
builder: (column) => ColumnFilters(column));
|
||||||
|
|
||||||
|
ColumnFilters<Uint8List> get storedFileHash => $composableBuilder(
|
||||||
|
column: $table.storedFileHash,
|
||||||
|
builder: (column) => ColumnFilters(column));
|
||||||
|
|
||||||
ColumnFilters<DateTime> get createdAt => $composableBuilder(
|
ColumnFilters<DateTime> get createdAt => $composableBuilder(
|
||||||
column: $table.createdAt, builder: (column) => ColumnFilters(column));
|
column: $table.createdAt, builder: (column) => ColumnFilters(column));
|
||||||
|
|
||||||
|
|
@ -9462,6 +9631,10 @@ class $$MediaFilesTableOrderingComposer
|
||||||
column: $table.encryptionNonce,
|
column: $table.encryptionNonce,
|
||||||
builder: (column) => ColumnOrderings(column));
|
builder: (column) => ColumnOrderings(column));
|
||||||
|
|
||||||
|
ColumnOrderings<Uint8List> get storedFileHash => $composableBuilder(
|
||||||
|
column: $table.storedFileHash,
|
||||||
|
builder: (column) => ColumnOrderings(column));
|
||||||
|
|
||||||
ColumnOrderings<DateTime> get createdAt => $composableBuilder(
|
ColumnOrderings<DateTime> get createdAt => $composableBuilder(
|
||||||
column: $table.createdAt, builder: (column) => ColumnOrderings(column));
|
column: $table.createdAt, builder: (column) => ColumnOrderings(column));
|
||||||
}
|
}
|
||||||
|
|
@ -9520,6 +9693,9 @@ class $$MediaFilesTableAnnotationComposer
|
||||||
GeneratedColumn<Uint8List> get encryptionNonce => $composableBuilder(
|
GeneratedColumn<Uint8List> get encryptionNonce => $composableBuilder(
|
||||||
column: $table.encryptionNonce, builder: (column) => column);
|
column: $table.encryptionNonce, builder: (column) => column);
|
||||||
|
|
||||||
|
GeneratedColumn<Uint8List> get storedFileHash => $composableBuilder(
|
||||||
|
column: $table.storedFileHash, builder: (column) => column);
|
||||||
|
|
||||||
GeneratedColumn<DateTime> get createdAt =>
|
GeneratedColumn<DateTime> get createdAt =>
|
||||||
$composableBuilder(column: $table.createdAt, builder: (column) => column);
|
$composableBuilder(column: $table.createdAt, builder: (column) => column);
|
||||||
|
|
||||||
|
|
@ -9582,6 +9758,7 @@ class $$MediaFilesTableTableManager extends RootTableManager<
|
||||||
Value<Uint8List?> encryptionKey = const Value.absent(),
|
Value<Uint8List?> encryptionKey = const Value.absent(),
|
||||||
Value<Uint8List?> encryptionMac = const Value.absent(),
|
Value<Uint8List?> encryptionMac = const Value.absent(),
|
||||||
Value<Uint8List?> encryptionNonce = const Value.absent(),
|
Value<Uint8List?> encryptionNonce = const Value.absent(),
|
||||||
|
Value<Uint8List?> storedFileHash = const Value.absent(),
|
||||||
Value<DateTime> createdAt = const Value.absent(),
|
Value<DateTime> createdAt = const Value.absent(),
|
||||||
Value<int> rowid = const Value.absent(),
|
Value<int> rowid = const Value.absent(),
|
||||||
}) =>
|
}) =>
|
||||||
|
|
@ -9600,6 +9777,7 @@ class $$MediaFilesTableTableManager extends RootTableManager<
|
||||||
encryptionKey: encryptionKey,
|
encryptionKey: encryptionKey,
|
||||||
encryptionMac: encryptionMac,
|
encryptionMac: encryptionMac,
|
||||||
encryptionNonce: encryptionNonce,
|
encryptionNonce: encryptionNonce,
|
||||||
|
storedFileHash: storedFileHash,
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
rowid: rowid,
|
rowid: rowid,
|
||||||
),
|
),
|
||||||
|
|
@ -9618,6 +9796,7 @@ class $$MediaFilesTableTableManager extends RootTableManager<
|
||||||
Value<Uint8List?> encryptionKey = const Value.absent(),
|
Value<Uint8List?> encryptionKey = const Value.absent(),
|
||||||
Value<Uint8List?> encryptionMac = const Value.absent(),
|
Value<Uint8List?> encryptionMac = const Value.absent(),
|
||||||
Value<Uint8List?> encryptionNonce = const Value.absent(),
|
Value<Uint8List?> encryptionNonce = const Value.absent(),
|
||||||
|
Value<Uint8List?> storedFileHash = const Value.absent(),
|
||||||
Value<DateTime> createdAt = const Value.absent(),
|
Value<DateTime> createdAt = const Value.absent(),
|
||||||
Value<int> rowid = const Value.absent(),
|
Value<int> rowid = const Value.absent(),
|
||||||
}) =>
|
}) =>
|
||||||
|
|
@ -9636,6 +9815,7 @@ class $$MediaFilesTableTableManager extends RootTableManager<
|
||||||
encryptionKey: encryptionKey,
|
encryptionKey: encryptionKey,
|
||||||
encryptionMac: encryptionMac,
|
encryptionMac: encryptionMac,
|
||||||
encryptionNonce: encryptionNonce,
|
encryptionNonce: encryptionNonce,
|
||||||
|
storedFileHash: storedFileHash,
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
rowid: rowid,
|
rowid: rowid,
|
||||||
),
|
),
|
||||||
|
|
@ -11590,6 +11770,7 @@ typedef $$ReceiptsTableCreateCompanionBuilder = ReceiptsCompanion Function({
|
||||||
Value<String?> messageId,
|
Value<String?> messageId,
|
||||||
required Uint8List message,
|
required Uint8List message,
|
||||||
Value<bool> contactWillSendsReceipt,
|
Value<bool> contactWillSendsReceipt,
|
||||||
|
Value<DateTime?> markForRetry,
|
||||||
Value<DateTime?> ackByServerAt,
|
Value<DateTime?> ackByServerAt,
|
||||||
Value<int> retryCount,
|
Value<int> retryCount,
|
||||||
Value<DateTime?> lastRetry,
|
Value<DateTime?> lastRetry,
|
||||||
|
|
@ -11602,6 +11783,7 @@ typedef $$ReceiptsTableUpdateCompanionBuilder = ReceiptsCompanion Function({
|
||||||
Value<String?> messageId,
|
Value<String?> messageId,
|
||||||
Value<Uint8List> message,
|
Value<Uint8List> message,
|
||||||
Value<bool> contactWillSendsReceipt,
|
Value<bool> contactWillSendsReceipt,
|
||||||
|
Value<DateTime?> markForRetry,
|
||||||
Value<DateTime?> ackByServerAt,
|
Value<DateTime?> ackByServerAt,
|
||||||
Value<int> retryCount,
|
Value<int> retryCount,
|
||||||
Value<DateTime?> lastRetry,
|
Value<DateTime?> lastRetry,
|
||||||
|
|
@ -11663,6 +11845,9 @@ class $$ReceiptsTableFilterComposer
|
||||||
column: $table.contactWillSendsReceipt,
|
column: $table.contactWillSendsReceipt,
|
||||||
builder: (column) => ColumnFilters(column));
|
builder: (column) => ColumnFilters(column));
|
||||||
|
|
||||||
|
ColumnFilters<DateTime> get markForRetry => $composableBuilder(
|
||||||
|
column: $table.markForRetry, builder: (column) => ColumnFilters(column));
|
||||||
|
|
||||||
ColumnFilters<DateTime> get ackByServerAt => $composableBuilder(
|
ColumnFilters<DateTime> get ackByServerAt => $composableBuilder(
|
||||||
column: $table.ackByServerAt, builder: (column) => ColumnFilters(column));
|
column: $table.ackByServerAt, builder: (column) => ColumnFilters(column));
|
||||||
|
|
||||||
|
|
@ -11735,6 +11920,10 @@ class $$ReceiptsTableOrderingComposer
|
||||||
column: $table.contactWillSendsReceipt,
|
column: $table.contactWillSendsReceipt,
|
||||||
builder: (column) => ColumnOrderings(column));
|
builder: (column) => ColumnOrderings(column));
|
||||||
|
|
||||||
|
ColumnOrderings<DateTime> get markForRetry => $composableBuilder(
|
||||||
|
column: $table.markForRetry,
|
||||||
|
builder: (column) => ColumnOrderings(column));
|
||||||
|
|
||||||
ColumnOrderings<DateTime> get ackByServerAt => $composableBuilder(
|
ColumnOrderings<DateTime> get ackByServerAt => $composableBuilder(
|
||||||
column: $table.ackByServerAt,
|
column: $table.ackByServerAt,
|
||||||
builder: (column) => ColumnOrderings(column));
|
builder: (column) => ColumnOrderings(column));
|
||||||
|
|
@ -11807,6 +11996,9 @@ class $$ReceiptsTableAnnotationComposer
|
||||||
GeneratedColumn<bool> get contactWillSendsReceipt => $composableBuilder(
|
GeneratedColumn<bool> get contactWillSendsReceipt => $composableBuilder(
|
||||||
column: $table.contactWillSendsReceipt, builder: (column) => column);
|
column: $table.contactWillSendsReceipt, builder: (column) => column);
|
||||||
|
|
||||||
|
GeneratedColumn<DateTime> get markForRetry => $composableBuilder(
|
||||||
|
column: $table.markForRetry, builder: (column) => column);
|
||||||
|
|
||||||
GeneratedColumn<DateTime> get ackByServerAt => $composableBuilder(
|
GeneratedColumn<DateTime> get ackByServerAt => $composableBuilder(
|
||||||
column: $table.ackByServerAt, builder: (column) => column);
|
column: $table.ackByServerAt, builder: (column) => column);
|
||||||
|
|
||||||
|
|
@ -11888,6 +12080,7 @@ class $$ReceiptsTableTableManager extends RootTableManager<
|
||||||
Value<String?> messageId = const Value.absent(),
|
Value<String?> messageId = const Value.absent(),
|
||||||
Value<Uint8List> message = const Value.absent(),
|
Value<Uint8List> message = const Value.absent(),
|
||||||
Value<bool> contactWillSendsReceipt = const Value.absent(),
|
Value<bool> contactWillSendsReceipt = const Value.absent(),
|
||||||
|
Value<DateTime?> markForRetry = const Value.absent(),
|
||||||
Value<DateTime?> ackByServerAt = const Value.absent(),
|
Value<DateTime?> ackByServerAt = const Value.absent(),
|
||||||
Value<int> retryCount = const Value.absent(),
|
Value<int> retryCount = const Value.absent(),
|
||||||
Value<DateTime?> lastRetry = const Value.absent(),
|
Value<DateTime?> lastRetry = const Value.absent(),
|
||||||
|
|
@ -11900,6 +12093,7 @@ class $$ReceiptsTableTableManager extends RootTableManager<
|
||||||
messageId: messageId,
|
messageId: messageId,
|
||||||
message: message,
|
message: message,
|
||||||
contactWillSendsReceipt: contactWillSendsReceipt,
|
contactWillSendsReceipt: contactWillSendsReceipt,
|
||||||
|
markForRetry: markForRetry,
|
||||||
ackByServerAt: ackByServerAt,
|
ackByServerAt: ackByServerAt,
|
||||||
retryCount: retryCount,
|
retryCount: retryCount,
|
||||||
lastRetry: lastRetry,
|
lastRetry: lastRetry,
|
||||||
|
|
@ -11912,6 +12106,7 @@ class $$ReceiptsTableTableManager extends RootTableManager<
|
||||||
Value<String?> messageId = const Value.absent(),
|
Value<String?> messageId = const Value.absent(),
|
||||||
required Uint8List message,
|
required Uint8List message,
|
||||||
Value<bool> contactWillSendsReceipt = const Value.absent(),
|
Value<bool> contactWillSendsReceipt = const Value.absent(),
|
||||||
|
Value<DateTime?> markForRetry = const Value.absent(),
|
||||||
Value<DateTime?> ackByServerAt = const Value.absent(),
|
Value<DateTime?> ackByServerAt = const Value.absent(),
|
||||||
Value<int> retryCount = const Value.absent(),
|
Value<int> retryCount = const Value.absent(),
|
||||||
Value<DateTime?> lastRetry = const Value.absent(),
|
Value<DateTime?> lastRetry = const Value.absent(),
|
||||||
|
|
@ -11924,6 +12119,7 @@ class $$ReceiptsTableTableManager extends RootTableManager<
|
||||||
messageId: messageId,
|
messageId: messageId,
|
||||||
message: message,
|
message: message,
|
||||||
contactWillSendsReceipt: contactWillSendsReceipt,
|
contactWillSendsReceipt: contactWillSendsReceipt,
|
||||||
|
markForRetry: markForRetry,
|
||||||
ackByServerAt: ackByServerAt,
|
ackByServerAt: ackByServerAt,
|
||||||
retryCount: retryCount,
|
retryCount: retryCount,
|
||||||
lastRetry: lastRetry,
|
lastRetry: lastRetry,
|
||||||
|
|
@ -13600,21 +13796,6 @@ final class $$GroupHistoriesTableReferences
|
||||||
return ProcessedTableManager(
|
return ProcessedTableManager(
|
||||||
manager.$state.copyWith(prefetchedData: [item]));
|
manager.$state.copyWith(prefetchedData: [item]));
|
||||||
}
|
}
|
||||||
|
|
||||||
static $ContactsTable _affectedContactIdTable(_$TwonlyDB db) =>
|
|
||||||
db.contacts.createAlias($_aliasNameGenerator(
|
|
||||||
db.groupHistories.affectedContactId, db.contacts.userId));
|
|
||||||
|
|
||||||
$$ContactsTableProcessedTableManager? get affectedContactId {
|
|
||||||
final $_column = $_itemColumn<int>('affected_contact_id');
|
|
||||||
if ($_column == null) return null;
|
|
||||||
final manager = $$ContactsTableTableManager($_db, $_db.contacts)
|
|
||||||
.filter((f) => f.userId.sqlEquals($_column));
|
|
||||||
final item = $_typedResult.readTableOrNull(_affectedContactIdTable($_db));
|
|
||||||
if (item == null) return manager;
|
|
||||||
return ProcessedTableManager(
|
|
||||||
manager.$state.copyWith(prefetchedData: [item]));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class $$GroupHistoriesTableFilterComposer
|
class $$GroupHistoriesTableFilterComposer
|
||||||
|
|
@ -13630,6 +13811,10 @@ class $$GroupHistoriesTableFilterComposer
|
||||||
column: $table.groupHistoryId,
|
column: $table.groupHistoryId,
|
||||||
builder: (column) => ColumnFilters(column));
|
builder: (column) => ColumnFilters(column));
|
||||||
|
|
||||||
|
ColumnFilters<int> get affectedContactId => $composableBuilder(
|
||||||
|
column: $table.affectedContactId,
|
||||||
|
builder: (column) => ColumnFilters(column));
|
||||||
|
|
||||||
ColumnFilters<String> get oldGroupName => $composableBuilder(
|
ColumnFilters<String> get oldGroupName => $composableBuilder(
|
||||||
column: $table.oldGroupName, builder: (column) => ColumnFilters(column));
|
column: $table.oldGroupName, builder: (column) => ColumnFilters(column));
|
||||||
|
|
||||||
|
|
@ -13688,26 +13873,6 @@ class $$GroupHistoriesTableFilterComposer
|
||||||
));
|
));
|
||||||
return composer;
|
return composer;
|
||||||
}
|
}
|
||||||
|
|
||||||
$$ContactsTableFilterComposer get affectedContactId {
|
|
||||||
final $$ContactsTableFilterComposer composer = $composerBuilder(
|
|
||||||
composer: this,
|
|
||||||
getCurrentColumn: (t) => t.affectedContactId,
|
|
||||||
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 $$GroupHistoriesTableOrderingComposer
|
class $$GroupHistoriesTableOrderingComposer
|
||||||
|
|
@ -13723,6 +13888,10 @@ class $$GroupHistoriesTableOrderingComposer
|
||||||
column: $table.groupHistoryId,
|
column: $table.groupHistoryId,
|
||||||
builder: (column) => ColumnOrderings(column));
|
builder: (column) => ColumnOrderings(column));
|
||||||
|
|
||||||
|
ColumnOrderings<int> get affectedContactId => $composableBuilder(
|
||||||
|
column: $table.affectedContactId,
|
||||||
|
builder: (column) => ColumnOrderings(column));
|
||||||
|
|
||||||
ColumnOrderings<String> get oldGroupName => $composableBuilder(
|
ColumnOrderings<String> get oldGroupName => $composableBuilder(
|
||||||
column: $table.oldGroupName,
|
column: $table.oldGroupName,
|
||||||
builder: (column) => ColumnOrderings(column));
|
builder: (column) => ColumnOrderings(column));
|
||||||
|
|
@ -13781,26 +13950,6 @@ class $$GroupHistoriesTableOrderingComposer
|
||||||
));
|
));
|
||||||
return composer;
|
return composer;
|
||||||
}
|
}
|
||||||
|
|
||||||
$$ContactsTableOrderingComposer get affectedContactId {
|
|
||||||
final $$ContactsTableOrderingComposer composer = $composerBuilder(
|
|
||||||
composer: this,
|
|
||||||
getCurrentColumn: (t) => t.affectedContactId,
|
|
||||||
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 $$GroupHistoriesTableAnnotationComposer
|
class $$GroupHistoriesTableAnnotationComposer
|
||||||
|
|
@ -13815,6 +13964,9 @@ class $$GroupHistoriesTableAnnotationComposer
|
||||||
GeneratedColumn<String> get groupHistoryId => $composableBuilder(
|
GeneratedColumn<String> get groupHistoryId => $composableBuilder(
|
||||||
column: $table.groupHistoryId, builder: (column) => column);
|
column: $table.groupHistoryId, builder: (column) => column);
|
||||||
|
|
||||||
|
GeneratedColumn<int> get affectedContactId => $composableBuilder(
|
||||||
|
column: $table.affectedContactId, builder: (column) => column);
|
||||||
|
|
||||||
GeneratedColumn<String> get oldGroupName => $composableBuilder(
|
GeneratedColumn<String> get oldGroupName => $composableBuilder(
|
||||||
column: $table.oldGroupName, builder: (column) => column);
|
column: $table.oldGroupName, builder: (column) => column);
|
||||||
|
|
||||||
|
|
@ -13871,26 +14023,6 @@ class $$GroupHistoriesTableAnnotationComposer
|
||||||
));
|
));
|
||||||
return composer;
|
return composer;
|
||||||
}
|
}
|
||||||
|
|
||||||
$$ContactsTableAnnotationComposer get affectedContactId {
|
|
||||||
final $$ContactsTableAnnotationComposer composer = $composerBuilder(
|
|
||||||
composer: this,
|
|
||||||
getCurrentColumn: (t) => t.affectedContactId,
|
|
||||||
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 $$GroupHistoriesTableTableManager extends RootTableManager<
|
class $$GroupHistoriesTableTableManager extends RootTableManager<
|
||||||
|
|
@ -13904,8 +14036,7 @@ class $$GroupHistoriesTableTableManager extends RootTableManager<
|
||||||
$$GroupHistoriesTableUpdateCompanionBuilder,
|
$$GroupHistoriesTableUpdateCompanionBuilder,
|
||||||
(GroupHistory, $$GroupHistoriesTableReferences),
|
(GroupHistory, $$GroupHistoriesTableReferences),
|
||||||
GroupHistory,
|
GroupHistory,
|
||||||
PrefetchHooks Function(
|
PrefetchHooks Function({bool groupId, bool contactId})> {
|
||||||
{bool groupId, bool contactId, bool affectedContactId})> {
|
|
||||||
$$GroupHistoriesTableTableManager(_$TwonlyDB db, $GroupHistoriesTable table)
|
$$GroupHistoriesTableTableManager(_$TwonlyDB db, $GroupHistoriesTable table)
|
||||||
: super(TableManagerState(
|
: super(TableManagerState(
|
||||||
db: db,
|
db: db,
|
||||||
|
|
@ -13974,8 +14105,7 @@ class $$GroupHistoriesTableTableManager extends RootTableManager<
|
||||||
$$GroupHistoriesTableReferences(db, table, e)
|
$$GroupHistoriesTableReferences(db, table, e)
|
||||||
))
|
))
|
||||||
.toList(),
|
.toList(),
|
||||||
prefetchHooksCallback: (
|
prefetchHooksCallback: ({groupId = false, contactId = false}) {
|
||||||
{groupId = false, contactId = false, affectedContactId = false}) {
|
|
||||||
return PrefetchHooks(
|
return PrefetchHooks(
|
||||||
db: db,
|
db: db,
|
||||||
explicitlyWatchedTables: [],
|
explicitlyWatchedTables: [],
|
||||||
|
|
@ -14014,17 +14144,6 @@ class $$GroupHistoriesTableTableManager extends RootTableManager<
|
||||||
.userId,
|
.userId,
|
||||||
) as T;
|
) as T;
|
||||||
}
|
}
|
||||||
if (affectedContactId) {
|
|
||||||
state = state.withJoin(
|
|
||||||
currentTable: table,
|
|
||||||
currentColumn: table.affectedContactId,
|
|
||||||
referencedTable: $$GroupHistoriesTableReferences
|
|
||||||
._affectedContactIdTable(db),
|
|
||||||
referencedColumn: $$GroupHistoriesTableReferences
|
|
||||||
._affectedContactIdTable(db)
|
|
||||||
.userId,
|
|
||||||
) as T;
|
|
||||||
}
|
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
},
|
},
|
||||||
|
|
@ -14047,8 +14166,7 @@ typedef $$GroupHistoriesTableProcessedTableManager = ProcessedTableManager<
|
||||||
$$GroupHistoriesTableUpdateCompanionBuilder,
|
$$GroupHistoriesTableUpdateCompanionBuilder,
|
||||||
(GroupHistory, $$GroupHistoriesTableReferences),
|
(GroupHistory, $$GroupHistoriesTableReferences),
|
||||||
GroupHistory,
|
GroupHistory,
|
||||||
PrefetchHooks Function(
|
PrefetchHooks Function({bool groupId, bool contactId})>;
|
||||||
{bool groupId, bool contactId, bool affectedContactId})>;
|
|
||||||
|
|
||||||
class $TwonlyDBManager {
|
class $TwonlyDBManager {
|
||||||
final _$TwonlyDB _db;
|
final _$TwonlyDB _db;
|
||||||
|
|
|
||||||
|
|
@ -1566,9 +1566,838 @@ class Shape17 extends i0.VersionedTable {
|
||||||
i1.GeneratedColumn<String> _column_100(String aliasedName) =>
|
i1.GeneratedColumn<String> _column_100(String aliasedName) =>
|
||||||
i1.GeneratedColumn<String>('draft_message', aliasedName, true,
|
i1.GeneratedColumn<String>('draft_message', aliasedName, true,
|
||||||
type: i1.DriftSqlType.string);
|
type: i1.DriftSqlType.string);
|
||||||
|
|
||||||
|
final class Schema4 extends i0.VersionedSchema {
|
||||||
|
Schema4({required super.database}) : super(version: 4);
|
||||||
|
@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 Shape2 mediaFiles = Shape2(
|
||||||
|
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_12,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape3 messages = Shape3(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'messages',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [
|
||||||
|
'PRIMARY KEY(message_id)',
|
||||||
|
],
|
||||||
|
columns: [
|
||||||
|
_column_50,
|
||||||
|
_column_51,
|
||||||
|
_column_52,
|
||||||
|
_column_37,
|
||||||
|
_column_53,
|
||||||
|
_column_54,
|
||||||
|
_column_55,
|
||||||
|
_column_56,
|
||||||
|
_column_46,
|
||||||
|
_column_57,
|
||||||
|
_column_58,
|
||||||
|
_column_59,
|
||||||
|
_column_60,
|
||||||
|
_column_12,
|
||||||
|
_column_61,
|
||||||
|
_column_62,
|
||||||
|
_column_63,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape4 messageHistories = Shape4(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'message_histories',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [
|
||||||
|
'PRIMARY KEY(id)',
|
||||||
|
],
|
||||||
|
columns: [
|
||||||
|
_column_64,
|
||||||
|
_column_65,
|
||||||
|
_column_66,
|
||||||
|
_column_53,
|
||||||
|
_column_12,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape5 reactions = Shape5(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'reactions',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [
|
||||||
|
'PRIMARY KEY(message_id, sender_id, emoji)',
|
||||||
|
],
|
||||||
|
columns: [
|
||||||
|
_column_65,
|
||||||
|
_column_67,
|
||||||
|
_column_68,
|
||||||
|
_column_12,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape6 groupMembers = Shape6(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'group_members',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [
|
||||||
|
'PRIMARY KEY(group_id, contact_id)',
|
||||||
|
],
|
||||||
|
columns: [
|
||||||
|
_column_50,
|
||||||
|
_column_69,
|
||||||
|
_column_70,
|
||||||
|
_column_71,
|
||||||
|
_column_72,
|
||||||
|
_column_12,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape7 receipts = Shape7(
|
||||||
|
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_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);
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.GeneratedColumn<int> _column_101(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<int>('affected_contact_id', aliasedName, true,
|
||||||
|
type: i1.DriftSqlType.int);
|
||||||
|
|
||||||
|
final class Schema5 extends i0.VersionedSchema {
|
||||||
|
Schema5({required super.database}) : super(version: 5);
|
||||||
|
@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 Shape3 messages = Shape3(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'messages',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [
|
||||||
|
'PRIMARY KEY(message_id)',
|
||||||
|
],
|
||||||
|
columns: [
|
||||||
|
_column_50,
|
||||||
|
_column_51,
|
||||||
|
_column_52,
|
||||||
|
_column_37,
|
||||||
|
_column_53,
|
||||||
|
_column_54,
|
||||||
|
_column_55,
|
||||||
|
_column_56,
|
||||||
|
_column_46,
|
||||||
|
_column_57,
|
||||||
|
_column_58,
|
||||||
|
_column_59,
|
||||||
|
_column_60,
|
||||||
|
_column_12,
|
||||||
|
_column_61,
|
||||||
|
_column_62,
|
||||||
|
_column_63,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape4 messageHistories = Shape4(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'message_histories',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [
|
||||||
|
'PRIMARY KEY(id)',
|
||||||
|
],
|
||||||
|
columns: [
|
||||||
|
_column_64,
|
||||||
|
_column_65,
|
||||||
|
_column_66,
|
||||||
|
_column_53,
|
||||||
|
_column_12,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape5 reactions = Shape5(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'reactions',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [
|
||||||
|
'PRIMARY KEY(message_id, sender_id, emoji)',
|
||||||
|
],
|
||||||
|
columns: [
|
||||||
|
_column_65,
|
||||||
|
_column_67,
|
||||||
|
_column_68,
|
||||||
|
_column_12,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape6 groupMembers = Shape6(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'group_members',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [
|
||||||
|
'PRIMARY KEY(group_id, contact_id)',
|
||||||
|
],
|
||||||
|
columns: [
|
||||||
|
_column_50,
|
||||||
|
_column_69,
|
||||||
|
_column_70,
|
||||||
|
_column_71,
|
||||||
|
_column_72,
|
||||||
|
_column_12,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape19 receipts = Shape19(
|
||||||
|
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_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 Shape18 extends i0.VersionedTable {
|
||||||
|
Shape18({required super.source, required super.alias}) : super.aliased();
|
||||||
|
i1.GeneratedColumn<String> get mediaId =>
|
||||||
|
columnsByName['media_id']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get type =>
|
||||||
|
columnsByName['type']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get uploadState =>
|
||||||
|
columnsByName['upload_state']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get downloadState =>
|
||||||
|
columnsByName['download_state']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<bool> get requiresAuthentication =>
|
||||||
|
columnsByName['requires_authentication']! as i1.GeneratedColumn<bool>;
|
||||||
|
i1.GeneratedColumn<bool> get stored =>
|
||||||
|
columnsByName['stored']! as i1.GeneratedColumn<bool>;
|
||||||
|
i1.GeneratedColumn<bool> get isDraftMedia =>
|
||||||
|
columnsByName['is_draft_media']! as i1.GeneratedColumn<bool>;
|
||||||
|
i1.GeneratedColumn<String> get reuploadRequestedBy =>
|
||||||
|
columnsByName['reupload_requested_by']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<int> get displayLimitInMilliseconds =>
|
||||||
|
columnsByName['display_limit_in_milliseconds']!
|
||||||
|
as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<bool> get removeAudio =>
|
||||||
|
columnsByName['remove_audio']! as i1.GeneratedColumn<bool>;
|
||||||
|
i1.GeneratedColumn<i2.Uint8List> get downloadToken =>
|
||||||
|
columnsByName['download_token']! as i1.GeneratedColumn<i2.Uint8List>;
|
||||||
|
i1.GeneratedColumn<i2.Uint8List> get encryptionKey =>
|
||||||
|
columnsByName['encryption_key']! as i1.GeneratedColumn<i2.Uint8List>;
|
||||||
|
i1.GeneratedColumn<i2.Uint8List> get encryptionMac =>
|
||||||
|
columnsByName['encryption_mac']! as i1.GeneratedColumn<i2.Uint8List>;
|
||||||
|
i1.GeneratedColumn<i2.Uint8List> get encryptionNonce =>
|
||||||
|
columnsByName['encryption_nonce']! as i1.GeneratedColumn<i2.Uint8List>;
|
||||||
|
i1.GeneratedColumn<i2.Uint8List> get storedFileHash =>
|
||||||
|
columnsByName['stored_file_hash']! as i1.GeneratedColumn<i2.Uint8List>;
|
||||||
|
i1.GeneratedColumn<DateTime> get createdAt =>
|
||||||
|
columnsByName['created_at']! as i1.GeneratedColumn<DateTime>;
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.GeneratedColumn<i2.Uint8List> _column_102(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<i2.Uint8List>('stored_file_hash', aliasedName, true,
|
||||||
|
type: i1.DriftSqlType.blob);
|
||||||
|
|
||||||
|
class Shape19 extends i0.VersionedTable {
|
||||||
|
Shape19({required super.source, required super.alias}) : super.aliased();
|
||||||
|
i1.GeneratedColumn<String> get receiptId =>
|
||||||
|
columnsByName['receipt_id']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<int> get contactId =>
|
||||||
|
columnsByName['contact_id']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<String> get messageId =>
|
||||||
|
columnsByName['message_id']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<i2.Uint8List> get message =>
|
||||||
|
columnsByName['message']! as i1.GeneratedColumn<i2.Uint8List>;
|
||||||
|
i1.GeneratedColumn<bool> get contactWillSendsReceipt =>
|
||||||
|
columnsByName['contact_will_sends_receipt']! as i1.GeneratedColumn<bool>;
|
||||||
|
i1.GeneratedColumn<DateTime> get markForRetry =>
|
||||||
|
columnsByName['mark_for_retry']! as i1.GeneratedColumn<DateTime>;
|
||||||
|
i1.GeneratedColumn<DateTime> get ackByServerAt =>
|
||||||
|
columnsByName['ack_by_server_at']! as i1.GeneratedColumn<DateTime>;
|
||||||
|
i1.GeneratedColumn<int> get retryCount =>
|
||||||
|
columnsByName['retry_count']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<DateTime> get lastRetry =>
|
||||||
|
columnsByName['last_retry']! as i1.GeneratedColumn<DateTime>;
|
||||||
|
i1.GeneratedColumn<DateTime> get createdAt =>
|
||||||
|
columnsByName['created_at']! as i1.GeneratedColumn<DateTime>;
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.GeneratedColumn<DateTime> _column_103(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<DateTime>('mark_for_retry', aliasedName, true,
|
||||||
|
type: i1.DriftSqlType.dateTime);
|
||||||
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, Schema5 schema) from4To5,
|
||||||
}) {
|
}) {
|
||||||
return (currentVersion, database) async {
|
return (currentVersion, database) async {
|
||||||
switch (currentVersion) {
|
switch (currentVersion) {
|
||||||
|
|
@ -1582,6 +2411,16 @@ i0.MigrationStepWithVersion migrationSteps({
|
||||||
final migrator = i1.Migrator(database, schema);
|
final migrator = i1.Migrator(database, schema);
|
||||||
await from2To3(migrator, schema);
|
await from2To3(migrator, schema);
|
||||||
return 3;
|
return 3;
|
||||||
|
case 3:
|
||||||
|
final schema = Schema4(database: database);
|
||||||
|
final migrator = i1.Migrator(database, schema);
|
||||||
|
await from3To4(migrator, schema);
|
||||||
|
return 4;
|
||||||
|
case 4:
|
||||||
|
final schema = Schema5(database: database);
|
||||||
|
final migrator = i1.Migrator(database, schema);
|
||||||
|
await from4To5(migrator, schema);
|
||||||
|
return 5;
|
||||||
default:
|
default:
|
||||||
throw ArgumentError.value('Unknown migration from $currentVersion');
|
throw ArgumentError.value('Unknown migration from $currentVersion');
|
||||||
}
|
}
|
||||||
|
|
@ -1591,9 +2430,13 @@ i0.MigrationStepWithVersion migrationSteps({
|
||||||
i1.OnUpgrade stepByStep({
|
i1.OnUpgrade stepByStep({
|
||||||
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, Schema5 schema) from4To5,
|
||||||
}) =>
|
}) =>
|
||||||
i0.VersionedSchema.stepByStepHelper(
|
i0.VersionedSchema.stepByStepHelper(
|
||||||
step: migrationSteps(
|
step: migrationSteps(
|
||||||
from1To2: from1To2,
|
from1To2: from1To2,
|
||||||
from2To3: from2To3,
|
from2To3: from2To3,
|
||||||
|
from3To4: from3To4,
|
||||||
|
from4To5: from4To5,
|
||||||
));
|
));
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
"registerUsernameSlogan": "Bitte wähle einen Benutzernamen, damit dich andere finden können!",
|
"registerUsernameSlogan": "Bitte wähle einen Benutzernamen, damit dich andere finden können!",
|
||||||
"registerUsernameDecoration": "Benutzername",
|
"registerUsernameDecoration": "Benutzername",
|
||||||
"registerUsernameLimits": "Der Benutzername muss mindestens 3 Zeichen lang sein.",
|
"registerUsernameLimits": "Der Benutzername muss mindestens 3 Zeichen lang sein.",
|
||||||
|
"registerProofOfWorkFailed": "Beim Captcha-Test gab es ein Problem. Bitte versuche es erneut.",
|
||||||
"registerSubmitButton": "Jetzt registrieren!",
|
"registerSubmitButton": "Jetzt registrieren!",
|
||||||
"registerTwonlyCodeText": "Hast du einen twonly-Code erhalten? Dann löse ihn entweder direkt hier oder später ein!",
|
"registerTwonlyCodeText": "Hast du einen twonly-Code erhalten? Dann löse ihn entweder direkt hier oder später ein!",
|
||||||
"registerTwonlyCodeLabel": "twonly-Code",
|
"registerTwonlyCodeLabel": "twonly-Code",
|
||||||
|
|
@ -197,7 +198,7 @@
|
||||||
"errorSessionNotAuthenticated": "Deine Sitzung ist nicht authentifiziert. Bitte melde dich an, um fortzufahren.",
|
"errorSessionNotAuthenticated": "Deine Sitzung ist nicht authentifiziert. Bitte melde dich an, um fortzufahren.",
|
||||||
"errorOnlyOneSessionAllowed": "Es ist nur eine aktive Sitzung pro Benutzer erlaubt. Bitte melde dich von anderen Geräten ab, um fortzufahren.",
|
"errorOnlyOneSessionAllowed": "Es ist nur eine aktive Sitzung pro Benutzer erlaubt. Bitte melde dich von anderen Geräten ab, um fortzufahren.",
|
||||||
"upgradeToPaidPlan": "Upgrade auf einen kostenpflichtigen Plan.",
|
"upgradeToPaidPlan": "Upgrade auf einen kostenpflichtigen Plan.",
|
||||||
"upgradeToPaidPlanButton": "Auf {planId} upgraden",
|
"upgradeToPaidPlanButton": "Auf {planId} upgraden{sufix}",
|
||||||
"partOfPaidPlanOf": "Du bist Teil des bezahlten Plans von {username}!",
|
"partOfPaidPlanOf": "Du bist Teil des bezahlten Plans von {username}!",
|
||||||
"errorNotEnoughCredit": "Du hast nicht genügend twonly-Guthaben.",
|
"errorNotEnoughCredit": "Du hast nicht genügend twonly-Guthaben.",
|
||||||
"errorPlanLimitReached": "Du hast das Limit deines Plans erreicht. Bitte upgrade deinen Plan.",
|
"errorPlanLimitReached": "Du hast das Limit deines Plans erreicht. Bitte upgrade deinen Plan.",
|
||||||
|
|
@ -205,17 +206,21 @@
|
||||||
"errorVoucherInvalid": "Der eingegebene Gutschein-Code ist nicht gültig.",
|
"errorVoucherInvalid": "Der eingegebene Gutschein-Code ist nicht gültig.",
|
||||||
"errorPlanUpgradeNotYearly": "Das Upgrade des Plans muss jährlich bezahlt werden, da der aktuelle Plan ebenfalls jährlich abgerechnet wird.",
|
"errorPlanUpgradeNotYearly": "Das Upgrade des Plans muss jährlich bezahlt werden, da der aktuelle Plan ebenfalls jährlich abgerechnet wird.",
|
||||||
"proFeature1": "✓ Unbegrenzte Medien-Datei-Uploads",
|
"proFeature1": "✓ Unbegrenzte Medien-Datei-Uploads",
|
||||||
"proFeature2": "1 zusätzlicher Plus Benutzer",
|
"proFeature2": "✓ 1 zusätzlicher Plus Benutzer",
|
||||||
"proFeature3": "Flammen wiederherstellen",
|
"proFeature3": "✓ Flammen wiederherstellen",
|
||||||
"proFeature4": "Cloud-Backup verschlüsselt (coming-soon)",
|
"proFeature4": "✓ twonly unterstützen",
|
||||||
"year": "year",
|
"year": "Jahr",
|
||||||
"month": "month",
|
"month": "Monat",
|
||||||
"familyFeature1": "✓ Alles von Pro",
|
"yearly": "Jährlich",
|
||||||
"familyFeature2": "4 zusätzliche Plus Benutzer",
|
"monthly": "Monatlich",
|
||||||
|
"familyFeature1": "✓ Unbegrenzte Medien-Datei-Uploads",
|
||||||
|
"familyFeature2": "✓ 4 zusätzliche Plus Benutzer",
|
||||||
|
"familyFeature3": "✓ Flammen wiederherstellen",
|
||||||
|
"familyFeature4": "✓ twonly unterstützen",
|
||||||
"redeemUserInviteCode": "Oder löse einen twonly-Code ein.",
|
"redeemUserInviteCode": "Oder löse einen twonly-Code ein.",
|
||||||
"freeFeature1": "10 Medien-Datei-Uploads pro Tag",
|
"freeFeature1": "✓ 10 Medien-Datei-Uploads pro Tag",
|
||||||
"plusFeature1": "✓ Unbegrenzte Medien-Datei-Uploads",
|
"plusFeature1": "✓ Unbegrenzte Medien-Datei-Uploads",
|
||||||
"plusFeature2": "Zusatzfunktionen (coming-soon)",
|
"plusFeature2": "✓ Zusatzfunktionen (coming-soon)",
|
||||||
"transactionHistory": "Transaktionshistorie",
|
"transactionHistory": "Transaktionshistorie",
|
||||||
"currentBalance": "Dein Guthaben",
|
"currentBalance": "Dein Guthaben",
|
||||||
"manageAdditionalUsers": "Zusätzliche Benutzer verwalten",
|
"manageAdditionalUsers": "Zusätzliche Benutzer verwalten",
|
||||||
|
|
@ -409,7 +414,7 @@
|
||||||
"notificationReactionToImage": "hat mit {reaction} auf dein Bild reagiert.",
|
"notificationReactionToImage": "hat mit {reaction} auf dein Bild reagiert.",
|
||||||
"notificationReactionToAudio": "hat mit {reaction} auf deine Sprachnachricht reagiert.",
|
"notificationReactionToAudio": "hat mit {reaction} auf deine Sprachnachricht reagiert.",
|
||||||
"notificationResponse": "hat dir{inGroup} geantwortet.",
|
"notificationResponse": "hat dir{inGroup} geantwortet.",
|
||||||
"notificationTitleUnknownUser": "Jemand",
|
"notificationTitleUnknownUser": "[Unbekannt]",
|
||||||
"notificationCategoryMessageTitle": "Nachrichten",
|
"notificationCategoryMessageTitle": "Nachrichten",
|
||||||
"notificationCategoryMessageDesc": "Nachrichten von anderen Benutzern.",
|
"notificationCategoryMessageDesc": "Nachrichten von anderen Benutzern.",
|
||||||
"groupContextMenuDeleteGroup": "Dadurch werden alle Nachrichten in diesem Chat dauerhaft gelöscht.",
|
"groupContextMenuDeleteGroup": "Dadurch werden alle Nachrichten in diesem Chat dauerhaft gelöscht.",
|
||||||
|
|
@ -451,5 +456,13 @@
|
||||||
"linkFromUsernameLong": "Wenn du den Link von der Person direkt erhalten hast, kannst du den Kontakt als verifiziert markieren, da der öffentliche Schlüssel im Link mit dem bereits für diesen Benutzer gespeicherten öffentlichen Schlüssel übereinstimmt.",
|
"linkFromUsernameLong": "Wenn du den Link von der Person direkt erhalten hast, kannst du den Kontakt als verifiziert markieren, da der öffentliche Schlüssel im Link mit dem bereits für diesen Benutzer gespeicherten öffentlichen Schlüssel übereinstimmt.",
|
||||||
"gotLinkFromFriend": "Ja, der Link kommt direkt von der Person.",
|
"gotLinkFromFriend": "Ja, der Link kommt direkt von der Person.",
|
||||||
"couldNotVerifyUsername": "{username} konnte nicht verifiziert werden",
|
"couldNotVerifyUsername": "{username} konnte nicht verifiziert werden",
|
||||||
"linkPubkeyDoesNotMatch": "Der öffentliche Schlüssel im Link stimmt nicht mit dem für diesen Kontakt gespeicherten öffentlichen Schlüssel überein. Triff die Person persönlich und scanne den QR-Code direkt!"
|
"linkPubkeyDoesNotMatch": "Der öffentliche Schlüssel im Link stimmt nicht mit dem für diesen Kontakt gespeicherten öffentlichen Schlüssel überein. Triff die Person persönlich und scanne den QR-Code direkt!",
|
||||||
|
"startWithCameraOpen": "Mit geöffneter Kamera starten",
|
||||||
|
"showImagePreviewWhenSending": "Bildvorschau bei der Auswahl von Empfängern anzeigen",
|
||||||
|
"verifiedPublicKey": "Der öffentliche Schlüssel von {username} wurde überprüft und ist gültig.",
|
||||||
|
"memoriesAYearAgo": "Vor einem Jahr",
|
||||||
|
"memoriesXYearsAgo": "Vor {years} Jahren",
|
||||||
|
"migrationOfMemories": "Migration von Mediendateien: {open} noch offen.",
|
||||||
|
"autoStoreAllSendUnlimitedMediaFiles": "Alle gesendeten Medien speichern",
|
||||||
|
"autoStoreAllSendUnlimitedMediaFilesSubtitle": "Wenn du diese Option aktivierst, werden alle Bilder, die du sendest, gespeichert, sofern sie mit einem unendlichen Countdown und nicht im twonly-Modus gesendet wurden."
|
||||||
}
|
}
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
"registerUsernameSlogan": "Please select a username so others can find you!",
|
"registerUsernameSlogan": "Please select a username so others can find you!",
|
||||||
"registerUsernameDecoration": "Username",
|
"registerUsernameDecoration": "Username",
|
||||||
"registerUsernameLimits": "Your username must be at least 3 characters long.",
|
"registerUsernameLimits": "Your username must be at least 3 characters long.",
|
||||||
|
"registerProofOfWorkFailed": "There was an issue with the captcha test. Please try again.",
|
||||||
"registerSubmitButton": "Register now!",
|
"registerSubmitButton": "Register now!",
|
||||||
"registerTwonlyCodeText": "Have you received a twonly code? Then redeem it either directly here or later!",
|
"registerTwonlyCodeText": "Have you received a twonly code? Then redeem it either directly here or later!",
|
||||||
"registerTwonlyCodeLabel": "twonly-Code",
|
"registerTwonlyCodeLabel": "twonly-Code",
|
||||||
|
|
@ -232,22 +233,26 @@
|
||||||
"errorPlanNotAllowed": "This feature is not available in your current plan.",
|
"errorPlanNotAllowed": "This feature is not available in your current plan.",
|
||||||
"errorPlanUpgradeNotYearly": "The plan upgrade must be paid for annually, as the current plan is also billed annually.",
|
"errorPlanUpgradeNotYearly": "The plan upgrade must be paid for annually, as the current plan is also billed annually.",
|
||||||
"upgradeToPaidPlan": "Upgrade to a paid plan.",
|
"upgradeToPaidPlan": "Upgrade to a paid plan.",
|
||||||
"upgradeToPaidPlanButton": "Upgrade subscription to {planId}",
|
"upgradeToPaidPlanButton": "Upgrade to {planId}{sufix}",
|
||||||
"partOfPaidPlanOf": "You are part of the paid plan of {username}!",
|
"partOfPaidPlanOf": "You are part of the paid plan of {username}!",
|
||||||
"year": "year",
|
"year": "year",
|
||||||
|
"yearly": "Yearly",
|
||||||
"month": "month",
|
"month": "month",
|
||||||
|
"monthly": "Monthly",
|
||||||
"proFeature1": "✓ Unlimited media file uploads",
|
"proFeature1": "✓ Unlimited media file uploads",
|
||||||
"proFeature2": "1 additional Plus user",
|
"proFeature2": "✓ 1 additional Plus user",
|
||||||
"proFeature3": "Cloud-Backup encrypted (coming-soon)",
|
"proFeature3": "✓ Restore flames",
|
||||||
"proFeature4": "Additional features (coming-soon)",
|
"proFeature4": "✓ Support twonly",
|
||||||
"familyFeature1": "✓ All from Pro",
|
"familyFeature1": "✓ Unlimited media file uploads",
|
||||||
"familyFeature2": "4 additional Plus users",
|
"familyFeature2": "✓ 4 additional Plus user",
|
||||||
|
"familyFeature3": "✓ Restore flames",
|
||||||
|
"familyFeature4": "✓ Support twonly",
|
||||||
"redeemUserInviteCode": "Or redeem a twonly-Code.",
|
"redeemUserInviteCode": "Or redeem a twonly-Code.",
|
||||||
"redeemUserInviteCodeTitle": "Redeem twonly-Code",
|
"redeemUserInviteCodeTitle": "Redeem twonly-Code",
|
||||||
"redeemUserInviteCodeSuccess": "Your plan has been successfully adjusted.",
|
"redeemUserInviteCodeSuccess": "Your plan has been successfully adjusted.",
|
||||||
"freeFeature1": "10 Media file uploads per day",
|
"freeFeature1": "✓ 10 Media file uploads per day",
|
||||||
"plusFeature1": "✓ Unlimited media file uploads",
|
"plusFeature1": "✓ Unlimited media file uploads",
|
||||||
"plusFeature2": "Additional features (coming-soon)",
|
"plusFeature2": "✓ Additional features (coming-soon)",
|
||||||
"transactionHistory": "Your transaction history",
|
"transactionHistory": "Your transaction history",
|
||||||
"manageSubscription": "Manage your subscription",
|
"manageSubscription": "Manage your subscription",
|
||||||
"nextPayment": "Next payment",
|
"nextPayment": "Next payment",
|
||||||
|
|
@ -439,7 +444,7 @@
|
||||||
"notificationReactionToImage": "has reacted with {reaction} to your image.",
|
"notificationReactionToImage": "has reacted with {reaction} to your image.",
|
||||||
"notificationReactionToAudio": "has reacted with {reaction} to your audio message.",
|
"notificationReactionToAudio": "has reacted with {reaction} to your audio message.",
|
||||||
"notificationResponse": "has responded{inGroup}.",
|
"notificationResponse": "has responded{inGroup}.",
|
||||||
"notificationTitleUnknownUser": "Someone",
|
"notificationTitleUnknownUser": "[Unknown]",
|
||||||
"notificationCategoryMessageTitle": "Messages",
|
"notificationCategoryMessageTitle": "Messages",
|
||||||
"notificationCategoryMessageDesc": "Messages from other users.",
|
"notificationCategoryMessageDesc": "Messages from other users.",
|
||||||
"groupContextMenuDeleteGroup": "This will permanently delete all messages in this chat.",
|
"groupContextMenuDeleteGroup": "This will permanently delete all messages in this chat.",
|
||||||
|
|
@ -481,5 +486,13 @@
|
||||||
"linkFromUsernameLong": "If you received the link from your friend, you can mark the user as verified, as the public key in the link matches the public key already stored for that user?",
|
"linkFromUsernameLong": "If you received the link from your friend, you can mark the user as verified, as the public key in the link matches the public key already stored for that user?",
|
||||||
"gotLinkFromFriend": "Yes, I got the link from my friend!",
|
"gotLinkFromFriend": "Yes, I got the link from my friend!",
|
||||||
"couldNotVerifyUsername": "Could not verify {username}",
|
"couldNotVerifyUsername": "Could not verify {username}",
|
||||||
"linkPubkeyDoesNotMatch": "The public key in the link does not match the public key stored for this contact. Try to meet your friend in person and scan the QR code directly!"
|
"linkPubkeyDoesNotMatch": "The public key in the link does not match the public key stored for this contact. Try to meet your friend in person and scan the QR code directly!",
|
||||||
|
"startWithCameraOpen": "Start with camera open",
|
||||||
|
"showImagePreviewWhenSending": "Display image preview when selecting recipients",
|
||||||
|
"verifiedPublicKey": "The public key of {username} has been verified and is valid.",
|
||||||
|
"memoriesAYearAgo": "One year ago",
|
||||||
|
"memoriesXYearsAgo": "{years} years ago",
|
||||||
|
"migrationOfMemories": "Migration of media files: {open} still to be processed.",
|
||||||
|
"autoStoreAllSendUnlimitedMediaFiles": "Save all sent media",
|
||||||
|
"autoStoreAllSendUnlimitedMediaFilesSubtitle": "If you enable this option, all images you send will be saved as long as they were sent with an infinite countdown and not in twonly mode."
|
||||||
}
|
}
|
||||||
|
|
@ -218,6 +218,12 @@ abstract class AppLocalizations {
|
||||||
/// **'Your username must be at least 3 characters long.'**
|
/// **'Your username must be at least 3 characters long.'**
|
||||||
String get registerUsernameLimits;
|
String get registerUsernameLimits;
|
||||||
|
|
||||||
|
/// No description provided for @registerProofOfWorkFailed.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'There was an issue with the captcha test. Please try again.'**
|
||||||
|
String get registerProofOfWorkFailed;
|
||||||
|
|
||||||
/// No description provided for @registerSubmitButton.
|
/// No description provided for @registerSubmitButton.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|
@ -1313,8 +1319,8 @@ abstract class AppLocalizations {
|
||||||
/// No description provided for @upgradeToPaidPlanButton.
|
/// No description provided for @upgradeToPaidPlanButton.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Upgrade subscription to {planId}'**
|
/// **'Upgrade to {planId}{sufix}'**
|
||||||
String upgradeToPaidPlanButton(Object planId);
|
String upgradeToPaidPlanButton(Object planId, Object sufix);
|
||||||
|
|
||||||
/// No description provided for @partOfPaidPlanOf.
|
/// No description provided for @partOfPaidPlanOf.
|
||||||
///
|
///
|
||||||
|
|
@ -1328,12 +1334,24 @@ abstract class AppLocalizations {
|
||||||
/// **'year'**
|
/// **'year'**
|
||||||
String get year;
|
String get year;
|
||||||
|
|
||||||
|
/// No description provided for @yearly.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Yearly'**
|
||||||
|
String get yearly;
|
||||||
|
|
||||||
/// No description provided for @month.
|
/// No description provided for @month.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'month'**
|
/// **'month'**
|
||||||
String get month;
|
String get month;
|
||||||
|
|
||||||
|
/// No description provided for @monthly.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Monthly'**
|
||||||
|
String get monthly;
|
||||||
|
|
||||||
/// No description provided for @proFeature1.
|
/// No description provided for @proFeature1.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|
@ -1343,33 +1361,45 @@ abstract class AppLocalizations {
|
||||||
/// No description provided for @proFeature2.
|
/// No description provided for @proFeature2.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'1 additional Plus user'**
|
/// **'✓ 1 additional Plus user'**
|
||||||
String get proFeature2;
|
String get proFeature2;
|
||||||
|
|
||||||
/// No description provided for @proFeature3.
|
/// No description provided for @proFeature3.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Cloud-Backup encrypted (coming-soon)'**
|
/// **'✓ Restore flames'**
|
||||||
String get proFeature3;
|
String get proFeature3;
|
||||||
|
|
||||||
/// No description provided for @proFeature4.
|
/// No description provided for @proFeature4.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Additional features (coming-soon)'**
|
/// **'✓ Support twonly'**
|
||||||
String get proFeature4;
|
String get proFeature4;
|
||||||
|
|
||||||
/// No description provided for @familyFeature1.
|
/// No description provided for @familyFeature1.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'✓ All from Pro'**
|
/// **'✓ Unlimited media file uploads'**
|
||||||
String get familyFeature1;
|
String get familyFeature1;
|
||||||
|
|
||||||
/// No description provided for @familyFeature2.
|
/// No description provided for @familyFeature2.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'4 additional Plus users'**
|
/// **'✓ 4 additional Plus user'**
|
||||||
String get familyFeature2;
|
String get familyFeature2;
|
||||||
|
|
||||||
|
/// No description provided for @familyFeature3.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'✓ Restore flames'**
|
||||||
|
String get familyFeature3;
|
||||||
|
|
||||||
|
/// No description provided for @familyFeature4.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'✓ Support twonly'**
|
||||||
|
String get familyFeature4;
|
||||||
|
|
||||||
/// No description provided for @redeemUserInviteCode.
|
/// No description provided for @redeemUserInviteCode.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|
@ -1391,7 +1421,7 @@ abstract class AppLocalizations {
|
||||||
/// No description provided for @freeFeature1.
|
/// No description provided for @freeFeature1.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'10 Media file uploads per day'**
|
/// **'✓ 10 Media file uploads per day'**
|
||||||
String get freeFeature1;
|
String get freeFeature1;
|
||||||
|
|
||||||
/// No description provided for @plusFeature1.
|
/// No description provided for @plusFeature1.
|
||||||
|
|
@ -1403,7 +1433,7 @@ abstract class AppLocalizations {
|
||||||
/// No description provided for @plusFeature2.
|
/// No description provided for @plusFeature2.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Additional features (coming-soon)'**
|
/// **'✓ Additional features (coming-soon)'**
|
||||||
String get plusFeature2;
|
String get plusFeature2;
|
||||||
|
|
||||||
/// No description provided for @transactionHistory.
|
/// No description provided for @transactionHistory.
|
||||||
|
|
@ -2555,7 +2585,7 @@ abstract class AppLocalizations {
|
||||||
/// No description provided for @notificationTitleUnknownUser.
|
/// No description provided for @notificationTitleUnknownUser.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Someone'**
|
/// **'[Unknown]'**
|
||||||
String get notificationTitleUnknownUser;
|
String get notificationTitleUnknownUser;
|
||||||
|
|
||||||
/// No description provided for @notificationCategoryMessageTitle.
|
/// No description provided for @notificationCategoryMessageTitle.
|
||||||
|
|
@ -2809,6 +2839,54 @@ abstract class AppLocalizations {
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'The public key in the link does not match the public key stored for this contact. Try to meet your friend in person and scan the QR code directly!'**
|
/// **'The public key in the link does not match the public key stored for this contact. Try to meet your friend in person and scan the QR code directly!'**
|
||||||
String get linkPubkeyDoesNotMatch;
|
String get linkPubkeyDoesNotMatch;
|
||||||
|
|
||||||
|
/// No description provided for @startWithCameraOpen.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Start with camera open'**
|
||||||
|
String get startWithCameraOpen;
|
||||||
|
|
||||||
|
/// No description provided for @showImagePreviewWhenSending.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Display image preview when selecting recipients'**
|
||||||
|
String get showImagePreviewWhenSending;
|
||||||
|
|
||||||
|
/// No description provided for @verifiedPublicKey.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'The public key of {username} has been verified and is valid.'**
|
||||||
|
String verifiedPublicKey(Object username);
|
||||||
|
|
||||||
|
/// No description provided for @memoriesAYearAgo.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'One year ago'**
|
||||||
|
String get memoriesAYearAgo;
|
||||||
|
|
||||||
|
/// No description provided for @memoriesXYearsAgo.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'{years} years ago'**
|
||||||
|
String memoriesXYearsAgo(Object years);
|
||||||
|
|
||||||
|
/// No description provided for @migrationOfMemories.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Migration of media files: {open} still to be processed.'**
|
||||||
|
String migrationOfMemories(Object open);
|
||||||
|
|
||||||
|
/// No description provided for @autoStoreAllSendUnlimitedMediaFiles.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Save all sent media'**
|
||||||
|
String get autoStoreAllSendUnlimitedMediaFiles;
|
||||||
|
|
||||||
|
/// No description provided for @autoStoreAllSendUnlimitedMediaFilesSubtitle.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'If you enable this option, all images you send will be saved as long as they were sent with an infinite countdown and not in twonly mode.'**
|
||||||
|
String get autoStoreAllSendUnlimitedMediaFilesSubtitle;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppLocalizationsDelegate
|
class _AppLocalizationsDelegate
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,10 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
String get registerUsernameLimits =>
|
String get registerUsernameLimits =>
|
||||||
'Der Benutzername muss mindestens 3 Zeichen lang sein.';
|
'Der Benutzername muss mindestens 3 Zeichen lang sein.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get registerProofOfWorkFailed =>
|
||||||
|
'Beim Captcha-Test gab es ein Problem. Bitte versuche es erneut.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get registerSubmitButton => 'Jetzt registrieren!';
|
String get registerSubmitButton => 'Jetzt registrieren!';
|
||||||
|
|
||||||
|
|
@ -685,8 +689,8 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
String get upgradeToPaidPlan => 'Upgrade auf einen kostenpflichtigen Plan.';
|
String get upgradeToPaidPlan => 'Upgrade auf einen kostenpflichtigen Plan.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String upgradeToPaidPlanButton(Object planId) {
|
String upgradeToPaidPlanButton(Object planId, Object sufix) {
|
||||||
return 'Auf $planId upgraden';
|
return 'Auf $planId upgraden$sufix';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -695,28 +699,40 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get year => 'year';
|
String get year => 'Jahr';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get month => 'month';
|
String get yearly => 'Jährlich';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get month => 'Monat';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get monthly => 'Monatlich';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get proFeature1 => '✓ Unbegrenzte Medien-Datei-Uploads';
|
String get proFeature1 => '✓ Unbegrenzte Medien-Datei-Uploads';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get proFeature2 => '1 zusätzlicher Plus Benutzer';
|
String get proFeature2 => '✓ 1 zusätzlicher Plus Benutzer';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get proFeature3 => 'Flammen wiederherstellen';
|
String get proFeature3 => '✓ Flammen wiederherstellen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get proFeature4 => 'Cloud-Backup verschlüsselt (coming-soon)';
|
String get proFeature4 => '✓ twonly unterstützen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get familyFeature1 => '✓ Alles von Pro';
|
String get familyFeature1 => '✓ Unbegrenzte Medien-Datei-Uploads';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get familyFeature2 => '4 zusätzliche Plus Benutzer';
|
String get familyFeature2 => '✓ 4 zusätzliche Plus Benutzer';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get familyFeature3 => '✓ Flammen wiederherstellen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get familyFeature4 => '✓ twonly unterstützen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get redeemUserInviteCode => 'Oder löse einen twonly-Code ein.';
|
String get redeemUserInviteCode => 'Oder löse einen twonly-Code ein.';
|
||||||
|
|
@ -729,13 +745,13 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
'Dein Plan wurde erfolgreich angepasst.';
|
'Dein Plan wurde erfolgreich angepasst.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get freeFeature1 => '10 Medien-Datei-Uploads pro Tag';
|
String get freeFeature1 => '✓ 10 Medien-Datei-Uploads pro Tag';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get plusFeature1 => '✓ Unbegrenzte Medien-Datei-Uploads';
|
String get plusFeature1 => '✓ Unbegrenzte Medien-Datei-Uploads';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get plusFeature2 => 'Zusatzfunktionen (coming-soon)';
|
String get plusFeature2 => '✓ Zusatzfunktionen (coming-soon)';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get transactionHistory => 'Transaktionshistorie';
|
String get transactionHistory => 'Transaktionshistorie';
|
||||||
|
|
@ -1409,7 +1425,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get notificationTitleUnknownUser => 'Jemand';
|
String get notificationTitleUnknownUser => '[Unbekannt]';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get notificationCategoryMessageTitle => 'Nachrichten';
|
String get notificationCategoryMessageTitle => 'Nachrichten';
|
||||||
|
|
@ -1554,4 +1570,37 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get linkPubkeyDoesNotMatch =>
|
String get linkPubkeyDoesNotMatch =>
|
||||||
'Der öffentliche Schlüssel im Link stimmt nicht mit dem für diesen Kontakt gespeicherten öffentlichen Schlüssel überein. Triff die Person persönlich und scanne den QR-Code direkt!';
|
'Der öffentliche Schlüssel im Link stimmt nicht mit dem für diesen Kontakt gespeicherten öffentlichen Schlüssel überein. Triff die Person persönlich und scanne den QR-Code direkt!';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get startWithCameraOpen => 'Mit geöffneter Kamera starten';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get showImagePreviewWhenSending =>
|
||||||
|
'Bildvorschau bei der Auswahl von Empfängern anzeigen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String verifiedPublicKey(Object username) {
|
||||||
|
return 'Der öffentliche Schlüssel von $username wurde überprüft und ist gültig.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get memoriesAYearAgo => 'Vor einem Jahr';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String memoriesXYearsAgo(Object years) {
|
||||||
|
return 'Vor $years Jahren';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String migrationOfMemories(Object open) {
|
||||||
|
return 'Migration von Mediendateien: $open noch offen.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get autoStoreAllSendUnlimitedMediaFiles =>
|
||||||
|
'Alle gesendeten Medien speichern';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get autoStoreAllSendUnlimitedMediaFilesSubtitle =>
|
||||||
|
'Wenn du diese Option aktivierst, werden alle Bilder, die du sendest, gespeichert, sofern sie mit einem unendlichen Countdown und nicht im twonly-Modus gesendet wurden.';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,10 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
String get registerUsernameLimits =>
|
String get registerUsernameLimits =>
|
||||||
'Your username must be at least 3 characters long.';
|
'Your username must be at least 3 characters long.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get registerProofOfWorkFailed =>
|
||||||
|
'There was an issue with the captcha test. Please try again.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get registerSubmitButton => 'Register now!';
|
String get registerSubmitButton => 'Register now!';
|
||||||
|
|
||||||
|
|
@ -679,8 +683,8 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
String get upgradeToPaidPlan => 'Upgrade to a paid plan.';
|
String get upgradeToPaidPlan => 'Upgrade to a paid plan.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String upgradeToPaidPlanButton(Object planId) {
|
String upgradeToPaidPlanButton(Object planId, Object sufix) {
|
||||||
return 'Upgrade subscription to $planId';
|
return 'Upgrade to $planId$sufix';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -691,26 +695,38 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get year => 'year';
|
String get year => 'year';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get yearly => 'Yearly';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get month => 'month';
|
String get month => 'month';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get monthly => 'Monthly';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get proFeature1 => '✓ Unlimited media file uploads';
|
String get proFeature1 => '✓ Unlimited media file uploads';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get proFeature2 => '1 additional Plus user';
|
String get proFeature2 => '✓ 1 additional Plus user';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get proFeature3 => 'Cloud-Backup encrypted (coming-soon)';
|
String get proFeature3 => '✓ Restore flames';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get proFeature4 => 'Additional features (coming-soon)';
|
String get proFeature4 => '✓ Support twonly';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get familyFeature1 => '✓ All from Pro';
|
String get familyFeature1 => '✓ Unlimited media file uploads';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get familyFeature2 => '4 additional Plus users';
|
String get familyFeature2 => '✓ 4 additional Plus user';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get familyFeature3 => '✓ Restore flames';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get familyFeature4 => '✓ Support twonly';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get redeemUserInviteCode => 'Or redeem a twonly-Code.';
|
String get redeemUserInviteCode => 'Or redeem a twonly-Code.';
|
||||||
|
|
@ -723,13 +739,13 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
'Your plan has been successfully adjusted.';
|
'Your plan has been successfully adjusted.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get freeFeature1 => '10 Media file uploads per day';
|
String get freeFeature1 => '✓ 10 Media file uploads per day';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get plusFeature1 => '✓ Unlimited media file uploads';
|
String get plusFeature1 => '✓ Unlimited media file uploads';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get plusFeature2 => 'Additional features (coming-soon)';
|
String get plusFeature2 => '✓ Additional features (coming-soon)';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get transactionHistory => 'Your transaction history';
|
String get transactionHistory => 'Your transaction history';
|
||||||
|
|
@ -1401,7 +1417,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get notificationTitleUnknownUser => 'Someone';
|
String get notificationTitleUnknownUser => '[Unknown]';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get notificationCategoryMessageTitle => 'Messages';
|
String get notificationCategoryMessageTitle => 'Messages';
|
||||||
|
|
@ -1544,4 +1560,36 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get linkPubkeyDoesNotMatch =>
|
String get linkPubkeyDoesNotMatch =>
|
||||||
'The public key in the link does not match the public key stored for this contact. Try to meet your friend in person and scan the QR code directly!';
|
'The public key in the link does not match the public key stored for this contact. Try to meet your friend in person and scan the QR code directly!';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get startWithCameraOpen => 'Start with camera open';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get showImagePreviewWhenSending =>
|
||||||
|
'Display image preview when selecting recipients';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String verifiedPublicKey(Object username) {
|
||||||
|
return 'The public key of $username has been verified and is valid.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get memoriesAYearAgo => 'One year ago';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String memoriesXYearsAgo(Object years) {
|
||||||
|
return '$years years ago';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String migrationOfMemories(Object open) {
|
||||||
|
return 'Migration of media files: $open still to be processed.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get autoStoreAllSendUnlimitedMediaFiles => 'Save all sent media';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get autoStoreAllSendUnlimitedMediaFilesSubtitle =>
|
||||||
|
'If you enable this option, all images you send will be saved as long as they were sent with an infinite countdown and not in twonly mode.';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,8 @@ class UserData {
|
||||||
|
|
||||||
@JsonKey(defaultValue: 'Free')
|
@JsonKey(defaultValue: 'Free')
|
||||||
String subscriptionPlan;
|
String subscriptionPlan;
|
||||||
|
|
||||||
|
String? subscriptionPlanIdStore;
|
||||||
DateTime? lastImageSend;
|
DateTime? lastImageSend;
|
||||||
int? todaysImageCounter;
|
int? todaysImageCounter;
|
||||||
|
|
||||||
|
|
@ -57,6 +59,12 @@ class UserData {
|
||||||
@JsonKey(defaultValue: true)
|
@JsonKey(defaultValue: true)
|
||||||
bool showFeedbackShortcut = true;
|
bool showFeedbackShortcut = true;
|
||||||
|
|
||||||
|
@JsonKey(defaultValue: false)
|
||||||
|
bool showShowImagePreviewWhenSending = false;
|
||||||
|
|
||||||
|
@JsonKey(defaultValue: true)
|
||||||
|
bool startWithCameraOpen = true;
|
||||||
|
|
||||||
List<String>? preSelectedEmojies;
|
List<String>? preSelectedEmojies;
|
||||||
|
|
||||||
Map<String, List<String>>? autoDownloadOptions;
|
Map<String, List<String>>? autoDownloadOptions;
|
||||||
|
|
@ -64,6 +72,9 @@ class UserData {
|
||||||
@JsonKey(defaultValue: false)
|
@JsonKey(defaultValue: false)
|
||||||
bool storeMediaFilesInGallery = false;
|
bool storeMediaFilesInGallery = false;
|
||||||
|
|
||||||
|
@JsonKey(defaultValue: false)
|
||||||
|
bool autoStoreAllSendUnlimitedMediaFiles = false;
|
||||||
|
|
||||||
String? lastPlanBallance;
|
String? lastPlanBallance;
|
||||||
String? additionalUserInvites;
|
String? additionalUserInvites;
|
||||||
|
|
||||||
|
|
@ -86,8 +97,11 @@ class UserData {
|
||||||
|
|
||||||
List<int>? lastChangeLogHash;
|
List<int>? lastChangeLogHash;
|
||||||
|
|
||||||
@JsonKey(defaultValue: false)
|
@JsonKey(defaultValue: true)
|
||||||
bool hideChangeLog = false;
|
bool hideChangeLog = true;
|
||||||
|
|
||||||
|
@JsonKey(defaultValue: true)
|
||||||
|
bool updateFCMToken = true;
|
||||||
|
|
||||||
// --- BACKUP ---
|
// --- BACKUP ---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) => UserData(
|
||||||
..disableVideoCompression =
|
..disableVideoCompression =
|
||||||
json['disableVideoCompression'] as bool? ?? false
|
json['disableVideoCompression'] as bool? ?? false
|
||||||
..deviceId = (json['deviceId'] as num?)?.toInt() ?? 0
|
..deviceId = (json['deviceId'] as num?)?.toInt() ?? 0
|
||||||
|
..subscriptionPlanIdStore = json['subscriptionPlanIdStore'] as String?
|
||||||
..lastImageSend = json['lastImageSend'] == null
|
..lastImageSend = json['lastImageSend'] == null
|
||||||
? null
|
? null
|
||||||
: DateTime.parse(json['lastImageSend'] as String)
|
: DateTime.parse(json['lastImageSend'] as String)
|
||||||
|
|
@ -31,6 +32,9 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) => UserData(
|
||||||
..requestedAudioPermission =
|
..requestedAudioPermission =
|
||||||
json['requestedAudioPermission'] as bool? ?? false
|
json['requestedAudioPermission'] as bool? ?? false
|
||||||
..showFeedbackShortcut = json['showFeedbackShortcut'] as bool? ?? true
|
..showFeedbackShortcut = json['showFeedbackShortcut'] as bool? ?? true
|
||||||
|
..showShowImagePreviewWhenSending =
|
||||||
|
json['showShowImagePreviewWhenSending'] as bool? ?? false
|
||||||
|
..startWithCameraOpen = json['startWithCameraOpen'] as bool? ?? true
|
||||||
..preSelectedEmojies = (json['preSelectedEmojies'] as List<dynamic>?)
|
..preSelectedEmojies = (json['preSelectedEmojies'] as List<dynamic>?)
|
||||||
?.map((e) => e as String)
|
?.map((e) => e as String)
|
||||||
.toList()
|
.toList()
|
||||||
|
|
@ -41,6 +45,8 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) => UserData(
|
||||||
)
|
)
|
||||||
..storeMediaFilesInGallery =
|
..storeMediaFilesInGallery =
|
||||||
json['storeMediaFilesInGallery'] as bool? ?? false
|
json['storeMediaFilesInGallery'] as bool? ?? false
|
||||||
|
..autoStoreAllSendUnlimitedMediaFiles =
|
||||||
|
json['autoStoreAllSendUnlimitedMediaFiles'] as bool? ?? false
|
||||||
..lastPlanBallance = json['lastPlanBallance'] as String?
|
..lastPlanBallance = json['lastPlanBallance'] as String?
|
||||||
..additionalUserInvites = json['additionalUserInvites'] as String?
|
..additionalUserInvites = json['additionalUserInvites'] as String?
|
||||||
..tutorialDisplayed = (json['tutorialDisplayed'] as List<dynamic>?)
|
..tutorialDisplayed = (json['tutorialDisplayed'] as List<dynamic>?)
|
||||||
|
|
@ -60,7 +66,8 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) => UserData(
|
||||||
..lastChangeLogHash = (json['lastChangeLogHash'] as List<dynamic>?)
|
..lastChangeLogHash = (json['lastChangeLogHash'] as List<dynamic>?)
|
||||||
?.map((e) => (e as num).toInt())
|
?.map((e) => (e as num).toInt())
|
||||||
.toList()
|
.toList()
|
||||||
..hideChangeLog = json['hideChangeLog'] as bool? ?? false
|
..hideChangeLog = json['hideChangeLog'] as bool? ?? true
|
||||||
|
..updateFCMToken = json['updateFCMToken'] as bool? ?? true
|
||||||
..nextTimeToShowBackupNotice = json['nextTimeToShowBackupNotice'] == null
|
..nextTimeToShowBackupNotice = json['nextTimeToShowBackupNotice'] == null
|
||||||
? null
|
? null
|
||||||
: DateTime.parse(json['nextTimeToShowBackupNotice'] as String)
|
: DateTime.parse(json['nextTimeToShowBackupNotice'] as String)
|
||||||
|
|
@ -84,15 +91,21 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
||||||
'disableVideoCompression': instance.disableVideoCompression,
|
'disableVideoCompression': instance.disableVideoCompression,
|
||||||
'deviceId': instance.deviceId,
|
'deviceId': instance.deviceId,
|
||||||
'subscriptionPlan': instance.subscriptionPlan,
|
'subscriptionPlan': instance.subscriptionPlan,
|
||||||
|
'subscriptionPlanIdStore': instance.subscriptionPlanIdStore,
|
||||||
'lastImageSend': instance.lastImageSend?.toIso8601String(),
|
'lastImageSend': instance.lastImageSend?.toIso8601String(),
|
||||||
'todaysImageCounter': instance.todaysImageCounter,
|
'todaysImageCounter': instance.todaysImageCounter,
|
||||||
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
|
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
|
||||||
'defaultShowTime': instance.defaultShowTime,
|
'defaultShowTime': instance.defaultShowTime,
|
||||||
'requestedAudioPermission': instance.requestedAudioPermission,
|
'requestedAudioPermission': instance.requestedAudioPermission,
|
||||||
'showFeedbackShortcut': instance.showFeedbackShortcut,
|
'showFeedbackShortcut': instance.showFeedbackShortcut,
|
||||||
|
'showShowImagePreviewWhenSending':
|
||||||
|
instance.showShowImagePreviewWhenSending,
|
||||||
|
'startWithCameraOpen': instance.startWithCameraOpen,
|
||||||
'preSelectedEmojies': instance.preSelectedEmojies,
|
'preSelectedEmojies': instance.preSelectedEmojies,
|
||||||
'autoDownloadOptions': instance.autoDownloadOptions,
|
'autoDownloadOptions': instance.autoDownloadOptions,
|
||||||
'storeMediaFilesInGallery': instance.storeMediaFilesInGallery,
|
'storeMediaFilesInGallery': instance.storeMediaFilesInGallery,
|
||||||
|
'autoStoreAllSendUnlimitedMediaFiles':
|
||||||
|
instance.autoStoreAllSendUnlimitedMediaFiles,
|
||||||
'lastPlanBallance': instance.lastPlanBallance,
|
'lastPlanBallance': instance.lastPlanBallance,
|
||||||
'additionalUserInvites': instance.additionalUserInvites,
|
'additionalUserInvites': instance.additionalUserInvites,
|
||||||
'tutorialDisplayed': instance.tutorialDisplayed,
|
'tutorialDisplayed': instance.tutorialDisplayed,
|
||||||
|
|
@ -104,6 +117,7 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
||||||
'currentSignedPreKeyIndexStart': instance.currentSignedPreKeyIndexStart,
|
'currentSignedPreKeyIndexStart': instance.currentSignedPreKeyIndexStart,
|
||||||
'lastChangeLogHash': instance.lastChangeLogHash,
|
'lastChangeLogHash': instance.lastChangeLogHash,
|
||||||
'hideChangeLog': instance.hideChangeLog,
|
'hideChangeLog': instance.hideChangeLog,
|
||||||
|
'updateFCMToken': instance.updateFCMToken,
|
||||||
'nextTimeToShowBackupNotice':
|
'nextTimeToShowBackupNotice':
|
||||||
instance.nextTimeToShowBackupNotice?.toIso8601String(),
|
instance.nextTimeToShowBackupNotice?.toIso8601String(),
|
||||||
'backupServer': instance.backupServer,
|
'backupServer': instance.backupServer,
|
||||||
|
|
|
||||||
|
|
@ -2085,6 +2085,136 @@ class ApplicationData_ReportUser extends $pb.GeneratedMessage {
|
||||||
void clearReason() => $_clearField(2);
|
void clearReason() => $_clearField(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ApplicationData_IPAPurchase extends $pb.GeneratedMessage {
|
||||||
|
factory ApplicationData_IPAPurchase({
|
||||||
|
$core.String? productId,
|
||||||
|
$core.String? source,
|
||||||
|
$core.String? verificationData,
|
||||||
|
}) {
|
||||||
|
final result = create();
|
||||||
|
if (productId != null) result.productId = productId;
|
||||||
|
if (source != null) result.source = source;
|
||||||
|
if (verificationData != null) result.verificationData = verificationData;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplicationData_IPAPurchase._();
|
||||||
|
|
||||||
|
factory ApplicationData_IPAPurchase.fromBuffer($core.List<$core.int> data,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromBuffer(data, registry);
|
||||||
|
factory ApplicationData_IPAPurchase.fromJson($core.String json,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromJson(json, registry);
|
||||||
|
|
||||||
|
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||||
|
_omitMessageNames ? '' : 'ApplicationData.IPAPurchase',
|
||||||
|
package:
|
||||||
|
const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'),
|
||||||
|
createEmptyInstance: create)
|
||||||
|
..aOS(1, _omitFieldNames ? '' : 'productId')
|
||||||
|
..aOS(2, _omitFieldNames ? '' : 'source')
|
||||||
|
..aOS(3, _omitFieldNames ? '' : 'verificationData')
|
||||||
|
..hasRequiredFields = false;
|
||||||
|
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
ApplicationData_IPAPurchase clone() =>
|
||||||
|
ApplicationData_IPAPurchase()..mergeFromMessage(this);
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
ApplicationData_IPAPurchase copyWith(
|
||||||
|
void Function(ApplicationData_IPAPurchase) updates) =>
|
||||||
|
super.copyWith(
|
||||||
|
(message) => updates(message as ApplicationData_IPAPurchase))
|
||||||
|
as ApplicationData_IPAPurchase;
|
||||||
|
|
||||||
|
@$core.override
|
||||||
|
$pb.BuilderInfo get info_ => _i;
|
||||||
|
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static ApplicationData_IPAPurchase create() =>
|
||||||
|
ApplicationData_IPAPurchase._();
|
||||||
|
@$core.override
|
||||||
|
ApplicationData_IPAPurchase createEmptyInstance() => create();
|
||||||
|
static $pb.PbList<ApplicationData_IPAPurchase> createRepeated() =>
|
||||||
|
$pb.PbList<ApplicationData_IPAPurchase>();
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static ApplicationData_IPAPurchase getDefault() => _defaultInstance ??=
|
||||||
|
$pb.GeneratedMessage.$_defaultFor<ApplicationData_IPAPurchase>(create);
|
||||||
|
static ApplicationData_IPAPurchase? _defaultInstance;
|
||||||
|
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$core.String get productId => $_getSZ(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
set productId($core.String value) => $_setString(0, value);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$core.bool hasProductId() => $_has(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
void clearProductId() => $_clearField(1);
|
||||||
|
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
$core.String get source => $_getSZ(1);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
set source($core.String value) => $_setString(1, value);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
$core.bool hasSource() => $_has(1);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
void clearSource() => $_clearField(2);
|
||||||
|
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
$core.String get verificationData => $_getSZ(2);
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
set verificationData($core.String value) => $_setString(2, value);
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
$core.bool hasVerificationData() => $_has(2);
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
void clearVerificationData() => $_clearField(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ApplicationData_IPAForceCheck extends $pb.GeneratedMessage {
|
||||||
|
factory ApplicationData_IPAForceCheck() => create();
|
||||||
|
|
||||||
|
ApplicationData_IPAForceCheck._();
|
||||||
|
|
||||||
|
factory ApplicationData_IPAForceCheck.fromBuffer($core.List<$core.int> data,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromBuffer(data, registry);
|
||||||
|
factory ApplicationData_IPAForceCheck.fromJson($core.String json,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromJson(json, registry);
|
||||||
|
|
||||||
|
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||||
|
_omitMessageNames ? '' : 'ApplicationData.IPAForceCheck',
|
||||||
|
package:
|
||||||
|
const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'),
|
||||||
|
createEmptyInstance: create)
|
||||||
|
..hasRequiredFields = false;
|
||||||
|
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
ApplicationData_IPAForceCheck clone() =>
|
||||||
|
ApplicationData_IPAForceCheck()..mergeFromMessage(this);
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
ApplicationData_IPAForceCheck copyWith(
|
||||||
|
void Function(ApplicationData_IPAForceCheck) updates) =>
|
||||||
|
super.copyWith(
|
||||||
|
(message) => updates(message as ApplicationData_IPAForceCheck))
|
||||||
|
as ApplicationData_IPAForceCheck;
|
||||||
|
|
||||||
|
@$core.override
|
||||||
|
$pb.BuilderInfo get info_ => _i;
|
||||||
|
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static ApplicationData_IPAForceCheck create() =>
|
||||||
|
ApplicationData_IPAForceCheck._();
|
||||||
|
@$core.override
|
||||||
|
ApplicationData_IPAForceCheck createEmptyInstance() => create();
|
||||||
|
static $pb.PbList<ApplicationData_IPAForceCheck> createRepeated() =>
|
||||||
|
$pb.PbList<ApplicationData_IPAForceCheck>();
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static ApplicationData_IPAForceCheck getDefault() => _defaultInstance ??=
|
||||||
|
$pb.GeneratedMessage.$_defaultFor<ApplicationData_IPAForceCheck>(create);
|
||||||
|
static ApplicationData_IPAForceCheck? _defaultInstance;
|
||||||
|
}
|
||||||
|
|
||||||
class ApplicationData_DeleteAccount extends $pb.GeneratedMessage {
|
class ApplicationData_DeleteAccount extends $pb.GeneratedMessage {
|
||||||
factory ApplicationData_DeleteAccount() => create();
|
factory ApplicationData_DeleteAccount() => create();
|
||||||
|
|
||||||
|
|
@ -2153,6 +2283,8 @@ enum ApplicationData_ApplicationData {
|
||||||
deleteAccount,
|
deleteAccount,
|
||||||
reportUser,
|
reportUser,
|
||||||
changeUsername,
|
changeUsername,
|
||||||
|
ipaPurchase,
|
||||||
|
ipaForceCheck,
|
||||||
notSet
|
notSet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2180,6 +2312,8 @@ class ApplicationData extends $pb.GeneratedMessage {
|
||||||
ApplicationData_DeleteAccount? deleteAccount,
|
ApplicationData_DeleteAccount? deleteAccount,
|
||||||
ApplicationData_ReportUser? reportUser,
|
ApplicationData_ReportUser? reportUser,
|
||||||
ApplicationData_ChangeUsername? changeUsername,
|
ApplicationData_ChangeUsername? changeUsername,
|
||||||
|
ApplicationData_IPAPurchase? ipaPurchase,
|
||||||
|
ApplicationData_IPAForceCheck? ipaForceCheck,
|
||||||
}) {
|
}) {
|
||||||
final result = create();
|
final result = create();
|
||||||
if (textMessage != null) result.textMessage = textMessage;
|
if (textMessage != null) result.textMessage = textMessage;
|
||||||
|
|
@ -2212,6 +2346,8 @@ class ApplicationData extends $pb.GeneratedMessage {
|
||||||
if (deleteAccount != null) result.deleteAccount = deleteAccount;
|
if (deleteAccount != null) result.deleteAccount = deleteAccount;
|
||||||
if (reportUser != null) result.reportUser = reportUser;
|
if (reportUser != null) result.reportUser = reportUser;
|
||||||
if (changeUsername != null) result.changeUsername = changeUsername;
|
if (changeUsername != null) result.changeUsername = changeUsername;
|
||||||
|
if (ipaPurchase != null) result.ipaPurchase = ipaPurchase;
|
||||||
|
if (ipaForceCheck != null) result.ipaForceCheck = ipaForceCheck;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2248,6 +2384,8 @@ class ApplicationData extends $pb.GeneratedMessage {
|
||||||
24: ApplicationData_ApplicationData.deleteAccount,
|
24: ApplicationData_ApplicationData.deleteAccount,
|
||||||
25: ApplicationData_ApplicationData.reportUser,
|
25: ApplicationData_ApplicationData.reportUser,
|
||||||
26: ApplicationData_ApplicationData.changeUsername,
|
26: ApplicationData_ApplicationData.changeUsername,
|
||||||
|
27: ApplicationData_ApplicationData.ipaPurchase,
|
||||||
|
28: ApplicationData_ApplicationData.ipaForceCheck,
|
||||||
0: ApplicationData_ApplicationData.notSet
|
0: ApplicationData_ApplicationData.notSet
|
||||||
};
|
};
|
||||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||||
|
|
@ -2277,7 +2415,9 @@ class ApplicationData extends $pb.GeneratedMessage {
|
||||||
23,
|
23,
|
||||||
24,
|
24,
|
||||||
25,
|
25,
|
||||||
26
|
26,
|
||||||
|
27,
|
||||||
|
28
|
||||||
])
|
])
|
||||||
..aOM<ApplicationData_TextMessage>(1, _omitFieldNames ? '' : 'textMessage',
|
..aOM<ApplicationData_TextMessage>(1, _omitFieldNames ? '' : 'textMessage',
|
||||||
protoName: 'textMessage',
|
protoName: 'textMessage',
|
||||||
|
|
@ -2361,6 +2501,13 @@ class ApplicationData extends $pb.GeneratedMessage {
|
||||||
26, _omitFieldNames ? '' : 'changeUsername',
|
26, _omitFieldNames ? '' : 'changeUsername',
|
||||||
protoName: 'changeUsername',
|
protoName: 'changeUsername',
|
||||||
subBuilder: ApplicationData_ChangeUsername.create)
|
subBuilder: ApplicationData_ChangeUsername.create)
|
||||||
|
..aOM<ApplicationData_IPAPurchase>(27, _omitFieldNames ? '' : 'ipaPurchase',
|
||||||
|
protoName: 'ipaPurchase',
|
||||||
|
subBuilder: ApplicationData_IPAPurchase.create)
|
||||||
|
..aOM<ApplicationData_IPAForceCheck>(
|
||||||
|
28, _omitFieldNames ? '' : 'ipaForceCheck',
|
||||||
|
protoName: 'ipaForceCheck',
|
||||||
|
subBuilder: ApplicationData_IPAForceCheck.create)
|
||||||
..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.')
|
||||||
|
|
@ -2652,6 +2799,29 @@ class ApplicationData extends $pb.GeneratedMessage {
|
||||||
void clearChangeUsername() => $_clearField(26);
|
void clearChangeUsername() => $_clearField(26);
|
||||||
@$pb.TagNumber(26)
|
@$pb.TagNumber(26)
|
||||||
ApplicationData_ChangeUsername ensureChangeUsername() => $_ensure(21);
|
ApplicationData_ChangeUsername ensureChangeUsername() => $_ensure(21);
|
||||||
|
|
||||||
|
@$pb.TagNumber(27)
|
||||||
|
ApplicationData_IPAPurchase get ipaPurchase => $_getN(22);
|
||||||
|
@$pb.TagNumber(27)
|
||||||
|
set ipaPurchase(ApplicationData_IPAPurchase value) => $_setField(27, value);
|
||||||
|
@$pb.TagNumber(27)
|
||||||
|
$core.bool hasIpaPurchase() => $_has(22);
|
||||||
|
@$pb.TagNumber(27)
|
||||||
|
void clearIpaPurchase() => $_clearField(27);
|
||||||
|
@$pb.TagNumber(27)
|
||||||
|
ApplicationData_IPAPurchase ensureIpaPurchase() => $_ensure(22);
|
||||||
|
|
||||||
|
@$pb.TagNumber(28)
|
||||||
|
ApplicationData_IPAForceCheck get ipaForceCheck => $_getN(23);
|
||||||
|
@$pb.TagNumber(28)
|
||||||
|
set ipaForceCheck(ApplicationData_IPAForceCheck value) =>
|
||||||
|
$_setField(28, value);
|
||||||
|
@$pb.TagNumber(28)
|
||||||
|
$core.bool hasIpaForceCheck() => $_has(23);
|
||||||
|
@$pb.TagNumber(28)
|
||||||
|
void clearIpaForceCheck() => $_clearField(28);
|
||||||
|
@$pb.TagNumber(28)
|
||||||
|
ApplicationData_IPAForceCheck ensureIpaForceCheck() => $_ensure(23);
|
||||||
}
|
}
|
||||||
|
|
||||||
class Response_PreKey extends $pb.GeneratedMessage {
|
class Response_PreKey extends $pb.GeneratedMessage {
|
||||||
|
|
|
||||||
|
|
@ -461,6 +461,24 @@ const ApplicationData$json = {
|
||||||
'9': 0,
|
'9': 0,
|
||||||
'10': 'changeUsername'
|
'10': 'changeUsername'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'1': 'ipaPurchase',
|
||||||
|
'3': 27,
|
||||||
|
'4': 1,
|
||||||
|
'5': 11,
|
||||||
|
'6': '.client_to_server.ApplicationData.IPAPurchase',
|
||||||
|
'9': 0,
|
||||||
|
'10': 'ipaPurchase'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'1': 'ipaForceCheck',
|
||||||
|
'3': 28,
|
||||||
|
'4': 1,
|
||||||
|
'5': 11,
|
||||||
|
'6': '.client_to_server.ApplicationData.IPAForceCheck',
|
||||||
|
'9': 0,
|
||||||
|
'10': 'ipaForceCheck'
|
||||||
|
},
|
||||||
],
|
],
|
||||||
'3': [
|
'3': [
|
||||||
ApplicationData_TextMessage$json,
|
ApplicationData_TextMessage$json,
|
||||||
|
|
@ -484,6 +502,8 @@ const ApplicationData$json = {
|
||||||
ApplicationData_UpdateSignedPreKey$json,
|
ApplicationData_UpdateSignedPreKey$json,
|
||||||
ApplicationData_DownloadDone$json,
|
ApplicationData_DownloadDone$json,
|
||||||
ApplicationData_ReportUser$json,
|
ApplicationData_ReportUser$json,
|
||||||
|
ApplicationData_IPAPurchase$json,
|
||||||
|
ApplicationData_IPAForceCheck$json,
|
||||||
ApplicationData_DeleteAccount$json
|
ApplicationData_DeleteAccount$json
|
||||||
],
|
],
|
||||||
'8': [
|
'8': [
|
||||||
|
|
@ -668,6 +688,27 @@ const ApplicationData_ReportUser$json = {
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||||
|
const ApplicationData_IPAPurchase$json = {
|
||||||
|
'1': 'IPAPurchase',
|
||||||
|
'2': [
|
||||||
|
{'1': 'product_id', '3': 1, '4': 1, '5': 9, '10': 'productId'},
|
||||||
|
{'1': 'source', '3': 2, '4': 1, '5': 9, '10': 'source'},
|
||||||
|
{
|
||||||
|
'1': 'verification_data',
|
||||||
|
'3': 3,
|
||||||
|
'4': 1,
|
||||||
|
'5': 9,
|
||||||
|
'10': 'verificationData'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||||
|
const ApplicationData_IPAForceCheck$json = {
|
||||||
|
'1': 'IPAForceCheck',
|
||||||
|
};
|
||||||
|
|
||||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||||
const ApplicationData_DeleteAccount$json = {
|
const ApplicationData_DeleteAccount$json = {
|
||||||
'1': 'DeleteAccount',
|
'1': 'DeleteAccount',
|
||||||
|
|
@ -713,29 +754,34 @@ final $typed_data.Uint8List applicationDataDescriptor = $convert.base64Decode(
|
||||||
'NhdGlvbkRhdGEuRGVsZXRlQWNjb3VudEgAUg1kZWxldGVBY2NvdW50Ek4KCnJlcG9ydFVzZXIY'
|
'NhdGlvbkRhdGEuRGVsZXRlQWNjb3VudEgAUg1kZWxldGVBY2NvdW50Ek4KCnJlcG9ydFVzZXIY'
|
||||||
'GSABKAsyLC5jbGllbnRfdG9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5SZXBvcnRVc2VySABSCn'
|
'GSABKAsyLC5jbGllbnRfdG9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5SZXBvcnRVc2VySABSCn'
|
||||||
'JlcG9ydFVzZXISWgoOY2hhbmdlVXNlcm5hbWUYGiABKAsyMC5jbGllbnRfdG9fc2VydmVyLkFw'
|
'JlcG9ydFVzZXISWgoOY2hhbmdlVXNlcm5hbWUYGiABKAsyMC5jbGllbnRfdG9fc2VydmVyLkFw'
|
||||||
'cGxpY2F0aW9uRGF0YS5DaGFuZ2VVc2VybmFtZUgAUg5jaGFuZ2VVc2VybmFtZRpqCgtUZXh0TW'
|
'cGxpY2F0aW9uRGF0YS5DaGFuZ2VVc2VybmFtZUgAUg5jaGFuZ2VVc2VybmFtZRJRCgtpcGFQdX'
|
||||||
'Vzc2FnZRIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQSEgoEYm9keRgDIAEoDFIEYm9keRIgCglw'
|
'JjaGFzZRgbIAEoCzItLmNsaWVudF90b19zZXJ2ZXIuQXBwbGljYXRpb25EYXRhLklQQVB1cmNo'
|
||||||
'dXNoX2RhdGEYBCABKAxIAFIIcHVzaERhdGGIAQFCDAoKX3B1c2hfZGF0YRovChFHZXRVc2VyQn'
|
'YXNlSABSC2lwYVB1cmNoYXNlElcKDWlwYUZvcmNlQ2hlY2sYHCABKAsyLy5jbGllbnRfdG9fc2'
|
||||||
'lVc2VybmFtZRIaCgh1c2VybmFtZRgBIAEoCVIIdXNlcm5hbWUaLAoOQ2hhbmdlVXNlcm5hbWUS'
|
'VydmVyLkFwcGxpY2F0aW9uRGF0YS5JUEFGb3JjZUNoZWNrSABSDWlwYUZvcmNlQ2hlY2saagoL'
|
||||||
'GgoIdXNlcm5hbWUYASABKAlSCHVzZXJuYW1lGjUKFFVwZGF0ZUdvb2dsZUZjbVRva2VuEh0KCm'
|
'VGV4dE1lc3NhZ2USFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkEhIKBGJvZHkYAyABKAxSBGJvZH'
|
||||||
'dvb2dsZV9mY20YASABKAlSCWdvb2dsZUZjbRomCgtHZXRVc2VyQnlJZBIXCgd1c2VyX2lkGAEg'
|
'kSIAoJcHVzaF9kYXRhGAQgASgMSABSCHB1c2hEYXRhiAEBQgwKCl9wdXNoX2RhdGEaLwoRR2V0'
|
||||||
'ASgDUgZ1c2VySWQaKQoNUmVkZWVtVm91Y2hlchIYCgd2b3VjaGVyGAEgASgJUgd2b3VjaGVyGn'
|
'VXNlckJ5VXNlcm5hbWUSGgoIdXNlcm5hbWUYASABKAlSCHVzZXJuYW1lGiwKDkNoYW5nZVVzZX'
|
||||||
'AKEVN3aXRjaFRvUGF5ZWRQbGFuEhcKB3BsYW5faWQYASABKAlSBnBsYW5JZBIfCgtwYXlfbW9u'
|
'JuYW1lEhoKCHVzZXJuYW1lGAEgASgJUgh1c2VybmFtZRo1ChRVcGRhdGVHb29nbGVGY21Ub2tl'
|
||||||
'dGhseRgCIAEoCFIKcGF5TW9udGhseRIhCgxhdXRvX3JlbmV3YWwYAyABKAhSC2F1dG9SZW5ld2'
|
'bhIdCgpnb29nbGVfZmNtGAEgASgJUglnb29nbGVGY20aJgoLR2V0VXNlckJ5SWQSFwoHdXNlcl'
|
||||||
'FsGjYKEVVwZGF0ZVBsYW5PcHRpb25zEiEKDGF1dG9fcmVuZXdhbBgBIAEoCFILYXV0b1JlbmV3'
|
'9pZBgBIAEoA1IGdXNlcklkGikKDVJlZGVlbVZvdWNoZXISGAoHdm91Y2hlchgBIAEoCVIHdm91'
|
||||||
'YWwaMAoNQ3JlYXRlVm91Y2hlchIfCgt2YWx1ZV9jZW50cxgBIAEoDVIKdmFsdWVDZW50cxoNCg'
|
'Y2hlchpwChFTd2l0Y2hUb1BheWVkUGxhbhIXCgdwbGFuX2lkGAEgASgJUgZwbGFuSWQSHwoLcG'
|
||||||
'tHZXRMb2NhdGlvbhoNCgtHZXRWb3VjaGVycxoTChFHZXRBdmFpbGFibGVQbGFucxoXChVHZXRB'
|
'F5X21vbnRobHkYAiABKAhSCnBheU1vbnRobHkSIQoMYXV0b19yZW5ld2FsGAMgASgIUgthdXRv'
|
||||||
'ZGRBY2NvdW50c0ludml0ZXMaFQoTR2V0Q3VycmVudFBsYW5JbmZvcxo3ChRSZWRlZW1BZGRpdG'
|
'UmVuZXdhbBo2ChFVcGRhdGVQbGFuT3B0aW9ucxIhCgxhdXRvX3JlbmV3YWwYASABKAhSC2F1dG'
|
||||||
'lvbmFsQ29kZRIfCgtpbnZpdGVfY29kZRgCIAEoCVIKaW52aXRlQ29kZRovChRSZW1vdmVBZGRp'
|
'9SZW5ld2FsGjAKDUNyZWF0ZVZvdWNoZXISHwoLdmFsdWVfY2VudHMYASABKA1SCnZhbHVlQ2Vu'
|
||||||
'dGlvbmFsVXNlchIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQaLQoSR2V0UHJla2V5c0J5VXNlck'
|
'dHMaDQoLR2V0TG9jYXRpb24aDQoLR2V0Vm91Y2hlcnMaEwoRR2V0QXZhaWxhYmxlUGxhbnMaFw'
|
||||||
'lkEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBoyChdHZXRTaWduZWRQcmVLZXlCeVVzZXJJZBIX'
|
'oVR2V0QWRkQWNjb3VudHNJbnZpdGVzGhUKE0dldEN1cnJlbnRQbGFuSW5mb3MaNwoUUmVkZWVt'
|
||||||
'Cgd1c2VyX2lkGAEgASgDUgZ1c2VySWQamwEKElVwZGF0ZVNpZ25lZFByZUtleRIoChBzaWduZW'
|
'QWRkaXRpb25hbENvZGUSHwoLaW52aXRlX2NvZGUYAiABKAlSCmludml0ZUNvZGUaLwoUUmVtb3'
|
||||||
'RfcHJla2V5X2lkGAEgASgDUg5zaWduZWRQcmVrZXlJZBIjCg1zaWduZWRfcHJla2V5GAIgASgM'
|
'ZlQWRkaXRpb25hbFVzZXISFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkGi0KEkdldFByZWtleXNC'
|
||||||
'UgxzaWduZWRQcmVrZXkSNgoXc2lnbmVkX3ByZWtleV9zaWduYXR1cmUYAyABKAxSFXNpZ25lZF'
|
'eVVzZXJJZBIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQaMgoXR2V0U2lnbmVkUHJlS2V5QnlVc2'
|
||||||
'ByZWtleVNpZ25hdHVyZRo1CgxEb3dubG9hZERvbmUSJQoOZG93bmxvYWRfdG9rZW4YASABKAxS'
|
'VySWQSFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkGpsBChJVcGRhdGVTaWduZWRQcmVLZXkSKAoQ'
|
||||||
'DWRvd25sb2FkVG9rZW4aTgoKUmVwb3J0VXNlchIoChByZXBvcnRlZF91c2VyX2lkGAEgASgDUg'
|
'c2lnbmVkX3ByZWtleV9pZBgBIAEoA1IOc2lnbmVkUHJla2V5SWQSIwoNc2lnbmVkX3ByZWtleR'
|
||||||
'5yZXBvcnRlZFVzZXJJZBIWCgZyZWFzb24YAiABKAlSBnJlYXNvbhoPCg1EZWxldGVBY2NvdW50'
|
'gCIAEoDFIMc2lnbmVkUHJla2V5EjYKF3NpZ25lZF9wcmVrZXlfc2lnbmF0dXJlGAMgASgMUhVz'
|
||||||
'QhEKD0FwcGxpY2F0aW9uRGF0YQ==');
|
'aWduZWRQcmVrZXlTaWduYXR1cmUaNQoMRG93bmxvYWREb25lEiUKDmRvd25sb2FkX3Rva2VuGA'
|
||||||
|
'EgASgMUg1kb3dubG9hZFRva2VuGk4KClJlcG9ydFVzZXISKAoQcmVwb3J0ZWRfdXNlcl9pZBgB'
|
||||||
|
'IAEoA1IOcmVwb3J0ZWRVc2VySWQSFgoGcmVhc29uGAIgASgJUgZyZWFzb24acQoLSVBBUHVyY2'
|
||||||
|
'hhc2USHQoKcHJvZHVjdF9pZBgBIAEoCVIJcHJvZHVjdElkEhYKBnNvdXJjZRgCIAEoCVIGc291'
|
||||||
|
'cmNlEisKEXZlcmlmaWNhdGlvbl9kYXRhGAMgASgJUhB2ZXJpZmljYXRpb25EYXRhGg8KDUlQQU'
|
||||||
|
'ZvcmNlQ2hlY2saDwoNRGVsZXRlQWNjb3VudEIRCg9BcHBsaWNhdGlvbkRhdGE=');
|
||||||
|
|
||||||
@$core.Deprecated('Use responseDescriptor instead')
|
@$core.Deprecated('Use responseDescriptor instead')
|
||||||
const Response$json = {
|
const Response$json = {
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,8 @@ class ErrorCode extends $pb.ProtobufEnum {
|
||||||
ErrorCode._(1032, _omitEnumNames ? '' : 'InvalidProofOfWork');
|
ErrorCode._(1032, _omitEnumNames ? '' : 'InvalidProofOfWork');
|
||||||
static const ErrorCode RegistrationDisabled =
|
static const ErrorCode RegistrationDisabled =
|
||||||
ErrorCode._(1033, _omitEnumNames ? '' : 'RegistrationDisabled');
|
ErrorCode._(1033, _omitEnumNames ? '' : 'RegistrationDisabled');
|
||||||
|
static const ErrorCode IPAPaymentExpired =
|
||||||
|
ErrorCode._(1034, _omitEnumNames ? '' : 'IPAPaymentExpired');
|
||||||
|
|
||||||
static const $core.List<ErrorCode> values = <ErrorCode>[
|
static const $core.List<ErrorCode> values = <ErrorCode>[
|
||||||
Unknown,
|
Unknown,
|
||||||
|
|
@ -125,6 +127,7 @@ class ErrorCode extends $pb.ProtobufEnum {
|
||||||
NewDeviceRegistered,
|
NewDeviceRegistered,
|
||||||
InvalidProofOfWork,
|
InvalidProofOfWork,
|
||||||
RegistrationDisabled,
|
RegistrationDisabled,
|
||||||
|
IPAPaymentExpired,
|
||||||
];
|
];
|
||||||
|
|
||||||
static final $core.Map<$core.int, ErrorCode> _byValue =
|
static final $core.Map<$core.int, ErrorCode> _byValue =
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ const ErrorCode$json = {
|
||||||
{'1': 'NewDeviceRegistered', '2': 1031},
|
{'1': 'NewDeviceRegistered', '2': 1031},
|
||||||
{'1': 'InvalidProofOfWork', '2': 1032},
|
{'1': 'InvalidProofOfWork', '2': 1032},
|
||||||
{'1': 'RegistrationDisabled', '2': 1033},
|
{'1': 'RegistrationDisabled', '2': 1033},
|
||||||
|
{'1': 'IPAPaymentExpired', '2': 1034},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -74,4 +75,5 @@ final $typed_data.Uint8List errorCodeDescriptor = $convert.base64Decode(
|
||||||
'bGFuRG93bmdyYWRlEIEIEhkKFFBsYW5VcGdyYWRlTm90WWVhcmx5EIIIEhgKE0ludmFsaWRTaW'
|
'bGFuRG93bmdyYWRlEIEIEhkKFFBsYW5VcGdyYWRlTm90WWVhcmx5EIIIEhgKE0ludmFsaWRTaW'
|
||||||
'duZWRQcmVLZXkQgwgSEwoOVXNlcklkTm90Rm91bmQQhAgSFwoSVXNlcklkQWxyZWFkeVRha2Vu'
|
'duZWRQcmVLZXkQgwgSEwoOVXNlcklkTm90Rm91bmQQhAgSFwoSVXNlcklkQWxyZWFkeVRha2Vu'
|
||||||
'EIUIEhcKEkFwcFZlcnNpb25PdXRkYXRlZBCGCBIYChNOZXdEZXZpY2VSZWdpc3RlcmVkEIcIEh'
|
'EIUIEhcKEkFwcFZlcnNpb25PdXRkYXRlZBCGCBIYChNOZXdEZXZpY2VSZWdpc3RlcmVkEIcIEh'
|
||||||
'cKEkludmFsaWRQcm9vZk9mV29yaxCICBIZChRSZWdpc3RyYXRpb25EaXNhYmxlZBCJCA==');
|
'cKEkludmFsaWRQcm9vZk9mV29yaxCICBIZChRSZWdpc3RyYXRpb25EaXNhYmxlZBCJCBIWChFJ'
|
||||||
|
'UEFQYXltZW50RXhwaXJlZBCKCA==');
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,14 @@ class ServerToClient extends $pb.GeneratedMessage {
|
||||||
V0 ensureV0() => $_ensure(0);
|
V0 ensureV0() => $_ensure(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
enum V0_Kind { response, newMessage, requestNewPreKeys, error, notSet }
|
enum V0_Kind {
|
||||||
|
response,
|
||||||
|
newMessage,
|
||||||
|
requestNewPreKeys,
|
||||||
|
error,
|
||||||
|
newMessages,
|
||||||
|
notSet
|
||||||
|
}
|
||||||
|
|
||||||
class V0 extends $pb.GeneratedMessage {
|
class V0 extends $pb.GeneratedMessage {
|
||||||
factory V0({
|
factory V0({
|
||||||
|
|
@ -101,6 +108,7 @@ class V0 extends $pb.GeneratedMessage {
|
||||||
NewMessage? newMessage,
|
NewMessage? newMessage,
|
||||||
$core.bool? requestNewPreKeys,
|
$core.bool? requestNewPreKeys,
|
||||||
$0.ErrorCode? error,
|
$0.ErrorCode? error,
|
||||||
|
NewMessages? newMessages,
|
||||||
}) {
|
}) {
|
||||||
final result = create();
|
final result = create();
|
||||||
if (seq != null) result.seq = seq;
|
if (seq != null) result.seq = seq;
|
||||||
|
|
@ -108,6 +116,7 @@ class V0 extends $pb.GeneratedMessage {
|
||||||
if (newMessage != null) result.newMessage = newMessage;
|
if (newMessage != null) result.newMessage = newMessage;
|
||||||
if (requestNewPreKeys != null) result.requestNewPreKeys = requestNewPreKeys;
|
if (requestNewPreKeys != null) result.requestNewPreKeys = requestNewPreKeys;
|
||||||
if (error != null) result.error = error;
|
if (error != null) result.error = error;
|
||||||
|
if (newMessages != null) result.newMessages = newMessages;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -125,6 +134,7 @@ class V0 extends $pb.GeneratedMessage {
|
||||||
3: V0_Kind.newMessage,
|
3: V0_Kind.newMessage,
|
||||||
4: V0_Kind.requestNewPreKeys,
|
4: V0_Kind.requestNewPreKeys,
|
||||||
6: V0_Kind.error,
|
6: V0_Kind.error,
|
||||||
|
7: V0_Kind.newMessages,
|
||||||
0: V0_Kind.notSet
|
0: V0_Kind.notSet
|
||||||
};
|
};
|
||||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||||
|
|
@ -132,7 +142,7 @@ class V0 extends $pb.GeneratedMessage {
|
||||||
package:
|
package:
|
||||||
const $pb.PackageName(_omitMessageNames ? '' : 'server_to_client'),
|
const $pb.PackageName(_omitMessageNames ? '' : 'server_to_client'),
|
||||||
createEmptyInstance: create)
|
createEmptyInstance: create)
|
||||||
..oo(0, [2, 3, 4, 6])
|
..oo(0, [2, 3, 4, 6, 7])
|
||||||
..a<$fixnum.Int64>(1, _omitFieldNames ? '' : 'seq', $pb.PbFieldType.OU6,
|
..a<$fixnum.Int64>(1, _omitFieldNames ? '' : 'seq', $pb.PbFieldType.OU6,
|
||||||
defaultOrMaker: $fixnum.Int64.ZERO)
|
defaultOrMaker: $fixnum.Int64.ZERO)
|
||||||
..aOM<Response>(2, _omitFieldNames ? '' : 'response',
|
..aOM<Response>(2, _omitFieldNames ? '' : 'response',
|
||||||
|
|
@ -145,6 +155,8 @@ class V0 extends $pb.GeneratedMessage {
|
||||||
defaultOrMaker: $0.ErrorCode.Unknown,
|
defaultOrMaker: $0.ErrorCode.Unknown,
|
||||||
valueOf: $0.ErrorCode.valueOf,
|
valueOf: $0.ErrorCode.valueOf,
|
||||||
enumValues: $0.ErrorCode.values)
|
enumValues: $0.ErrorCode.values)
|
||||||
|
..aOM<NewMessages>(7, _omitFieldNames ? '' : 'newMessages',
|
||||||
|
protoName: 'newMessages', subBuilder: NewMessages.create)
|
||||||
..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.')
|
||||||
|
|
@ -217,6 +229,17 @@ class V0 extends $pb.GeneratedMessage {
|
||||||
$core.bool hasError() => $_has(4);
|
$core.bool hasError() => $_has(4);
|
||||||
@$pb.TagNumber(6)
|
@$pb.TagNumber(6)
|
||||||
void clearError() => $_clearField(6);
|
void clearError() => $_clearField(6);
|
||||||
|
|
||||||
|
@$pb.TagNumber(7)
|
||||||
|
NewMessages get newMessages => $_getN(5);
|
||||||
|
@$pb.TagNumber(7)
|
||||||
|
set newMessages(NewMessages value) => $_setField(7, value);
|
||||||
|
@$pb.TagNumber(7)
|
||||||
|
$core.bool hasNewMessages() => $_has(5);
|
||||||
|
@$pb.TagNumber(7)
|
||||||
|
void clearNewMessages() => $_clearField(7);
|
||||||
|
@$pb.TagNumber(7)
|
||||||
|
NewMessages ensureNewMessages() => $_ensure(5);
|
||||||
}
|
}
|
||||||
|
|
||||||
class NewMessage extends $pb.GeneratedMessage {
|
class NewMessage extends $pb.GeneratedMessage {
|
||||||
|
|
@ -287,6 +310,58 @@ class NewMessage extends $pb.GeneratedMessage {
|
||||||
void clearFromUserId() => $_clearField(2);
|
void clearFromUserId() => $_clearField(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class NewMessages extends $pb.GeneratedMessage {
|
||||||
|
factory NewMessages({
|
||||||
|
$core.Iterable<NewMessage>? newMessages,
|
||||||
|
}) {
|
||||||
|
final result = create();
|
||||||
|
if (newMessages != null) result.newMessages.addAll(newMessages);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
NewMessages._();
|
||||||
|
|
||||||
|
factory NewMessages.fromBuffer($core.List<$core.int> data,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromBuffer(data, registry);
|
||||||
|
factory NewMessages.fromJson($core.String json,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromJson(json, registry);
|
||||||
|
|
||||||
|
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||||
|
_omitMessageNames ? '' : 'NewMessages',
|
||||||
|
package:
|
||||||
|
const $pb.PackageName(_omitMessageNames ? '' : 'server_to_client'),
|
||||||
|
createEmptyInstance: create)
|
||||||
|
..pc<NewMessage>(
|
||||||
|
1, _omitFieldNames ? '' : 'newMessages', $pb.PbFieldType.PM,
|
||||||
|
protoName: 'newMessages', subBuilder: NewMessage.create)
|
||||||
|
..hasRequiredFields = false;
|
||||||
|
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
NewMessages clone() => NewMessages()..mergeFromMessage(this);
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
NewMessages copyWith(void Function(NewMessages) updates) =>
|
||||||
|
super.copyWith((message) => updates(message as NewMessages))
|
||||||
|
as NewMessages;
|
||||||
|
|
||||||
|
@$core.override
|
||||||
|
$pb.BuilderInfo get info_ => _i;
|
||||||
|
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static NewMessages create() => NewMessages._();
|
||||||
|
@$core.override
|
||||||
|
NewMessages createEmptyInstance() => create();
|
||||||
|
static $pb.PbList<NewMessages> createRepeated() => $pb.PbList<NewMessages>();
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static NewMessages getDefault() => _defaultInstance ??=
|
||||||
|
$pb.GeneratedMessage.$_defaultFor<NewMessages>(create);
|
||||||
|
static NewMessages? _defaultInstance;
|
||||||
|
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$pb.PbList<NewMessage> get newMessages => $_getList(0);
|
||||||
|
}
|
||||||
|
|
||||||
class Response_Authenticated extends $pb.GeneratedMessage {
|
class Response_Authenticated extends $pb.GeneratedMessage {
|
||||||
factory Response_Authenticated({
|
factory Response_Authenticated({
|
||||||
$core.String? plan,
|
$core.String? plan,
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,15 @@ const V0$json = {
|
||||||
'9': 0,
|
'9': 0,
|
||||||
'10': 'newMessage'
|
'10': 'newMessage'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'1': 'newMessages',
|
||||||
|
'3': 7,
|
||||||
|
'4': 1,
|
||||||
|
'5': 11,
|
||||||
|
'6': '.server_to_client.NewMessages',
|
||||||
|
'9': 0,
|
||||||
|
'10': 'newMessages'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
'1': 'RequestNewPreKeys',
|
'1': 'RequestNewPreKeys',
|
||||||
'3': 4,
|
'3': 4,
|
||||||
|
|
@ -88,9 +97,10 @@ const V0$json = {
|
||||||
final $typed_data.Uint8List v0Descriptor = $convert.base64Decode(
|
final $typed_data.Uint8List v0Descriptor = $convert.base64Decode(
|
||||||
'CgJWMBIQCgNzZXEYASABKARSA3NlcRI4CghyZXNwb25zZRgCIAEoCzIaLnNlcnZlcl90b19jbG'
|
'CgJWMBIQCgNzZXEYASABKARSA3NlcRI4CghyZXNwb25zZRgCIAEoCzIaLnNlcnZlcl90b19jbG'
|
||||||
'llbnQuUmVzcG9uc2VIAFIIcmVzcG9uc2USPgoKbmV3TWVzc2FnZRgDIAEoCzIcLnNlcnZlcl90'
|
'llbnQuUmVzcG9uc2VIAFIIcmVzcG9uc2USPgoKbmV3TWVzc2FnZRgDIAEoCzIcLnNlcnZlcl90'
|
||||||
'b19jbGllbnQuTmV3TWVzc2FnZUgAUgpuZXdNZXNzYWdlEi4KEVJlcXVlc3ROZXdQcmVLZXlzGA'
|
'b19jbGllbnQuTmV3TWVzc2FnZUgAUgpuZXdNZXNzYWdlEkEKC25ld01lc3NhZ2VzGAcgASgLMh'
|
||||||
'QgASgISABSEVJlcXVlc3ROZXdQcmVLZXlzEigKBWVycm9yGAYgASgOMhAuZXJyb3IuRXJyb3JD'
|
'0uc2VydmVyX3RvX2NsaWVudC5OZXdNZXNzYWdlc0gAUgtuZXdNZXNzYWdlcxIuChFSZXF1ZXN0'
|
||||||
'b2RlSABSBWVycm9yQgYKBEtpbmQ=');
|
'TmV3UHJlS2V5cxgEIAEoCEgAUhFSZXF1ZXN0TmV3UHJlS2V5cxIoCgVlcnJvchgGIAEoDjIQLm'
|
||||||
|
'Vycm9yLkVycm9yQ29kZUgAUgVlcnJvckIGCgRLaW5k');
|
||||||
|
|
||||||
@$core.Deprecated('Use newMessageDescriptor instead')
|
@$core.Deprecated('Use newMessageDescriptor instead')
|
||||||
const NewMessage$json = {
|
const NewMessage$json = {
|
||||||
|
|
@ -106,6 +116,26 @@ final $typed_data.Uint8List newMessageDescriptor = $convert.base64Decode(
|
||||||
'CgpOZXdNZXNzYWdlEiAKDGZyb21fdXNlcl9pZBgCIAEoA1IKZnJvbVVzZXJJZBISCgRib2R5GA'
|
'CgpOZXdNZXNzYWdlEiAKDGZyb21fdXNlcl9pZBgCIAEoA1IKZnJvbVVzZXJJZBISCgRib2R5GA'
|
||||||
'EgASgMUgRib2R5');
|
'EgASgMUgRib2R5');
|
||||||
|
|
||||||
|
@$core.Deprecated('Use newMessagesDescriptor instead')
|
||||||
|
const NewMessages$json = {
|
||||||
|
'1': 'NewMessages',
|
||||||
|
'2': [
|
||||||
|
{
|
||||||
|
'1': 'newMessages',
|
||||||
|
'3': 1,
|
||||||
|
'4': 3,
|
||||||
|
'5': 11,
|
||||||
|
'6': '.server_to_client.NewMessage',
|
||||||
|
'10': 'newMessages'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Descriptor for `NewMessages`. Decode as a `google.protobuf.DescriptorProto`.
|
||||||
|
final $typed_data.Uint8List newMessagesDescriptor = $convert.base64Decode(
|
||||||
|
'CgtOZXdNZXNzYWdlcxI+CgtuZXdNZXNzYWdlcxgBIAMoCzIcLnNlcnZlcl90b19jbGllbnQuTm'
|
||||||
|
'V3TWVzc2FnZVILbmV3TWVzc2FnZXM=');
|
||||||
|
|
||||||
@$core.Deprecated('Use responseDescriptor instead')
|
@$core.Deprecated('Use responseDescriptor instead')
|
||||||
const Response$json = {
|
const Response$json = {
|
||||||
'1': 'Response',
|
'1': 'Response',
|
||||||
|
|
|
||||||
13
lib/src/model/purchases/purchasable_product.dart
Normal file
13
lib/src/model/purchases/purchasable_product.dart
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import 'package:in_app_purchase/in_app_purchase.dart';
|
||||||
|
|
||||||
|
enum ProductStatus { purchasable, purchased, pending }
|
||||||
|
|
||||||
|
class PurchasableProduct {
|
||||||
|
PurchasableProduct(this.productDetails) : status = ProductStatus.purchasable;
|
||||||
|
String get id => productDetails.id;
|
||||||
|
String get title => productDetails.title;
|
||||||
|
String get description => productDetails.description;
|
||||||
|
String get price => productDetails.price;
|
||||||
|
ProductStatus status;
|
||||||
|
ProductDetails productDetails;
|
||||||
|
}
|
||||||
|
|
@ -1,17 +1,10 @@
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:twonly/src/services/subscription.service.dart';
|
|
||||||
|
|
||||||
class CustomChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
class CustomChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
||||||
bool _isConnected = false;
|
bool _isConnected = false;
|
||||||
bool get isConnected => _isConnected;
|
bool get isConnected => _isConnected;
|
||||||
SubscriptionPlan plan = SubscriptionPlan.Free;
|
|
||||||
Future<void> updateConnectionState(bool update) async {
|
Future<void> updateConnectionState(bool update) async {
|
||||||
_isConnected = update;
|
_isConnected = update;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updatePlan(SubscriptionPlan newPlan) async {
|
|
||||||
plan = newPlan;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
213
lib/src/providers/purchases.provider.dart
Normal file
213
lib/src/providers/purchases.provider.dart
Normal file
|
|
@ -0,0 +1,213 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:in_app_purchase/in_app_purchase.dart';
|
||||||
|
import 'package:twonly/globals.dart';
|
||||||
|
import 'package:twonly/src/constants/subscription.keys.dart';
|
||||||
|
import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart';
|
||||||
|
import 'package:twonly/src/model/purchases/purchasable_product.dart';
|
||||||
|
import 'package:twonly/src/services/subscription.service.dart';
|
||||||
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
|
// Gives the option to override in tests.
|
||||||
|
class IAPConnection {
|
||||||
|
static InAppPurchase? _instance;
|
||||||
|
static set instance(InAppPurchase value) {
|
||||||
|
_instance = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static InAppPurchase get instance {
|
||||||
|
_instance ??= InAppPurchase.instance;
|
||||||
|
return _instance!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum StoreState { loading, available, notAvailable }
|
||||||
|
|
||||||
|
Timer? globalForceIpaCheck;
|
||||||
|
|
||||||
|
class PurchasesProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
||||||
|
PurchasesProvider() {
|
||||||
|
final purchaseUpdated = iapConnection.purchaseStream;
|
||||||
|
_subscription = purchaseUpdated.listen(
|
||||||
|
_onPurchaseUpdate,
|
||||||
|
onDone: _updateStreamOnDone,
|
||||||
|
onError: _updateStreamOnError,
|
||||||
|
);
|
||||||
|
|
||||||
|
loadPurchases();
|
||||||
|
}
|
||||||
|
|
||||||
|
SubscriptionPlan plan = SubscriptionPlan.Free;
|
||||||
|
StoreState storeState = StoreState.loading;
|
||||||
|
List<PurchasableProduct> products = [];
|
||||||
|
|
||||||
|
late StreamSubscription<List<PurchaseDetails>> _subscription;
|
||||||
|
final InAppPurchase iapConnection = IAPConnection.instance;
|
||||||
|
|
||||||
|
bool _userTriggeredBuyButton = false;
|
||||||
|
|
||||||
|
void updatePlan(SubscriptionPlan newPlan) {
|
||||||
|
plan = newPlan;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> loadPurchases() async {
|
||||||
|
final available = await iapConnection.isAvailable();
|
||||||
|
if (!available) {
|
||||||
|
storeState = StoreState.notAvailable;
|
||||||
|
Log.error('Store is not available');
|
||||||
|
notifyListeners();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const ids = <String>{
|
||||||
|
SubscriptionKeys.proMonthly,
|
||||||
|
SubscriptionKeys.proYearly,
|
||||||
|
SubscriptionKeys.familyYearly,
|
||||||
|
};
|
||||||
|
final response = await iapConnection.queryProductDetails(ids);
|
||||||
|
if (response.notFoundIDs.isNotEmpty) {
|
||||||
|
Log.error(response.notFoundIDs);
|
||||||
|
}
|
||||||
|
products = response.productDetails.map(PurchasableProduct.new).toList();
|
||||||
|
if (products.isEmpty) {
|
||||||
|
Log.error('Could not load any products from the store!');
|
||||||
|
}
|
||||||
|
storeState = StoreState.available;
|
||||||
|
notifyListeners();
|
||||||
|
|
||||||
|
final user = await getUser();
|
||||||
|
if (user != null && isPayingUser(planFromString(user.subscriptionPlan))) {
|
||||||
|
Log.info('Started IPA timer for verification.');
|
||||||
|
globalForceIpaCheck = Timer(const Duration(seconds: 5), () async {
|
||||||
|
Log.warn('Force Ipa check was not stopped. Requesting forced check...');
|
||||||
|
await apiService.forceIpaCheck();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await iapConnection.restorePurchases();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> buy(PurchasableProduct product) async {
|
||||||
|
final purchaseParam = PurchaseParam(productDetails: product.productDetails);
|
||||||
|
switch (product.id) {
|
||||||
|
// case storeKeyConsumable:
|
||||||
|
// await iapConnection.buyConsumable(purchaseParam: purchaseParam);
|
||||||
|
case SubscriptionKeys.proMonthly:
|
||||||
|
case SubscriptionKeys.proYearly:
|
||||||
|
case SubscriptionKeys.familyYearly:
|
||||||
|
_userTriggeredBuyButton = true;
|
||||||
|
Log.info('User wants to buy ${product.id}');
|
||||||
|
|
||||||
|
await iapConnection.buyNonConsumable(purchaseParam: purchaseParam);
|
||||||
|
default:
|
||||||
|
throw ArgumentError.value(
|
||||||
|
product.productDetails,
|
||||||
|
'${product.id} is not a known product',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onPurchaseUpdate(
|
||||||
|
List<PurchaseDetails> purchaseDetailsList,
|
||||||
|
) async {
|
||||||
|
for (final purchaseDetails in purchaseDetailsList) {
|
||||||
|
await _handlePurchase(purchaseDetails);
|
||||||
|
}
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> _verifyPurchase(PurchaseDetails purchaseDetails) async {
|
||||||
|
if (kDebugMode) {
|
||||||
|
Log.info(purchaseDetails.productID);
|
||||||
|
Log.info(purchaseDetails.verificationData.serverVerificationData);
|
||||||
|
// if (Platform.isIOS) {
|
||||||
|
// final data = purchaseDetails.verificationData.serverVerificationData;
|
||||||
|
// printWrapped(data);
|
||||||
|
// final datas = data.split('.')[1];
|
||||||
|
// printWrapped(datas);
|
||||||
|
// }
|
||||||
|
Log.info(purchaseDetails.verificationData.source);
|
||||||
|
}
|
||||||
|
final res = await apiService.ipaPurchase(
|
||||||
|
purchaseDetails.productID,
|
||||||
|
purchaseDetails.verificationData.source,
|
||||||
|
purchaseDetails.verificationData.serverVerificationData,
|
||||||
|
);
|
||||||
|
// plan is updated in the apiProvider, as the server updates its states and responses with
|
||||||
|
// an ok authenticated which is processed in the apiProvider...
|
||||||
|
if (res.isSuccess) {
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
await updateUserdata((u) {
|
||||||
|
u.subscriptionPlanIdStore = purchaseDetails.productID;
|
||||||
|
return u;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (res.isError) {
|
||||||
|
if (res.error == ErrorCode.IPAPaymentExpired &&
|
||||||
|
_userTriggeredBuyButton &&
|
||||||
|
Platform.isIOS) {
|
||||||
|
await launchUrl(
|
||||||
|
Uri.parse('https://apps.apple.com/account/subscriptions'),
|
||||||
|
mode: LaunchMode.externalApplication,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res.isSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _handlePurchase(PurchaseDetails purchaseDetails) async {
|
||||||
|
Log.info(
|
||||||
|
'_handlePurchase: ${purchaseDetails.productID}, ${purchaseDetails.status}',
|
||||||
|
);
|
||||||
|
if (purchaseDetails.status == PurchaseStatus.purchased) {
|
||||||
|
await _verifyPurchase(purchaseDetails);
|
||||||
|
}
|
||||||
|
if (purchaseDetails.status == PurchaseStatus.restored &&
|
||||||
|
purchaseDetails.error == null) {
|
||||||
|
globalForceIpaCheck?.cancel();
|
||||||
|
|
||||||
|
final user = await getUser();
|
||||||
|
|
||||||
|
if (user != null &&
|
||||||
|
(user.subscriptionPlan != SubscriptionPlan.Family.name &&
|
||||||
|
user.subscriptionPlan != SubscriptionPlan.Pro.name)) {
|
||||||
|
for (var i = 0; i < 100; i++) {
|
||||||
|
if (apiService.isAuthenticated) {
|
||||||
|
Log.info(
|
||||||
|
'current user does not have a sub: ${purchaseDetails.productID}',
|
||||||
|
);
|
||||||
|
await _verifyPurchase(purchaseDetails);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
await Future.delayed(const Duration(seconds: 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (purchaseDetails.status == PurchaseStatus.error) {
|
||||||
|
await iapConnection.restorePurchases();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (purchaseDetails.pendingCompletePurchase) {
|
||||||
|
await iapConnection.completePurchase(purchaseDetails);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_subscription.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _updateStreamOnDone() {
|
||||||
|
_subscription.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _updateStreamOnError(dynamic error) {
|
||||||
|
// Handle error here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -50,7 +50,7 @@ final lockRetransStore = Mutex();
|
||||||
/// errors or network changes.
|
/// errors or network changes.
|
||||||
class ApiService {
|
class ApiService {
|
||||||
ApiService();
|
ApiService();
|
||||||
final String apiHost = kReleaseMode ? 'api.twonly.eu' : '10.99.0.140:3030';
|
final String apiHost = kReleaseMode ? 'api.twonly.eu' : '192.168.2.178:3030';
|
||||||
// final String apiHost = kReleaseMode ? 'api.twonly.eu' : 'dev.twonly.eu';
|
// final String apiHost = kReleaseMode ? 'api.twonly.eu' : 'dev.twonly.eu';
|
||||||
final String apiSecure = kReleaseMode ? 's' : '';
|
final String apiSecure = kReleaseMode ? 's' : '';
|
||||||
|
|
||||||
|
|
@ -98,12 +98,13 @@ class ApiService {
|
||||||
unawaited(signalHandleNewServerConnection());
|
unawaited(signalHandleNewServerConnection());
|
||||||
unawaited(fetchGroupStatesForUnjoinedGroups());
|
unawaited(fetchGroupStatesForUnjoinedGroups());
|
||||||
unawaited(fetchMissingGroupPublicKey());
|
unawaited(fetchMissingGroupPublicKey());
|
||||||
|
unawaited(checkForDeletedUsernames());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> onConnected() async {
|
Future<void> onConnected() async {
|
||||||
await authenticate();
|
await authenticate();
|
||||||
_reconnectionDelay = 5;
|
_reconnectionDelay = 1;
|
||||||
globalCallbackConnectionState(isConnected: true);
|
globalCallbackConnectionState(isConnected: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,16 +113,21 @@ class ApiService {
|
||||||
isAuthenticated = false;
|
isAuthenticated = false;
|
||||||
globalCallbackConnectionState(isConnected: false);
|
globalCallbackConnectionState(isConnected: false);
|
||||||
await twonlyDB.mediaFilesDao.resetPendingDownloadState();
|
await twonlyDB.mediaFilesDao.resetPendingDownloadState();
|
||||||
|
await startReconnectionTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> startReconnectionTimer() async {
|
Future<void> startReconnectionTimer() async {
|
||||||
|
if (reconnectionTimer?.isActive ?? false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
reconnectionTimer?.cancel();
|
reconnectionTimer?.cancel();
|
||||||
reconnectionTimer ??=
|
Log.info('Starting reconnection timer with $_reconnectionDelay s delay');
|
||||||
Timer(Duration(seconds: _reconnectionDelay), () async {
|
reconnectionTimer = Timer(Duration(seconds: _reconnectionDelay), () async {
|
||||||
|
Log.info('Reconnection timer triggered');
|
||||||
reconnectionTimer = null;
|
reconnectionTimer = null;
|
||||||
await connect(force: true);
|
await connect();
|
||||||
});
|
});
|
||||||
_reconnectionDelay += 5;
|
_reconnectionDelay = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> close(Function callback) async {
|
Future<void> close(Function callback) async {
|
||||||
|
|
@ -143,18 +149,13 @@ class ApiService {
|
||||||
.onConnectivityChanged
|
.onConnectivityChanged
|
||||||
.listen((List<ConnectivityResult> result) async {
|
.listen((List<ConnectivityResult> result) async {
|
||||||
if (!result.contains(ConnectivityResult.none)) {
|
if (!result.contains(ConnectivityResult.none)) {
|
||||||
await connect(force: true);
|
await connect();
|
||||||
}
|
}
|
||||||
// Received changes in available connectivity types!
|
// Received changes in available connectivity types!
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> connect({bool force = false}) async {
|
Future<bool> connect() async {
|
||||||
if (reconnectionTimer != null && !force) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
reconnectionTimer?.cancel();
|
|
||||||
reconnectionTimer = null;
|
|
||||||
return lockConnecting.protect<bool>(() async {
|
return lockConnecting.protect<bool>(() async {
|
||||||
if (_channel != null) {
|
if (_channel != null) {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -181,6 +182,7 @@ class ApiService {
|
||||||
|
|
||||||
Future<void> _onDone() async {
|
Future<void> _onDone() async {
|
||||||
Log.info('websocket closed without error');
|
Log.info('websocket closed without error');
|
||||||
|
_reconnectionDelay = 60 * 2; // the server closed the connection...
|
||||||
await onClosed();
|
await onClosed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -290,6 +292,7 @@ class ApiService {
|
||||||
if (_channel == null) {
|
if (_channel == null) {
|
||||||
Log.warn('sending request while api is not connected');
|
Log.warn('sending request while api is not connected');
|
||||||
if (!await connect()) {
|
if (!await connect()) {
|
||||||
|
Log.warn('could not connected again');
|
||||||
return Result.error(ErrorCode.InternalError);
|
return Result.error(ErrorCode.InternalError);
|
||||||
}
|
}
|
||||||
if (_channel == null) {
|
if (_channel == null) {
|
||||||
|
|
@ -300,6 +303,17 @@ class ApiService {
|
||||||
_channel!.sink.add(requestBytes);
|
_channel!.sink.add(requestBytes);
|
||||||
|
|
||||||
final res = asResult(await _waitForResponse(seq));
|
final res = asResult(await _waitForResponse(seq));
|
||||||
|
if (res.isSuccess) {
|
||||||
|
final ok = res.value as server.Response_Ok;
|
||||||
|
if (ok.hasAuthenticated()) {
|
||||||
|
final authenticated = ok.authenticated;
|
||||||
|
await updateUserdata((user) {
|
||||||
|
user.subscriptionPlan = authenticated.plan;
|
||||||
|
return user;
|
||||||
|
});
|
||||||
|
globalCallbackUpdatePlan(planFromString(authenticated.plan));
|
||||||
|
}
|
||||||
|
}
|
||||||
if (res.isError) {
|
if (res.isError) {
|
||||||
Log.warn('Got error from server: ${res.error}');
|
Log.warn('Got error from server: ${res.error}');
|
||||||
if (res.error == ErrorCode.AppVersionOutdated) {
|
if (res.error == ErrorCode.AppVersionOutdated) {
|
||||||
|
|
@ -339,9 +353,8 @@ class ApiService {
|
||||||
if (contact != null) {
|
if (contact != null) {
|
||||||
await twonlyDB.contactsDao.updateContact(
|
await twonlyDB.contactsDao.updateContact(
|
||||||
contactId,
|
contactId,
|
||||||
ContactsCompanion(
|
const ContactsCompanion(
|
||||||
accountDeleted: const Value(true),
|
accountDeleted: Value(true),
|
||||||
username: Value('${contact.username} (${contact.userId})'),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -375,15 +388,6 @@ class ApiService {
|
||||||
final result = await sendRequestSync(req, authenticated: false);
|
final result = await sendRequestSync(req, authenticated: false);
|
||||||
|
|
||||||
if (result.isSuccess) {
|
if (result.isSuccess) {
|
||||||
final ok = result.value as server.Response_Ok;
|
|
||||||
if (ok.hasAuthenticated()) {
|
|
||||||
final authenticated = ok.authenticated;
|
|
||||||
await updateUserdata((user) {
|
|
||||||
user.subscriptionPlan = authenticated.plan;
|
|
||||||
return user;
|
|
||||||
});
|
|
||||||
globalCallbackUpdatePlan(planFromString(authenticated.plan));
|
|
||||||
}
|
|
||||||
Log.info('websocket is authenticated');
|
Log.info('websocket is authenticated');
|
||||||
unawaited(onAuthenticated());
|
unawaited(onAuthenticated());
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -491,6 +495,20 @@ class ApiService {
|
||||||
return sendRequestSync(req);
|
return sendRequestSync(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> checkForDeletedUsernames() async {
|
||||||
|
final users = await twonlyDB.contactsDao
|
||||||
|
.getContactsByUsername('[deleted]', username2: '[Unknown]');
|
||||||
|
for (final user in users) {
|
||||||
|
final userData = await getUserById(user.userId);
|
||||||
|
if (userData != null) {
|
||||||
|
await twonlyDB.contactsDao.updateContact(
|
||||||
|
user.userId,
|
||||||
|
ContactsCompanion(username: Value(utf8.decode(userData.username))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<Response_UserData?> getUserById(int userId) async {
|
Future<Response_UserData?> getUserById(int userId) async {
|
||||||
final get = ApplicationData_GetUserById()..userId = Int64(userId);
|
final get = ApplicationData_GetUserById()..userId = Int64(userId);
|
||||||
final appData = ApplicationData()..getUserById = get;
|
final appData = ApplicationData()..getUserById = get;
|
||||||
|
|
@ -662,6 +680,22 @@ class ApiService {
|
||||||
return sendRequestSync(req);
|
return sendRequestSync(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<Result> ipaPurchase(
|
||||||
|
String productId,
|
||||||
|
String source,
|
||||||
|
String verificationData,
|
||||||
|
) async {
|
||||||
|
final appData = ApplicationData(
|
||||||
|
ipaPurchase: ApplicationData_IPAPurchase(
|
||||||
|
productId: productId,
|
||||||
|
source: source,
|
||||||
|
verificationData: verificationData,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
final req = createClientToServerFromApplicationData(appData);
|
||||||
|
return sendRequestSync(req);
|
||||||
|
}
|
||||||
|
|
||||||
Future<Result> changeUsername(String username) async {
|
Future<Result> changeUsername(String username) async {
|
||||||
final get = ApplicationData_ChangeUsername()..username = username;
|
final get = ApplicationData_ChangeUsername()..username = username;
|
||||||
final appData = ApplicationData()..changeUsername = get;
|
final appData = ApplicationData()..changeUsername = get;
|
||||||
|
|
@ -669,6 +703,15 @@ class ApiService {
|
||||||
return sendRequestSync(req);
|
return sendRequestSync(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<Result> forceIpaCheck() async {
|
||||||
|
final req = createClientToServerFromApplicationData(
|
||||||
|
ApplicationData(
|
||||||
|
ipaForceCheck: ApplicationData_IPAForceCheck(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return sendRequestSync(req);
|
||||||
|
}
|
||||||
|
|
||||||
Future<Result> updateSignedPreKey(
|
Future<Result> updateSignedPreKey(
|
||||||
int signedPreKeyId,
|
int signedPreKeyId,
|
||||||
Uint8List signedPreKey,
|
Uint8List signedPreKey,
|
||||||
|
|
@ -722,6 +765,28 @@ class ApiService {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<Response_PlanBallance?> loadPlanBalance({bool useCache = true}) async {
|
||||||
|
final ballance = await getPlanBallance();
|
||||||
|
if (ballance != null) {
|
||||||
|
await updateUserdata((u) {
|
||||||
|
u.lastPlanBallance = ballance.writeToJson();
|
||||||
|
return u;
|
||||||
|
});
|
||||||
|
return ballance;
|
||||||
|
}
|
||||||
|
final user = await getUser();
|
||||||
|
if (user != null && user.lastPlanBallance != null && useCache) {
|
||||||
|
try {
|
||||||
|
return Response_PlanBallance.fromJson(
|
||||||
|
user.lastPlanBallance!,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
Log.error('from json: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ballance;
|
||||||
|
}
|
||||||
|
|
||||||
Future<Result> sendTextMessage(
|
Future<Result> sendTextMessage(
|
||||||
int target,
|
int target,
|
||||||
Uint8List msg,
|
Uint8List msg,
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ Future<void> handleContactUpdate(
|
||||||
switch (contactUpdate.type) {
|
switch (contactUpdate.type) {
|
||||||
case EncryptedContent_ContactUpdate_Type.REQUEST:
|
case EncryptedContent_ContactUpdate_Type.REQUEST:
|
||||||
Log.info('Got a contact update request from $fromUserId');
|
Log.info('Got a contact update request from $fromUserId');
|
||||||
await notifyContactsAboutProfileChange(onlyToContact: fromUserId);
|
await sendContactMyProfileData(fromUserId);
|
||||||
|
|
||||||
case EncryptedContent_ContactUpdate_Type.UPDATE:
|
case EncryptedContent_ContactUpdate_Type.UPDATE:
|
||||||
Log.info('Got a contact update $fromUserId');
|
Log.info('Got a contact update $fromUserId');
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,10 @@ Future<void> handleGroupUpdate(
|
||||||
|
|
||||||
final group = (await twonlyDB.groupsDao.getGroup(groupId))!;
|
final group = (await twonlyDB.groupsDao.getGroup(groupId))!;
|
||||||
|
|
||||||
|
if (!group.isDirectChat) {
|
||||||
|
unawaited(fetchGroupState(group));
|
||||||
|
}
|
||||||
|
|
||||||
switch (actionType) {
|
switch (actionType) {
|
||||||
case GroupActionType.updatedGroupName:
|
case GroupActionType.updatedGroupName:
|
||||||
await twonlyDB.groupsDao.insertGroupAction(
|
await twonlyDB.groupsDao.insertGroupAction(
|
||||||
|
|
@ -173,10 +177,6 @@ Future<void> handleGroupUpdate(
|
||||||
case GroupActionType.createdGroup:
|
case GroupActionType.createdGroup:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!group.isDirectChat) {
|
|
||||||
unawaited(fetchGroupState(group));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> handleGroupJoin(
|
Future<bool> handleGroupJoin(
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,7 @@ Future<void> handleUploadStatusUpdate(TaskStatusUpdate update) async {
|
||||||
);
|
);
|
||||||
final mediaService = MediaFileService(media);
|
final mediaService = MediaFileService(media);
|
||||||
|
|
||||||
await mediaService.setUploadState(UploadState.uploaded);
|
await mediaService.setUploadState(UploadState.uploading);
|
||||||
// In all other cases just try the upload again...
|
// In all other cases just try the upload again...
|
||||||
await startBackgroundMediaUpload(mediaService);
|
await startBackgroundMediaUpload(mediaService);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -132,6 +132,15 @@ Future<void> startBackgroundMediaUpload(MediaFileService mediaService) async {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if the user has enabled auto storing and the file
|
||||||
|
// was send with unlimited counter not in twonly-Mode then store the file
|
||||||
|
if (gUser.autoStoreAllSendUnlimitedMediaFiles &&
|
||||||
|
!mediaService.mediaFile.requiresAuthentication &&
|
||||||
|
!mediaService.storedPath.existsSync() &&
|
||||||
|
mediaService.mediaFile.displayLimitInMilliseconds == null) {
|
||||||
|
await mediaService.storeMediaFile();
|
||||||
|
}
|
||||||
|
|
||||||
if (!mediaService.encryptedPath.existsSync()) {
|
if (!mediaService.encryptedPath.existsSync()) {
|
||||||
await _encryptMediaFiles(mediaService);
|
await _encryptMediaFiles(mediaService);
|
||||||
if (!mediaService.encryptedPath.existsSync()) {
|
if (!mediaService.encryptedPath.existsSync()) {
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ final lockRetransmission = Mutex();
|
||||||
|
|
||||||
Future<void> tryTransmitMessages() async {
|
Future<void> tryTransmitMessages() async {
|
||||||
return lockRetransmission.protect(() async {
|
return lockRetransmission.protect(() async {
|
||||||
final receipts = await twonlyDB.receiptsDao.getReceiptsNotAckByServer();
|
final receipts = await twonlyDB.receiptsDao.getReceiptsForRetransmission();
|
||||||
|
|
||||||
if (receipts.isEmpty) return;
|
if (receipts.isEmpty) return;
|
||||||
|
|
||||||
|
|
@ -289,26 +289,18 @@ Future<void> notifyContactAboutOpeningMessage(
|
||||||
await updateLastMessageId(contactId, biggestMessageId);
|
await updateLastMessageId(contactId, biggestMessageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> notifyContactsAboutProfileChange({int? onlyToContact}) async {
|
Future<void> sendContactMyProfileData(int contactId) async {
|
||||||
if (gUser.avatarSvg == null) return;
|
List<int>? avatarSvgCompressed;
|
||||||
|
if (gUser.avatarSvg != null) {
|
||||||
|
avatarSvgCompressed = gzip.encode(utf8.encode(gUser.avatarSvg!));
|
||||||
|
}
|
||||||
final encryptedContent = pb.EncryptedContent(
|
final encryptedContent = pb.EncryptedContent(
|
||||||
contactUpdate: pb.EncryptedContent_ContactUpdate(
|
contactUpdate: pb.EncryptedContent_ContactUpdate(
|
||||||
type: pb.EncryptedContent_ContactUpdate_Type.UPDATE,
|
type: pb.EncryptedContent_ContactUpdate_Type.UPDATE,
|
||||||
avatarSvgCompressed: gzip.encode(utf8.encode(gUser.avatarSvg!)),
|
avatarSvgCompressed: avatarSvgCompressed,
|
||||||
displayName: gUser.displayName,
|
displayName: gUser.displayName,
|
||||||
username: gUser.username,
|
username: gUser.username,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
await sendCipherText(contactId, encryptedContent);
|
||||||
if (onlyToContact != null) {
|
|
||||||
await sendCipherText(onlyToContact, encryptedContent);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final contacts = await twonlyDB.contactsDao.getAllNotBlockedContacts();
|
|
||||||
|
|
||||||
for (final contact in contacts) {
|
|
||||||
await sendCipherText(contact.userId, encryptedContent);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:hashlib/random.dart';
|
import 'package:hashlib/random.dart';
|
||||||
import 'package:mutex/mutex.dart';
|
import 'package:mutex/mutex.dart';
|
||||||
|
|
@ -10,6 +11,7 @@ import 'package:twonly/src/model/protobuf/api/websocket/client_to_server.pb.dart
|
||||||
import 'package:twonly/src/model/protobuf/api/websocket/client_to_server.pb.dart';
|
import 'package:twonly/src/model/protobuf/api/websocket/client_to_server.pb.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'
|
||||||
as server;
|
as server;
|
||||||
|
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pbserver.dart';
|
||||||
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
|
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
|
||||||
import 'package:twonly/src/services/api/client2client/contact.c2c.dart';
|
import 'package:twonly/src/services/api/client2client/contact.c2c.dart';
|
||||||
import 'package:twonly/src/services/api/client2client/groups.c2c.dart';
|
import 'package:twonly/src/services/api/client2client/groups.c2c.dart';
|
||||||
|
|
@ -35,9 +37,15 @@ Future<void> handleServerMessage(server.ServerToClient msg) async {
|
||||||
if (msg.v0.hasRequestNewPreKeys()) {
|
if (msg.v0.hasRequestNewPreKeys()) {
|
||||||
response = await handleRequestNewPreKey();
|
response = await handleRequestNewPreKey();
|
||||||
} else if (msg.v0.hasNewMessage()) {
|
} else if (msg.v0.hasNewMessage()) {
|
||||||
final body = Uint8List.fromList(msg.v0.newMessage.body);
|
await handleClient2ClientMessage(msg.v0.newMessage);
|
||||||
final fromUserId = msg.v0.newMessage.fromUserId.toInt();
|
} else if (msg.v0.hasNewMessages()) {
|
||||||
await handleClient2ClientMessage(fromUserId, body);
|
for (final newMessage in msg.v0.newMessages.newMessages) {
|
||||||
|
try {
|
||||||
|
await handleClient2ClientMessage(newMessage);
|
||||||
|
} catch (e) {
|
||||||
|
Log.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.error('Unknown server message: $msg');
|
Log.error('Unknown server message: $msg');
|
||||||
}
|
}
|
||||||
|
|
@ -56,7 +64,10 @@ DateTime lastPushKeyRequest = DateTime.now().subtract(const Duration(hours: 1));
|
||||||
|
|
||||||
Mutex protectReceiptCheck = Mutex();
|
Mutex protectReceiptCheck = Mutex();
|
||||||
|
|
||||||
Future<void> handleClient2ClientMessage(int fromUserId, Uint8List body) async {
|
Future<void> handleClient2ClientMessage(NewMessage newMessage) async {
|
||||||
|
final body = Uint8List.fromList(newMessage.body);
|
||||||
|
final fromUserId = newMessage.fromUserId.toInt();
|
||||||
|
|
||||||
final message = Message.fromBuffer(body);
|
final message = Message.fromBuffer(body);
|
||||||
final receiptId = message.receiptId;
|
final receiptId = message.receiptId;
|
||||||
|
|
||||||
|
|
@ -112,13 +123,17 @@ Future<void> handleClient2ClientMessage(int fromUserId, Uint8List body) async {
|
||||||
.getContactByUserId(fromUserId)
|
.getContactByUserId(fromUserId)
|
||||||
.getSingleOrNull() ==
|
.getSingleOrNull() ==
|
||||||
null) {
|
null) {
|
||||||
|
final user = await apiService.getUserById(fromUserId);
|
||||||
|
|
||||||
/// In case the user does not exists, just create a dummy user which was deleted by the user, so the message
|
/// In case the user does not exists, just create a dummy user which was deleted by the user, so the message
|
||||||
/// can be inserted into the receipts database
|
/// can be inserted into the receipts database
|
||||||
await twonlyDB.contactsDao.insertContact(
|
await twonlyDB.contactsDao.insertContact(
|
||||||
ContactsCompanion(
|
ContactsCompanion(
|
||||||
userId: Value(fromUserId),
|
userId: Value(fromUserId),
|
||||||
deletedByUser: const Value(true),
|
deletedByUser: const Value(true),
|
||||||
username: const Value('[deleted]'),
|
username: Value(
|
||||||
|
user == null ? '[Unknown]' : utf8.decode(user.username),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -174,6 +189,15 @@ Future<PlaintextContent?> handleEncryptedMessage(
|
||||||
..type = decryptionErrorType!);
|
..type = decryptionErrorType!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We got a valid message fromUserId, so mark all messages which where
|
||||||
|
// send to the user but not yet ACK for retransmission. All marked messages
|
||||||
|
// will be either transmitted again after a new server connection (minimum 20 seconds).
|
||||||
|
// In case the server sends the ACK before they will be deleted.
|
||||||
|
// This ensures that 1. all messages will be received by the other person and
|
||||||
|
// that they will be retransmitted in case the server deleted them as they
|
||||||
|
// where not downloaded within the 40 days
|
||||||
|
await twonlyDB.receiptsDao.markMessagesForRetry(fromUserId);
|
||||||
|
|
||||||
final senderProfileCounter = await checkForProfileUpdate(fromUserId, content);
|
final senderProfileCounter = await checkForProfileUpdate(fromUserId, content);
|
||||||
|
|
||||||
if (content.hasContactRequest()) {
|
if (content.hasContactRequest()) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
// ignore_for_file: unreachable_from_main
|
// ignore_for_file: unreachable_from_main
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
import 'dart:io' show Platform;
|
import 'dart:io' show Platform;
|
||||||
|
|
||||||
import 'package:firebase_core/firebase_core.dart';
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
|
|
@ -9,39 +10,50 @@ import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/constants/secure_storage_keys.dart';
|
import 'package:twonly/src/constants/secure_storage_keys.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/storage.dart';
|
||||||
|
|
||||||
import '../../firebase_options.dart';
|
import '../../firebase_options.dart';
|
||||||
|
|
||||||
// see more here: https://firebase.google.com/docs/cloud-messaging/flutter/receive?hl=de
|
// see more here: https://firebase.google.com/docs/cloud-messaging/flutter/receive?hl=de
|
||||||
|
|
||||||
Future<void> initFCMAfterAuthenticated() async {
|
Future<void> checkForTokenUpdates() async {
|
||||||
if (globalIsAppInBackground) return;
|
|
||||||
|
|
||||||
const storage = FlutterSecureStorage();
|
const storage = FlutterSecureStorage();
|
||||||
|
|
||||||
final storedToken = await storage.read(key: SecureStorageKeys.googleFcm);
|
final storedToken = await storage.read(key: SecureStorageKeys.googleFcm);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (Platform.isIOS) {
|
if (Platform.isIOS) {
|
||||||
final apnsToken = await FirebaseMessaging.instance.getAPNSToken();
|
var apnsToken = await FirebaseMessaging.instance.getAPNSToken();
|
||||||
|
for (var i = 0; i < 20; i++) {
|
||||||
|
if (apnsToken != null) break;
|
||||||
|
await Future<void>.delayed(const Duration(seconds: 1));
|
||||||
|
apnsToken = await FirebaseMessaging.instance.getAPNSToken();
|
||||||
|
}
|
||||||
if (apnsToken == null) {
|
if (apnsToken == null) {
|
||||||
Log.error('Error getting apnsToken');
|
Log.error('Could not get APNS token even after 20s...');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final fcmToken = await FirebaseMessaging.instance.getToken();
|
|
||||||
if (fcmToken == null) {
|
|
||||||
Log.error('Error getting fcmToken');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
final fcmToken = await FirebaseMessaging.instance.getToken();
|
||||||
|
if (fcmToken == null) {
|
||||||
|
Log.error('Could not get fcm token');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Log.info('Loaded fcm token');
|
||||||
if (storedToken == null || fcmToken != storedToken) {
|
if (storedToken == null || fcmToken != storedToken) {
|
||||||
await apiService.updateFCMToken(fcmToken);
|
await updateUserdata((u) {
|
||||||
|
u.updateFCMToken = true;
|
||||||
|
return u;
|
||||||
|
});
|
||||||
await storage.write(key: SecureStorageKeys.googleFcm, value: fcmToken);
|
await storage.write(key: SecureStorageKeys.googleFcm, value: fcmToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
FirebaseMessaging.instance.onTokenRefresh.listen((fcmToken) async {
|
FirebaseMessaging.instance.onTokenRefresh.listen((fcmToken) async {
|
||||||
await apiService.updateFCMToken(fcmToken);
|
await updateUserdata((u) {
|
||||||
|
u.updateFCMToken = true;
|
||||||
|
return u;
|
||||||
|
});
|
||||||
await storage.write(key: SecureStorageKeys.googleFcm, value: fcmToken);
|
await storage.write(key: SecureStorageKeys.googleFcm, value: fcmToken);
|
||||||
}).onError((err) {
|
}).onError((err) {
|
||||||
Log.error('could not listen on token refresh');
|
Log.error('could not listen on token refresh');
|
||||||
|
|
@ -51,11 +63,30 @@ Future<void> initFCMAfterAuthenticated() async {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> initFCMAfterAuthenticated() async {
|
||||||
|
if (gUser.updateFCMToken) {
|
||||||
|
const storage = FlutterSecureStorage();
|
||||||
|
final storedToken = await storage.read(key: SecureStorageKeys.googleFcm);
|
||||||
|
if (storedToken != null) {
|
||||||
|
final res = await apiService.updateFCMToken(storedToken);
|
||||||
|
if (res.isSuccess) {
|
||||||
|
Log.info('Uploaded new fmt token!');
|
||||||
|
await updateUserdata((u) {
|
||||||
|
u.updateFCMToken = false;
|
||||||
|
return u;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> initFCMService() async {
|
Future<void> initFCMService() async {
|
||||||
await Firebase.initializeApp(
|
await Firebase.initializeApp(
|
||||||
options: DefaultFirebaseOptions.currentPlatform,
|
options: DefaultFirebaseOptions.currentPlatform,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
unawaited(checkForTokenUpdates());
|
||||||
|
|
||||||
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
|
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
|
||||||
|
|
||||||
// You may set the permission requests to "provisional" which allows the user to choose what type
|
// You may set the permission requests to "provisional" which allows the user to choose what type
|
||||||
|
|
@ -65,12 +96,12 @@ Future<void> initFCMService() async {
|
||||||
await FirebaseMessaging.instance.requestPermission();
|
await FirebaseMessaging.instance.requestPermission();
|
||||||
|
|
||||||
// For apple platforms, ensure the APNS token is available before making any FCM plugin API calls
|
// For apple platforms, ensure the APNS token is available before making any FCM plugin API calls
|
||||||
if (Platform.isIOS) {
|
// if (Platform.isIOS) {
|
||||||
final apnsToken = await FirebaseMessaging.instance.getAPNSToken();
|
// final apnsToken = await FirebaseMessaging.instance.getAPNSToken();
|
||||||
if (apnsToken == null) {
|
// if (apnsToken == null) {
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
FirebaseMessaging.onMessage.listen(handleRemoteMessage);
|
FirebaseMessaging.onMessage.listen(handleRemoteMessage);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
180
lib/src/services/intent/links.intent.dart
Normal file
180
lib/src/services/intent/links.intent.dart
Normal file
|
|
@ -0,0 +1,180 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:drift/drift.dart' show Value;
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_sharing_intent/model/sharing_file.dart';
|
||||||
|
import 'package:twonly/globals.dart';
|
||||||
|
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||||
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
|
import 'package:twonly/src/services/api/mediafiles/upload.service.dart';
|
||||||
|
import 'package:twonly/src/services/signal/session.signal.dart';
|
||||||
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
import 'package:twonly/src/views/camera/share_image_editor_view.dart';
|
||||||
|
import 'package:twonly/src/views/chats/add_new_user.view.dart';
|
||||||
|
import 'package:twonly/src/views/components/alert_dialog.dart';
|
||||||
|
import 'package:twonly/src/views/contact/contact.view.dart';
|
||||||
|
import 'package:twonly/src/views/public_profile.view.dart';
|
||||||
|
|
||||||
|
Future<void> handleIntentUrl(BuildContext context, Uri uri) async {
|
||||||
|
if (!uri.scheme.startsWith('http')) return;
|
||||||
|
if (uri.host != 'me.twonly.eu') return;
|
||||||
|
if (uri.hasEmptyPath) return;
|
||||||
|
|
||||||
|
final publicKey = uri.hasFragment ? uri.fragment : null;
|
||||||
|
final userPaths = uri.path.split('/');
|
||||||
|
if (userPaths.length != 2) return;
|
||||||
|
final username = userPaths[1];
|
||||||
|
|
||||||
|
if (!context.mounted) return;
|
||||||
|
|
||||||
|
if (username == gUser.username) {
|
||||||
|
await Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) {
|
||||||
|
return const PublicProfileView();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.info(
|
||||||
|
'Opened via deep link!: username = $username public_key = ${uri.fragment}',
|
||||||
|
);
|
||||||
|
final contacts = await twonlyDB.contactsDao.getContactsByUsername(username);
|
||||||
|
if (contacts.isEmpty) {
|
||||||
|
if (!context.mounted) return;
|
||||||
|
Uint8List? publicKeyBytes;
|
||||||
|
if (publicKey != null) {
|
||||||
|
publicKeyBytes = base64Url.decode(publicKey);
|
||||||
|
}
|
||||||
|
await Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) {
|
||||||
|
return AddNewUserView(
|
||||||
|
username: username,
|
||||||
|
publicKey: publicKeyBytes,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (publicKey != null) {
|
||||||
|
try {
|
||||||
|
final contact = contacts.first;
|
||||||
|
final storedPublicKey = await getPublicKeyFromContact(contact.userId);
|
||||||
|
final receivedPublicKey = base64Url.decode(publicKey);
|
||||||
|
if (storedPublicKey == null ||
|
||||||
|
receivedPublicKey.isEmpty ||
|
||||||
|
!context.mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (storedPublicKey.equals(receivedPublicKey)) {
|
||||||
|
if (!contact.verified) {
|
||||||
|
final markAsVerified = await showAlertDialog(
|
||||||
|
context,
|
||||||
|
context.lang.linkFromUsername(contact.username),
|
||||||
|
context.lang.linkFromUsernameLong,
|
||||||
|
customOk: context.lang.gotLinkFromFriend,
|
||||||
|
);
|
||||||
|
if (markAsVerified) {
|
||||||
|
await twonlyDB.contactsDao.updateContact(
|
||||||
|
contact.userId,
|
||||||
|
const ContactsCompanion(
|
||||||
|
verified: Value(true),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) {
|
||||||
|
return ContactView(contact.userId);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await showAlertDialog(
|
||||||
|
context,
|
||||||
|
context.lang.couldNotVerifyUsername(contact.username),
|
||||||
|
context.lang.linkPubkeyDoesNotMatch,
|
||||||
|
customCancel: '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
Log.warn(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> handleIntentMediaFile(
|
||||||
|
BuildContext context,
|
||||||
|
String filePath,
|
||||||
|
MediaType type,
|
||||||
|
) async {
|
||||||
|
final file = File(filePath);
|
||||||
|
if (!file.existsSync()) {
|
||||||
|
Log.error('The shared intent file does not exits.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final newMediaService = await initializeMediaUpload(
|
||||||
|
type,
|
||||||
|
gUser.defaultShowTime,
|
||||||
|
);
|
||||||
|
if (newMediaService == null) {
|
||||||
|
Log.error('Could not create new media file for intent shared file');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
file.copySync(newMediaService.originalPath.path);
|
||||||
|
if (!context.mounted) return;
|
||||||
|
|
||||||
|
await Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => ShareImageEditorView(
|
||||||
|
mediaFileService: newMediaService,
|
||||||
|
sharedFromGallery: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> handleIntentSharedFile(
|
||||||
|
BuildContext context,
|
||||||
|
List<SharedFile> files,
|
||||||
|
) async {
|
||||||
|
for (final file in files) {
|
||||||
|
if (file.value == null) {
|
||||||
|
Log.error(
|
||||||
|
'Got shared media, but value is empty: getMediaStream ${file.mimeType}',
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Log.info('got file via intent ${file.type} ${file.value}');
|
||||||
|
|
||||||
|
switch (file.type) {
|
||||||
|
case SharedMediaType.URL:
|
||||||
|
// await handleIntentUrl(context, Uri.parse(file.value!));
|
||||||
|
case SharedMediaType.IMAGE:
|
||||||
|
var type = MediaType.image;
|
||||||
|
if (file.value!.endsWith('.gif')) {
|
||||||
|
type = MediaType.gif;
|
||||||
|
}
|
||||||
|
await handleIntentMediaFile(context, file.value!, type);
|
||||||
|
case SharedMediaType.VIDEO:
|
||||||
|
await handleIntentMediaFile(context, file.value!, MediaType.video);
|
||||||
|
// ignore: no_default_cases
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
break; // only handle one file...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/services/mediafiles/compression.service.dart';
|
import 'package:twonly/src/services/mediafiles/compression.service.dart';
|
||||||
import 'package:twonly/src/services/mediafiles/thumbnail.service.dart';
|
import 'package:twonly/src/services/mediafiles/thumbnail.service.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
||||||
class MediaFileService {
|
class MediaFileService {
|
||||||
MediaFileService(this.mediaFile);
|
MediaFileService(this.mediaFile);
|
||||||
|
|
@ -219,10 +220,26 @@ class MediaFileService {
|
||||||
await tempPath.copy(storedPath.path);
|
await tempPath.copy(storedPath.path);
|
||||||
} else {
|
} else {
|
||||||
Log.error(
|
Log.error(
|
||||||
'Could not store image neither as tempPath does not exists.',
|
'Could not store image neither as ${tempPath.path} does not exists.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
unawaited(createThumbnail());
|
unawaited(createThumbnail());
|
||||||
|
await hashStoredMedia();
|
||||||
|
// updateFromDb is done in hashStoredMedia()
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> hashStoredMedia() async {
|
||||||
|
if (!storedPath.existsSync()) {
|
||||||
|
Log.error('could not create hash value as media file is not stored.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final checksum = await sha256File(storedPath);
|
||||||
|
await twonlyDB.mediaFilesDao.updateMedia(
|
||||||
|
mediaFile.mediaId,
|
||||||
|
MediaFilesCompanion(
|
||||||
|
storedFileHash: Value(Uint8List.fromList(checksum)),
|
||||||
|
),
|
||||||
|
);
|
||||||
await updateFromDB();
|
await updateFromDB();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -175,8 +175,7 @@ Future<void> showLocalPushNotification(
|
||||||
|
|
||||||
await flutterLocalNotificationsPlugin.show(
|
await flutterLocalNotificationsPlugin.show(
|
||||||
pushUser.userId.toInt() %
|
pushUser.userId.toInt() %
|
||||||
// ignore: avoid_js_rounded_ints
|
2147483647, // Invalid argument (id): must fit within the size of a 32-bit integer
|
||||||
2373257871630019505, // Invalid argument (id): must fit within the size of a 32-bit integer
|
|
||||||
title,
|
title,
|
||||||
body,
|
body,
|
||||||
notificationDetails,
|
notificationDetails,
|
||||||
|
|
|
||||||
|
|
@ -303,6 +303,15 @@ Future<PushNotification?> getPushNotificationFromEncryptedContent(
|
||||||
return pushNotification;
|
return pushNotification;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> requestNewPushKeysForUser(int toUserId) async {
|
||||||
|
await sendCipherText(
|
||||||
|
toUserId,
|
||||||
|
EncryptedContent()
|
||||||
|
..pushKeys = (EncryptedContent_PushKeys()
|
||||||
|
..type = EncryptedContent_PushKeys_Type.REQUEST),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// this will trigger a push notification
|
/// this will trigger a push notification
|
||||||
/// push notification only containing the message kind and username
|
/// push notification only containing the message kind and username
|
||||||
Future<Uint8List?> encryptPushNotification(
|
Future<Uint8List?> encryptPushNotification(
|
||||||
|
|
@ -326,15 +335,16 @@ Future<Uint8List?> encryptPushNotification(
|
||||||
// this will be enforced after every app uses this system... :/
|
// this will be enforced after every app uses this system... :/
|
||||||
// return null;
|
// return null;
|
||||||
Log.warn('Using insecure key as the receiver does not send a push key!');
|
Log.warn('Using insecure key as the receiver does not send a push key!');
|
||||||
|
await requestNewPushKeysForUser(toUserId);
|
||||||
await sendCipherText(
|
|
||||||
toUserId,
|
|
||||||
EncryptedContent()
|
|
||||||
..pushKeys = (EncryptedContent_PushKeys()
|
|
||||||
..type = EncryptedContent_PushKeys_Type.REQUEST),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
final createdAt = DateTime.fromMillisecondsSinceEpoch(
|
||||||
|
pushUser.pushKeys.last.createdAtUnixTimestamp.toInt(),
|
||||||
|
);
|
||||||
|
final timeBefore = DateTime.now().subtract(const Duration(days: 8));
|
||||||
|
if (createdAt.isBefore(timeBefore)) {
|
||||||
|
await requestNewPushKeysForUser(toUserId);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
key = pushUser.pushKeys.last.key;
|
key = pushUser.pushKeys.last.key;
|
||||||
keyId = pushUser.pushKeys.last.id.toInt();
|
keyId = pushUser.pushKeys.last.id.toInt();
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,9 @@ Future<String> readLast1000Lines() async {
|
||||||
Future<void> _writeLogToFile(LogRecord record) async {
|
Future<void> _writeLogToFile(LogRecord record) async {
|
||||||
final directory = await getApplicationSupportDirectory();
|
final directory = await getApplicationSupportDirectory();
|
||||||
final logFile = File('${directory.path}/app.log');
|
final logFile = File('${directory.path}/app.log');
|
||||||
|
if (!logFile.existsSync()) {
|
||||||
|
logFile.createSync(recursive: true);
|
||||||
|
}
|
||||||
|
|
||||||
// Prepare the log message
|
// Prepare the log message
|
||||||
final logMessage =
|
final logMessage =
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
import 'package:convert/convert.dart';
|
||||||
|
import 'package:crypto/crypto.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
||||||
|
|
@ -361,3 +363,18 @@ String getAvatarSvg(Uint8List avatarSvgCompressed) {
|
||||||
final raw = gzip.decode(avatarSvgCompressed);
|
final raw = gzip.decode(avatarSvgCompressed);
|
||||||
return utf8.decode(raw);
|
return utf8.decode(raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void printWrapped(String text) {
|
||||||
|
final pattern = RegExp('.{1,800}'); // 800 is the size of each chunk
|
||||||
|
// ignore: avoid_print
|
||||||
|
pattern.allMatches(text).forEach((match) => print(match.group(0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<int>> sha256File(File file) async {
|
||||||
|
final input = file.openRead();
|
||||||
|
final sha256Sink = AccumulatorSink<Digest>();
|
||||||
|
final converter = sha256.startChunkedConversion(sha256Sink);
|
||||||
|
await input.forEach(converter.add);
|
||||||
|
converter.close();
|
||||||
|
return sha256Sink.events.single.bytes;
|
||||||
|
}
|
||||||
|
|
|
||||||
104
lib/src/utils/screenshot.dart
Normal file
104
lib/src/utils/screenshot.dart
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'dart:ui' as io;
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
|
class ScreenshotImage {
|
||||||
|
ScreenshotImage({
|
||||||
|
this.image,
|
||||||
|
this.imageBytes,
|
||||||
|
this.imageBytesFuture,
|
||||||
|
this.file,
|
||||||
|
});
|
||||||
|
|
||||||
|
io.Image? image;
|
||||||
|
Uint8List? imageBytes;
|
||||||
|
Future<Uint8List>? imageBytesFuture;
|
||||||
|
File? file;
|
||||||
|
|
||||||
|
Future<Uint8List?> getBytes() async {
|
||||||
|
if (imageBytes != null) {
|
||||||
|
return imageBytes;
|
||||||
|
}
|
||||||
|
if (imageBytesFuture != null) {
|
||||||
|
return imageBytesFuture;
|
||||||
|
}
|
||||||
|
if (file != null) {
|
||||||
|
return file!.readAsBytes();
|
||||||
|
}
|
||||||
|
if (image == null) return null;
|
||||||
|
final img = await image!.toByteData(format: io.ImageByteFormat.png);
|
||||||
|
if (img == null) {
|
||||||
|
Log.error('Got no image');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return imageBytes = img.buffer.asUint8List();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScreenshotController {
|
||||||
|
ScreenshotController() {
|
||||||
|
_containerKey = GlobalKey();
|
||||||
|
}
|
||||||
|
late GlobalKey _containerKey;
|
||||||
|
|
||||||
|
Future<ScreenshotImage?> capture({double? pixelRatio}) async {
|
||||||
|
try {
|
||||||
|
final findRenderObject = _containerKey.currentContext?.findRenderObject();
|
||||||
|
if (findRenderObject == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final boundary = findRenderObject as RenderRepaintBoundary;
|
||||||
|
final context = _containerKey.currentContext;
|
||||||
|
var tmpPixelRatio = pixelRatio;
|
||||||
|
if (tmpPixelRatio == null) {
|
||||||
|
if (context != null && context.mounted) {
|
||||||
|
tmpPixelRatio =
|
||||||
|
tmpPixelRatio ?? MediaQuery.of(context).devicePixelRatio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final image = await boundary.toImage(pixelRatio: tmpPixelRatio ?? 1);
|
||||||
|
return ScreenshotImage(image: image);
|
||||||
|
} catch (e) {
|
||||||
|
Log.error(e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Screenshot extends StatefulWidget {
|
||||||
|
const Screenshot({
|
||||||
|
required this.child,
|
||||||
|
required this.controller,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
final Widget? child;
|
||||||
|
final ScreenshotController controller;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<Screenshot> createState() {
|
||||||
|
return ScreenshotState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScreenshotState extends State<Screenshot> with TickerProviderStateMixin {
|
||||||
|
late ScreenshotController _controller;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_controller = widget.controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return RepaintBoundary(
|
||||||
|
key: _controller._containerKey,
|
||||||
|
child: widget.child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,7 +7,7 @@ import 'package:provider/provider.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/constants/secure_storage_keys.dart';
|
import 'package:twonly/src/constants/secure_storage_keys.dart';
|
||||||
import 'package:twonly/src/model/json/userdata.dart';
|
import 'package:twonly/src/model/json/userdata.dart';
|
||||||
import 'package:twonly/src/providers/connection.provider.dart';
|
import 'package:twonly/src/providers/purchases.provider.dart';
|
||||||
import 'package:twonly/src/services/subscription.service.dart';
|
import 'package:twonly/src/services/subscription.service.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
|
|
@ -40,7 +40,7 @@ Future<void> updateUsersPlan(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
SubscriptionPlan plan,
|
SubscriptionPlan plan,
|
||||||
) async {
|
) async {
|
||||||
context.read<CustomChangeProvider>().plan = plan;
|
context.read<PurchasesProvider>().plan = plan;
|
||||||
|
|
||||||
await updateUserdata((user) {
|
await updateUserdata((user) {
|
||||||
user.subscriptionPlan = plan.name;
|
user.subscriptionPlan = plan.name;
|
||||||
|
|
@ -48,7 +48,7 @@ Future<void> updateUsersPlan(
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
await context.read<CustomChangeProvider>().updatePlan(plan);
|
context.read<PurchasesProvider>().updatePlan(plan);
|
||||||
}
|
}
|
||||||
|
|
||||||
Mutex updateProtection = Mutex();
|
Mutex updateProtection = Mutex();
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:camera/camera.dart';
|
import 'package:camera/camera.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:screenshot/screenshot.dart';
|
import 'package:twonly/src/utils/screenshot.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/components/media_view_sizing.dart';
|
import 'package:twonly/src/views/components/media_view_sizing.dart';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:camera/camera.dart';
|
import 'package:camera/camera.dart';
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
@ -19,6 +18,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/utils/qr.dart';
|
import 'package:twonly/src/utils/qr.dart';
|
||||||
|
import 'package:twonly/src/utils/screenshot.dart';
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/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/permissions_view.dart';
|
import 'package:twonly/src/views/camera/camera_preview_components/permissions_view.dart';
|
||||||
|
|
@ -28,6 +28,7 @@ import 'package:twonly/src/views/camera/camera_preview_components/zoom_selector.
|
||||||
import 'package:twonly/src/views/camera/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_view.dart';
|
import 'package:twonly/src/views/camera/share_image_editor_view.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/media_view_sizing.dart';
|
import 'package:twonly/src/views/components/media_view_sizing.dart';
|
||||||
import 'package:twonly/src/views/home.view.dart';
|
import 'package:twonly/src/views/home.view.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
@ -316,7 +317,6 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
|
|
||||||
Future<void> takePicture() async {
|
Future<void> takePicture() async {
|
||||||
if (_sharePreviewIsShown || _isVideoRecording) return;
|
if (_sharePreviewIsShown || _isVideoRecording) return;
|
||||||
late Future<Uint8List?> imageBytes;
|
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_sharePreviewIsShown = true;
|
_sharePreviewIsShown = true;
|
||||||
|
|
@ -337,18 +337,18 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Platform.isIOS) {
|
if (mc.cameraController?.value.flashMode != FlashMode.off) {
|
||||||
// android has a problem with this. Flash is turned off in the pausePreview function.
|
|
||||||
await mc.cameraController?.setFlashMode(FlashMode.off);
|
await mc.cameraController?.setFlashMode(FlashMode.off);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
imageBytes = mc.screenshotController
|
final image = await mc.screenshotController
|
||||||
.capture(pixelRatio: MediaQuery.of(context).devicePixelRatio);
|
.capture(pixelRatio: MediaQuery.of(context).devicePixelRatio);
|
||||||
|
|
||||||
if (await pushMediaEditor(imageBytes, null)) {
|
if (await pushMediaEditor(image, null)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
@ -357,7 +357,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> pushMediaEditor(
|
Future<bool> pushMediaEditor(
|
||||||
Future<Uint8List?>? imageBytes,
|
ScreenshotImage? imageBytes,
|
||||||
File? videoFilePath, {
|
File? videoFilePath, {
|
||||||
bool sharedFromGallery = false,
|
bool sharedFromGallery = false,
|
||||||
MediaType? mediaType,
|
MediaType? mediaType,
|
||||||
|
|
@ -397,6 +397,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
sharedFromGallery: sharedFromGallery,
|
sharedFromGallery: sharedFromGallery,
|
||||||
sendToGroup: widget.sendToGroup,
|
sendToGroup: widget.sendToGroup,
|
||||||
mediaFileService: mediaFileService,
|
mediaFileService: mediaFileService,
|
||||||
|
mainCameraController: mc,
|
||||||
),
|
),
|
||||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||||
return child;
|
return child;
|
||||||
|
|
@ -477,7 +478,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
Log.info('Picket from gallery: ${pickedFile.path}');
|
Log.info('Picket from gallery: ${pickedFile.path}');
|
||||||
|
|
||||||
File? videoFilePath;
|
File? videoFilePath;
|
||||||
Future<Uint8List>? imageBytes;
|
ScreenshotImage? image;
|
||||||
MediaType? mediaType;
|
MediaType? mediaType;
|
||||||
|
|
||||||
final isImage =
|
final isImage =
|
||||||
|
|
@ -486,13 +487,13 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
if (pickedFile.name.contains('.gif')) {
|
if (pickedFile.name.contains('.gif')) {
|
||||||
mediaType = MediaType.gif;
|
mediaType = MediaType.gif;
|
||||||
}
|
}
|
||||||
imageBytes = pickedFile.readAsBytes();
|
image = ScreenshotImage(imageBytesFuture: pickedFile.readAsBytes());
|
||||||
} else {
|
} else {
|
||||||
videoFilePath = File(pickedFile.path);
|
videoFilePath = File(pickedFile.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
await pushMediaEditor(
|
await pushMediaEditor(
|
||||||
imageBytes,
|
image,
|
||||||
videoFilePath,
|
videoFilePath,
|
||||||
sharedFromGallery: true,
|
sharedFromGallery: true,
|
||||||
mediaType: mediaType,
|
mediaType: mediaType,
|
||||||
|
|
@ -639,10 +640,10 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
if (_galleryLoadedImageIsShown)
|
if (_galleryLoadedImageIsShown)
|
||||||
Center(
|
Center(
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: 20,
|
height: 60,
|
||||||
height: 20,
|
width: 60,
|
||||||
child: CircularProgressIndicator(
|
child: ThreeRotatingDots(
|
||||||
strokeWidth: 1,
|
size: 40,
|
||||||
color: context.color.primary,
|
color: context.color.primary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -820,15 +821,12 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
...widget.mainCameraController.scannedNewProfiles.values
|
...widget.mainCameraController.scannedNewProfiles.values
|
||||||
.map(
|
.map(
|
||||||
(c) {
|
(c) {
|
||||||
|
if (c.isLoading) return Container();
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
if (c.isLoading) return;
|
|
||||||
c.isLoading = true;
|
c.isLoading = true;
|
||||||
widget.mainCameraController.setState();
|
widget.mainCameraController.setState();
|
||||||
await addNewContactFromPublicProfile(c.profile);
|
await addNewContactFromPublicProfile(c.profile);
|
||||||
widget.mainCameraController.scannedNewProfiles
|
|
||||||
.remove(c.profile.userId.toInt());
|
|
||||||
widget.mainCameraController.setState();
|
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,18 @@ import 'dart:io';
|
||||||
import 'package:camera/camera.dart';
|
import 'package:camera/camera.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:drift/drift.dart' show Value;
|
import 'package:drift/drift.dart' show Value;
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter/widgets.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:screenshot/screenshot.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/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/model/protobuf/client/generated/qr.pb.dart';
|
import 'package:twonly/src/model/protobuf/client/generated/qr.pb.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/qr.dart';
|
import 'package:twonly/src/utils/qr.dart';
|
||||||
|
import 'package:twonly/src/utils/screenshot.dart';
|
||||||
import 'package:twonly/src/views/camera/camera_preview_components/camera_preview_controller_view.dart';
|
import 'package:twonly/src/views/camera/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/painters/barcode_detector_painter.dart';
|
||||||
|
|
||||||
|
|
@ -52,8 +54,9 @@ class MainCameraController {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Log.warn(e);
|
Log.warn(e);
|
||||||
}
|
}
|
||||||
await cameraController?.dispose();
|
final cameraControllerTemp = cameraController;
|
||||||
cameraController = null;
|
cameraController = null;
|
||||||
|
await cameraControllerTemp?.dispose();
|
||||||
initCameraStarted = false;
|
initCameraStarted = false;
|
||||||
selectedCameraDetails = SelectedCameraDetails();
|
selectedCameraDetails = SelectedCameraDetails();
|
||||||
}
|
}
|
||||||
|
|
@ -217,11 +220,27 @@ class MainCameraController {
|
||||||
const ContactsCompanion(verified: Value(true)),
|
const ContactsCompanion(verified: Value(true)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
await HapticFeedback.heavyImpact();
|
||||||
|
if (verificationOk) {
|
||||||
|
globalRootScaffoldMessengerKey.currentState?.showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(
|
||||||
|
globalRootScaffoldMessengerKey.currentContext?.lang
|
||||||
|
.verifiedPublicKey(
|
||||||
|
getContactDisplayName(contact),
|
||||||
|
) ??
|
||||||
|
'',
|
||||||
|
),
|
||||||
|
duration: const Duration(seconds: 6),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (profile.username != gUser.username) {
|
if (profile.username != gUser.username) {
|
||||||
if (scannedNewProfiles[profile.userId.toInt()] == null) {
|
if (scannedNewProfiles[profile.userId.toInt()] == null) {
|
||||||
|
await HapticFeedback.heavyImpact();
|
||||||
scannedNewProfiles[profile.userId.toInt()] = ScannedNewProfile(
|
scannedNewProfiles[profile.userId.toInt()] = ScannedNewProfile(
|
||||||
profile: profile,
|
profile: profile,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
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';
|
||||||
|
|
@ -15,7 +16,7 @@ class SaveToGalleryButton extends StatefulWidget {
|
||||||
required this.mediaService,
|
required this.mediaService,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
final Future<bool> Function() storeImageAsOriginal;
|
final Future<Uint8List?> Function() storeImageAsOriginal;
|
||||||
final bool displayButtonLabel;
|
final bool displayButtonLabel;
|
||||||
final MediaFileService mediaService;
|
final MediaFileService mediaService;
|
||||||
final bool isLoading;
|
final bool isLoading;
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import 'package:twonly/src/views/camera/image_editor/data/image_item.dart';
|
||||||
/// Layer class with some common properties
|
/// Layer class with some common properties
|
||||||
class Layer {
|
class Layer {
|
||||||
Layer({
|
Layer({
|
||||||
|
required this.key,
|
||||||
this.offset = Offset.zero,
|
this.offset = Offset.zero,
|
||||||
this.opacity = 1,
|
this.opacity = 1,
|
||||||
this.isEditing = false,
|
this.isEditing = false,
|
||||||
|
|
@ -16,6 +17,7 @@ class Layer {
|
||||||
this.rotation = 0,
|
this.rotation = 0,
|
||||||
this.scale = 1,
|
this.scale = 1,
|
||||||
});
|
});
|
||||||
|
Key key;
|
||||||
Offset offset;
|
Offset offset;
|
||||||
double rotation;
|
double rotation;
|
||||||
double scale;
|
double scale;
|
||||||
|
|
@ -29,18 +31,25 @@ class Layer {
|
||||||
/// Attributes used by [BackgroundLayer]
|
/// Attributes used by [BackgroundLayer]
|
||||||
class BackgroundLayerData extends Layer {
|
class BackgroundLayerData extends Layer {
|
||||||
BackgroundLayerData({
|
BackgroundLayerData({
|
||||||
|
required super.key,
|
||||||
required this.image,
|
required this.image,
|
||||||
});
|
});
|
||||||
ImageItem image;
|
ImageItem image;
|
||||||
|
bool imageLoaded = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
class FilterLayerData extends Layer {
|
class FilterLayerData extends Layer {
|
||||||
|
FilterLayerData({
|
||||||
|
required super.key,
|
||||||
|
this.page = 1,
|
||||||
|
});
|
||||||
int page = 1;
|
int page = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attributes used by [EmojiLayer]
|
/// Attributes used by [EmojiLayer]
|
||||||
class EmojiLayerData extends Layer {
|
class EmojiLayerData extends Layer {
|
||||||
EmojiLayerData({
|
EmojiLayerData({
|
||||||
|
required super.key,
|
||||||
this.text = '',
|
this.text = '',
|
||||||
this.size = 64,
|
this.size = 64,
|
||||||
super.offset,
|
super.offset,
|
||||||
|
|
@ -56,6 +65,7 @@ class EmojiLayerData extends Layer {
|
||||||
/// Attributes used by [TextLayer]
|
/// Attributes used by [TextLayer]
|
||||||
class TextLayerData extends Layer {
|
class TextLayerData extends Layer {
|
||||||
TextLayerData({
|
TextLayerData({
|
||||||
|
required super.key,
|
||||||
required this.textLayersBefore,
|
required this.textLayersBefore,
|
||||||
this.text = '',
|
this.text = '',
|
||||||
super.offset,
|
super.offset,
|
||||||
|
|
@ -72,6 +82,7 @@ class TextLayerData extends Layer {
|
||||||
class DrawLayerData extends Layer {
|
class DrawLayerData extends Layer {
|
||||||
// String text;
|
// String text;
|
||||||
DrawLayerData({
|
DrawLayerData({
|
||||||
|
required super.key,
|
||||||
super.offset,
|
super.offset,
|
||||||
super.opacity,
|
super.opacity,
|
||||||
super.rotation,
|
super.rotation,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
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/image_editor/data/layer.dart';
|
||||||
|
|
||||||
/// Main layer
|
|
||||||
class BackgroundLayer extends StatefulWidget {
|
class BackgroundLayer extends StatefulWidget {
|
||||||
const BackgroundLayer({
|
const BackgroundLayer({
|
||||||
required this.layerData,
|
required this.layerData,
|
||||||
|
|
@ -23,7 +22,17 @@ class _BackgroundLayerState extends State<BackgroundLayer> {
|
||||||
height: widget.layerData.image.height.toDouble(),
|
height: widget.layerData.image.height.toDouble(),
|
||||||
// color: Theme.of(context).colorScheme.surface,
|
// color: Theme.of(context).colorScheme.surface,
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
child: Image.memory(widget.layerData.image.bytes),
|
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;
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:hand_signature/signature.dart';
|
import 'package:hand_signature/signature.dart';
|
||||||
// ignore: implementation_imports
|
// ignore: implementation_imports
|
||||||
import 'package:hand_signature/src/utils.dart';
|
import 'package:hand_signature/src/utils.dart';
|
||||||
import 'package:screenshot/screenshot.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/image_editor/action_button.dart';
|
import 'package:twonly/src/views/camera/image_editor/action_button.dart';
|
||||||
import 'package:twonly/src/views/camera/image_editor/data/layer.dart';
|
import 'package:twonly/src/views/camera/image_editor/data/layer.dart';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -119,6 +119,11 @@ class _EmojiLayerState extends State<EmojiLayer> {
|
||||||
setState(() {
|
setState(() {
|
||||||
twoPointerWhereDown = details.pointerCount >= 2;
|
twoPointerWhereDown = details.pointerCount >= 2;
|
||||||
widget.layerData.size = initialScale * details.scale;
|
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 =
|
widget.layerData.rotation =
|
||||||
initialRotation + details.rotation;
|
initialRotation + details.rotation;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,12 +23,14 @@ class LayersViewer extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
...layers.whereType<BackgroundLayerData>().map((layerItem) {
|
...layers.whereType<BackgroundLayerData>().map((layerItem) {
|
||||||
return BackgroundLayer(
|
return BackgroundLayer(
|
||||||
|
key: layerItem.key,
|
||||||
layerData: layerItem,
|
layerData: layerItem,
|
||||||
onUpdate: onUpdate,
|
onUpdate: onUpdate,
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
...layers.whereType<FilterLayerData>().map((layerItem) {
|
...layers.whereType<FilterLayerData>().map((layerItem) {
|
||||||
return FilterLayer(
|
return FilterLayer(
|
||||||
|
key: layerItem.key,
|
||||||
layerData: layerItem,
|
layerData: layerItem,
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
|
@ -40,12 +42,13 @@ class LayersViewer extends StatelessWidget {
|
||||||
.map((layerItem) {
|
.map((layerItem) {
|
||||||
if (layerItem is EmojiLayerData) {
|
if (layerItem is EmojiLayerData) {
|
||||||
return EmojiLayer(
|
return EmojiLayer(
|
||||||
key: GlobalKey(),
|
key: layerItem.key,
|
||||||
layerData: layerItem,
|
layerData: layerItem,
|
||||||
onUpdate: onUpdate,
|
onUpdate: onUpdate,
|
||||||
);
|
);
|
||||||
} else if (layerItem is DrawLayerData) {
|
} else if (layerItem is DrawLayerData) {
|
||||||
return DrawLayer(
|
return DrawLayer(
|
||||||
|
key: layerItem.key,
|
||||||
layerData: layerItem,
|
layerData: layerItem,
|
||||||
onUpdate: onUpdate,
|
onUpdate: onUpdate,
|
||||||
);
|
);
|
||||||
|
|
@ -54,7 +57,7 @@ class LayersViewer extends StatelessWidget {
|
||||||
}),
|
}),
|
||||||
...layers.whereType<TextLayerData>().map((layerItem) {
|
...layers.whereType<TextLayerData>().map((layerItem) {
|
||||||
return TextLayer(
|
return TextLayer(
|
||||||
// key: GlobalKey(),
|
key: layerItem.key,
|
||||||
layerData: layerItem,
|
layerData: layerItem,
|
||||||
onUpdate: onUpdate,
|
onUpdate: onUpdate,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ class EmojiPickerBottom extends StatelessWidget {
|
||||||
Navigator.pop(
|
Navigator.pop(
|
||||||
context,
|
context,
|
||||||
EmojiLayerData(
|
EmojiLayerData(
|
||||||
|
key: GlobalKey(),
|
||||||
text: emoji.emoji,
|
text: emoji.emoji,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,10 @@
|
||||||
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/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.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:screenshot/screenshot.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';
|
||||||
|
|
@ -15,7 +15,9 @@ 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';
|
||||||
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/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/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/image_editor/action_button.dart';
|
||||||
import 'package:twonly/src/views/camera/image_editor/data/image_item.dart';
|
import 'package:twonly/src/views/camera/image_editor/data/image_item.dart';
|
||||||
|
|
@ -38,11 +40,13 @@ class ShareImageEditorView extends StatefulWidget {
|
||||||
super.key,
|
super.key,
|
||||||
this.imageBytesFuture,
|
this.imageBytesFuture,
|
||||||
this.sendToGroup,
|
this.sendToGroup,
|
||||||
|
this.mainCameraController,
|
||||||
});
|
});
|
||||||
final Future<Uint8List?>? imageBytesFuture;
|
final ScreenshotImage? imageBytesFuture;
|
||||||
final Group? sendToGroup;
|
final Group? sendToGroup;
|
||||||
final bool sharedFromGallery;
|
final bool sharedFromGallery;
|
||||||
final MediaFileService mediaFileService;
|
final MediaFileService mediaFileService;
|
||||||
|
final MainCameraController? mainCameraController;
|
||||||
@override
|
@override
|
||||||
State<ShareImageEditorView> createState() => _ShareImageEditorView();
|
State<ShareImageEditorView> createState() => _ShareImageEditorView();
|
||||||
}
|
}
|
||||||
|
|
@ -60,6 +64,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
VideoPlayerController? videoController;
|
VideoPlayerController? videoController;
|
||||||
ImageItem currentImage = ImageItem();
|
ImageItem currentImage = ImageItem();
|
||||||
ScreenshotController screenshotController = ScreenshotController();
|
ScreenshotController screenshotController = ScreenshotController();
|
||||||
|
Timer? _imageLoadingTimer;
|
||||||
|
|
||||||
MediaFileService get mediaService => widget.mediaFileService;
|
MediaFileService get mediaService => widget.mediaFileService;
|
||||||
MediaFile get media => widget.mediaFileService.mediaFile;
|
MediaFile get media => widget.mediaFileService.mediaFile;
|
||||||
|
|
@ -69,7 +74,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
if (media.type != MediaType.gif) {
|
if (media.type != MediaType.gif) {
|
||||||
layers.add(FilterLayerData());
|
layers.add(FilterLayerData(key: GlobalKey()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (widget.sendToGroup != null) {
|
if (widget.sendToGroup != null) {
|
||||||
|
|
@ -82,9 +87,11 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
loadImage(widget.imageBytesFuture!);
|
loadImage(widget.imageBytesFuture!);
|
||||||
} else {
|
} else {
|
||||||
if (widget.mediaFileService.tempPath.existsSync()) {
|
if (widget.mediaFileService.tempPath.existsSync()) {
|
||||||
loadImage(widget.mediaFileService.tempPath.readAsBytes());
|
loadImage(ScreenshotImage(file: widget.mediaFileService.tempPath));
|
||||||
} else if (widget.mediaFileService.originalPath.existsSync()) {
|
} else if (widget.mediaFileService.originalPath.existsSync()) {
|
||||||
loadImage(widget.mediaFileService.originalPath.readAsBytes());
|
loadImage(
|
||||||
|
ScreenshotImage(file: widget.mediaFileService.originalPath),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -114,6 +121,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
isDraftMedia: Value(false),
|
isDraftMedia: Value(false),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
_imageLoadingTimer?.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -129,6 +137,82 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _setMaxShowTime(int? maxShowTime) async {
|
||||||
|
await mediaService.setDisplayLimit(maxShowTime);
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() {});
|
||||||
|
await updateUserdata((user) {
|
||||||
|
user.defaultShowTime = maxShowTime;
|
||||||
|
return user;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _setImageDisplayTime() async {
|
||||||
|
if (media.type == MediaType.video) {
|
||||||
|
await mediaService.setDisplayLimit(
|
||||||
|
(media.displayLimitInMilliseconds == null) ? 0 : null,
|
||||||
|
);
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final options = [
|
||||||
|
1000,
|
||||||
|
2000,
|
||||||
|
3000,
|
||||||
|
4000,
|
||||||
|
5000,
|
||||||
|
6000,
|
||||||
|
7000,
|
||||||
|
8000,
|
||||||
|
9000,
|
||||||
|
10000,
|
||||||
|
15000,
|
||||||
|
20000,
|
||||||
|
null,
|
||||||
|
];
|
||||||
|
|
||||||
|
var initialItem = options.length - 1;
|
||||||
|
if (media.displayLimitInMilliseconds != null) {
|
||||||
|
initialItem = options.indexOf(media.displayLimitInMilliseconds);
|
||||||
|
if (initialItem == -1) {
|
||||||
|
initialItem = options.length - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await showCupertinoModalPopup<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) => Container(
|
||||||
|
height: 350,
|
||||||
|
padding: const EdgeInsets.only(top: 6),
|
||||||
|
margin:
|
||||||
|
EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
|
||||||
|
color: CupertinoColors.systemBackground.resolveFrom(context),
|
||||||
|
child: SafeArea(
|
||||||
|
top: false,
|
||||||
|
child: CupertinoPicker(
|
||||||
|
magnification: 1.22,
|
||||||
|
squeeze: 1.2,
|
||||||
|
useMagnifier: true,
|
||||||
|
itemExtent: 32,
|
||||||
|
scrollController: FixedExtentScrollController(
|
||||||
|
initialItem: initialItem,
|
||||||
|
),
|
||||||
|
onSelectedItemChanged: (int selectedItem) {
|
||||||
|
_setMaxShowTime(options[selectedItem]);
|
||||||
|
},
|
||||||
|
children: options.map((e) {
|
||||||
|
return Center(
|
||||||
|
child: Text(e == null ? '∞' : '${e ~/ 1000}s'),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
List<Widget> get actionsAtTheRight {
|
List<Widget> get actionsAtTheRight {
|
||||||
if (layers.isNotEmpty &&
|
if (layers.isNotEmpty &&
|
||||||
layers.last.isEditing &&
|
layers.last.isEditing &&
|
||||||
|
|
@ -147,6 +231,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
removedLayers.clear();
|
removedLayers.clear();
|
||||||
layers.add(
|
layers.add(
|
||||||
TextLayerData(
|
TextLayerData(
|
||||||
|
key: GlobalKey(),
|
||||||
textLayersBefore: layers.whereType<TextLayerData>().length,
|
textLayersBefore: layers.whereType<TextLayerData>().length,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -161,7 +246,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
undoLayers.clear();
|
undoLayers.clear();
|
||||||
removedLayers.clear();
|
removedLayers.clear();
|
||||||
layers.add(DrawLayerData());
|
layers.add(DrawLayerData(key: GlobalKey()));
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
@ -199,33 +284,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
: Icons.repeat_one_rounded
|
: Icons.repeat_one_rounded
|
||||||
: Icons.timer_outlined,
|
: Icons.timer_outlined,
|
||||||
tooltipText: context.lang.protectAsARealTwonly,
|
tooltipText: context.lang.protectAsARealTwonly,
|
||||||
onPressed: () async {
|
onPressed: _setImageDisplayTime,
|
||||||
if (media.type == MediaType.video) {
|
|
||||||
await mediaService.setDisplayLimit(
|
|
||||||
(media.displayLimitInMilliseconds == null) ? 0 : null,
|
|
||||||
);
|
|
||||||
if (!mounted) return;
|
|
||||||
setState(() {});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int? maxShowTime;
|
|
||||||
if (media.displayLimitInMilliseconds == null) {
|
|
||||||
maxShowTime = 1000;
|
|
||||||
} else if (media.displayLimitInMilliseconds == 1000) {
|
|
||||||
maxShowTime = 5000;
|
|
||||||
} else if (media.displayLimitInMilliseconds == 5000) {
|
|
||||||
maxShowTime = 12000;
|
|
||||||
} else if (media.displayLimitInMilliseconds == 12000) {
|
|
||||||
maxShowTime = 20000;
|
|
||||||
}
|
|
||||||
await mediaService.setDisplayLimit(maxShowTime);
|
|
||||||
if (!mounted) return;
|
|
||||||
setState(() {});
|
|
||||||
await updateUserdata((user) {
|
|
||||||
user.defaultShowTime = maxShowTime;
|
|
||||||
return user;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (media.type == MediaType.video)
|
if (media.type == MediaType.video)
|
||||||
|
|
@ -380,11 +439,11 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Uint8List?> 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;
|
final image = (layers.first as BackgroundLayerData).image.bytes;
|
||||||
return image;
|
return ScreenshotImage(imageBytes: image);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -392,6 +451,10 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
x.showCustomButtons = false;
|
x.showCustomButtons = false;
|
||||||
}
|
}
|
||||||
setState(() {});
|
setState(() {});
|
||||||
|
|
||||||
|
// Make a short delay, so the setState does have its effect...
|
||||||
|
await Future.delayed(const Duration(milliseconds: 10));
|
||||||
|
|
||||||
final image = await screenshotController.capture(
|
final image = await screenshotController.capture(
|
||||||
pixelRatio: pixelRatio,
|
pixelRatio: pixelRatio,
|
||||||
);
|
);
|
||||||
|
|
@ -409,22 +472,33 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> storeImageAsOriginal() async {
|
Future<Uint8List?> storeImageAsOriginal() async {
|
||||||
if (mediaService.overlayImagePath.existsSync()) {
|
if (mediaService.overlayImagePath.existsSync()) {
|
||||||
mediaService.overlayImagePath.deleteSync();
|
mediaService.overlayImagePath.deleteSync();
|
||||||
}
|
}
|
||||||
if (mediaService.tempPath.existsSync()) {
|
if (mediaService.tempPath.existsSync()) {
|
||||||
mediaService.tempPath.deleteSync();
|
mediaService.tempPath.deleteSync();
|
||||||
}
|
}
|
||||||
|
if (mediaService.originalPath.existsSync()) {
|
||||||
|
if (media.type != MediaType.video) {
|
||||||
|
mediaService.originalPath.deleteSync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var bytes = imageBytes;
|
||||||
if (media.type == MediaType.gif) {
|
if (media.type == MediaType.gif) {
|
||||||
mediaService.originalPath.writeAsBytesSync(imageBytes!.toList());
|
mediaService.originalPath.writeAsBytesSync(imageBytes!.toList());
|
||||||
} else {
|
} else {
|
||||||
final imageBytes = await getEditedImageBytes();
|
final image = await getEditedImageBytes();
|
||||||
if (imageBytes == null) return false;
|
if (image == null) return null;
|
||||||
|
bytes = await image.getBytes();
|
||||||
|
if (bytes == null) {
|
||||||
|
Log.error('imageBytes are empty');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
if (media.type == MediaType.image || media.type == MediaType.gif) {
|
if (media.type == MediaType.image || media.type == MediaType.gif) {
|
||||||
mediaService.originalPath.writeAsBytesSync(imageBytes);
|
mediaService.originalPath.writeAsBytesSync(bytes);
|
||||||
} else if (media.type == MediaType.video) {
|
} else if (media.type == MediaType.video) {
|
||||||
mediaService.overlayImagePath.writeAsBytesSync(imageBytes);
|
mediaService.overlayImagePath.writeAsBytesSync(bytes);
|
||||||
} else {
|
} else {
|
||||||
Log.error('MediaType not supported: ${media.type}');
|
Log.error('MediaType not supported: ${media.type}');
|
||||||
}
|
}
|
||||||
|
|
@ -444,12 +518,11 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
.renameSync(MediaFileService(mediaFile).storedPath.path);
|
.renameSync(MediaFileService(mediaFile).storedPath.path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadImage(Future<Uint8List?> imageBytesFuture) async {
|
Future<void> loadImage(ScreenshotImage imageBytesFuture) async {
|
||||||
imageBytes = await imageBytesFuture;
|
imageBytes = await imageBytesFuture.getBytes();
|
||||||
|
|
||||||
// store this image so it can be used as a draft in case the app is restarted
|
// 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());
|
||||||
|
|
||||||
|
|
@ -458,17 +531,42 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
|
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
|
|
||||||
|
Future.delayed(const Duration(milliseconds: 500), () async {
|
||||||
|
if (context.mounted) {
|
||||||
|
await widget.mainCameraController?.closeCamera();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setState(() {
|
||||||
layers.insert(
|
layers.insert(
|
||||||
0,
|
0,
|
||||||
BackgroundLayerData(
|
BackgroundLayerData(
|
||||||
|
key: GlobalKey(),
|
||||||
image: currentImage,
|
image: currentImage,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
// It is important that the user can sending the image only when the image is fully loaded otherwise if the user
|
||||||
|
// will click on send before the image is painted the screenshot will be transparent..
|
||||||
|
_imageLoadingTimer =
|
||||||
|
Timer.periodic(const Duration(milliseconds: 10), (timer) {
|
||||||
|
final imageLayer = layers.first;
|
||||||
|
if (imageLayer is BackgroundLayerData) {
|
||||||
|
if (imageLayer.imageLoaded) {
|
||||||
|
timer.cancel();
|
||||||
|
Future.delayed(const Duration(milliseconds: 50), () {
|
||||||
|
Log.info(imageLayer.imageLoaded);
|
||||||
|
if (context.mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
sendingOrLoadingImage = false;
|
sendingOrLoadingImage = false;
|
||||||
loadingImage = false;
|
loadingImage = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> sendImageToSinglePerson() async {
|
Future<void> sendImageToSinglePerson() async {
|
||||||
if (sendingOrLoadingImage) return;
|
if (sendingOrLoadingImage) return;
|
||||||
|
|
@ -476,18 +574,18 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
sendingOrLoadingImage = true;
|
sendingOrLoadingImage = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
await storeImageAsOriginal();
|
|
||||||
|
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
|
|
||||||
|
// must be awaited so the widget for the screenshot is not already disposed when sending..
|
||||||
|
await storeImageAsOriginal();
|
||||||
|
|
||||||
// Insert media file into the messages database and start uploading process in the background
|
// Insert media file into the messages database and start uploading process in the background
|
||||||
await insertMediaFileInMessagesTable(
|
await insertMediaFileInMessagesTable(
|
||||||
mediaService,
|
mediaService,
|
||||||
[widget.sendToGroup!.groupId],
|
[widget.sendToGroup!.groupId],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (context.mounted) {
|
if (mounted) {
|
||||||
// ignore: use_build_context_synchronously
|
|
||||||
Navigator.pop(context, true);
|
Navigator.pop(context, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -526,6 +624,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
removedLayers.clear();
|
removedLayers.clear();
|
||||||
layers.add(
|
layers.add(
|
||||||
TextLayerData(
|
TextLayerData(
|
||||||
|
key: GlobalKey(),
|
||||||
offset: Offset(0, tabDownPosition),
|
offset: Offset(0, tabDownPosition),
|
||||||
textLayersBefore: layers.whereType<TextLayerData>().length,
|
textLayersBefore: layers.whereType<TextLayerData>().length,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,13 @@
|
||||||
|
|
||||||
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/daos/groups.dao.dart';
|
import 'package:twonly/src/database/daos/groups.dao.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/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';
|
||||||
|
|
@ -26,7 +28,7 @@ class ShareImageView extends StatefulWidget {
|
||||||
});
|
});
|
||||||
final HashSet<String> selectedGroupIds;
|
final HashSet<String> selectedGroupIds;
|
||||||
final void Function(String, bool) updateSelectedGroupIds;
|
final void Function(String, bool) updateSelectedGroupIds;
|
||||||
final Future<bool>? mediaStoreFuture;
|
final Future<Uint8List?>? mediaStoreFuture;
|
||||||
final MediaFileService mediaFileService;
|
final MediaFileService mediaFileService;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -41,6 +43,7 @@ class _ShareImageView extends State<ShareImageView> {
|
||||||
|
|
||||||
bool sendingImage = false;
|
bool sendingImage = false;
|
||||||
bool mediaStoreFutureReady = false;
|
bool mediaStoreFutureReady = false;
|
||||||
|
Uint8List? _imageBytes;
|
||||||
bool hideArchivedUsers = true;
|
bool hideArchivedUsers = true;
|
||||||
final TextEditingController searchUserName = TextEditingController();
|
final TextEditingController searchUserName = TextEditingController();
|
||||||
late StreamSubscription<List<Group>> allGroupSub;
|
late StreamSubscription<List<Group>> allGroupSub;
|
||||||
|
|
@ -63,10 +66,9 @@ class _ShareImageView extends State<ShareImageView> {
|
||||||
|
|
||||||
Future<void> initAsync() async {
|
Future<void> initAsync() async {
|
||||||
if (widget.mediaStoreFuture != null) {
|
if (widget.mediaStoreFuture != null) {
|
||||||
await widget.mediaStoreFuture;
|
_imageBytes = await widget.mediaStoreFuture;
|
||||||
}
|
}
|
||||||
mediaStoreFutureReady = true;
|
mediaStoreFutureReady = true;
|
||||||
// unawaited(startBackgroundMediaUpload(widget.mediaFileService));
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
@ -235,12 +237,31 @@ class _ShareImageView extends State<ShareImageView> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
floatingActionButton: SizedBox(
|
floatingActionButton: SizedBox(
|
||||||
height: 120,
|
height: 168,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
padding: const EdgeInsets.only(bottom: 20, right: 20),
|
||||||
child: Row(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
|
if (widget.mediaFileService.mediaFile.type == MediaType.image &&
|
||||||
|
_imageBytes != null &&
|
||||||
|
gUser.showShowImagePreviewWhenSending)
|
||||||
|
SizedBox(
|
||||||
|
height: 100,
|
||||||
|
child: Container(
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border:
|
||||||
|
Border.all(color: context.color.primary, width: 2),
|
||||||
|
color: context.color.primary,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
child: Image.memory(_imageBytes!),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
FilledButton.icon(
|
FilledButton.icon(
|
||||||
icon: !mediaStoreFutureReady || sendingImage
|
icon: !mediaStoreFutureReady || sendingImage
|
||||||
? SizedBox(
|
? SizedBox(
|
||||||
|
|
@ -282,13 +303,13 @@ class _ShareImageView extends State<ShareImageView> {
|
||||||
const EdgeInsets.symmetric(vertical: 10, horizontal: 30),
|
const EdgeInsets.symmetric(vertical: 10, horizontal: 30),
|
||||||
),
|
),
|
||||||
backgroundColor: WidgetStateProperty.all<Color>(
|
backgroundColor: WidgetStateProperty.all<Color>(
|
||||||
mediaStoreFutureReady || widget.selectedGroupIds.isEmpty
|
!mediaStoreFutureReady || widget.selectedGroupIds.isEmpty
|
||||||
? Theme.of(context).colorScheme.secondary
|
? context.color.onSurface
|
||||||
: Theme.of(context).colorScheme.primary,
|
: context.color.primary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
label: Text(
|
label: Text(
|
||||||
context.lang.shareImagedEditorSendImage,
|
'${context.lang.shareImagedEditorSendImage} (${widget.selectedGroupIds.length})',
|
||||||
style: const TextStyle(fontSize: 17),
|
style: const TextStyle(fontSize: 17),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -169,7 +169,7 @@ class _SearchUsernameView extends State<AddNewUserView> {
|
||||||
},
|
},
|
||||||
inputFormatters: [
|
inputFormatters: [
|
||||||
LengthLimitingTextInputFormatter(12),
|
LengthLimitingTextInputFormatter(12),
|
||||||
FilteringTextInputFormatter.allow(RegExp('[a-z0-9A-Z]')),
|
FilteringTextInputFormatter.allow(RegExp('[a-z0-9A-Z._]')),
|
||||||
],
|
],
|
||||||
controller: searchUserName,
|
controller: searchUserName,
|
||||||
decoration:
|
decoration:
|
||||||
|
|
@ -284,7 +284,7 @@ class ContactsListView extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
await notifyContactsAboutProfileChange();
|
await sendContactMyProfileData(contact.userId);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import 'package:provider/provider.dart';
|
||||||
import 'package:twonly/globals.dart';
|
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/providers/connection.provider.dart';
|
import 'package:twonly/src/providers/connection.provider.dart';
|
||||||
|
import 'package:twonly/src/providers/purchases.provider.dart';
|
||||||
import 'package:twonly/src/services/subscription.service.dart';
|
import 'package:twonly/src/services/subscription.service.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';
|
||||||
|
|
@ -93,7 +94,7 @@ class _ChatListViewState extends State<ChatListView> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final isConnected = context.watch<CustomChangeProvider>().isConnected;
|
final isConnected = context.watch<CustomChangeProvider>().isConnected;
|
||||||
final plan = context.watch<CustomChangeProvider>().plan;
|
final plan = context.watch<PurchasesProvider>().plan;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Row(
|
title: Row(
|
||||||
|
|
@ -203,7 +204,7 @@ class _ChatListViewState extends State<ChatListView> {
|
||||||
child: RefreshIndicator(
|
child: RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
await apiService.close(() {});
|
await apiService.close(() {});
|
||||||
await apiService.connect(force: true);
|
await apiService.connect();
|
||||||
await Future.delayed(const Duration(seconds: 1));
|
await Future.delayed(const Duration(seconds: 1));
|
||||||
},
|
},
|
||||||
child: (_groupsNotPinned.isEmpty &&
|
child: (_groupsNotPinned.isEmpty &&
|
||||||
|
|
|
||||||
|
|
@ -159,7 +159,7 @@ class _UserListItem extends State<GroupListItem> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> onTap() async {
|
Future<void> onTap() async {
|
||||||
if (_currentMessage == null) {
|
if (_currentMessage == null && widget.group.totalMediaCounter == 0) {
|
||||||
await Navigator.push(
|
await Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import 'package:twonly/src/views/chats/chat_messages_components/entries/chat_dat
|
||||||
import 'package:twonly/src/views/chats/chat_messages_components/message_input.dart';
|
import 'package:twonly/src/views/chats/chat_messages_components/message_input.dart';
|
||||||
import 'package:twonly/src/views/chats/chat_messages_components/response_container.dart';
|
import 'package:twonly/src/views/chats/chat_messages_components/response_container.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/blink.component.dart';
|
||||||
import 'package:twonly/src/views/components/flame.dart';
|
import 'package:twonly/src/views/components/flame.dart';
|
||||||
import 'package:twonly/src/views/components/verified_shield.dart';
|
import 'package:twonly/src/views/components/verified_shield.dart';
|
||||||
import 'package:twonly/src/views/contact/contact.view.dart';
|
import 'package:twonly/src/views/contact/contact.view.dart';
|
||||||
|
|
@ -372,22 +373,12 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
final chatMessage = messages[i].message!;
|
final chatMessage = messages[i].message!;
|
||||||
return Transform.translate(
|
return BlinkWidget(
|
||||||
offset: Offset(
|
enabled: focusedScrollItem == i,
|
||||||
(focusedScrollItem == i)
|
|
||||||
? (chatMessage.senderId == null)
|
|
||||||
? -8
|
|
||||||
: 8
|
|
||||||
: 0,
|
|
||||||
0,
|
|
||||||
),
|
|
||||||
child: Transform.scale(
|
|
||||||
scale: (focusedScrollItem == i) ? 1.05 : 1,
|
|
||||||
child: ChatListEntry(
|
child: ChatListEntry(
|
||||||
key: Key(chatMessage.messageId),
|
key: Key(chatMessage.messageId),
|
||||||
message: messages[i].message!,
|
message: messages[i].message!,
|
||||||
nextMessage:
|
nextMessage: (i > 0) ? messages[i - 1].message : null,
|
||||||
(i > 0) ? messages[i - 1].message : null,
|
|
||||||
prevMessage: ((i + 1) < messages.length)
|
prevMessage: ((i + 1) < messages.length)
|
||||||
? messages[i + 1].message
|
? messages[i + 1].message
|
||||||
: null,
|
: null,
|
||||||
|
|
@ -402,7 +393,6 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
||||||
textFieldFocus.requestFocus();
|
textFieldFocus.requestFocus();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,7 @@ class _MessageInputState extends State<MessageInput> {
|
||||||
}
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
_recordingState = RecordingState.recording;
|
_recordingState = RecordingState.recording;
|
||||||
|
_currentDuration = 0;
|
||||||
});
|
});
|
||||||
await HapticFeedback.heavyImpact();
|
await HapticFeedback.heavyImpact();
|
||||||
final audioTmpPath =
|
final audioTmpPath =
|
||||||
|
|
@ -220,8 +221,43 @@ class _MessageInputState extends State<MessageInput> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: (_recordingState == RecordingState.recording)
|
child: Stack(
|
||||||
? Row(
|
children: [
|
||||||
|
TextField(
|
||||||
|
controller: _textFieldController,
|
||||||
|
focusNode: widget.textFieldFocus,
|
||||||
|
keyboardType: TextInputType.multiline,
|
||||||
|
showCursor:
|
||||||
|
_recordingState != RecordingState.recording,
|
||||||
|
maxLines: 4,
|
||||||
|
minLines: 1,
|
||||||
|
onChanged: (value) async {
|
||||||
|
setState(() {});
|
||||||
|
await twonlyDB.groupsDao.updateGroup(
|
||||||
|
widget.group.groupId,
|
||||||
|
GroupsCompanion(
|
||||||
|
draftMessage:
|
||||||
|
Value(_textFieldController.text),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onSubmitted: (_) {
|
||||||
|
_sendMessage();
|
||||||
|
},
|
||||||
|
style: const TextStyle(fontSize: 17),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: context.lang.chatListDetailInput,
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
border: InputBorder.none,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (_recordingState == RecordingState.recording)
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: context.color.surfaceContainer,
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
const Padding(
|
const Padding(
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
|
|
@ -241,8 +277,10 @@ class _MessageInputState extends State<MessageInput> {
|
||||||
formatMsToMinSec(
|
formatMsToMinSec(
|
||||||
_currentDuration,
|
_currentDuration,
|
||||||
),
|
),
|
||||||
style: const TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white,
|
color: isDarkMode(context)
|
||||||
|
? Colors.white
|
||||||
|
: Colors.black,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -269,32 +307,9 @@ class _MessageInputState extends State<MessageInput> {
|
||||||
const SizedBox(width: 20),
|
const SizedBox(width: 20),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
)
|
|
||||||
: TextField(
|
|
||||||
controller: _textFieldController,
|
|
||||||
focusNode: widget.textFieldFocus,
|
|
||||||
keyboardType: TextInputType.multiline,
|
|
||||||
maxLines: 4,
|
|
||||||
minLines: 1,
|
|
||||||
onChanged: (value) async {
|
|
||||||
setState(() {});
|
|
||||||
await twonlyDB.groupsDao.updateGroup(
|
|
||||||
widget.group.groupId,
|
|
||||||
GroupsCompanion(
|
|
||||||
draftMessage:
|
|
||||||
Value(_textFieldController.text),
|
|
||||||
),
|
),
|
||||||
);
|
|
||||||
},
|
|
||||||
onSubmitted: (_) {
|
|
||||||
_sendMessage();
|
|
||||||
},
|
|
||||||
style: const TextStyle(fontSize: 17),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: context.lang.chatListDetailInput,
|
|
||||||
contentPadding: EdgeInsets.zero,
|
|
||||||
border: InputBorder.none,
|
|
||||||
),
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (_textFieldController.text == '')
|
if (_textFieldController.text == '')
|
||||||
|
|
@ -355,7 +370,9 @@ class _MessageInputState extends State<MessageInput> {
|
||||||
height: 60,
|
height: 60,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(90),
|
borderRadius: BorderRadius.circular(90),
|
||||||
color: Colors.black,
|
color: isDarkMode(context)
|
||||||
|
? Colors.black
|
||||||
|
: Colors.white,
|
||||||
),
|
),
|
||||||
child: const Center(
|
child: const Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|
|
||||||
|
|
@ -174,7 +174,8 @@ class _MessageSendStateIconState extends State<MessageSendStateIcon> {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (mediaFile.uploadState == UploadState.preprocessing) {
|
if (mediaFile.uploadState == UploadState.preprocessing ||
|
||||||
|
mediaFile.uploadState == UploadState.initialized) {
|
||||||
text = context.lang.inProcess;
|
text = context.lang.inProcess;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
75
lib/src/views/components/blink.component.dart
Normal file
75
lib/src/views/components/blink.component.dart
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/scheduler.dart';
|
||||||
|
|
||||||
|
class BlinkWidget extends StatefulWidget {
|
||||||
|
const BlinkWidget({
|
||||||
|
required this.child,
|
||||||
|
required this.enabled,
|
||||||
|
super.key,
|
||||||
|
this.blinkDuration = const Duration(milliseconds: 2500),
|
||||||
|
this.interval = const Duration(milliseconds: 250),
|
||||||
|
this.visibleOpacity = 1.0,
|
||||||
|
this.hiddenOpacity = 0.4,
|
||||||
|
});
|
||||||
|
final bool enabled;
|
||||||
|
final Widget child;
|
||||||
|
final Duration blinkDuration;
|
||||||
|
final Duration interval;
|
||||||
|
final double visibleOpacity;
|
||||||
|
final double hiddenOpacity;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<BlinkWidget> createState() => _BlinkWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BlinkWidgetState extends State<BlinkWidget>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
late Ticker _ticker;
|
||||||
|
bool _visible = true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_ticker = createTicker(_onTick);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(covariant BlinkWidget oldWidget) {
|
||||||
|
if (oldWidget.enabled != widget.enabled) {
|
||||||
|
if (widget.enabled) {
|
||||||
|
_ticker
|
||||||
|
..stop()
|
||||||
|
..start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onTick(Duration elapsed) {
|
||||||
|
var visible = true;
|
||||||
|
if (elapsed.inMilliseconds < widget.blinkDuration.inMilliseconds) {
|
||||||
|
visible = elapsed.inMilliseconds % (widget.interval.inMilliseconds * 2) <
|
||||||
|
widget.interval.inMilliseconds;
|
||||||
|
} else {
|
||||||
|
_ticker.stop();
|
||||||
|
}
|
||||||
|
setState(() => _visible = visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_ticker.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AnimatedOpacity(
|
||||||
|
opacity: _visible ? widget.visibleOpacity : widget.hiddenOpacity,
|
||||||
|
duration: Duration(
|
||||||
|
milliseconds: widget.blinkDuration.inMilliseconds ~/ 3,
|
||||||
|
),
|
||||||
|
child: widget.child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,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/flame.service.dart';
|
import 'package:twonly/src/services/flame.service.dart';
|
||||||
import 'package:twonly/src/services/subscription.service.dart';
|
import 'package:twonly/src/services/subscription.service.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/components/animate_icon.dart';
|
import 'package:twonly/src/views/components/animate_icon.dart';
|
||||||
import 'package:twonly/src/views/components/better_list_title.dart';
|
import 'package:twonly/src/views/components/better_list_title.dart';
|
||||||
|
|
@ -71,6 +72,9 @@ class _MaxFlameListTitleState extends State<MaxFlameListTitle> {
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Log.info(
|
||||||
|
'Restoring flames from ${_directChat!.flameCounter} to ${_directChat!.maxFlameCounter}',
|
||||||
|
);
|
||||||
await twonlyDB.groupsDao.updateGroup(
|
await twonlyDB.groupsDao.updateGroup(
|
||||||
_groupId,
|
_groupId,
|
||||||
GroupsCompanion(
|
GroupsCompanion(
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,9 @@ class _MediaViewSizingState extends State<MediaViewSizing> {
|
||||||
|
|
||||||
final aspectRatioWidth = availableWidth;
|
final aspectRatioWidth = availableWidth;
|
||||||
final aspectRatioHeight = (aspectRatioWidth * 16) / 9;
|
final aspectRatioHeight = (aspectRatioWidth * 16) / 9;
|
||||||
|
if (aspectRatioHeight > availableHeight) {
|
||||||
|
needToDownSizeImage = true;
|
||||||
|
}
|
||||||
if (widget.requiredHeight != null) {
|
if (widget.requiredHeight != null) {
|
||||||
if (aspectRatioHeight < availableHeight) {
|
if (aspectRatioHeight < availableHeight) {
|
||||||
if ((screenSize.height - widget.requiredHeight!) < aspectRatioHeight) {
|
if ((screenSize.height - widget.requiredHeight!) < aspectRatioHeight) {
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,22 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'package:app_links/app_links.dart';
|
import 'package:app_links/app_links.dart';
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:drift/drift.dart' show Value;
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
|
import 'package:flutter_sharing_intent/flutter_sharing_intent.dart';
|
||||||
|
import 'package:flutter_sharing_intent/model/sharing_file.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/twonly.db.dart';
|
import 'package:twonly/src/services/intent/links.intent.dart';
|
||||||
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||||
import 'package:twonly/src/services/notifications/setup.notifications.dart';
|
import 'package:twonly/src/services/notifications/setup.notifications.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/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/add_new_user.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/components/alert_dialog.dart';
|
|
||||||
import 'package:twonly/src/views/contact/contact.view.dart';
|
|
||||||
import 'package:twonly/src/views/memories/memories.view.dart';
|
import 'package:twonly/src/views/memories/memories.view.dart';
|
||||||
import 'package:twonly/src/views/public_profile.view.dart';
|
|
||||||
|
|
||||||
void Function(int) globalUpdateOfHomeViewPageIndex = (a) {};
|
void Function(int) globalUpdateOfHomeViewPageIndex = (a) {};
|
||||||
|
|
||||||
|
|
@ -61,6 +54,7 @@ class HomeViewState extends State<HomeView> {
|
||||||
final MainCameraController _mainCameraController = MainCameraController();
|
final MainCameraController _mainCameraController = MainCameraController();
|
||||||
|
|
||||||
final PageController homeViewPageController = PageController(initialPage: 1);
|
final PageController homeViewPageController = PageController(initialPage: 1);
|
||||||
|
late StreamSubscription<List<SharedFile>> _intentStreamSub;
|
||||||
late StreamSubscription<Uri> _deepLinkSub;
|
late StreamSubscription<Uri> _deepLinkSub;
|
||||||
|
|
||||||
double buttonDiameter = 100;
|
double buttonDiameter = 100;
|
||||||
|
|
@ -121,99 +115,21 @@ class HomeViewState extends State<HomeView> {
|
||||||
|
|
||||||
// Subscribe to all events (initial link and further)
|
// Subscribe to all events (initial link and further)
|
||||||
_deepLinkSub = AppLinks().uriLinkStream.listen((uri) async {
|
_deepLinkSub = AppLinks().uriLinkStream.listen((uri) async {
|
||||||
if (!uri.scheme.startsWith('http')) return;
|
if (mounted) await handleIntentUrl(context, uri);
|
||||||
if (uri.host != 'me.twonly.eu') return;
|
});
|
||||||
if (uri.hasEmptyPath) return;
|
|
||||||
|
|
||||||
final publicKey = uri.hasFragment ? uri.fragment : null;
|
_intentStreamSub = FlutterSharingIntent.instance.getMediaStream().listen(
|
||||||
final userPaths = uri.path.split('/');
|
(f) {
|
||||||
if (userPaths.length != 2) return;
|
if (mounted) handleIntentSharedFile(context, f);
|
||||||
final username = userPaths[1];
|
},
|
||||||
|
// ignore: inference_failure_on_untyped_parameter
|
||||||
if (!mounted) return;
|
onError: (err) {
|
||||||
|
Log.error('getIntentDataStream error: $err');
|
||||||
if (username == gUser.username) {
|
|
||||||
await Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) {
|
|
||||||
return const PublicProfileView();
|
|
||||||
},
|
},
|
||||||
),
|
|
||||||
);
|
);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.info(
|
FlutterSharingIntent.instance.getInitialSharing().then((f) {
|
||||||
'Opened via deep link!: username = $username public_key = ${uri.fragment}',
|
if (mounted) handleIntentSharedFile(context, f);
|
||||||
);
|
|
||||||
final contacts =
|
|
||||||
await twonlyDB.contactsDao.getContactsByUsername(username);
|
|
||||||
if (contacts.isEmpty) {
|
|
||||||
if (!mounted) return;
|
|
||||||
Uint8List? publicKeyBytes;
|
|
||||||
if (publicKey != null) {
|
|
||||||
publicKeyBytes = base64Url.decode(publicKey);
|
|
||||||
}
|
|
||||||
await Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) {
|
|
||||||
return AddNewUserView(
|
|
||||||
username: username,
|
|
||||||
publicKey: publicKeyBytes,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else if (publicKey != null) {
|
|
||||||
try {
|
|
||||||
final contact = contacts.first;
|
|
||||||
final storedPublicKey = await getPublicKeyFromContact(contact.userId);
|
|
||||||
final receivedPublicKey = base64Url.decode(publicKey);
|
|
||||||
if (storedPublicKey == null ||
|
|
||||||
receivedPublicKey.isEmpty ||
|
|
||||||
!mounted) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (storedPublicKey.equals(receivedPublicKey)) {
|
|
||||||
if (!contact.verified) {
|
|
||||||
final markAsVerified = await showAlertDialog(
|
|
||||||
context,
|
|
||||||
context.lang.linkFromUsername(contact.username),
|
|
||||||
context.lang.linkFromUsernameLong,
|
|
||||||
customOk: context.lang.gotLinkFromFriend,
|
|
||||||
);
|
|
||||||
if (markAsVerified) {
|
|
||||||
await twonlyDB.contactsDao.updateContact(
|
|
||||||
contact.userId,
|
|
||||||
const ContactsCompanion(
|
|
||||||
verified: Value(true),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) {
|
|
||||||
return ContactView(contact.userId);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await showAlertDialog(
|
|
||||||
context,
|
|
||||||
context.lang.couldNotVerifyUsername(contact.username),
|
|
||||||
context.lang.linkPubkeyDoesNotMatch,
|
|
||||||
customCancel: '',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
Log.warn(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -222,6 +138,7 @@ class HomeViewState extends State<HomeView> {
|
||||||
unawaited(selectNotificationStream.close());
|
unawaited(selectNotificationStream.close());
|
||||||
disableCameraTimer?.cancel();
|
disableCameraTimer?.cancel();
|
||||||
_mainCameraController.closeCamera();
|
_mainCameraController.closeCamera();
|
||||||
|
_intentStreamSub.cancel();
|
||||||
_deepLinkSub.cancel();
|
_deepLinkSub.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
@ -230,11 +147,11 @@ class HomeViewState extends State<HomeView> {
|
||||||
final notificationAppLaunchDetails =
|
final notificationAppLaunchDetails =
|
||||||
await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails();
|
await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails();
|
||||||
|
|
||||||
if (notificationAppLaunchDetails != null) {
|
if (widget.initialPage == 0 ||
|
||||||
if (notificationAppLaunchDetails.didNotificationLaunchApp) {
|
(notificationAppLaunchDetails != null &&
|
||||||
|
notificationAppLaunchDetails.didNotificationLaunchApp)) {
|
||||||
globalUpdateOfHomeViewPageIndex(0);
|
globalUpdateOfHomeViewPageIndex(0);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
final draftMedia = await twonlyDB.mediaFilesDao.getDraftMediaFile();
|
final draftMedia = await twonlyDB.mediaFilesDao.getDraftMediaFile();
|
||||||
if (draftMedia != null) {
|
if (draftMedia != null) {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/model/memory_item.model.dart';
|
import 'package:twonly/src/model/memory_item.model.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/components/loader.dart';
|
||||||
import 'package:twonly/src/views/memories/memories_item_thumbnail.dart';
|
import 'package:twonly/src/views/memories/memories_item_thumbnail.dart';
|
||||||
import 'package:twonly/src/views/memories/memories_photo_slider.view.dart';
|
import 'package:twonly/src/views/memories/memories_photo_slider.view.dart';
|
||||||
|
|
||||||
|
|
@ -18,12 +19,14 @@ class MemoriesView extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class MemoriesViewState extends State<MemoriesView> {
|
class MemoriesViewState extends State<MemoriesView> {
|
||||||
bool verticalGallery = false;
|
int _filesToMigrate = 0;
|
||||||
List<MemoryItem> galleryItems = [];
|
List<MemoryItem> galleryItems = [];
|
||||||
Map<String, List<int>> orderedByMonth = {};
|
Map<String, List<int>> orderedByMonth = {};
|
||||||
List<String> months = [];
|
List<String> months = [];
|
||||||
StreamSubscription<List<MediaFile>>? messageSub;
|
StreamSubscription<List<MediaFile>>? messageSub;
|
||||||
|
|
||||||
|
final Map<int, List<MemoryItem>> _galleryItemsLastYears = {};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
@ -37,6 +40,21 @@ class MemoriesViewState extends State<MemoriesView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initAsync() async {
|
Future<void> initAsync() async {
|
||||||
|
final nonHashedFiles =
|
||||||
|
await twonlyDB.mediaFilesDao.getAllNonHashedStoredMediaFiles();
|
||||||
|
if (nonHashedFiles.isNotEmpty) {
|
||||||
|
setState(() {
|
||||||
|
_filesToMigrate = nonHashedFiles.length;
|
||||||
|
});
|
||||||
|
for (final mediaFile in nonHashedFiles) {
|
||||||
|
final mediaService = MediaFileService(mediaFile);
|
||||||
|
await mediaService.hashStoredMedia();
|
||||||
|
setState(() {
|
||||||
|
_filesToMigrate -= 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_filesToMigrate = 0;
|
||||||
|
}
|
||||||
await messageSub?.cancel();
|
await messageSub?.cancel();
|
||||||
final msgStream = twonlyDB.mediaFilesDao.watchAllStoredMediaFiles();
|
final msgStream = twonlyDB.mediaFilesDao.watchAllStoredMediaFiles();
|
||||||
|
|
||||||
|
|
@ -46,6 +64,9 @@ class MemoriesViewState extends State<MemoriesView> {
|
||||||
months = [];
|
months = [];
|
||||||
var lastMonth = '';
|
var lastMonth = '';
|
||||||
galleryItems = [];
|
galleryItems = [];
|
||||||
|
|
||||||
|
final now = DateTime.now();
|
||||||
|
|
||||||
for (final mediaFile in mediaFiles) {
|
for (final mediaFile in mediaFiles) {
|
||||||
final mediaService = MediaFileService(mediaFile);
|
final mediaService = MediaFileService(mediaFile);
|
||||||
if (!mediaService.imagePreviewAvailable) continue;
|
if (!mediaService.imagePreviewAvailable) continue;
|
||||||
|
|
@ -54,12 +75,21 @@ class MemoriesViewState extends State<MemoriesView> {
|
||||||
await mediaService.createThumbnail();
|
await mediaService.createThumbnail();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
galleryItems.add(
|
final item = MemoryItem(
|
||||||
MemoryItem(
|
|
||||||
mediaService: mediaService,
|
mediaService: mediaService,
|
||||||
messages: [],
|
messages: [],
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
galleryItems.add(item);
|
||||||
|
if (mediaFile.createdAt.month == now.month &&
|
||||||
|
mediaFile.createdAt.day == now.day) {
|
||||||
|
final diff = now.year - mediaFile.createdAt.year;
|
||||||
|
if (diff > 0) {
|
||||||
|
if (!_galleryItemsLastYears.containsKey(diff)) {
|
||||||
|
_galleryItemsLastYears[diff] = [];
|
||||||
|
}
|
||||||
|
_galleryItemsLastYears[diff]!.add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
galleryItems.sort(
|
galleryItems.sort(
|
||||||
(a, b) => b.mediaService.mediaFile.createdAt.compareTo(
|
(a, b) => b.mediaService.mediaFile.createdAt.compareTo(
|
||||||
|
|
@ -83,19 +113,106 @@ class MemoriesViewState extends State<MemoriesView> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
Widget child = Center(
|
||||||
appBar: AppBar(title: const Text('Memories')),
|
|
||||||
body: Scrollbar(
|
|
||||||
child: (galleryItems.isEmpty)
|
|
||||||
? Center(
|
|
||||||
child: Text(
|
child: Text(
|
||||||
context.lang.memoriesEmpty,
|
context.lang.memoriesEmpty,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
)
|
);
|
||||||
: ListView.builder(
|
if (_filesToMigrate > 0) {
|
||||||
itemCount: months.length * 2,
|
child = Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
ThreeRotatingDots(
|
||||||
|
size: 40,
|
||||||
|
color: context.color.primary,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Text(
|
||||||
|
context.lang.migrationOfMemories(_filesToMigrate),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (galleryItems.isNotEmpty) {
|
||||||
|
child = ListView.builder(
|
||||||
|
itemCount:
|
||||||
|
(months.length * 2) + (_galleryItemsLastYears.isEmpty ? 0 : 1),
|
||||||
itemBuilder: (context, mIndex) {
|
itemBuilder: (context, mIndex) {
|
||||||
|
if (_galleryItemsLastYears.isNotEmpty && mIndex == 0) {
|
||||||
|
return SizedBox(
|
||||||
|
height: 140,
|
||||||
|
width: MediaQuery.sizeOf(context).width,
|
||||||
|
child: ListView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
children: _galleryItemsLastYears.entries.map(
|
||||||
|
(item) {
|
||||||
|
var text = context.lang.memoriesAYearAgo;
|
||||||
|
if (item.key > 1) {
|
||||||
|
text = context.lang.memoriesXYearsAgo(item.key);
|
||||||
|
}
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () async {
|
||||||
|
await open(context, item.value, 0);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
boxShadow: const [
|
||||||
|
BoxShadow(
|
||||||
|
spreadRadius: -12,
|
||||||
|
blurRadius: 12,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
height: 150,
|
||||||
|
width: 120,
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Positioned.fill(
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
child: Image.file(
|
||||||
|
item.value.first.mediaService.storedPath,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
bottom: 10,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
child: Text(
|
||||||
|
text,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 20,
|
||||||
|
shadows: [
|
||||||
|
Shadow(
|
||||||
|
color: Color.fromARGB(122, 0, 0, 0),
|
||||||
|
blurRadius: 5,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (_galleryItemsLastYears.isNotEmpty) {
|
||||||
|
mIndex -= 1;
|
||||||
|
}
|
||||||
if (mIndex.isEven) {
|
if (mIndex.isEven) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
|
|
@ -106,8 +223,7 @@ class MemoriesViewState extends State<MemoriesView> {
|
||||||
return GridView.builder(
|
return GridView.builder(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
physics: const ClampingScrollPhysics(),
|
physics: const ClampingScrollPhysics(),
|
||||||
gridDelegate:
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
const SliverGridDelegateWithFixedCrossAxisCount(
|
|
||||||
crossAxisCount: 4,
|
crossAxisCount: 4,
|
||||||
childAspectRatio: 9 / 16,
|
childAspectRatio: 9 / 16,
|
||||||
),
|
),
|
||||||
|
|
@ -117,18 +233,28 @@ class MemoriesViewState extends State<MemoriesView> {
|
||||||
return MemoriesItemThumbnail(
|
return MemoriesItemThumbnail(
|
||||||
galleryItem: galleryItems[gaIndex],
|
galleryItem: galleryItems[gaIndex],
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await open(context, gaIndex);
|
await open(context, galleryItems, gaIndex);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: const Text('Memories')),
|
||||||
|
body: Scrollbar(
|
||||||
|
child: child,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> open(BuildContext context, int index) async {
|
Future<void> open(
|
||||||
|
BuildContext context,
|
||||||
|
List<MemoryItem> galleryItems,
|
||||||
|
int index,
|
||||||
|
) async {
|
||||||
await Navigator.push(
|
await Navigator.push(
|
||||||
context,
|
context,
|
||||||
PageRouteBuilder(
|
PageRouteBuilder(
|
||||||
|
|
@ -136,15 +262,9 @@ class MemoriesViewState extends State<MemoriesView> {
|
||||||
pageBuilder: (context, a1, a2) => MemoriesPhotoSliderView(
|
pageBuilder: (context, a1, a2) => MemoriesPhotoSliderView(
|
||||||
galleryItems: galleryItems,
|
galleryItems: galleryItems,
|
||||||
initialIndex: index,
|
initialIndex: index,
|
||||||
scrollDirection: verticalGallery ? Axis.vertical : Axis.horizontal,
|
|
||||||
),
|
),
|
||||||
// transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
|
||||||
// return child;
|
|
||||||
// },
|
|
||||||
// transitionDuration: Duration.zero,
|
|
||||||
// reverseTransitionDuration: Duration.zero,
|
|
||||||
),
|
),
|
||||||
) as bool?;
|
) as bool?;
|
||||||
setState(() {});
|
if (mounted) setState(() {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,7 @@ class _MemoriesPhotoSliderViewState extends State<MemoriesPhotoSliderView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
orgMediaService.storedPath
|
orgMediaService.storedPath
|
||||||
.copySync(newMediaService.tempPath.path);
|
.copySync(newMediaService.originalPath.path);
|
||||||
|
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,19 +53,19 @@ class OnboardingView extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
PageViewModel(
|
// PageViewModel(
|
||||||
title: context.lang.onboardingSendTwonliesTitle,
|
// title: context.lang.onboardingSendTwonliesTitle,
|
||||||
body: context.lang.onboardingSendTwonliesBody,
|
// body: context.lang.onboardingSendTwonliesBody,
|
||||||
image: Center(
|
// image: Center(
|
||||||
child: Padding(
|
// child: Padding(
|
||||||
padding: const EdgeInsets.only(top: 100),
|
// padding: const EdgeInsets.only(top: 100),
|
||||||
child: Lottie.asset(
|
// child: Lottie.asset(
|
||||||
'assets/animations/twonlies.json',
|
// 'assets/animations/twonlies.json',
|
||||||
repeat: false,
|
// repeat: false,
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
PageViewModel(
|
PageViewModel(
|
||||||
title: context.lang.onboardingNotProductTitle,
|
title: context.lang.onboardingNotProductTitle,
|
||||||
bodyWidget: Column(
|
bodyWidget: Column(
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ class _RegisterViewState extends State<RegisterView> {
|
||||||
bool _isTryingToRegister = false;
|
bool _isTryingToRegister = false;
|
||||||
bool _isValidUserName = false;
|
bool _isValidUserName = false;
|
||||||
bool _showUserNameError = false;
|
bool _showUserNameError = false;
|
||||||
|
bool _showProofOfWorkError = false;
|
||||||
|
|
||||||
late Future<int>? proofOfWork;
|
late Future<int>? proofOfWork;
|
||||||
|
|
||||||
|
|
@ -63,6 +64,7 @@ class _RegisterViewState extends State<RegisterView> {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isTryingToRegister = true;
|
_isTryingToRegister = true;
|
||||||
_showUserNameError = false;
|
_showUserNameError = false;
|
||||||
|
_showProofOfWorkError = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
late int proof;
|
late int proof;
|
||||||
|
|
@ -93,6 +95,7 @@ class _RegisterViewState extends State<RegisterView> {
|
||||||
Log.info('Got user_id ${res.value} from server');
|
Log.info('Got user_id ${res.value} from server');
|
||||||
userId = res.value.userid.toInt() as int;
|
userId = res.value.userid.toInt() as int;
|
||||||
} else {
|
} else {
|
||||||
|
proofOfWork = null;
|
||||||
if (res.error == ErrorCode.RegistrationDisabled) {
|
if (res.error == ErrorCode.RegistrationDisabled) {
|
||||||
_registrationDisabled = true;
|
_registrationDisabled = true;
|
||||||
return;
|
return;
|
||||||
|
|
@ -103,9 +106,12 @@ class _RegisterViewState extends State<RegisterView> {
|
||||||
return createNewUser();
|
return createNewUser();
|
||||||
}
|
}
|
||||||
if (res.error == ErrorCode.InvalidProofOfWork) {
|
if (res.error == ErrorCode.InvalidProofOfWork) {
|
||||||
Log.error('Proof of Work is invalid. Try again.');
|
|
||||||
await deleteLocalUserData();
|
await deleteLocalUserData();
|
||||||
return createNewUser();
|
setState(() {
|
||||||
|
_showProofOfWorkError = true;
|
||||||
|
_isTryingToRegister = false;
|
||||||
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
@ -232,7 +238,7 @@ class _RegisterViewState extends State<RegisterView> {
|
||||||
},
|
},
|
||||||
inputFormatters: [
|
inputFormatters: [
|
||||||
LengthLimitingTextInputFormatter(12),
|
LengthLimitingTextInputFormatter(12),
|
||||||
FilteringTextInputFormatter.allow(RegExp('[a-z0-9A-Z]')),
|
FilteringTextInputFormatter.allow(RegExp('[a-z0-9A-Z._]')),
|
||||||
],
|
],
|
||||||
style: const TextStyle(fontSize: 17),
|
style: const TextStyle(fontSize: 17),
|
||||||
decoration: getInputDecoration(
|
decoration: getInputDecoration(
|
||||||
|
|
@ -248,7 +254,17 @@ class _RegisterViewState extends State<RegisterView> {
|
||||||
),
|
),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 30),
|
const SizedBox(height: 10),
|
||||||
|
Text(
|
||||||
|
context.lang.registerProofOfWorkFailed,
|
||||||
|
style: TextStyle(
|
||||||
|
color:
|
||||||
|
_showProofOfWorkError ? Colors.red : Colors.transparent,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
FilledButton.icon(
|
FilledButton.icon(
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
@ -11,7 +10,6 @@ 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/components/alert_dialog.dart';
|
import 'package:twonly/src/views/components/alert_dialog.dart';
|
||||||
import 'package:twonly/src/views/settings/account/refund_credits.view.dart';
|
import 'package:twonly/src/views/settings/account/refund_credits.view.dart';
|
||||||
import 'package:twonly/src/views/settings/subscription/subscription.view.dart';
|
|
||||||
|
|
||||||
class AccountView extends StatefulWidget {
|
class AccountView extends StatefulWidget {
|
||||||
const AccountView({super.key});
|
const AccountView({super.key});
|
||||||
|
|
@ -31,7 +29,7 @@ class _AccountViewState extends State<AccountView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initAsync() async {
|
Future<void> initAsync() async {
|
||||||
final ballance = await loadPlanBalance(useCache: false);
|
final ballance = await apiService.loadPlanBalance(useCache: false);
|
||||||
if (ballance == null || !mounted) return;
|
if (ballance == null || !mounted) return;
|
||||||
var ballanceInCents = ballance.transactions
|
var ballanceInCents = ballance.transactions
|
||||||
.where(
|
.where(
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
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/src/views/settings/subscription/voucher.view.dart';
|
import 'package:twonly/src/views/settings/subscription_custom/voucher.view.dart';
|
||||||
// import 'package:url_launcher/url_launcher.dart';
|
// import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
class RefundCreditsView extends StatefulWidget {
|
class RefundCreditsView extends StatefulWidget {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/providers/settings.provider.dart';
|
import 'package:twonly/src/providers/settings.provider.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';
|
||||||
|
|
@ -15,20 +16,9 @@ class AppearanceView extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppearanceViewState extends State<AppearanceView> {
|
class _AppearanceViewState extends State<AppearanceView> {
|
||||||
bool showFeedbackShortcut = false;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
unawaited(initAsync());
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> initAsync() async {
|
|
||||||
final user = await getUser();
|
|
||||||
if (user == null) return;
|
|
||||||
setState(() {
|
|
||||||
showFeedbackShortcut = user.showFeedbackShortcut;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _showSelectThemeMode(BuildContext context) async {
|
Future<void> _showSelectThemeMode(BuildContext context) async {
|
||||||
|
|
@ -87,7 +77,29 @@ class _AppearanceViewState extends State<AppearanceView> {
|
||||||
u.showFeedbackShortcut = !u.showFeedbackShortcut;
|
u.showFeedbackShortcut = !u.showFeedbackShortcut;
|
||||||
return u;
|
return u;
|
||||||
});
|
});
|
||||||
await initAsync();
|
setState(() {
|
||||||
|
// gUser
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> toggleStartWithCameraOpen() async {
|
||||||
|
await updateUserdata((u) {
|
||||||
|
u.startWithCameraOpen = !u.startWithCameraOpen;
|
||||||
|
return u;
|
||||||
|
});
|
||||||
|
setState(() {
|
||||||
|
// gUser
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> toggleShowImagePreviewWhenSending() async {
|
||||||
|
await updateUserdata((u) {
|
||||||
|
u.showShowImagePreviewWhenSending = !u.showShowImagePreviewWhenSending;
|
||||||
|
return u;
|
||||||
|
});
|
||||||
|
setState(() {
|
||||||
|
// gUser
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -113,10 +125,26 @@ class _AppearanceViewState extends State<AppearanceView> {
|
||||||
title: Text(context.lang.contactUsShortcut),
|
title: Text(context.lang.contactUsShortcut),
|
||||||
onTap: toggleShowFeedbackIcon,
|
onTap: toggleShowFeedbackIcon,
|
||||||
trailing: Switch(
|
trailing: Switch(
|
||||||
value: !showFeedbackShortcut,
|
value: !gUser.showFeedbackShortcut,
|
||||||
onChanged: (a) => toggleShowFeedbackIcon(),
|
onChanged: (a) => toggleShowFeedbackIcon(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text(context.lang.startWithCameraOpen),
|
||||||
|
onTap: toggleStartWithCameraOpen,
|
||||||
|
trailing: Switch(
|
||||||
|
value: gUser.startWithCameraOpen,
|
||||||
|
onChanged: (a) => toggleStartWithCameraOpen(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text(context.lang.showImagePreviewWhenSending),
|
||||||
|
onTap: toggleShowImagePreviewWhenSending,
|
||||||
|
trailing: Switch(
|
||||||
|
value: gUser.showShowImagePreviewWhenSending,
|
||||||
|
onChanged: (a) => toggleShowImagePreviewWhenSending(),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,15 @@ class _DataAndStorageViewState extends State<DataAndStorageView> {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> toggleAutoStoreMediaFiles() async {
|
||||||
|
await updateUserdata((u) {
|
||||||
|
u.autoStoreAllSendUnlimitedMediaFiles =
|
||||||
|
!u.autoStoreAllSendUnlimitedMediaFiles;
|
||||||
|
return u;
|
||||||
|
});
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final autoDownloadOptions =
|
final autoDownloadOptions =
|
||||||
|
|
@ -65,6 +74,18 @@ class _DataAndStorageViewState extends State<DataAndStorageView> {
|
||||||
onChanged: (a) => toggleStoreInGallery(),
|
onChanged: (a) => toggleStoreInGallery(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text(context.lang.autoStoreAllSendUnlimitedMediaFiles),
|
||||||
|
subtitle: Text(
|
||||||
|
context.lang.autoStoreAllSendUnlimitedMediaFilesSubtitle,
|
||||||
|
style: const TextStyle(fontSize: 9),
|
||||||
|
),
|
||||||
|
onTap: toggleAutoStoreMediaFiles,
|
||||||
|
trailing: Switch(
|
||||||
|
value: gUser.autoStoreAllSendUnlimitedMediaFiles,
|
||||||
|
onChanged: (a) => toggleAutoStoreMediaFiles(),
|
||||||
|
),
|
||||||
|
),
|
||||||
if (Platform.isAndroid)
|
if (Platform.isAndroid)
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import 'package:avatar_maker/avatar_maker.dart';
|
||||||
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/services/api/messages.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';
|
||||||
|
|
||||||
|
|
@ -31,7 +30,6 @@ class _ModifyAvatarState extends State<ModifyAvatar> {
|
||||||
..avatarCounter = user.avatarCounter + 1;
|
..avatarCounter = user.avatarCounter + 1;
|
||||||
return user;
|
return user;
|
||||||
});
|
});
|
||||||
await notifyContactsAboutProfileChange();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AvatarMakerThemeData getAvatarMakerTheme(BuildContext context) {
|
AvatarMakerThemeData getAvatarMakerTheme(BuildContext context) {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ 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/model/protobuf/api/websocket/error.pb.dart';
|
import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart';
|
||||||
import 'package:twonly/src/services/api/messages.dart';
|
|
||||||
import 'package:twonly/src/services/twonly_safe/common.twonly_safe.dart';
|
import 'package:twonly/src/services/twonly_safe/common.twonly_safe.dart';
|
||||||
import 'package:twonly/src/services/twonly_safe/create_backup.twonly_safe.dart';
|
import 'package:twonly/src/services/twonly_safe/create_backup.twonly_safe.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
@ -52,7 +51,6 @@ class _ProfileViewState extends State<ProfileView> {
|
||||||
..avatarCounter = user.avatarCounter + 1;
|
..avatarCounter = user.avatarCounter + 1;
|
||||||
return user;
|
return user;
|
||||||
});
|
});
|
||||||
await notifyContactsAboutProfileChange();
|
|
||||||
setState(() {}); // gUser has updated
|
setState(() {}); // gUser has updated
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -86,7 +84,6 @@ class _ProfileViewState extends State<ProfileView> {
|
||||||
..avatarCounter = user.avatarCounter + 1;
|
..avatarCounter = user.avatarCounter + 1;
|
||||||
return user;
|
return user;
|
||||||
});
|
});
|
||||||
await notifyContactsAboutProfileChange();
|
|
||||||
setState(() {}); // gUser has updated
|
setState(() {}); // gUser has updated
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -146,7 +143,7 @@ class _ProfileViewState extends State<ProfileView> {
|
||||||
maxLength: 12,
|
maxLength: 12,
|
||||||
inputFormatters: [
|
inputFormatters: [
|
||||||
LengthLimitingTextInputFormatter(12),
|
LengthLimitingTextInputFormatter(12),
|
||||||
FilteringTextInputFormatter.allow(RegExp('[a-z0-9A-Z]')),
|
FilteringTextInputFormatter.allow(RegExp('[a-z0-9A-Z._]')),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
if (context.mounted && username != null && username != '') {
|
if (context.mounted && username != null && username != '') {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.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/better_list_title.dart';
|
import 'package:twonly/src/views/components/better_list_title.dart';
|
||||||
|
import 'package:twonly/src/views/public_profile.view.dart';
|
||||||
import 'package:twonly/src/views/settings/account.view.dart';
|
import 'package:twonly/src/views/settings/account.view.dart';
|
||||||
import 'package:twonly/src/views/settings/appearance.view.dart';
|
import 'package:twonly/src/views/settings/appearance.view.dart';
|
||||||
import 'package:twonly/src/views/settings/backup/backup.view.dart';
|
import 'package:twonly/src/views/settings/backup/backup.view.dart';
|
||||||
|
|
@ -82,13 +83,22 @@ class _SettingsMainViewState extends State<SettingsMainView> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Align(
|
Align(
|
||||||
// alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
// child: IconButton(
|
child: IconButton(
|
||||||
// onPressed: () {},
|
onPressed: () {
|
||||||
// icon: FaIcon(FontAwesomeIcons.qrcode),
|
Navigator.push(
|
||||||
// ),
|
context,
|
||||||
// )
|
MaterialPageRoute(
|
||||||
|
builder: (context) {
|
||||||
|
return const PublicProfileView();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: const FaIcon(FontAwesomeIcons.qrcode),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ 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/components/alert_dialog.dart';
|
import 'package:twonly/src/views/components/alert_dialog.dart';
|
||||||
import 'package:twonly/src/views/settings/subscription/subscription.view.dart';
|
import 'package:twonly/src/views/settings/subscription_custom/subscription.view.dart';
|
||||||
|
|
||||||
Future<List<Response_AddAccountsInvite>?> loadAdditionalUserInvites() async {
|
Future<List<Response_AddAccountsInvite>?> loadAdditionalUserInvites() async {
|
||||||
final ballance = await apiService.getAdditionalUserInvites();
|
final ballance = await apiService.getAdditionalUserInvites();
|
||||||
|
|
@ -65,12 +65,9 @@ class _AdditionalUsersViewState extends State<AdditionalUsersView> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var plusInvites = <Response_AddAccountsInvite>[];
|
var plusInvites = <Response_AddAccountsInvite>[];
|
||||||
var freeInvites = <Response_AddAccountsInvite>[];
|
|
||||||
if (additionalInvites != null) {
|
if (additionalInvites != null) {
|
||||||
plusInvites =
|
plusInvites =
|
||||||
additionalInvites!.where((x) => x.planId == 'Plus').toList();
|
additionalInvites!.where((x) => x.planId == 'Plus').toList();
|
||||||
freeInvites =
|
|
||||||
additionalInvites!.where((x) => x.planId == 'Free').toList();
|
|
||||||
}
|
}
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
|
|
@ -95,11 +92,10 @@ class _AdditionalUsersViewState extends State<AdditionalUsersView> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (plusInvites.isNotEmpty)
|
if (plusInvites.isNotEmpty)
|
||||||
ListTile(
|
Text(
|
||||||
title: Text(
|
|
||||||
context.lang.additionalUsersPlusTokens,
|
context.lang.additionalUsersPlusTokens,
|
||||||
style: const TextStyle(fontSize: 13),
|
textAlign: TextAlign.center,
|
||||||
),
|
style: const TextStyle(fontSize: 16),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
|
|
@ -111,23 +107,6 @@ class _AdditionalUsersViewState extends State<AdditionalUsersView> {
|
||||||
children: plusInvites.map(AdditionalUserInvite.new).toList(),
|
children: plusInvites.map(AdditionalUserInvite.new).toList(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (freeInvites.isNotEmpty)
|
|
||||||
ListTile(
|
|
||||||
title: Text(
|
|
||||||
context.lang.additionalUsersFreeTokens,
|
|
||||||
style: const TextStyle(fontSize: 13),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: GridView.count(
|
|
||||||
crossAxisCount: 2,
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
childAspectRatio: 16 / 5,
|
|
||||||
shrinkWrap: true,
|
|
||||||
children: freeInvites.map(AdditionalUserInvite.new).toList(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -200,7 +179,7 @@ class _AdditionalAccountState extends State<AdditionalAccount> {
|
||||||
final remove = await showAlertDialog(
|
final remove = await showAlertDialog(
|
||||||
context,
|
context,
|
||||||
'Remove this additional user',
|
'Remove this additional user',
|
||||||
'The additional user will automatically be downgraded to the preview plan after removal and you will receive a new invitation code to give to another person.',
|
'The additional user will automatically be downgraded to the free plan after removal and you will receive a new invitation code to give to another person.',
|
||||||
);
|
);
|
||||||
if (remove) {
|
if (remove) {
|
||||||
final res = await apiService
|
final res = await apiService
|
||||||
|
|
|
||||||
|
|
@ -1,111 +1,24 @@
|
||||||
// ignore_for_file: inference_failure_on_instance_creation
|
// ignore_for_file: inference_failure_on_instance_creation
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
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:intl/intl.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.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/model/protobuf/api/websocket/error.pb.dart';
|
import 'package:twonly/src/model/protobuf/api/websocket/error.pb.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/providers/connection.provider.dart';
|
import 'package:twonly/src/model/purchases/purchasable_product.dart';
|
||||||
|
import 'package:twonly/src/providers/purchases.provider.dart';
|
||||||
import 'package:twonly/src/services/subscription.service.dart';
|
import 'package:twonly/src/services/subscription.service.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/views/components/better_list_title.dart';
|
import 'package:twonly/src/views/components/better_list_title.dart';
|
||||||
import 'package:twonly/src/views/settings/subscription/additional_users.view.dart';
|
import 'package:twonly/src/views/settings/subscription/additional_users.view.dart';
|
||||||
import 'package:twonly/src/views/settings/subscription/checkout.view.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'package:twonly/src/views/settings/subscription/manage_subscription.view.dart';
|
|
||||||
import 'package:twonly/src/views/settings/subscription/transaction.view.dart';
|
|
||||||
import 'package:twonly/src/views/settings/subscription/voucher.view.dart';
|
|
||||||
|
|
||||||
String localePrizing(BuildContext context, int cents) {
|
|
||||||
final myLocale = Localizations.localeOf(context);
|
|
||||||
final euros = cents / 100;
|
|
||||||
|
|
||||||
if (euros == euros.toInt()) {
|
|
||||||
return '${euros.toInt()}€';
|
|
||||||
}
|
|
||||||
|
|
||||||
return NumberFormat.currency(
|
|
||||||
locale: myLocale.toString(),
|
|
||||||
symbol: '€',
|
|
||||||
decimalDigits: 2,
|
|
||||||
).format(cents / 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Response_PlanBallance?> loadPlanBalance({bool useCache = true}) async {
|
|
||||||
final ballance = await apiService.getPlanBallance();
|
|
||||||
if (ballance != null) {
|
|
||||||
await updateUserdata((u) {
|
|
||||||
u.lastPlanBallance = ballance.writeToJson();
|
|
||||||
return u;
|
|
||||||
});
|
|
||||||
return ballance;
|
|
||||||
}
|
|
||||||
final user = await getUser();
|
|
||||||
if (user != null && user.lastPlanBallance != null && useCache) {
|
|
||||||
try {
|
|
||||||
return Response_PlanBallance.fromJson(
|
|
||||||
user.lastPlanBallance!,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
Log.error('from json: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ballance;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ignore: constant_identifier_names
|
|
||||||
const int MONTHLY_PAYMENT_DAYS = 30;
|
|
||||||
// ignore: constant_identifier_names
|
|
||||||
const int YEARLY_PAYMENT_DAYS = 365;
|
|
||||||
|
|
||||||
int calculateRefund(Response_PlanBallance current) {
|
|
||||||
var refund = getPlanPrice(SubscriptionPlan.Pro, paidMonthly: true);
|
|
||||||
|
|
||||||
if (current.paymentPeriodDays == YEARLY_PAYMENT_DAYS) {
|
|
||||||
final elapsedDays = DateTime.now()
|
|
||||||
.difference(
|
|
||||||
DateTime.fromMillisecondsSinceEpoch(
|
|
||||||
current.lastPaymentDoneUnixTimestamp.toInt() * 1000,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.inDays;
|
|
||||||
if (elapsedDays < current.paymentPeriodDays.toInt()) {
|
|
||||||
// User has yearly plan with 10€
|
|
||||||
// used it half a year and wants now to upgrade => gets 5€ discount...
|
|
||||||
// math.ceil(((365-(365/2))/365)*10)
|
|
||||||
// => 5€
|
|
||||||
|
|
||||||
refund = (((YEARLY_PAYMENT_DAYS - elapsedDays) / YEARLY_PAYMENT_DAYS) *
|
|
||||||
getPlanPrice(SubscriptionPlan.Pro, paidMonthly: false) /
|
|
||||||
100)
|
|
||||||
.ceil() *
|
|
||||||
100;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
final elapsedDays = DateTime.now()
|
|
||||||
.difference(
|
|
||||||
DateTime.fromMillisecondsSinceEpoch(
|
|
||||||
current.lastPaymentDoneUnixTimestamp.toInt() * 1000,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.inDays;
|
|
||||||
if (elapsedDays > 14) {
|
|
||||||
refund = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return refund;
|
|
||||||
}
|
|
||||||
|
|
||||||
class SubscriptionView extends StatefulWidget {
|
class SubscriptionView extends StatefulWidget {
|
||||||
const SubscriptionView({super.key, this.redirectError});
|
const SubscriptionView({super.key});
|
||||||
|
|
||||||
final ErrorCode? redirectError;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SubscriptionView> createState() => _SubscriptionViewState();
|
State<SubscriptionView> createState() => _SubscriptionViewState();
|
||||||
|
|
@ -124,7 +37,7 @@ class _SubscriptionViewState extends State<SubscriptionView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initAsync() async {
|
Future<void> initAsync() async {
|
||||||
ballance = await loadPlanBalance();
|
ballance = await apiService.loadPlanBalance();
|
||||||
if (ballance != null && ballance!.hasAdditionalAccountOwnerId()) {
|
if (ballance != null && ballance!.hasAdditionalAccountOwnerId()) {
|
||||||
final ownerId = ballance!.additionalAccountOwnerId.toInt();
|
final ownerId = ballance!.additionalAccountOwnerId.toInt();
|
||||||
final contact = await twonlyDB.contactsDao
|
final contact = await twonlyDB.contactsDao
|
||||||
|
|
@ -137,61 +50,18 @@ class _SubscriptionViewState extends State<SubscriptionView> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setState(() {});
|
setState(() {});
|
||||||
|
await apiService.forceIpaCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final myLocale = Localizations.localeOf(context);
|
final currentPlan = context.watch<PurchasesProvider>().plan;
|
||||||
String? formattedBalance;
|
|
||||||
DateTime? nextPayment;
|
|
||||||
final currentPlan = context.read<CustomChangeProvider>().plan;
|
|
||||||
|
|
||||||
if (ballance != null) {
|
|
||||||
final lastPaymentDateTime = DateTime.fromMillisecondsSinceEpoch(
|
|
||||||
ballance!.lastPaymentDoneUnixTimestamp.toInt() * 1000,
|
|
||||||
);
|
|
||||||
if (isPayingUser(currentPlan)) {
|
|
||||||
nextPayment = lastPaymentDateTime
|
|
||||||
.add(Duration(days: ballance!.paymentPeriodDays.toInt()));
|
|
||||||
}
|
|
||||||
final ballanceInCents =
|
|
||||||
ballance!.transactions.map((a) => a.depositCents.toInt()).sum;
|
|
||||||
formattedBalance = NumberFormat.currency(
|
|
||||||
locale: myLocale.toString(),
|
|
||||||
symbol: '€',
|
|
||||||
decimalDigits: 2,
|
|
||||||
).format(ballanceInCents / 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
var refund = 0;
|
|
||||||
if (currentPlan == SubscriptionPlan.Pro && ballance != null) {
|
|
||||||
refund = calculateRefund(ballance!);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(context.lang.settingsSubscription),
|
title: Text(context.lang.settingsSubscription),
|
||||||
),
|
),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: [
|
children: [
|
||||||
if (widget.redirectError != null)
|
|
||||||
Center(
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
margin: const EdgeInsets.all(16),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.orangeAccent,
|
|
||||||
borderRadius: BorderRadius.circular(15),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
(widget.redirectError == ErrorCode.PlanLimitReached)
|
|
||||||
? context.lang.planLimitReached
|
|
||||||
: context.lang.planNotAllowed,
|
|
||||||
style: const TextStyle(color: Colors.black),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(32),
|
padding: const EdgeInsets.all(32),
|
||||||
child: Center(
|
child: Center(
|
||||||
|
|
@ -220,7 +90,12 @@ class _SubscriptionViewState extends State<SubscriptionView> {
|
||||||
style: const TextStyle(color: Colors.orange),
|
style: const TextStyle(color: Colors.orange),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (!isPayingUser(currentPlan))
|
if (isPayingUser(currentPlan))
|
||||||
|
PlanCard(
|
||||||
|
plan: currentPlan,
|
||||||
|
),
|
||||||
|
if (!isPayingUser(currentPlan) ||
|
||||||
|
currentPlan == SubscriptionPlan.Tester) ...[
|
||||||
Center(
|
Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(18),
|
padding: const EdgeInsets.all(18),
|
||||||
|
|
@ -231,48 +106,16 @@ class _SubscriptionViewState extends State<SubscriptionView> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (!isPayingUser(currentPlan) ||
|
|
||||||
currentPlan == SubscriptionPlan.Tester)
|
|
||||||
PlanCard(
|
PlanCard(
|
||||||
plan: SubscriptionPlan.Pro,
|
plan: SubscriptionPlan.Pro,
|
||||||
onTap: () async {
|
onPurchase: initAsync,
|
||||||
await Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) {
|
|
||||||
return const CheckoutView(
|
|
||||||
plan: SubscriptionPlan.Pro,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
|
||||||
await initAsync();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
if (currentPlan != SubscriptionPlan.Family)
|
|
||||||
PlanCard(
|
PlanCard(
|
||||||
plan: SubscriptionPlan.Family,
|
plan: SubscriptionPlan.Family,
|
||||||
refund: refund,
|
onPurchase: initAsync,
|
||||||
onTap: () async {
|
|
||||||
await Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) {
|
|
||||||
return CheckoutView(
|
|
||||||
plan: SubscriptionPlan.Family,
|
|
||||||
refund: (refund > 0) ? refund : null,
|
|
||||||
disableMonthlyOption:
|
|
||||||
currentPlan == SubscriptionPlan.Pro &&
|
|
||||||
ballance!.paymentPeriodDays.toInt() ==
|
|
||||||
YEARLY_PAYMENT_DAYS,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
],
|
||||||
await initAsync();
|
if (currentPlan == SubscriptionPlan.Free) ...[
|
||||||
},
|
|
||||||
),
|
|
||||||
if (!isPayingUser(currentPlan)) ...[
|
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
Center(
|
Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
|
|
@ -287,58 +130,11 @@ class _SubscriptionViewState extends State<SubscriptionView> {
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
PlanCard(
|
PlanCard(
|
||||||
plan: SubscriptionPlan.Plus,
|
plan: SubscriptionPlan.Plus,
|
||||||
onTap: () async {
|
onPurchase: initAsync,
|
||||||
await redeemUserInviteCode(context, SubscriptionPlan.Plus.name);
|
|
||||||
await initAsync();
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
if (currentPlan != SubscriptionPlan.Family) const Divider(),
|
if (currentPlan != SubscriptionPlan.Family) const Divider(),
|
||||||
BetterListTile(
|
|
||||||
icon: FontAwesomeIcons.gears,
|
|
||||||
text: context.lang.manageSubscription,
|
|
||||||
subtitle: (nextPayment != null)
|
|
||||||
? Text(
|
|
||||||
'${context.lang.nextPayment}: ${DateFormat.yMMMMd(myLocale.toString()).format(nextPayment)}',
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
onTap: () async {
|
|
||||||
await Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) {
|
|
||||||
return ManageSubscriptionView(
|
|
||||||
ballance: ballance,
|
|
||||||
nextPayment: nextPayment,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
await initAsync();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
BetterListTile(
|
|
||||||
icon: FontAwesomeIcons.moneyBillTransfer,
|
|
||||||
text: context.lang.transactionHistory,
|
|
||||||
subtitle: (formattedBalance != null)
|
|
||||||
? Text('${context.lang.currentBalance}: $formattedBalance')
|
|
||||||
: null,
|
|
||||||
onTap: () async {
|
|
||||||
if (formattedBalance == null) return;
|
|
||||||
await Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) {
|
|
||||||
return TransactionView(
|
|
||||||
transactions: ballance?.transactions,
|
|
||||||
formattedBalance: formattedBalance!,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
if (isPayingUser(currentPlan) ||
|
if (isPayingUser(currentPlan) ||
|
||||||
currentPlan == SubscriptionPlan.Tester)
|
currentPlan == SubscriptionPlan.Tester)
|
||||||
BetterListTile(
|
BetterListTile(
|
||||||
|
|
@ -359,64 +155,72 @@ class _SubscriptionViewState extends State<SubscriptionView> {
|
||||||
await initAsync();
|
await initAsync();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
BetterListTile(
|
|
||||||
icon: FontAwesomeIcons.ticket,
|
|
||||||
text: context.lang.createOrRedeemVoucher,
|
|
||||||
onTap: () async {
|
|
||||||
await Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) {
|
|
||||||
return const VoucherView();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
await initAsync();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const SizedBox(height: 30),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int getPlanPrice(SubscriptionPlan plan, {required bool paidMonthly}) {
|
class PlanCard extends StatefulWidget {
|
||||||
switch (plan) {
|
|
||||||
case SubscriptionPlan.Pro:
|
|
||||||
return paidMonthly ? 100 : 1000;
|
|
||||||
case SubscriptionPlan.Family:
|
|
||||||
return paidMonthly ? 200 : 2000;
|
|
||||||
// ignore: no_default_cases
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class PlanCard extends StatelessWidget {
|
|
||||||
const PlanCard({
|
const PlanCard({
|
||||||
required this.plan,
|
required this.plan,
|
||||||
super.key,
|
super.key,
|
||||||
this.refund,
|
this.onPurchase,
|
||||||
this.onTap,
|
|
||||||
this.paidMonthly,
|
this.paidMonthly,
|
||||||
});
|
});
|
||||||
final SubscriptionPlan plan;
|
final SubscriptionPlan plan;
|
||||||
final void Function()? onTap;
|
final void Function()? onPurchase;
|
||||||
final int? refund;
|
|
||||||
final bool? paidMonthly;
|
final bool? paidMonthly;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PlanCard> createState() => _PlanCardState();
|
||||||
|
}
|
||||||
|
|
||||||
|
String getFormattedPrice(PurchasableProduct product) {
|
||||||
|
if (product.price.contains('€')) {
|
||||||
|
return product.price.replaceAll(',00', '').replaceAll('.00', '');
|
||||||
|
}
|
||||||
|
return product.price;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PlanCardState extends State<PlanCard> {
|
||||||
|
Future<void> onButtonPressed(PurchasableProduct? product) async {
|
||||||
|
if (widget.onPurchase == null) return;
|
||||||
|
if (widget.plan == SubscriptionPlan.Free ||
|
||||||
|
widget.plan == SubscriptionPlan.Plus) {
|
||||||
|
await redeemUserInviteCode(context, SubscriptionPlan.Plus.name);
|
||||||
|
widget.onPurchase!();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (product == null) return;
|
||||||
|
await context.read<PurchasesProvider>().buy(product);
|
||||||
|
widget.onPurchase!();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final yearlyPrice = getPlanPrice(plan, paidMonthly: false);
|
final products = context.watch<PurchasesProvider>().products;
|
||||||
final monthlyPrice = getPlanPrice(plan, paidMonthly: true);
|
final currentPlan = context.watch<PurchasesProvider>().plan;
|
||||||
|
PurchasableProduct? yearlyProduct;
|
||||||
|
PurchasableProduct? monthlyProduct;
|
||||||
|
|
||||||
|
for (final product in products) {
|
||||||
|
if (product.id.toLowerCase().startsWith(widget.plan.name.toLowerCase())) {
|
||||||
|
if (product.id.toLowerCase().contains('monthly')) {
|
||||||
|
monthlyProduct = product;
|
||||||
|
} else if (product.id.toLowerCase().contains('yearly')) {
|
||||||
|
yearlyProduct = product;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var features = <String>[];
|
var features = <String>[];
|
||||||
|
|
||||||
switch (plan.name) {
|
switch (widget.plan.name) {
|
||||||
case 'Free':
|
case 'Free':
|
||||||
features = [context.lang.freeFeature1];
|
features = [context.lang.freeFeature1];
|
||||||
case 'Plus':
|
case 'Plus':
|
||||||
features = [context.lang.plusFeature1, context.lang.plusFeature2];
|
features = [context.lang.plusFeature1]; //, context.lang.plusFeature2];
|
||||||
case 'Tester':
|
case 'Tester':
|
||||||
case 'Pro':
|
case 'Pro':
|
||||||
features = [
|
features = [
|
||||||
|
|
@ -424,21 +228,20 @@ class PlanCard extends StatelessWidget {
|
||||||
context.lang.proFeature2,
|
context.lang.proFeature2,
|
||||||
context.lang.proFeature3,
|
context.lang.proFeature3,
|
||||||
context.lang.proFeature4,
|
context.lang.proFeature4,
|
||||||
|
// context.lang.proFeature4,
|
||||||
];
|
];
|
||||||
case 'Family':
|
case 'Family':
|
||||||
features = [
|
features = [
|
||||||
context.lang.proFeature1,
|
context.lang.familyFeature1,
|
||||||
context.lang.familyFeature2,
|
context.lang.familyFeature2,
|
||||||
context.lang.proFeature3,
|
context.lang.familyFeature3,
|
||||||
context.lang.proFeature4,
|
context.lang.familyFeature4,
|
||||||
];
|
];
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(left: 16, right: 16),
|
padding: const EdgeInsets.only(left: 16, right: 16),
|
||||||
child: GestureDetector(
|
|
||||||
onTap: onTap,
|
|
||||||
child: Card(
|
child: Card(
|
||||||
elevation: 4,
|
elevation: 4,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
|
|
@ -453,29 +256,31 @@ class PlanCard extends StatelessWidget {
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
plan.name,
|
widget.plan.name,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (yearlyPrice != 0) const SizedBox(height: 10),
|
if (yearlyProduct != null && currentPlan != widget.plan)
|
||||||
if (yearlyPrice != 0 && paidMonthly == null)
|
const SizedBox(height: 10),
|
||||||
|
if (yearlyProduct != null &&
|
||||||
|
widget.paidMonthly == null &&
|
||||||
|
currentPlan != widget.plan)
|
||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
if (paidMonthly == null || paidMonthly!)
|
|
||||||
Text(
|
Text(
|
||||||
'${localePrizing(context, yearlyPrice)}/${context.lang.year}',
|
'${getFormattedPrice(yearlyProduct)}/${context.lang.year}',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (paidMonthly == null || !paidMonthly!)
|
if (monthlyProduct != null)
|
||||||
Text(
|
Text(
|
||||||
'${localePrizing(context, monthlyPrice)}/${context.lang.month}',
|
'${getFormattedPrice(monthlyProduct)}/${context.lang.month}',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
|
|
@ -484,11 +289,11 @@ class PlanCard extends StatelessWidget {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (paidMonthly != null)
|
if (widget.paidMonthly != null)
|
||||||
Text(
|
Text(
|
||||||
(paidMonthly!)
|
(widget.paidMonthly!)
|
||||||
? '${localePrizing(context, monthlyPrice)}/${context.lang.month}'
|
? '${getFormattedPrice(monthlyProduct!)}/${context.lang.month}'
|
||||||
: '${localePrizing(context, yearlyPrice)}/${context.lang.year}',
|
: '${getFormattedPrice(yearlyProduct!)}/${context.lang.year}',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
|
|
@ -507,29 +312,47 @@ class PlanCard extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (refund != null && refund! > 0)
|
const SizedBox(height: 10),
|
||||||
Padding(
|
if (currentPlan == widget.plan)
|
||||||
padding: const EdgeInsets.only(top: 7),
|
FilledButton.icon(
|
||||||
child: Text(
|
onPressed: () async {
|
||||||
context.lang
|
var url = 'https://apps.apple.com/account/subscriptions';
|
||||||
.subscriptionRefund(localePrizing(context, refund!)),
|
if (Platform.isAndroid) {
|
||||||
textAlign: TextAlign.center,
|
url =
|
||||||
style: TextStyle(
|
'https://play.google.com/store/account/subscriptions?sku=${gUser.subscriptionPlanIdStore}&package=eu.twonly';
|
||||||
color: context.color.primary,
|
}
|
||||||
fontSize: 12,
|
await launchUrl(
|
||||||
|
Uri.parse(url),
|
||||||
|
mode: LaunchMode.externalApplication,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
label: const Text('Manage subscription'),
|
||||||
),
|
),
|
||||||
),
|
if (widget.onPurchase != null && monthlyProduct != null)
|
||||||
),
|
OutlinedButton.icon(
|
||||||
if (onTap != null)
|
onPressed: () => onButtonPressed(monthlyProduct),
|
||||||
Padding(
|
label: (widget.plan == SubscriptionPlan.Free ||
|
||||||
padding: const EdgeInsets.only(top: 10),
|
widget.plan == SubscriptionPlan.Plus)
|
||||||
child: FilledButton.icon(
|
|
||||||
onPressed: onTap,
|
|
||||||
label: (plan == SubscriptionPlan.Free ||
|
|
||||||
plan == SubscriptionPlan.Plus)
|
|
||||||
? Text(context.lang.redeemUserInviteCodeTitle)
|
? Text(context.lang.redeemUserInviteCodeTitle)
|
||||||
: Text(
|
: Text(
|
||||||
context.lang.upgradeToPaidPlanButton(plan.name),
|
context.lang.upgradeToPaidPlanButton(
|
||||||
|
widget.plan.name,
|
||||||
|
' (${context.lang.monthly})',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (widget.onPurchase != null &&
|
||||||
|
(yearlyProduct != null ||
|
||||||
|
currentPlan == SubscriptionPlan.Free))
|
||||||
|
FilledButton.icon(
|
||||||
|
onPressed: () => onButtonPressed(yearlyProduct),
|
||||||
|
label: (widget.plan == SubscriptionPlan.Free ||
|
||||||
|
widget.plan == SubscriptionPlan.Plus)
|
||||||
|
? Text(context.lang.redeemUserInviteCodeTitle)
|
||||||
|
: Text(
|
||||||
|
context.lang.upgradeToPaidPlanButton(
|
||||||
|
widget.plan.name,
|
||||||
|
' (${context.lang.yearly})',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -537,7 +360,6 @@ class PlanCard extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -593,7 +415,7 @@ Future<void> redeemUserInviteCode(BuildContext context, String newPlan) async {
|
||||||
);
|
);
|
||||||
// reconnect to load new plan.
|
// reconnect to load new plan.
|
||||||
await apiService.close(() {});
|
await apiService.close(() {});
|
||||||
await apiService.connect(force: true);
|
await apiService.connect();
|
||||||
} else {
|
} else {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,17 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/src/services/subscription.service.dart';
|
import 'package:twonly/src/services/subscription.service.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/views/settings/subscription/select_payment.view.dart';
|
import 'package:twonly/src/views/settings/subscription_custom/select_payment.view.dart';
|
||||||
import 'package:twonly/src/views/settings/subscription/subscription.view.dart';
|
import 'package:twonly/src/views/settings/subscription_custom/subscription.view.dart';
|
||||||
|
|
||||||
class CheckoutView extends StatefulWidget {
|
class CheckoutView extends StatefulWidget {
|
||||||
const CheckoutView({
|
const CheckoutView({
|
||||||
required this.plan,
|
required this.plan,
|
||||||
super.key,
|
super.key,
|
||||||
this.refund,
|
|
||||||
this.disableMonthlyOption,
|
this.disableMonthlyOption,
|
||||||
});
|
});
|
||||||
|
|
||||||
final SubscriptionPlan plan;
|
final SubscriptionPlan plan;
|
||||||
final int? refund;
|
|
||||||
final bool? disableMonthlyOption;
|
final bool? disableMonthlyOption;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -76,29 +74,6 @@ class _CheckoutViewState extends State<CheckoutView> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (widget.refund != null)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
child: Card(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
context.lang.refund,
|
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'+${localePrizing(context, widget.refund!)}',
|
|
||||||
textAlign: TextAlign.end,
|
|
||||||
style: TextStyle(color: context.color.primary),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
child: Card(
|
child: Card(
|
||||||
|
|
@ -133,7 +108,6 @@ class _CheckoutViewState extends State<CheckoutView> {
|
||||||
return SelectPaymentView(
|
return SelectPaymentView(
|
||||||
plan: widget.plan,
|
plan: widget.plan,
|
||||||
payMonthly: paidMonthly,
|
payMonthly: paidMonthly,
|
||||||
refund: widget.refund,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart';
|
import 'package:twonly/src/model/protobuf/api/websocket/error.pb.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/providers/connection.provider.dart';
|
import 'package:twonly/src/providers/purchases.provider.dart';
|
||||||
import 'package:twonly/src/services/subscription.service.dart';
|
import 'package:twonly/src/services/subscription.service.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/views/settings/subscription/subscription.view.dart';
|
import 'package:twonly/src/views/settings/subscription_custom/subscription.view.dart';
|
||||||
|
|
||||||
class ManageSubscriptionView extends StatefulWidget {
|
class ManageSubscriptionView extends StatefulWidget {
|
||||||
const ManageSubscriptionView({
|
const ManageSubscriptionView({
|
||||||
|
|
@ -65,7 +64,7 @@ class _ManageSubscriptionViewState extends State<ManageSubscriptionView> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final plan = context.read<CustomChangeProvider>().plan;
|
final plan = context.watch<PurchasesProvider>().plan;
|
||||||
final myLocale = Localizations.localeOf(context);
|
final myLocale = Localizations.localeOf(context);
|
||||||
final paidMonthly = ballance?.paymentPeriodDays == MONTHLY_PAYMENT_DAYS;
|
final paidMonthly = ballance?.paymentPeriodDays == MONTHLY_PAYMENT_DAYS;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue