mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 13:08:42 +00:00
commit
702829b458
138 changed files with 7112 additions and 2092 deletions
4
.github/workflows/dev_github.yml
vendored
4
.github/workflows/dev_github.yml
vendored
|
|
@ -1,4 +1,4 @@
|
||||||
name: Publish on Github
|
name: Flutter analyze & test
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch: {}
|
workflow_dispatch: {}
|
||||||
|
|
@ -9,7 +9,7 @@ on:
|
||||||
- lib/**/*.dart
|
- lib/**/*.dart
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_and_publish:
|
flutter_analyze_and_test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
|
|
||||||
10
CHANGELOG.md
10
CHANGELOG.md
|
|
@ -1,12 +1,16 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.0.61
|
||||||
|
|
||||||
|
- Improving image editor when changing colors
|
||||||
|
- Fixing message decryption error
|
||||||
|
- Fixing issue with user deletion
|
||||||
|
- Fixing issue with flame counter sync
|
||||||
|
- Dependency and Flutter upgrade
|
||||||
|
|
||||||
## 0.0.60
|
## 0.0.60
|
||||||
|
|
||||||
- Improved logging to debug the 'Tap to load' issue.
|
- Improved logging to debug the 'Tap to load' issue.
|
||||||
|
|
||||||
==> If you encounter any issues, please send your debug log via the feedback button along with a short description of the error so that we can resolve them. :)
|
|
||||||
|
|
||||||
- Display your own avatar in the title bar of the chat list.
|
- Display your own avatar in the title bar of the chat list.
|
||||||
- Created a default avatar image in case none was set.
|
- Created a default avatar image in case none was set.
|
||||||
- Improved UI handling when requesting microphone access for the first time.
|
- Improved UI handling when requesting microphone access for the first time.
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ analyzer:
|
||||||
inference_failure_on_instance_creation: ignore
|
inference_failure_on_instance_creation: ignore
|
||||||
avoid_positional_boolean_parameters: ignore
|
avoid_positional_boolean_parameters: ignore
|
||||||
inference_failure_on_collection_literal: ignore
|
inference_failure_on_collection_literal: ignore
|
||||||
|
matching_super_parameters: ignore
|
||||||
exclude:
|
exclude:
|
||||||
- "lib/src/model/protobuf/**"
|
- "lib/src/model/protobuf/**"
|
||||||
- "lib/src/model/protobuf/api/websocket/**"
|
- "lib/src/model/protobuf/api/websocket/**"
|
||||||
|
|
@ -23,3 +24,4 @@ linter:
|
||||||
public_member_api_docs: false
|
public_member_api_docs: false
|
||||||
avoid_catches_without_on_clauses: false
|
avoid_catches_without_on_clauses: false
|
||||||
document_ignores: false
|
document_ignores: false
|
||||||
|
discarded_futures: false
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ if (keystorePropertiesFile.exists()) {
|
||||||
android {
|
android {
|
||||||
namespace = "eu.twonly"
|
namespace = "eu.twonly"
|
||||||
// compileSdk = flutter.compileSdkVersion
|
// compileSdk = flutter.compileSdkVersion
|
||||||
compileSdk 35
|
compileSdk 36
|
||||||
//ndkVersion = flutter.ndkVersion
|
//ndkVersion = flutter.ndkVersion
|
||||||
ndkVersion = "27.0.12077973"
|
ndkVersion = "27.0.12077973"
|
||||||
|
|
||||||
|
|
@ -37,7 +37,7 @@ android {
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
// You can update the following values to match your application needs.
|
// You can update the following values to match your application needs.
|
||||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||||
minSdk = 23
|
minSdkVersion = flutter.minSdkVersion
|
||||||
targetSdk = flutter.targetSdkVersion
|
targetSdk = flutter.targetSdkVersion
|
||||||
versionCode = flutter.versionCode
|
versionCode = flutter.versionCode
|
||||||
versionName = flutter.versionName
|
versionName = flutter.versionName
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ plugins {
|
||||||
// START: FlutterFire Configuration
|
// START: FlutterFire Configuration
|
||||||
id "com.google.gms.google-services" version "4.3.15" apply false
|
id "com.google.gms.google-services" version "4.3.15" apply false
|
||||||
// END: FlutterFire Configuration
|
// END: FlutterFire Configuration
|
||||||
id "org.jetbrains.kotlin.android" version "2.0.20" apply false
|
id "org.jetbrains.kotlin.android" version "2.1.0" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
include ":app"
|
include ":app"
|
||||||
|
|
|
||||||
1
drift_schemas/twonly_database/drift_schema_v17.json
Normal file
1
drift_schemas/twonly_database/drift_schema_v17.json
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -1,5 +1,5 @@
|
||||||
# Uncomment this line to define a global platform for your project
|
# Uncomment this line to define a global platform for your project
|
||||||
platform :ios, '14.0'
|
platform :ios, '15.0'
|
||||||
use_frameworks!
|
use_frameworks!
|
||||||
|
|
||||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||||
|
|
|
||||||
149
ios/Podfile.lock
149
ios/Podfile.lock
|
|
@ -9,56 +9,56 @@ PODS:
|
||||||
- Flutter
|
- Flutter
|
||||||
- device_info_plus (0.0.1):
|
- device_info_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- Firebase (11.15.0):
|
- Firebase (12.4.0):
|
||||||
- Firebase/Core (= 11.15.0)
|
- Firebase/Core (= 12.4.0)
|
||||||
- Firebase/Core (11.15.0):
|
- Firebase/Core (12.4.0):
|
||||||
- Firebase/CoreOnly
|
- Firebase/CoreOnly
|
||||||
- FirebaseAnalytics (~> 11.15.0)
|
- FirebaseAnalytics (~> 12.4.0)
|
||||||
- Firebase/CoreOnly (11.15.0):
|
- Firebase/CoreOnly (12.4.0):
|
||||||
- FirebaseCore (~> 11.15.0)
|
- FirebaseCore (~> 12.4.0)
|
||||||
- Firebase/Messaging (11.15.0):
|
- Firebase/Messaging (12.4.0):
|
||||||
- Firebase/CoreOnly
|
- Firebase/CoreOnly
|
||||||
- FirebaseMessaging (~> 11.15.0)
|
- FirebaseMessaging (~> 12.4.0)
|
||||||
- firebase_core (3.15.2):
|
- firebase_core (4.2.0):
|
||||||
- Firebase/CoreOnly (= 11.15.0)
|
- Firebase/CoreOnly (= 12.4.0)
|
||||||
- Flutter
|
- Flutter
|
||||||
- firebase_messaging (15.2.10):
|
- firebase_messaging (16.0.3):
|
||||||
- Firebase/Messaging (= 11.15.0)
|
- Firebase/Messaging (= 12.4.0)
|
||||||
- firebase_core
|
- firebase_core
|
||||||
- Flutter
|
- Flutter
|
||||||
- FirebaseAnalytics (11.15.0):
|
- FirebaseAnalytics (12.4.0):
|
||||||
- FirebaseAnalytics/Default (= 11.15.0)
|
- FirebaseAnalytics/Default (= 12.4.0)
|
||||||
- FirebaseCore (~> 11.15.0)
|
- FirebaseCore (~> 12.4.0)
|
||||||
- FirebaseInstallations (~> 11.0)
|
- FirebaseInstallations (~> 12.4.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 (11.15.0):
|
- FirebaseAnalytics/Default (12.4.0):
|
||||||
- FirebaseCore (~> 11.15.0)
|
- FirebaseCore (~> 12.4.0)
|
||||||
- FirebaseInstallations (~> 11.0)
|
- FirebaseInstallations (~> 12.4.0)
|
||||||
- GoogleAppMeasurement/Default (= 11.15.0)
|
- GoogleAppMeasurement/Default (= 12.4.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 (11.15.0):
|
- FirebaseCore (12.4.0):
|
||||||
- FirebaseCoreInternal (~> 11.15.0)
|
- FirebaseCoreInternal (~> 12.4.0)
|
||||||
- GoogleUtilities/Environment (~> 8.1)
|
- GoogleUtilities/Environment (~> 8.1)
|
||||||
- GoogleUtilities/Logger (~> 8.1)
|
- GoogleUtilities/Logger (~> 8.1)
|
||||||
- FirebaseCoreInternal (11.15.0):
|
- FirebaseCoreInternal (12.4.0):
|
||||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||||
- FirebaseInstallations (11.15.0):
|
- FirebaseInstallations (12.4.0):
|
||||||
- FirebaseCore (~> 11.15.0)
|
- FirebaseCore (~> 12.4.0)
|
||||||
- GoogleUtilities/Environment (~> 8.1)
|
- GoogleUtilities/Environment (~> 8.1)
|
||||||
- GoogleUtilities/UserDefaults (~> 8.1)
|
- GoogleUtilities/UserDefaults (~> 8.1)
|
||||||
- PromisesObjC (~> 2.4)
|
- PromisesObjC (~> 2.4)
|
||||||
- FirebaseMessaging (11.15.0):
|
- FirebaseMessaging (12.4.0):
|
||||||
- FirebaseCore (~> 11.15.0)
|
- FirebaseCore (~> 12.4.0)
|
||||||
- FirebaseInstallations (~> 11.0)
|
- FirebaseInstallations (~> 12.4.0)
|
||||||
- GoogleDataTransport (~> 10.0)
|
- GoogleDataTransport (~> 10.1)
|
||||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||||
- GoogleUtilities/Environment (~> 8.1)
|
- GoogleUtilities/Environment (~> 8.1)
|
||||||
- GoogleUtilities/Reachability (~> 8.1)
|
- GoogleUtilities/Reachability (~> 8.1)
|
||||||
|
|
@ -70,7 +70,7 @@ PODS:
|
||||||
- Mantle
|
- Mantle
|
||||||
- SDWebImage
|
- SDWebImage
|
||||||
- SDWebImageWebPCoder
|
- SDWebImageWebPCoder
|
||||||
- flutter_keyboard_visibility (0.0.1):
|
- flutter_keyboard_visibility_temp_fork (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- flutter_local_notifications (0.0.1):
|
- flutter_local_notifications (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
|
@ -82,27 +82,28 @@ PODS:
|
||||||
- gal (1.0.0):
|
- gal (1.0.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- GoogleAdsOnDeviceConversion (2.1.0):
|
- GoogleAdsOnDeviceConversion (3.1.0):
|
||||||
|
- 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 (11.15.0):
|
- GoogleAppMeasurement/Core (12.4.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 (11.15.0):
|
- GoogleAppMeasurement/Default (12.4.0):
|
||||||
- GoogleAdsOnDeviceConversion (= 2.1.0)
|
- GoogleAdsOnDeviceConversion (~> 3.1.0)
|
||||||
- GoogleAppMeasurement/Core (= 11.15.0)
|
- GoogleAppMeasurement/Core (= 12.4.0)
|
||||||
- GoogleAppMeasurement/IdentitySupport (= 11.15.0)
|
- GoogleAppMeasurement/IdentitySupport (= 12.4.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 (11.15.0):
|
- GoogleAppMeasurement/IdentitySupport (12.4.0):
|
||||||
- GoogleAppMeasurement/Core (= 11.15.0)
|
- GoogleAppMeasurement/Core (= 12.4.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)
|
||||||
|
|
@ -190,9 +191,9 @@ PODS:
|
||||||
- restart_app (0.0.1):
|
- restart_app (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- ScreenProtectorKit (1.3.1)
|
- ScreenProtectorKit (1.3.1)
|
||||||
- SDWebImage (5.21.1):
|
- SDWebImage (5.21.3):
|
||||||
- SDWebImage/Core (= 5.21.1)
|
- SDWebImage/Core (= 5.21.3)
|
||||||
- SDWebImage/Core (5.21.1)
|
- SDWebImage/Core (5.21.3)
|
||||||
- SDWebImageWebPCoder (0.14.6):
|
- SDWebImageWebPCoder (0.14.6):
|
||||||
- libwebp (~> 1.0)
|
- libwebp (~> 1.0)
|
||||||
- SDWebImage/Core (~> 5.17)
|
- SDWebImage/Core (~> 5.17)
|
||||||
|
|
@ -204,32 +205,32 @@ PODS:
|
||||||
- sqflite_darwin (0.0.4):
|
- sqflite_darwin (0.0.4):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- sqlite3 (3.50.3):
|
- sqlite3 (3.50.4):
|
||||||
- sqlite3/common (= 3.50.3)
|
- sqlite3/common (= 3.50.4)
|
||||||
- sqlite3/common (3.50.3)
|
- sqlite3/common (3.50.4)
|
||||||
- sqlite3/dbstatvtab (3.50.3):
|
- sqlite3/dbstatvtab (3.50.4):
|
||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
- sqlite3/fts5 (3.50.3):
|
- sqlite3/fts5 (3.50.4):
|
||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
- sqlite3/math (3.50.3):
|
- sqlite3/math (3.50.4):
|
||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
- sqlite3/perf-threadsafe (3.50.3):
|
- sqlite3/perf-threadsafe (3.50.4):
|
||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
- sqlite3/rtree (3.50.3):
|
- sqlite3/rtree (3.50.4):
|
||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
- sqlite3/session (3.50.3):
|
- sqlite3/session (3.50.4):
|
||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
- sqlite3_flutter_libs (0.0.1):
|
- sqlite3_flutter_libs (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- sqlite3 (~> 3.50.3)
|
- sqlite3 (~> 3.50.4)
|
||||||
- sqlite3/dbstatvtab
|
- sqlite3/dbstatvtab
|
||||||
- sqlite3/fts5
|
- sqlite3/fts5
|
||||||
- sqlite3/math
|
- sqlite3/math
|
||||||
- sqlite3/perf-threadsafe
|
- sqlite3/perf-threadsafe
|
||||||
- sqlite3/rtree
|
- sqlite3/rtree
|
||||||
- sqlite3/session
|
- sqlite3/session
|
||||||
- SwiftProtobuf (1.30.0)
|
- SwiftProtobuf (1.32.0)
|
||||||
- url_launcher_ios (0.0.1):
|
- url_launcher_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- video_compress (0.3.0):
|
- video_compress (0.3.0):
|
||||||
|
|
@ -255,7 +256,7 @@ DEPENDENCIES:
|
||||||
- FirebaseMessaging
|
- FirebaseMessaging
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
- flutter_image_compress_common (from `.symlinks/plugins/flutter_image_compress_common/ios`)
|
- flutter_image_compress_common (from `.symlinks/plugins/flutter_image_compress_common/ios`)
|
||||||
- flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/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_zxing (from `.symlinks/plugins/flutter_zxing/ios`)
|
- flutter_zxing (from `.symlinks/plugins/flutter_zxing/ios`)
|
||||||
|
|
@ -319,8 +320,8 @@ EXTERNAL SOURCES:
|
||||||
:path: Flutter
|
:path: Flutter
|
||||||
flutter_image_compress_common:
|
flutter_image_compress_common:
|
||||||
:path: ".symlinks/plugins/flutter_image_compress_common/ios"
|
:path: ".symlinks/plugins/flutter_image_compress_common/ios"
|
||||||
flutter_keyboard_visibility:
|
flutter_keyboard_visibility_temp_fork:
|
||||||
:path: ".symlinks/plugins/flutter_keyboard_visibility/ios"
|
:path: ".symlinks/plugins/flutter_keyboard_visibility_temp_fork/ios"
|
||||||
flutter_local_notifications:
|
flutter_local_notifications:
|
||||||
:path: ".symlinks/plugins/flutter_local_notifications/ios"
|
:path: ".symlinks/plugins/flutter_local_notifications/ios"
|
||||||
flutter_secure_storage_darwin:
|
flutter_secure_storage_darwin:
|
||||||
|
|
@ -362,32 +363,32 @@ EXTERNAL SOURCES:
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
background_downloader: 50e91d979067b82081aba359d7d916b3ba5fadad
|
background_downloader: 50e91d979067b82081aba359d7d916b3ba5fadad
|
||||||
camera_avfoundation: be3be85408cd4126f250386828e9b1dfa40ab436
|
camera_avfoundation: 5675ca25298b6f81fa0a325188e7df62cc217741
|
||||||
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
|
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
|
||||||
cryptography_flutter_plus: 44f4e9e4079395fcbb3e7809c0ac2c6ae2d9576f
|
cryptography_flutter_plus: 44f4e9e4079395fcbb3e7809c0ac2c6ae2d9576f
|
||||||
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
|
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
|
||||||
Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e
|
Firebase: f07b15ae5a6ec0f93713e30b923d9970d144af3e
|
||||||
firebase_core: 995454a784ff288be5689b796deb9e9fa3601818
|
firebase_core: 744984dbbed8b3036abf34f0b98d80f130a7e464
|
||||||
firebase_messaging: f4a41dd102ac18b840eba3f39d67e77922d3f707
|
firebase_messaging: 82c70650c426a0a14873e1acdb9ec2b443c4e8b4
|
||||||
FirebaseAnalytics: 6433dfd311ba78084fc93bdfc145e8cb75740eae
|
FirebaseAnalytics: 0fc2b20091f0ddd21bf73397cf8f0eb5346dc24f
|
||||||
FirebaseCore: efb3893e5b94f32b86e331e3bd6dadf18b66568e
|
FirebaseCore: bb595f3114953664e3c1dc032f008a244147cfd3
|
||||||
FirebaseCoreInternal: 9afa45b1159304c963da48addb78275ef701c6b4
|
FirebaseCoreInternal: d7f5a043c2cd01a08103ab586587c1468047bca6
|
||||||
FirebaseInstallations: 317270fec08a5d418fdbc8429282238cab3ac843
|
FirebaseInstallations: ae9f4902cb5bf1d0c5eaa31ec1f4e5495a0714e2
|
||||||
FirebaseMessaging: 3b26e2cee503815e01c3701236b020aa9b576f09
|
FirebaseMessaging: d33971b7bb252745ea6cd31ab190d1a1df4b8ed5
|
||||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||||
flutter_image_compress_common: 1697a328fd72bfb335507c6bca1a65fa5ad87df1
|
flutter_image_compress_common: 1697a328fd72bfb335507c6bca1a65fa5ad87df1
|
||||||
flutter_keyboard_visibility: 4625131e43015dbbe759d9b20daaf77e0e3f6619
|
flutter_keyboard_visibility_temp_fork: 95b2d534bacf6ac62e7fcbe5c2a9e2c2a17ce06f
|
||||||
flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb
|
flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb
|
||||||
flutter_secure_storage_darwin: ce237a8775b39723566dc72571190a3769d70468
|
flutter_secure_storage_darwin: ce237a8775b39723566dc72571190a3769d70468
|
||||||
flutter_zxing: e8bcc43bd3056c70c271b732ed94e7a16fd62f93
|
flutter_zxing: e8bcc43bd3056c70c271b732ed94e7a16fd62f93
|
||||||
gal: baecd024ebfd13c441269ca7404792a7152fde89
|
gal: baecd024ebfd13c441269ca7404792a7152fde89
|
||||||
GoogleAdsOnDeviceConversion: 2be6297a4f048459e0ae17fad9bfd2844e10cf64
|
GoogleAdsOnDeviceConversion: e03a386840803ea7eef3fd22a061930142c039c1
|
||||||
GoogleAppMeasurement: 700dce7541804bec33db590a5c496b663fbe2539
|
GoogleAppMeasurement: 1e718274b7e015cefd846ac1fcf7820c70dc017d
|
||||||
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
||||||
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
||||||
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
|
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
|
||||||
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
|
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
|
||||||
local_auth_darwin: d2e8c53ef0c4f43c646462e3415432c4dab3ae19
|
local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb
|
||||||
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
|
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
|
||||||
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
||||||
no_screenshot: 6d183496405a3ab709a67a54e5cd0f639e94729e
|
no_screenshot: 6d183496405a3ab709a67a54e5cd0f639e94729e
|
||||||
|
|
@ -397,19 +398,19 @@ SPEC CHECKSUMS:
|
||||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||||
restart_app: 9cda5378aacc5000e3f66ee76a9201534e7d3ecf
|
restart_app: 9cda5378aacc5000e3f66ee76a9201534e7d3ecf
|
||||||
ScreenProtectorKit: 83a6281b02c7a5902ee6eac4f5045f674e902ae4
|
ScreenProtectorKit: 83a6281b02c7a5902ee6eac4f5045f674e902ae4
|
||||||
SDWebImage: f29024626962457f3470184232766516dee8dfea
|
SDWebImage: 16309af6d214ba3f77a7c6f6fdda888cb313a50a
|
||||||
SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380
|
SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380
|
||||||
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
|
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
|
||||||
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
|
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
|
||||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||||
sqlite3: 83105acd294c9137c026e2da1931c30b4588ab81
|
sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b
|
||||||
sqlite3_flutter_libs: 616267f2fca40e9c6af8c5d82324e05667040b6e
|
sqlite3_flutter_libs: 83f8e9f5b6554077f1d93119fe20ebaa5f3a9ef1
|
||||||
SwiftProtobuf: 3697407f0d5b23bedeba9c2eaaf3ec6fdff69349
|
SwiftProtobuf: 81e341191afbddd64aa031bd12862dccfab2f639
|
||||||
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
|
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
|
||||||
video_compress: f2133a07762889d67f0711ac831faa26f956980e
|
video_compress: f2133a07762889d67f0711ac831faa26f956980e
|
||||||
video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b
|
video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b
|
||||||
video_thumbnail: b637e0ad5f588ca9945f6e2c927f73a69a661140
|
video_thumbnail: b637e0ad5f588ca9945f6e2c927f73a69a661140
|
||||||
|
|
||||||
PODFILE CHECKSUM: a01f0821a361ca6708e29b1299e8becf492a8a71
|
PODFILE CHECKSUM: 47470fbd5b59affa461eaf943ac57acce81e0ee8
|
||||||
|
|
||||||
COCOAPODS: 1.16.2
|
COCOAPODS: 1.16.2
|
||||||
|
|
|
||||||
|
|
@ -609,7 +609,7 @@
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SUPPORTED_PLATFORMS = iphoneos;
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
|
|
@ -752,7 +752,7 @@
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = YES;
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
|
|
@ -805,7 +805,7 @@
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SUPPORTED_PLATFORMS = iphoneos;
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
synthetic-package: false
|
|
||||||
arb-dir: lib/src/localization
|
arb-dir: lib/src/localization
|
||||||
template-arb-file: app_en.arb
|
template-arb-file: app_en.arb
|
||||||
output-localization-file: app_localizations.dart
|
output-localization-file: app_localizations.dart
|
||||||
|
|
|
||||||
20
lib/app.dart
20
lib/app.dart
|
|
@ -28,16 +28,18 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||||
globalIsAppInBackground = false;
|
globalIsAppInBackground = false;
|
||||||
WidgetsBinding.instance.addObserver(this);
|
WidgetsBinding.instance.addObserver(this);
|
||||||
|
|
||||||
globalCallbackConnectionState = ({required bool isConnected}) {
|
globalCallbackConnectionState = ({required bool isConnected}) async {
|
||||||
context.read<CustomChangeProvider>().updateConnectionState(isConnected);
|
await context
|
||||||
setUserPlan();
|
.read<CustomChangeProvider>()
|
||||||
|
.updateConnectionState(isConnected);
|
||||||
|
await setUserPlan();
|
||||||
};
|
};
|
||||||
|
|
||||||
globalCallbackUpdatePlan = (String planId) {
|
globalCallbackUpdatePlan = (String planId) async {
|
||||||
context.read<CustomChangeProvider>().updatePlan(planId);
|
await context.read<CustomChangeProvider>().updatePlan(planId);
|
||||||
};
|
};
|
||||||
|
|
||||||
initAsync();
|
unawaited(initAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setUserPlan() async {
|
Future<void> setUserPlan() async {
|
||||||
|
|
@ -77,12 +79,12 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||||
if (wasPaused) {
|
if (wasPaused) {
|
||||||
globalIsAppInBackground = false;
|
globalIsAppInBackground = false;
|
||||||
twonlyDB.markUpdated();
|
twonlyDB.markUpdated();
|
||||||
apiService.connect(force: true);
|
unawaited(apiService.connect(force: true));
|
||||||
}
|
}
|
||||||
} else if (state == AppLifecycleState.paused) {
|
} else if (state == AppLifecycleState.paused) {
|
||||||
wasPaused = true;
|
wasPaused = true;
|
||||||
globalIsAppInBackground = true;
|
globalIsAppInBackground = true;
|
||||||
handleUploadWhenAppGoesBackground();
|
unawaited(handleUploadWhenAppGoesBackground());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -138,7 +140,7 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||||
initialRoute: '/',
|
initialRoute: '/',
|
||||||
routes: {
|
routes: {
|
||||||
'/': (context) => const AppMainWidget(initialPage: 1),
|
'/': (context) => const AppMainWidget(initialPage: 1),
|
||||||
'/chats': (context) => const AppMainWidget(initialPage: 0)
|
'/chats': (context) => const AppMainWidget(initialPage: 0),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ class ContactsDao extends DatabaseAccessor<TwonlyDatabase>
|
||||||
with _$ContactsDaoMixin {
|
with _$ContactsDaoMixin {
|
||||||
// this constructor is required so that the main database can create an instance
|
// this constructor is required so that the main database can create an instance
|
||||||
// of this object.
|
// of this object.
|
||||||
|
// ignore: matching_super_parameters
|
||||||
ContactsDao(super.db);
|
ContactsDao(super.db);
|
||||||
|
|
||||||
Future<int> insertContact(ContactsCompanion contact) async {
|
Future<int> insertContact(ContactsCompanion contact) async {
|
||||||
|
|
@ -102,7 +103,9 @@ class ContactsDao extends DatabaseAccessor<TwonlyDatabase>
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateContact(
|
Future<void> updateContact(
|
||||||
int userId, ContactsCompanion updatedValues) async {
|
int userId,
|
||||||
|
ContactsCompanion updatedValues,
|
||||||
|
) async {
|
||||||
await (update(contacts)..where((c) => c.userId.equals(userId)))
|
await (update(contacts)..where((c) => c.userId.equals(userId)))
|
||||||
.write(updatedValues);
|
.write(updatedValues);
|
||||||
if (updatedValues.blocked.present ||
|
if (updatedValues.blocked.present ||
|
||||||
|
|
@ -117,10 +120,12 @@ class ContactsDao extends DatabaseAccessor<TwonlyDatabase>
|
||||||
|
|
||||||
Stream<List<Contact>> watchNotAcceptedContacts() {
|
Stream<List<Contact>> watchNotAcceptedContacts() {
|
||||||
return (select(contacts)
|
return (select(contacts)
|
||||||
..where((t) =>
|
..where(
|
||||||
|
(t) =>
|
||||||
t.accepted.equals(false) &
|
t.accepted.equals(false) &
|
||||||
t.archived.equals(false) &
|
t.archived.equals(false) &
|
||||||
t.blocked.equals(false)))
|
t.blocked.equals(false),
|
||||||
|
))
|
||||||
.watch();
|
.watch();
|
||||||
// return (select(contacts)).watch();
|
// return (select(contacts)).watch();
|
||||||
}
|
}
|
||||||
|
|
@ -132,10 +137,12 @@ class ContactsDao extends DatabaseAccessor<TwonlyDatabase>
|
||||||
|
|
||||||
Stream<List<Contact>> watchContactsForShareView() {
|
Stream<List<Contact>> watchContactsForShareView() {
|
||||||
return (select(contacts)
|
return (select(contacts)
|
||||||
..where((t) =>
|
..where(
|
||||||
|
(t) =>
|
||||||
t.accepted.equals(true) &
|
t.accepted.equals(true) &
|
||||||
t.blocked.equals(false) &
|
t.blocked.equals(false) &
|
||||||
t.deleted.equals(false))
|
t.deleted.equals(false),
|
||||||
|
)
|
||||||
..orderBy([(t) => OrderingTerm.desc(t.lastMessageExchange)]))
|
..orderBy([(t) => OrderingTerm.desc(t.lastMessageExchange)]))
|
||||||
.watch();
|
.watch();
|
||||||
}
|
}
|
||||||
|
|
@ -177,8 +184,9 @@ class ContactsDao extends DatabaseAccessor<TwonlyDatabase>
|
||||||
Stream<int?> watchContactsRequested() {
|
Stream<int?> watchContactsRequested() {
|
||||||
final count = contacts.requested.count(distinct: true);
|
final count = contacts.requested.count(distinct: true);
|
||||||
final query = selectOnly(contacts)
|
final query = selectOnly(contacts)
|
||||||
..where(contacts.requested.equals(true) &
|
..where(
|
||||||
contacts.accepted.equals(true).not())
|
contacts.requested.equals(true) & contacts.accepted.equals(true).not(),
|
||||||
|
)
|
||||||
..addColumns([count]);
|
..addColumns([count]);
|
||||||
return query.map((row) => row.read(count)).watchSingle();
|
return query.map((row) => row.read(count)).watchSingle();
|
||||||
}
|
}
|
||||||
|
|
@ -187,6 +195,16 @@ class ContactsDao extends DatabaseAccessor<TwonlyDatabase>
|
||||||
return select(contacts).watch();
|
return select(contacts).watch();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> modifyFlameCounterForTesting() async {
|
||||||
|
await update(contacts).write(
|
||||||
|
ContactsCompanion(
|
||||||
|
lastFlameCounterChange: Value(DateTime.now()),
|
||||||
|
flameCounter: const Value(1337),
|
||||||
|
lastFlameSync: const Value(null),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Stream<int> watchFlameCounter(int userId) {
|
Stream<int> watchFlameCounter(int userId) {
|
||||||
return (select(contacts)
|
return (select(contacts)
|
||||||
..where(
|
..where(
|
||||||
|
|
|
||||||
|
|
@ -8,17 +8,21 @@ part 'media_uploads_dao.g.dart';
|
||||||
@DriftAccessor(tables: [MediaUploads])
|
@DriftAccessor(tables: [MediaUploads])
|
||||||
class MediaUploadsDao extends DatabaseAccessor<TwonlyDatabase>
|
class MediaUploadsDao extends DatabaseAccessor<TwonlyDatabase>
|
||||||
with _$MediaUploadsDaoMixin {
|
with _$MediaUploadsDaoMixin {
|
||||||
|
// ignore: matching_super_parameters
|
||||||
MediaUploadsDao(super.db);
|
MediaUploadsDao(super.db);
|
||||||
|
|
||||||
Future<List<MediaUpload>> getMediaUploadsForRetry() {
|
Future<List<MediaUpload>> getMediaUploadsForRetry() {
|
||||||
return (select(mediaUploads)
|
return (select(mediaUploads)
|
||||||
..where(
|
..where(
|
||||||
(t) => t.state.equals(UploadState.receiverNotified.name).not()))
|
(t) => t.state.equals(UploadState.receiverNotified.name).not(),
|
||||||
|
))
|
||||||
.get();
|
.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> updateMediaUpload(
|
Future<int> updateMediaUpload(
|
||||||
int mediaUploadId, MediaUploadsCompanion updatedValues) {
|
int mediaUploadId,
|
||||||
|
MediaUploadsCompanion updatedValues,
|
||||||
|
) {
|
||||||
return (update(mediaUploads)
|
return (update(mediaUploads)
|
||||||
..where((c) => c.mediaUploadId.equals(mediaUploadId)))
|
..where((c) => c.mediaUploadId.equals(mediaUploadId)))
|
||||||
.write(updatedValues);
|
.write(updatedValues);
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,12 @@ class MessageRetransmissionDao extends DatabaseAccessor<TwonlyDatabase>
|
||||||
with _$MessageRetransmissionDaoMixin {
|
with _$MessageRetransmissionDaoMixin {
|
||||||
// this constructor is required so that the main database can create an instance
|
// this constructor is required so that the main database can create an instance
|
||||||
// of this object.
|
// of this object.
|
||||||
|
// ignore: matching_super_parameters
|
||||||
MessageRetransmissionDao(super.db);
|
MessageRetransmissionDao(super.db);
|
||||||
|
|
||||||
Future<int?> insertRetransmission(
|
Future<int?> insertRetransmission(
|
||||||
MessageRetransmissionsCompanion message) async {
|
MessageRetransmissionsCompanion message,
|
||||||
|
) async {
|
||||||
try {
|
try {
|
||||||
return await into(messageRetransmissions).insert(message);
|
return await into(messageRetransmissions).insert(message);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -25,18 +27,22 @@ class MessageRetransmissionDao extends DatabaseAccessor<TwonlyDatabase>
|
||||||
Future<void> purgeOldRetransmissions() async {
|
Future<void> purgeOldRetransmissions() async {
|
||||||
// delete entries older than two weeks
|
// delete entries older than two weeks
|
||||||
await (delete(messageRetransmissions)
|
await (delete(messageRetransmissions)
|
||||||
..where((t) => (t.acknowledgeByServerAt.isSmallerThanValue(
|
..where(
|
||||||
|
(t) => (t.acknowledgeByServerAt.isSmallerThanValue(
|
||||||
DateTime.now().subtract(
|
DateTime.now().subtract(
|
||||||
const Duration(days: 25),
|
const Duration(days: 25),
|
||||||
),
|
),
|
||||||
))))
|
)),
|
||||||
|
))
|
||||||
.go();
|
.go();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<int>> getRetransmitAbleMessages() async {
|
Future<List<int>> getRetransmitAbleMessages() async {
|
||||||
final countDeleted = await (delete(messageRetransmissions)
|
final countDeleted = await (delete(messageRetransmissions)
|
||||||
..where((t) =>
|
..where(
|
||||||
t.encryptedHash.isNull() & t.acknowledgeByServerAt.isNotNull()))
|
(t) =>
|
||||||
|
t.encryptedHash.isNull() & t.acknowledgeByServerAt.isNotNull(),
|
||||||
|
))
|
||||||
.go();
|
.go();
|
||||||
|
|
||||||
if (countDeleted > 0) {
|
if (countDeleted > 0) {
|
||||||
|
|
@ -51,11 +57,18 @@ class MessageRetransmissionDao extends DatabaseAccessor<TwonlyDatabase>
|
||||||
}
|
}
|
||||||
|
|
||||||
SingleOrNullSelectable<MessageRetransmission> getRetransmissionById(
|
SingleOrNullSelectable<MessageRetransmission> getRetransmissionById(
|
||||||
int retransmissionId) {
|
int retransmissionId,
|
||||||
|
) {
|
||||||
return select(messageRetransmissions)
|
return select(messageRetransmissions)
|
||||||
..where((t) => t.retransmissionId.equals(retransmissionId));
|
..where((t) => t.retransmissionId.equals(retransmissionId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Stream<List<MessageRetransmission>> watchAllMessages() {
|
||||||
|
return (select(messageRetransmissions)
|
||||||
|
..orderBy([(t) => OrderingTerm.asc(t.retransmissionId)]))
|
||||||
|
.watch();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> updateRetransmission(
|
Future<void> updateRetransmission(
|
||||||
int retransmissionId,
|
int retransmissionId,
|
||||||
MessageRetransmissionsCompanion updatedValues,
|
MessageRetransmissionsCompanion updatedValues,
|
||||||
|
|
@ -67,9 +80,11 @@ class MessageRetransmissionDao extends DatabaseAccessor<TwonlyDatabase>
|
||||||
|
|
||||||
Future<int> resetAckStatusFor(int fromUserId, Uint8List encryptedHash) async {
|
Future<int> resetAckStatusFor(int fromUserId, Uint8List encryptedHash) async {
|
||||||
return ((update(messageRetransmissions))
|
return ((update(messageRetransmissions))
|
||||||
..where((m) =>
|
..where(
|
||||||
|
(m) =>
|
||||||
m.contactId.equals(fromUserId) &
|
m.contactId.equals(fromUserId) &
|
||||||
m.encryptedHash.equals(encryptedHash)))
|
m.encryptedHash.equals(encryptedHash),
|
||||||
|
))
|
||||||
.write(
|
.write(
|
||||||
const MessageRetransmissionsCompanion(
|
const MessageRetransmissionsCompanion(
|
||||||
acknowledgeByServerAt: Value(null),
|
acknowledgeByServerAt: Value(null),
|
||||||
|
|
@ -78,11 +93,15 @@ class MessageRetransmissionDao extends DatabaseAccessor<TwonlyDatabase>
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<MessageRetransmission?> getRetransmissionFromHash(
|
Future<MessageRetransmission?> getRetransmissionFromHash(
|
||||||
int fromUserId, Uint8List encryptedHash) async {
|
int fromUserId,
|
||||||
|
Uint8List encryptedHash,
|
||||||
|
) async {
|
||||||
return ((select(messageRetransmissions))
|
return ((select(messageRetransmissions))
|
||||||
..where((m) =>
|
..where(
|
||||||
|
(m) =>
|
||||||
m.contactId.equals(fromUserId) &
|
m.contactId.equals(fromUserId) &
|
||||||
m.encryptedHash.equals(encryptedHash)))
|
m.encryptedHash.equals(encryptedHash),
|
||||||
|
))
|
||||||
.getSingleOrNull();
|
.getSingleOrNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,26 +11,31 @@ class MessagesDao extends DatabaseAccessor<TwonlyDatabase>
|
||||||
with _$MessagesDaoMixin {
|
with _$MessagesDaoMixin {
|
||||||
// this constructor is required so that the main database can create an instance
|
// this constructor is required so that the main database can create an instance
|
||||||
// of this object.
|
// of this object.
|
||||||
|
// ignore: matching_super_parameters
|
||||||
MessagesDao(super.db);
|
MessagesDao(super.db);
|
||||||
|
|
||||||
Stream<List<Message>> watchMessageNotOpened(int contactId) {
|
Stream<List<Message>> watchMessageNotOpened(int contactId) {
|
||||||
return (select(messages)
|
return (select(messages)
|
||||||
..where((t) =>
|
..where(
|
||||||
|
(t) =>
|
||||||
t.openedAt.isNull() &
|
t.openedAt.isNull() &
|
||||||
t.contactId.equals(contactId) &
|
t.contactId.equals(contactId) &
|
||||||
t.errorWhileSending.equals(false))
|
t.errorWhileSending.equals(false),
|
||||||
|
)
|
||||||
..orderBy([(t) => OrderingTerm.desc(t.sendAt)]))
|
..orderBy([(t) => OrderingTerm.desc(t.sendAt)]))
|
||||||
.watch();
|
.watch();
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<List<Message>> watchMediaMessageNotOpened(int contactId) {
|
Stream<List<Message>> watchMediaMessageNotOpened(int contactId) {
|
||||||
return (select(messages)
|
return (select(messages)
|
||||||
..where((t) =>
|
..where(
|
||||||
|
(t) =>
|
||||||
t.openedAt.isNull() &
|
t.openedAt.isNull() &
|
||||||
t.contactId.equals(contactId) &
|
t.contactId.equals(contactId) &
|
||||||
t.errorWhileSending.equals(false) &
|
t.errorWhileSending.equals(false) &
|
||||||
t.messageOtherId.isNotNull() &
|
t.messageOtherId.isNotNull() &
|
||||||
t.kind.equals(MessageKind.media.name))
|
t.kind.equals(MessageKind.media.name),
|
||||||
|
)
|
||||||
..orderBy([(t) => OrderingTerm.asc(t.sendAt)]))
|
..orderBy([(t) => OrderingTerm.asc(t.sendAt)]))
|
||||||
.watch();
|
.watch();
|
||||||
}
|
}
|
||||||
|
|
@ -45,27 +50,33 @@ class MessagesDao extends DatabaseAccessor<TwonlyDatabase>
|
||||||
|
|
||||||
Stream<List<Message>> watchAllMessagesFrom(int contactId) {
|
Stream<List<Message>> watchAllMessagesFrom(int contactId) {
|
||||||
return (select(messages)
|
return (select(messages)
|
||||||
..where((t) =>
|
..where(
|
||||||
|
(t) =>
|
||||||
t.contactId.equals(contactId) &
|
t.contactId.equals(contactId) &
|
||||||
t.contentJson.isNotNull() &
|
t.contentJson.isNotNull() &
|
||||||
(t.openedAt.isNull() |
|
(t.openedAt.isNull() |
|
||||||
t.mediaStored.equals(true) |
|
t.mediaStored.equals(true) |
|
||||||
t.openedAt.isBiggerThanValue(
|
t.openedAt.isBiggerThanValue(
|
||||||
DateTime.now().subtract(const Duration(days: 1)))))
|
DateTime.now().subtract(const Duration(days: 1)),
|
||||||
|
)),
|
||||||
|
)
|
||||||
..orderBy([(t) => OrderingTerm.asc(t.sendAt)]))
|
..orderBy([(t) => OrderingTerm.asc(t.sendAt)]))
|
||||||
.watch();
|
.watch();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> removeOldMessages() {
|
Future<void> removeOldMessages() {
|
||||||
return (update(messages)
|
return (update(messages)
|
||||||
..where((t) =>
|
..where(
|
||||||
|
(t) =>
|
||||||
(t.openedAt.isSmallerThanValue(
|
(t.openedAt.isSmallerThanValue(
|
||||||
DateTime.now().subtract(const Duration(days: 1)),
|
DateTime.now().subtract(const Duration(days: 1)),
|
||||||
) |
|
) |
|
||||||
(t.sendAt.isSmallerThanValue(
|
(t.sendAt.isSmallerThanValue(
|
||||||
DateTime.now().subtract(const Duration(days: 1))) &
|
DateTime.now().subtract(const Duration(days: 3)),
|
||||||
|
) &
|
||||||
t.errorWhileSending.equals(true))) &
|
t.errorWhileSending.equals(true))) &
|
||||||
t.kind.equals(MessageKind.textMessage.name)))
|
t.kind.equals(MessageKind.textMessage.name),
|
||||||
|
))
|
||||||
.write(const MessagesCompanion(contentJson: Value(null)));
|
.write(const MessagesCompanion(contentJson: Value(null)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -99,13 +110,15 @@ class MessagesDao extends DatabaseAccessor<TwonlyDatabase>
|
||||||
|
|
||||||
Future<List<Message>> getAllNonACKMessagesFromUser() {
|
Future<List<Message>> getAllNonACKMessagesFromUser() {
|
||||||
return (select(messages)
|
return (select(messages)
|
||||||
..where((t) =>
|
..where(
|
||||||
|
(t) =>
|
||||||
t.acknowledgeByUser.equals(false) &
|
t.acknowledgeByUser.equals(false) &
|
||||||
t.messageOtherId.isNull() &
|
t.messageOtherId.isNull() &
|
||||||
t.errorWhileSending.equals(false) &
|
t.errorWhileSending.equals(false) &
|
||||||
t.sendAt.isBiggerThanValue(
|
t.sendAt.isBiggerThanValue(
|
||||||
DateTime.now().subtract(const Duration(minutes: 10)),
|
DateTime.now().subtract(const Duration(minutes: 10)),
|
||||||
)))
|
),
|
||||||
|
))
|
||||||
.get();
|
.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -133,11 +146,13 @@ class MessagesDao extends DatabaseAccessor<TwonlyDatabase>
|
||||||
Future<void> openedAllNonMediaMessages(int contactId) {
|
Future<void> openedAllNonMediaMessages(int contactId) {
|
||||||
final updates = MessagesCompanion(openedAt: Value(DateTime.now()));
|
final updates = MessagesCompanion(openedAt: Value(DateTime.now()));
|
||||||
return (update(messages)
|
return (update(messages)
|
||||||
..where((t) =>
|
..where(
|
||||||
|
(t) =>
|
||||||
t.contactId.equals(contactId) &
|
t.contactId.equals(contactId) &
|
||||||
t.messageOtherId.isNotNull() &
|
t.messageOtherId.isNotNull() &
|
||||||
t.openedAt.isNull() &
|
t.openedAt.isNull() &
|
||||||
t.kind.equals(MessageKind.media.name).not()))
|
t.kind.equals(MessageKind.media.name).not(),
|
||||||
|
))
|
||||||
.write(updates);
|
.write(updates);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -148,44 +163,59 @@ class MessagesDao extends DatabaseAccessor<TwonlyDatabase>
|
||||||
const updates =
|
const updates =
|
||||||
MessagesCompanion(downloadState: Value(DownloadState.pending));
|
MessagesCompanion(downloadState: Value(DownloadState.pending));
|
||||||
return (update(messages)
|
return (update(messages)
|
||||||
..where((t) =>
|
..where(
|
||||||
|
(t) =>
|
||||||
t.messageOtherId.isNotNull() &
|
t.messageOtherId.isNotNull() &
|
||||||
t.downloadState.equals(DownloadState.downloading.index) &
|
t.downloadState.equals(DownloadState.downloading.index) &
|
||||||
t.kind.equals(MessageKind.media.name)))
|
t.kind.equals(MessageKind.media.name),
|
||||||
|
))
|
||||||
.write(updates);
|
.write(updates);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> openedAllNonMediaMessagesFromOtherUser(int contactId) {
|
Future<void> openedAllNonMediaMessagesFromOtherUser(int contactId) {
|
||||||
final updates = MessagesCompanion(openedAt: Value(DateTime.now()));
|
final updates = MessagesCompanion(openedAt: Value(DateTime.now()));
|
||||||
return (update(messages)
|
return (update(messages)
|
||||||
..where((t) =>
|
..where(
|
||||||
|
(t) =>
|
||||||
t.contactId.equals(contactId) &
|
t.contactId.equals(contactId) &
|
||||||
t.messageOtherId
|
t.messageOtherId
|
||||||
.isNull() & // only mark messages open that where send
|
.isNull() & // only mark messages open that where send
|
||||||
t.openedAt.isNull() &
|
t.openedAt.isNull() &
|
||||||
t.kind.equals(MessageKind.media.name).not()))
|
t.kind.equals(MessageKind.media.name).not(),
|
||||||
|
))
|
||||||
.write(updates);
|
.write(updates);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateMessageByOtherUser(
|
Future<void> updateMessageByOtherUser(
|
||||||
int userId, int messageId, MessagesCompanion updatedValues) {
|
int userId,
|
||||||
|
int messageId,
|
||||||
|
MessagesCompanion updatedValues,
|
||||||
|
) {
|
||||||
return (update(messages)
|
return (update(messages)
|
||||||
..where((c) =>
|
..where(
|
||||||
c.contactId.equals(userId) & c.messageId.equals(messageId)))
|
(c) => c.contactId.equals(userId) & c.messageId.equals(messageId),
|
||||||
|
))
|
||||||
.write(updatedValues);
|
.write(updatedValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateMessageByOtherMessageId(
|
Future<void> updateMessageByOtherMessageId(
|
||||||
int userId, int messageOtherId, MessagesCompanion updatedValues) {
|
int userId,
|
||||||
|
int messageOtherId,
|
||||||
|
MessagesCompanion updatedValues,
|
||||||
|
) {
|
||||||
return (update(messages)
|
return (update(messages)
|
||||||
..where((c) =>
|
..where(
|
||||||
|
(c) =>
|
||||||
c.contactId.equals(userId) &
|
c.contactId.equals(userId) &
|
||||||
c.messageOtherId.equals(messageOtherId)))
|
c.messageOtherId.equals(messageOtherId),
|
||||||
|
))
|
||||||
.write(updatedValues);
|
.write(updatedValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateMessageByMessageId(
|
Future<void> updateMessageByMessageId(
|
||||||
int messageId, MessagesCompanion updatedValues) {
|
int messageId,
|
||||||
|
MessagesCompanion updatedValues,
|
||||||
|
) {
|
||||||
return (update(messages)..where((c) => c.messageId.equals(messageId)))
|
return (update(messages)..where((c) => c.messageId.equals(messageId)))
|
||||||
.write(updatedValues);
|
.write(updatedValues);
|
||||||
}
|
}
|
||||||
|
|
@ -207,17 +237,22 @@ class MessagesDao extends DatabaseAccessor<TwonlyDatabase>
|
||||||
|
|
||||||
Future<void> deleteMessagesByContactId(int contactId) {
|
Future<void> deleteMessagesByContactId(int contactId) {
|
||||||
return (delete(messages)
|
return (delete(messages)
|
||||||
..where((t) =>
|
..where(
|
||||||
t.contactId.equals(contactId) & t.mediaStored.equals(false)))
|
(t) => t.contactId.equals(contactId) & t.mediaStored.equals(false),
|
||||||
|
))
|
||||||
.go();
|
.go();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteMessagesByContactIdAndOtherMessageId(
|
Future<void> deleteMessagesByContactIdAndOtherMessageId(
|
||||||
int contactId, int messageOtherId) {
|
int contactId,
|
||||||
|
int messageOtherId,
|
||||||
|
) {
|
||||||
return (delete(messages)
|
return (delete(messages)
|
||||||
..where((t) =>
|
..where(
|
||||||
|
(t) =>
|
||||||
t.contactId.equals(contactId) &
|
t.contactId.equals(contactId) &
|
||||||
t.messageOtherId.equals(messageOtherId)))
|
t.messageOtherId.equals(messageOtherId),
|
||||||
|
))
|
||||||
.go();
|
.go();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -234,9 +269,11 @@ class MessagesDao extends DatabaseAccessor<TwonlyDatabase>
|
||||||
int messageOtherId,
|
int messageOtherId,
|
||||||
) async {
|
) async {
|
||||||
final query = select(messages)
|
final query = select(messages)
|
||||||
..where((t) =>
|
..where(
|
||||||
|
(t) =>
|
||||||
t.messageOtherId.equals(messageOtherId) &
|
t.messageOtherId.equals(messageOtherId) &
|
||||||
t.contactId.equals(fromUserId));
|
t.contactId.equals(fromUserId),
|
||||||
|
);
|
||||||
final entry = await query.get();
|
final entry = await query.get();
|
||||||
return entry.isNotEmpty;
|
return entry.isNotEmpty;
|
||||||
}
|
}
|
||||||
|
|
@ -252,16 +289,23 @@ class MessagesDao extends DatabaseAccessor<TwonlyDatabase>
|
||||||
}
|
}
|
||||||
|
|
||||||
SingleOrNullSelectable<Message> getMessageByOtherMessageId(
|
SingleOrNullSelectable<Message> getMessageByOtherMessageId(
|
||||||
int fromUserId, int messageId) {
|
int fromUserId,
|
||||||
|
int messageId,
|
||||||
|
) {
|
||||||
return select(messages)
|
return select(messages)
|
||||||
..where((t) =>
|
..where(
|
||||||
t.messageOtherId.equals(messageId) & t.contactId.equals(fromUserId));
|
(t) =>
|
||||||
|
t.messageOtherId.equals(messageId) & t.contactId.equals(fromUserId),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
SingleOrNullSelectable<Message> getMessageByIdAndContactId(
|
SingleOrNullSelectable<Message> getMessageByIdAndContactId(
|
||||||
int fromUserId, int messageId) {
|
int fromUserId,
|
||||||
|
int messageId,
|
||||||
|
) {
|
||||||
return select(messages)
|
return select(messages)
|
||||||
..where((t) =>
|
..where(
|
||||||
t.messageId.equals(messageId) & t.contactId.equals(fromUserId));
|
(t) => t.messageId.equals(messageId) & t.contactId.equals(fromUserId),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,16 @@ import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
part 'signal_dao.g.dart';
|
part 'signal_dao.g.dart';
|
||||||
|
|
||||||
@DriftAccessor(tables: [
|
@DriftAccessor(
|
||||||
|
tables: [
|
||||||
SignalContactPreKeys,
|
SignalContactPreKeys,
|
||||||
SignalContactSignedPreKeys,
|
SignalContactSignedPreKeys,
|
||||||
])
|
],
|
||||||
|
)
|
||||||
class SignalDao extends DatabaseAccessor<TwonlyDatabase> with _$SignalDaoMixin {
|
class SignalDao extends DatabaseAccessor<TwonlyDatabase> with _$SignalDaoMixin {
|
||||||
// this constructor is required so that the main database can create an instance
|
// this constructor is required so that the main database can create an instance
|
||||||
// of this object.
|
// of this object.
|
||||||
|
// ignore: matching_super_parameters
|
||||||
SignalDao(super.db);
|
SignalDao(super.db);
|
||||||
Future<void> deleteAllByContactId(int contactId) async {
|
Future<void> deleteAllByContactId(int contactId) async {
|
||||||
await (delete(signalContactPreKeys)
|
await (delete(signalContactPreKeys)
|
||||||
|
|
@ -48,10 +51,13 @@ class SignalDao extends DatabaseAccessor<TwonlyDatabase> with _$SignalDaoMixin {
|
||||||
if (preKey != null) {
|
if (preKey != null) {
|
||||||
// remove the pre key...
|
// remove the pre key...
|
||||||
await (delete(signalContactPreKeys)
|
await (delete(signalContactPreKeys)
|
||||||
..where((tbl) =>
|
..where(
|
||||||
|
(tbl) =>
|
||||||
tbl.contactId.equals(contactId) &
|
tbl.contactId.equals(contactId) &
|
||||||
tbl.preKeyId.equals(preKey.preKeyId)))
|
tbl.preKeyId.equals(preKey.preKeyId),
|
||||||
|
))
|
||||||
.go();
|
.go();
|
||||||
|
Log.info('Using prekey ${preKey.preKeyId} for $contactId');
|
||||||
return preKey;
|
return preKey;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -59,7 +65,8 @@ class SignalDao extends DatabaseAccessor<TwonlyDatabase> with _$SignalDaoMixin {
|
||||||
|
|
||||||
// 3: Insert multiple pre-keys
|
// 3: Insert multiple pre-keys
|
||||||
Future<void> insertPreKeys(
|
Future<void> insertPreKeys(
|
||||||
List<SignalContactPreKeysCompanion> preKeys) async {
|
List<SignalContactPreKeysCompanion> preKeys,
|
||||||
|
) async {
|
||||||
for (final preKey in preKeys) {
|
for (final preKey in preKeys) {
|
||||||
try {
|
try {
|
||||||
await into(signalContactPreKeys).insert(preKey);
|
await into(signalContactPreKeys).insert(preKey);
|
||||||
|
|
@ -78,7 +85,8 @@ class SignalDao extends DatabaseAccessor<TwonlyDatabase> with _$SignalDaoMixin {
|
||||||
|
|
||||||
// 5: Insert or update signed pre-key by contact ID
|
// 5: Insert or update signed pre-key by contact ID
|
||||||
Future<void> insertOrUpdateSignedPreKeyByContactId(
|
Future<void> insertOrUpdateSignedPreKeyByContactId(
|
||||||
SignalContactSignedPreKeysCompanion signedPreKey) async {
|
SignalContactSignedPreKeysCompanion signedPreKey,
|
||||||
|
) async {
|
||||||
await (delete(signalContactSignedPreKeys)
|
await (delete(signalContactSignedPreKeys)
|
||||||
..where((t) => t.contactId.equals(signedPreKey.contactId.value)))
|
..where((t) => t.contactId.equals(signedPreKey.contactId.value)))
|
||||||
.go();
|
.go();
|
||||||
|
|
@ -86,21 +94,35 @@ class SignalDao extends DatabaseAccessor<TwonlyDatabase> with _$SignalDaoMixin {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> purgeOutDatedPreKeys() async {
|
Future<void> purgeOutDatedPreKeys() async {
|
||||||
// other pre keys are valid 25 days
|
// Deletion is a workaround for the issue, that own pre keys where deleted after 40 days, while they could be 30days
|
||||||
await (delete(signalContactSignedPreKeys)
|
// on the server + 25 days on the others device old, resulting in the issue that the receiver could not decrypt the
|
||||||
..where((t) => (t.createdAt.isSmallerThanValue(
|
// messages...
|
||||||
DateTime.now().subtract(
|
await (delete(signalContactPreKeys)
|
||||||
const Duration(days: 25),
|
..where(
|
||||||
),
|
(t) => (t.createdAt.isSmallerThanValue(
|
||||||
))))
|
DateTime(2025, 10, 10),
|
||||||
|
)),
|
||||||
|
))
|
||||||
.go();
|
.go();
|
||||||
// own pre keys are valid for 40 days
|
// other pre keys are valid 100 days
|
||||||
|
await (delete(signalContactPreKeys)
|
||||||
|
..where(
|
||||||
|
(t) => (t.createdAt.isSmallerThanValue(
|
||||||
|
DateTime.now().subtract(
|
||||||
|
const Duration(days: 100),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
))
|
||||||
|
.go();
|
||||||
|
// own pre keys are valid for 180 days
|
||||||
await (delete(twonlyDB.signalPreKeyStores)
|
await (delete(twonlyDB.signalPreKeyStores)
|
||||||
..where((t) => (t.createdAt.isSmallerThanValue(
|
..where(
|
||||||
|
(t) => (t.createdAt.isSmallerThanValue(
|
||||||
DateTime.now().subtract(
|
DateTime.now().subtract(
|
||||||
const Duration(days: 40),
|
const Duration(days: 365),
|
||||||
),
|
),
|
||||||
))))
|
)),
|
||||||
|
))
|
||||||
.go();
|
.go();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,11 @@ class ConnectIdentityKeyStore extends IdentityKeyStore {
|
||||||
@override
|
@override
|
||||||
Future<IdentityKey?> getIdentity(SignalProtocolAddress address) async {
|
Future<IdentityKey?> getIdentity(SignalProtocolAddress address) async {
|
||||||
final identity = await (twonlyDB.select(twonlyDB.signalIdentityKeyStores)
|
final identity = await (twonlyDB.select(twonlyDB.signalIdentityKeyStores)
|
||||||
..where((t) =>
|
..where(
|
||||||
|
(t) =>
|
||||||
t.deviceId.equals(address.getDeviceId()) &
|
t.deviceId.equals(address.getDeviceId()) &
|
||||||
t.name.equals(address.getName())))
|
t.name.equals(address.getName()),
|
||||||
|
))
|
||||||
.getSingleOrNull();
|
.getSingleOrNull();
|
||||||
if (identity == null) return null;
|
if (identity == null) return null;
|
||||||
return IdentityKey.fromBytes(identity.identityKey, 0);
|
return IdentityKey.fromBytes(identity.identityKey, 0);
|
||||||
|
|
@ -28,8 +30,11 @@ class ConnectIdentityKeyStore extends IdentityKeyStore {
|
||||||
Future<int> getLocalRegistrationId() async => localRegistrationId;
|
Future<int> getLocalRegistrationId() async => localRegistrationId;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> isTrustedIdentity(SignalProtocolAddress address,
|
Future<bool> isTrustedIdentity(
|
||||||
IdentityKey? identityKey, Direction? direction) async {
|
SignalProtocolAddress address,
|
||||||
|
IdentityKey? identityKey,
|
||||||
|
Direction? direction,
|
||||||
|
) async {
|
||||||
final trusted = await getIdentity(address);
|
final trusted = await getIdentity(address);
|
||||||
if (identityKey == null) {
|
if (identityKey == null) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -41,7 +46,9 @@ class ConnectIdentityKeyStore extends IdentityKeyStore {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> saveIdentity(
|
Future<bool> saveIdentity(
|
||||||
SignalProtocolAddress address, IdentityKey? identityKey) async {
|
SignalProtocolAddress address,
|
||||||
|
IdentityKey? identityKey,
|
||||||
|
) async {
|
||||||
if (identityKey == null) {
|
if (identityKey == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -55,9 +62,11 @@ class ConnectIdentityKeyStore extends IdentityKeyStore {
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
await (twonlyDB.update(twonlyDB.signalIdentityKeyStores)
|
await (twonlyDB.update(twonlyDB.signalIdentityKeyStores)
|
||||||
..where((t) =>
|
..where(
|
||||||
|
(t) =>
|
||||||
t.deviceId.equals(address.getDeviceId()) &
|
t.deviceId.equals(address.getDeviceId()) &
|
||||||
t.name.equals(address.getName())))
|
t.name.equals(address.getName()),
|
||||||
|
))
|
||||||
.write(
|
.write(
|
||||||
SignalIdentityKeyStoresCompanion(
|
SignalIdentityKeyStoresCompanion(
|
||||||
identityKey: Value(identityKey.serialize()),
|
identityKey: Value(identityKey.serialize()),
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ class ConnectPreKeyStore extends PreKeyStore {
|
||||||
if (preKeyRecord.isEmpty) {
|
if (preKeyRecord.isEmpty) {
|
||||||
throw InvalidKeyIdException('No such preKey record! - $preKeyId');
|
throw InvalidKeyIdException('No such preKey record! - $preKeyId');
|
||||||
}
|
}
|
||||||
|
Log.info('Contact used preKey $preKeyId');
|
||||||
final preKey = preKeyRecord.first.preKey;
|
final preKey = preKeyRecord.first.preKey;
|
||||||
return PreKeyRecord.fromBuffer(preKey);
|
return PreKeyRecord.fromBuffer(preKey);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,14 +11,17 @@ class ConnectSenderKeyStore extends SenderKeyStore {
|
||||||
.getSingleOrNull();
|
.getSingleOrNull();
|
||||||
if (identity == null) {
|
if (identity == null) {
|
||||||
throw InvalidKeyIdException(
|
throw InvalidKeyIdException(
|
||||||
'No such sender key record! - $senderKeyName');
|
'No such sender key record! - $senderKeyName',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return SenderKeyRecord.fromSerialized(identity.senderKey);
|
return SenderKeyRecord.fromSerialized(identity.senderKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> storeSenderKey(
|
Future<void> storeSenderKey(
|
||||||
SenderKeyName senderKeyName, SenderKeyRecord record) async {
|
SenderKeyName senderKeyName,
|
||||||
|
SenderKeyRecord record,
|
||||||
|
) async {
|
||||||
await twonlyDB.into(twonlyDB.signalSenderKeyStores).insert(
|
await twonlyDB.into(twonlyDB.signalSenderKeyStores).insert(
|
||||||
SignalSenderKeyStoresCompanion(
|
SignalSenderKeyStoresCompanion(
|
||||||
senderKey: Value(record.serialize()),
|
senderKey: Value(record.serialize()),
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,11 @@ class ConnectSessionStore extends SessionStore {
|
||||||
@override
|
@override
|
||||||
Future<bool> containsSession(SignalProtocolAddress address) async {
|
Future<bool> containsSession(SignalProtocolAddress address) async {
|
||||||
final sessions = await (twonlyDB.select(twonlyDB.signalSessionStores)
|
final sessions = await (twonlyDB.select(twonlyDB.signalSessionStores)
|
||||||
..where((tbl) =>
|
..where(
|
||||||
|
(tbl) =>
|
||||||
tbl.deviceId.equals(address.getDeviceId()) &
|
tbl.deviceId.equals(address.getDeviceId()) &
|
||||||
tbl.name.equals(address.getName())))
|
tbl.name.equals(address.getName()),
|
||||||
|
))
|
||||||
.get();
|
.get();
|
||||||
return sessions.isNotEmpty;
|
return sessions.isNotEmpty;
|
||||||
}
|
}
|
||||||
|
|
@ -24,9 +26,11 @@ class ConnectSessionStore extends SessionStore {
|
||||||
@override
|
@override
|
||||||
Future<void> deleteSession(SignalProtocolAddress address) async {
|
Future<void> deleteSession(SignalProtocolAddress address) async {
|
||||||
await (twonlyDB.delete(twonlyDB.signalSessionStores)
|
await (twonlyDB.delete(twonlyDB.signalSessionStores)
|
||||||
..where((tbl) =>
|
..where(
|
||||||
|
(tbl) =>
|
||||||
tbl.deviceId.equals(address.getDeviceId()) &
|
tbl.deviceId.equals(address.getDeviceId()) &
|
||||||
tbl.name.equals(address.getName())))
|
tbl.name.equals(address.getName()),
|
||||||
|
))
|
||||||
.go();
|
.go();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -34,7 +38,8 @@ class ConnectSessionStore extends SessionStore {
|
||||||
Future<List<int>> getSubDeviceSessions(String name) async {
|
Future<List<int>> getSubDeviceSessions(String name) async {
|
||||||
final deviceIds = await (twonlyDB.select(twonlyDB.signalSessionStores)
|
final deviceIds = await (twonlyDB.select(twonlyDB.signalSessionStores)
|
||||||
..where(
|
..where(
|
||||||
(tbl) => tbl.deviceId.equals(1).not() & tbl.name.equals(name)))
|
(tbl) => tbl.deviceId.equals(1).not() & tbl.name.equals(name),
|
||||||
|
))
|
||||||
.get();
|
.get();
|
||||||
return deviceIds.map((row) => row.deviceId).toList();
|
return deviceIds.map((row) => row.deviceId).toList();
|
||||||
}
|
}
|
||||||
|
|
@ -42,9 +47,11 @@ class ConnectSessionStore extends SessionStore {
|
||||||
@override
|
@override
|
||||||
Future<SessionRecord> loadSession(SignalProtocolAddress address) async {
|
Future<SessionRecord> loadSession(SignalProtocolAddress address) async {
|
||||||
final dbSession = await (twonlyDB.select(twonlyDB.signalSessionStores)
|
final dbSession = await (twonlyDB.select(twonlyDB.signalSessionStores)
|
||||||
..where((tbl) =>
|
..where(
|
||||||
|
(tbl) =>
|
||||||
tbl.deviceId.equals(address.getDeviceId()) &
|
tbl.deviceId.equals(address.getDeviceId()) &
|
||||||
tbl.name.equals(address.getName())))
|
tbl.name.equals(address.getName()),
|
||||||
|
))
|
||||||
.get();
|
.get();
|
||||||
|
|
||||||
if (dbSession.isEmpty) {
|
if (dbSession.isEmpty) {
|
||||||
|
|
@ -56,7 +63,9 @@ class ConnectSessionStore extends SessionStore {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> storeSession(
|
Future<void> storeSession(
|
||||||
SignalProtocolAddress address, SessionRecord record) async {
|
SignalProtocolAddress address,
|
||||||
|
SessionRecord record,
|
||||||
|
) async {
|
||||||
final sessionCompanion = SignalSessionStoresCompanion(
|
final sessionCompanion = SignalSessionStoresCompanion(
|
||||||
deviceId: Value(address.getDeviceId()),
|
deviceId: Value(address.getDeviceId()),
|
||||||
name: Value(address.getName()),
|
name: Value(address.getName()),
|
||||||
|
|
@ -69,9 +78,11 @@ class ConnectSessionStore extends SessionStore {
|
||||||
.insert(sessionCompanion);
|
.insert(sessionCompanion);
|
||||||
} else {
|
} else {
|
||||||
await (twonlyDB.update(twonlyDB.signalSessionStores)
|
await (twonlyDB.update(twonlyDB.signalSessionStores)
|
||||||
..where((tbl) =>
|
..where(
|
||||||
|
(tbl) =>
|
||||||
tbl.deviceId.equals(address.getDeviceId()) &
|
tbl.deviceId.equals(address.getDeviceId()) &
|
||||||
tbl.name.equals(address.getName())))
|
tbl.name.equals(address.getName()),
|
||||||
|
))
|
||||||
.write(sessionCompanion);
|
.write(sessionCompanion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,9 @@ import 'package:twonly/src/database/signal/connect_signed_pre_key_store.dart';
|
||||||
|
|
||||||
class ConnectSignalProtocolStore implements SignalProtocolStore {
|
class ConnectSignalProtocolStore implements SignalProtocolStore {
|
||||||
ConnectSignalProtocolStore(
|
ConnectSignalProtocolStore(
|
||||||
IdentityKeyPair identityKeyPair, int registrationId) {
|
IdentityKeyPair identityKeyPair,
|
||||||
|
int registrationId,
|
||||||
|
) {
|
||||||
_identityKeyStore =
|
_identityKeyStore =
|
||||||
ConnectIdentityKeyStore(identityKeyPair, registrationId);
|
ConnectIdentityKeyStore(identityKeyPair, registrationId);
|
||||||
}
|
}
|
||||||
|
|
@ -27,12 +29,17 @@ class ConnectSignalProtocolStore implements SignalProtocolStore {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> saveIdentity(
|
Future<bool> saveIdentity(
|
||||||
SignalProtocolAddress address, IdentityKey? identityKey) async =>
|
SignalProtocolAddress address,
|
||||||
|
IdentityKey? identityKey,
|
||||||
|
) async =>
|
||||||
_identityKeyStore.saveIdentity(address, identityKey);
|
_identityKeyStore.saveIdentity(address, identityKey);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> isTrustedIdentity(SignalProtocolAddress address,
|
Future<bool> isTrustedIdentity(
|
||||||
IdentityKey? identityKey, Direction direction) async =>
|
SignalProtocolAddress address,
|
||||||
|
IdentityKey? identityKey,
|
||||||
|
Direction direction,
|
||||||
|
) async =>
|
||||||
_identityKeyStore.isTrustedIdentity(address, identityKey, direction);
|
_identityKeyStore.isTrustedIdentity(address, identityKey, direction);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -67,7 +74,9 @@ class ConnectSignalProtocolStore implements SignalProtocolStore {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> storeSession(
|
Future<void> storeSession(
|
||||||
SignalProtocolAddress address, SessionRecord record) async {
|
SignalProtocolAddress address,
|
||||||
|
SessionRecord record,
|
||||||
|
) async {
|
||||||
await sessionStore.storeSession(address, record);
|
await sessionStore.storeSession(address, record);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -95,7 +104,9 @@ class ConnectSignalProtocolStore implements SignalProtocolStore {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> storeSignedPreKey(
|
Future<void> storeSignedPreKey(
|
||||||
int signedPreKeyId, SignedPreKeyRecord record) async {
|
int signedPreKeyId,
|
||||||
|
SignedPreKeyRecord record,
|
||||||
|
) async {
|
||||||
await signedPreKeyStore.storeSignedPreKey(signedPreKeyId, record);
|
await signedPreKeyStore.storeSignedPreKey(signedPreKeyId, record);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,5 +16,8 @@ class MessageRetransmissions extends Table {
|
||||||
BlobColumn get pushData => blob().nullable()();
|
BlobColumn get pushData => blob().nullable()();
|
||||||
BlobColumn get encryptedHash => blob().nullable()();
|
BlobColumn get encryptedHash => blob().nullable()();
|
||||||
|
|
||||||
|
IntColumn get retryCount => integer().withDefault(const Constant(0))();
|
||||||
|
DateTimeColumn get lastRetry => dateTime().nullable()();
|
||||||
|
|
||||||
DateTimeColumn get acknowledgeByServerAt => dateTime().nullable()();
|
DateTimeColumn get acknowledgeByServerAt => dateTime().nullable()();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,8 @@ import 'package:twonly/src/utils/log.dart';
|
||||||
part 'twonly_database.g.dart';
|
part 'twonly_database.g.dart';
|
||||||
|
|
||||||
// You can then create a database class that includes this table
|
// You can then create a database class that includes this table
|
||||||
@DriftDatabase(tables: [
|
@DriftDatabase(
|
||||||
|
tables: [
|
||||||
Contacts,
|
Contacts,
|
||||||
Messages,
|
Messages,
|
||||||
MediaUploads,
|
MediaUploads,
|
||||||
|
|
@ -33,24 +34,27 @@ part 'twonly_database.g.dart';
|
||||||
SignalSessionStores,
|
SignalSessionStores,
|
||||||
SignalContactPreKeys,
|
SignalContactPreKeys,
|
||||||
SignalContactSignedPreKeys,
|
SignalContactSignedPreKeys,
|
||||||
MessageRetransmissions
|
MessageRetransmissions,
|
||||||
], daos: [
|
],
|
||||||
|
daos: [
|
||||||
MessagesDao,
|
MessagesDao,
|
||||||
ContactsDao,
|
ContactsDao,
|
||||||
MediaUploadsDao,
|
MediaUploadsDao,
|
||||||
SignalDao,
|
SignalDao,
|
||||||
MessageRetransmissionDao
|
MessageRetransmissionDao,
|
||||||
])
|
],
|
||||||
|
)
|
||||||
class TwonlyDatabase extends _$TwonlyDatabase {
|
class TwonlyDatabase extends _$TwonlyDatabase {
|
||||||
TwonlyDatabase([QueryExecutor? e])
|
TwonlyDatabase([QueryExecutor? e])
|
||||||
: super(
|
: super(
|
||||||
e ?? _openConnection(),
|
e ?? _openConnection(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// ignore: matching_super_parameters
|
||||||
TwonlyDatabase.forTesting(DatabaseConnection super.connection);
|
TwonlyDatabase.forTesting(DatabaseConnection super.connection);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get schemaVersion => 16;
|
int get schemaVersion => 17;
|
||||||
|
|
||||||
static QueryExecutor _openConnection() {
|
static QueryExecutor _openConnection() {
|
||||||
return driftDatabase(
|
return driftDatabase(
|
||||||
|
|
@ -74,17 +78,21 @@ class TwonlyDatabase extends _$TwonlyDatabase {
|
||||||
from2To3: (m, schema) async {
|
from2To3: (m, schema) async {
|
||||||
await m.addColumn(schema.contacts, schema.contacts.archived);
|
await m.addColumn(schema.contacts, schema.contacts.archived);
|
||||||
await m.addColumn(
|
await m.addColumn(
|
||||||
schema.contacts, schema.contacts.deleteMessagesAfterXMinutes);
|
schema.contacts,
|
||||||
|
schema.contacts.deleteMessagesAfterXMinutes,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
from3To4: (m, schema) async {
|
from3To4: (m, schema) async {
|
||||||
await m.createTable(schema.mediaUploads);
|
await m.createTable(schema.mediaUploads);
|
||||||
await m.alterTable(TableMigration(
|
await m.alterTable(
|
||||||
|
TableMigration(
|
||||||
schema.mediaUploads,
|
schema.mediaUploads,
|
||||||
columnTransformer: {
|
columnTransformer: {
|
||||||
schema.mediaUploads.metadata:
|
schema.mediaUploads.metadata:
|
||||||
schema.mediaUploads.metadata.cast<String>(),
|
schema.mediaUploads.metadata.cast<String>(),
|
||||||
},
|
},
|
||||||
));
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
from4To5: (m, schema) async {
|
from4To5: (m, schema) async {
|
||||||
await m.createTable(schema.mediaDownloads);
|
await m.createTable(schema.mediaDownloads);
|
||||||
|
|
@ -102,13 +110,15 @@ class TwonlyDatabase extends _$TwonlyDatabase {
|
||||||
await m.addColumn(schema.contacts, schema.contacts.lastFlameSync);
|
await m.addColumn(schema.contacts, schema.contacts.lastFlameSync);
|
||||||
},
|
},
|
||||||
from8To9: (m, schema) async {
|
from8To9: (m, schema) async {
|
||||||
await m.alterTable(TableMigration(
|
await m.alterTable(
|
||||||
|
TableMigration(
|
||||||
schema.mediaUploads,
|
schema.mediaUploads,
|
||||||
columnTransformer: {
|
columnTransformer: {
|
||||||
schema.mediaUploads.metadata:
|
schema.mediaUploads.metadata:
|
||||||
schema.mediaUploads.metadata.cast<String>(),
|
schema.mediaUploads.metadata.cast<String>(),
|
||||||
},
|
},
|
||||||
));
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
from9To10: (m, schema) async {
|
from9To10: (m, schema) async {
|
||||||
await m.createTable(schema.signalContactPreKeys);
|
await m.createTable(schema.signalContactPreKeys);
|
||||||
|
|
@ -119,26 +129,44 @@ class TwonlyDatabase extends _$TwonlyDatabase {
|
||||||
await m.createTable(schema.messageRetransmissions);
|
await m.createTable(schema.messageRetransmissions);
|
||||||
},
|
},
|
||||||
from11To12: (m, schema) async {
|
from11To12: (m, schema) async {
|
||||||
await m.addColumn(schema.messageRetransmissions,
|
await m.addColumn(
|
||||||
schema.messageRetransmissions.willNotGetACKByUser);
|
schema.messageRetransmissions,
|
||||||
|
schema.messageRetransmissions.willNotGetACKByUser,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
from12To13: (m, schema) async {
|
from12To13: (m, schema) async {
|
||||||
await m.dropColumn(
|
await m.dropColumn(
|
||||||
schema.messageRetransmissions, 'will_not_get_a_c_k_by_user');
|
schema.messageRetransmissions,
|
||||||
|
'will_not_get_a_c_k_by_user',
|
||||||
|
);
|
||||||
},
|
},
|
||||||
from13To14: (m, schema) async {
|
from13To14: (m, schema) async {
|
||||||
await m.addColumn(schema.messageRetransmissions,
|
await m.addColumn(
|
||||||
schema.messageRetransmissions.encryptedHash);
|
schema.messageRetransmissions,
|
||||||
|
schema.messageRetransmissions.encryptedHash,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
from14To15: (m, schema) async {
|
from14To15: (m, schema) async {
|
||||||
await m.dropColumn(schema.mediaUploads, 'upload_tokens');
|
await m.dropColumn(schema.mediaUploads, 'upload_tokens');
|
||||||
await m.dropColumn(schema.mediaUploads, 'already_notified');
|
await m.dropColumn(schema.mediaUploads, 'already_notified');
|
||||||
await m.addColumn(
|
await m.addColumn(
|
||||||
schema.messages, schema.messages.mediaRetransmissionState);
|
schema.messages,
|
||||||
|
schema.messages.mediaRetransmissionState,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
from15To16: (m, schema) async {
|
from15To16: (m, schema) async {
|
||||||
await m.deleteTable('media_downloads');
|
await m.deleteTable('media_downloads');
|
||||||
},
|
},
|
||||||
|
from16To17: (m, schema) async {
|
||||||
|
await m.addColumn(
|
||||||
|
schema.messageRetransmissions,
|
||||||
|
schema.messageRetransmissions.lastRetry,
|
||||||
|
);
|
||||||
|
await m.addColumn(
|
||||||
|
schema.messageRetransmissions,
|
||||||
|
schema.messageRetransmissions.retryCount,
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -150,8 +178,8 @@ class TwonlyDatabase extends _$TwonlyDatabase {
|
||||||
|
|
||||||
Future<void> printTableSizes() async {
|
Future<void> printTableSizes() async {
|
||||||
final result = await customSelect(
|
final result = await customSelect(
|
||||||
'SELECT name, SUM(pgsize) as size FROM dbstat GROUP BY name')
|
'SELECT name, SUM(pgsize) as size FROM dbstat GROUP BY name',
|
||||||
.get();
|
).get();
|
||||||
|
|
||||||
for (final row in result) {
|
for (final row in result) {
|
||||||
final tableName = row.read<String>('name');
|
final tableName = row.read<String>('name');
|
||||||
|
|
@ -173,11 +201,13 @@ class TwonlyDatabase extends _$TwonlyDatabase {
|
||||||
await delete(signalContactPreKeys).go();
|
await delete(signalContactPreKeys).go();
|
||||||
await delete(signalContactSignedPreKeys).go();
|
await delete(signalContactSignedPreKeys).go();
|
||||||
await (delete(signalPreKeyStores)
|
await (delete(signalPreKeyStores)
|
||||||
..where((t) => (t.createdAt.isSmallerThanValue(
|
..where(
|
||||||
|
(t) => (t.createdAt.isSmallerThanValue(
|
||||||
DateTime.now().subtract(
|
DateTime.now().subtract(
|
||||||
const Duration(days: 25),
|
const Duration(days: 25),
|
||||||
),
|
),
|
||||||
))))
|
)),
|
||||||
|
))
|
||||||
.go();
|
.go();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3983,6 +3983,20 @@ class $MessageRetransmissionsTable extends MessageRetransmissions
|
||||||
late final GeneratedColumn<Uint8List> encryptedHash =
|
late final GeneratedColumn<Uint8List> encryptedHash =
|
||||||
GeneratedColumn<Uint8List>('encrypted_hash', aliasedName, true,
|
GeneratedColumn<Uint8List>('encrypted_hash', aliasedName, true,
|
||||||
type: DriftSqlType.blob, requiredDuringInsert: false);
|
type: DriftSqlType.blob, requiredDuringInsert: false);
|
||||||
|
static const VerificationMeta _retryCountMeta =
|
||||||
|
const VerificationMeta('retryCount');
|
||||||
|
@override
|
||||||
|
late final GeneratedColumn<int> retryCount = GeneratedColumn<int>(
|
||||||
|
'retry_count', aliasedName, false,
|
||||||
|
type: DriftSqlType.int,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
defaultValue: const Constant(0));
|
||||||
|
static const VerificationMeta _lastRetryMeta =
|
||||||
|
const VerificationMeta('lastRetry');
|
||||||
|
@override
|
||||||
|
late final GeneratedColumn<DateTime> lastRetry = GeneratedColumn<DateTime>(
|
||||||
|
'last_retry', aliasedName, true,
|
||||||
|
type: DriftSqlType.dateTime, requiredDuringInsert: false);
|
||||||
static const VerificationMeta _acknowledgeByServerAtMeta =
|
static const VerificationMeta _acknowledgeByServerAtMeta =
|
||||||
const VerificationMeta('acknowledgeByServerAt');
|
const VerificationMeta('acknowledgeByServerAt');
|
||||||
@override
|
@override
|
||||||
|
|
@ -3997,6 +4011,8 @@ class $MessageRetransmissionsTable extends MessageRetransmissions
|
||||||
plaintextContent,
|
plaintextContent,
|
||||||
pushData,
|
pushData,
|
||||||
encryptedHash,
|
encryptedHash,
|
||||||
|
retryCount,
|
||||||
|
lastRetry,
|
||||||
acknowledgeByServerAt
|
acknowledgeByServerAt
|
||||||
];
|
];
|
||||||
@override
|
@override
|
||||||
|
|
@ -4044,6 +4060,16 @@ class $MessageRetransmissionsTable extends MessageRetransmissions
|
||||||
encryptedHash.isAcceptableOrUnknown(
|
encryptedHash.isAcceptableOrUnknown(
|
||||||
data['encrypted_hash']!, _encryptedHashMeta));
|
data['encrypted_hash']!, _encryptedHashMeta));
|
||||||
}
|
}
|
||||||
|
if (data.containsKey('retry_count')) {
|
||||||
|
context.handle(
|
||||||
|
_retryCountMeta,
|
||||||
|
retryCount.isAcceptableOrUnknown(
|
||||||
|
data['retry_count']!, _retryCountMeta));
|
||||||
|
}
|
||||||
|
if (data.containsKey('last_retry')) {
|
||||||
|
context.handle(_lastRetryMeta,
|
||||||
|
lastRetry.isAcceptableOrUnknown(data['last_retry']!, _lastRetryMeta));
|
||||||
|
}
|
||||||
if (data.containsKey('acknowledge_by_server_at')) {
|
if (data.containsKey('acknowledge_by_server_at')) {
|
||||||
context.handle(
|
context.handle(
|
||||||
_acknowledgeByServerAtMeta,
|
_acknowledgeByServerAtMeta,
|
||||||
|
|
@ -4071,6 +4097,10 @@ class $MessageRetransmissionsTable extends MessageRetransmissions
|
||||||
.read(DriftSqlType.blob, data['${effectivePrefix}push_data']),
|
.read(DriftSqlType.blob, data['${effectivePrefix}push_data']),
|
||||||
encryptedHash: attachedDatabase.typeMapping
|
encryptedHash: attachedDatabase.typeMapping
|
||||||
.read(DriftSqlType.blob, data['${effectivePrefix}encrypted_hash']),
|
.read(DriftSqlType.blob, data['${effectivePrefix}encrypted_hash']),
|
||||||
|
retryCount: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.int, data['${effectivePrefix}retry_count'])!,
|
||||||
|
lastRetry: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.dateTime, data['${effectivePrefix}last_retry']),
|
||||||
acknowledgeByServerAt: attachedDatabase.typeMapping.read(
|
acknowledgeByServerAt: attachedDatabase.typeMapping.read(
|
||||||
DriftSqlType.dateTime,
|
DriftSqlType.dateTime,
|
||||||
data['${effectivePrefix}acknowledge_by_server_at']),
|
data['${effectivePrefix}acknowledge_by_server_at']),
|
||||||
|
|
@ -4091,6 +4121,8 @@ class MessageRetransmission extends DataClass
|
||||||
final Uint8List plaintextContent;
|
final Uint8List plaintextContent;
|
||||||
final Uint8List? pushData;
|
final Uint8List? pushData;
|
||||||
final Uint8List? encryptedHash;
|
final Uint8List? encryptedHash;
|
||||||
|
final int retryCount;
|
||||||
|
final DateTime? lastRetry;
|
||||||
final DateTime? acknowledgeByServerAt;
|
final DateTime? acknowledgeByServerAt;
|
||||||
const MessageRetransmission(
|
const MessageRetransmission(
|
||||||
{required this.retransmissionId,
|
{required this.retransmissionId,
|
||||||
|
|
@ -4099,6 +4131,8 @@ class MessageRetransmission extends DataClass
|
||||||
required this.plaintextContent,
|
required this.plaintextContent,
|
||||||
this.pushData,
|
this.pushData,
|
||||||
this.encryptedHash,
|
this.encryptedHash,
|
||||||
|
required this.retryCount,
|
||||||
|
this.lastRetry,
|
||||||
this.acknowledgeByServerAt});
|
this.acknowledgeByServerAt});
|
||||||
@override
|
@override
|
||||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
|
|
@ -4115,6 +4149,10 @@ class MessageRetransmission extends DataClass
|
||||||
if (!nullToAbsent || encryptedHash != null) {
|
if (!nullToAbsent || encryptedHash != null) {
|
||||||
map['encrypted_hash'] = Variable<Uint8List>(encryptedHash);
|
map['encrypted_hash'] = Variable<Uint8List>(encryptedHash);
|
||||||
}
|
}
|
||||||
|
map['retry_count'] = Variable<int>(retryCount);
|
||||||
|
if (!nullToAbsent || lastRetry != null) {
|
||||||
|
map['last_retry'] = Variable<DateTime>(lastRetry);
|
||||||
|
}
|
||||||
if (!nullToAbsent || acknowledgeByServerAt != null) {
|
if (!nullToAbsent || acknowledgeByServerAt != null) {
|
||||||
map['acknowledge_by_server_at'] =
|
map['acknowledge_by_server_at'] =
|
||||||
Variable<DateTime>(acknowledgeByServerAt);
|
Variable<DateTime>(acknowledgeByServerAt);
|
||||||
|
|
@ -4136,6 +4174,10 @@ class MessageRetransmission extends DataClass
|
||||||
encryptedHash: encryptedHash == null && nullToAbsent
|
encryptedHash: encryptedHash == null && nullToAbsent
|
||||||
? const Value.absent()
|
? const Value.absent()
|
||||||
: Value(encryptedHash),
|
: Value(encryptedHash),
|
||||||
|
retryCount: Value(retryCount),
|
||||||
|
lastRetry: lastRetry == null && nullToAbsent
|
||||||
|
? const Value.absent()
|
||||||
|
: Value(lastRetry),
|
||||||
acknowledgeByServerAt: acknowledgeByServerAt == null && nullToAbsent
|
acknowledgeByServerAt: acknowledgeByServerAt == null && nullToAbsent
|
||||||
? const Value.absent()
|
? const Value.absent()
|
||||||
: Value(acknowledgeByServerAt),
|
: Value(acknowledgeByServerAt),
|
||||||
|
|
@ -4153,6 +4195,8 @@ class MessageRetransmission extends DataClass
|
||||||
serializer.fromJson<Uint8List>(json['plaintextContent']),
|
serializer.fromJson<Uint8List>(json['plaintextContent']),
|
||||||
pushData: serializer.fromJson<Uint8List?>(json['pushData']),
|
pushData: serializer.fromJson<Uint8List?>(json['pushData']),
|
||||||
encryptedHash: serializer.fromJson<Uint8List?>(json['encryptedHash']),
|
encryptedHash: serializer.fromJson<Uint8List?>(json['encryptedHash']),
|
||||||
|
retryCount: serializer.fromJson<int>(json['retryCount']),
|
||||||
|
lastRetry: serializer.fromJson<DateTime?>(json['lastRetry']),
|
||||||
acknowledgeByServerAt:
|
acknowledgeByServerAt:
|
||||||
serializer.fromJson<DateTime?>(json['acknowledgeByServerAt']),
|
serializer.fromJson<DateTime?>(json['acknowledgeByServerAt']),
|
||||||
);
|
);
|
||||||
|
|
@ -4167,6 +4211,8 @@ class MessageRetransmission extends DataClass
|
||||||
'plaintextContent': serializer.toJson<Uint8List>(plaintextContent),
|
'plaintextContent': serializer.toJson<Uint8List>(plaintextContent),
|
||||||
'pushData': serializer.toJson<Uint8List?>(pushData),
|
'pushData': serializer.toJson<Uint8List?>(pushData),
|
||||||
'encryptedHash': serializer.toJson<Uint8List?>(encryptedHash),
|
'encryptedHash': serializer.toJson<Uint8List?>(encryptedHash),
|
||||||
|
'retryCount': serializer.toJson<int>(retryCount),
|
||||||
|
'lastRetry': serializer.toJson<DateTime?>(lastRetry),
|
||||||
'acknowledgeByServerAt':
|
'acknowledgeByServerAt':
|
||||||
serializer.toJson<DateTime?>(acknowledgeByServerAt),
|
serializer.toJson<DateTime?>(acknowledgeByServerAt),
|
||||||
};
|
};
|
||||||
|
|
@ -4179,6 +4225,8 @@ class MessageRetransmission extends DataClass
|
||||||
Uint8List? plaintextContent,
|
Uint8List? plaintextContent,
|
||||||
Value<Uint8List?> pushData = const Value.absent(),
|
Value<Uint8List?> pushData = const Value.absent(),
|
||||||
Value<Uint8List?> encryptedHash = const Value.absent(),
|
Value<Uint8List?> encryptedHash = const Value.absent(),
|
||||||
|
int? retryCount,
|
||||||
|
Value<DateTime?> lastRetry = const Value.absent(),
|
||||||
Value<DateTime?> acknowledgeByServerAt = const Value.absent()}) =>
|
Value<DateTime?> acknowledgeByServerAt = const Value.absent()}) =>
|
||||||
MessageRetransmission(
|
MessageRetransmission(
|
||||||
retransmissionId: retransmissionId ?? this.retransmissionId,
|
retransmissionId: retransmissionId ?? this.retransmissionId,
|
||||||
|
|
@ -4188,6 +4236,8 @@ class MessageRetransmission extends DataClass
|
||||||
pushData: pushData.present ? pushData.value : this.pushData,
|
pushData: pushData.present ? pushData.value : this.pushData,
|
||||||
encryptedHash:
|
encryptedHash:
|
||||||
encryptedHash.present ? encryptedHash.value : this.encryptedHash,
|
encryptedHash.present ? encryptedHash.value : this.encryptedHash,
|
||||||
|
retryCount: retryCount ?? this.retryCount,
|
||||||
|
lastRetry: lastRetry.present ? lastRetry.value : this.lastRetry,
|
||||||
acknowledgeByServerAt: acknowledgeByServerAt.present
|
acknowledgeByServerAt: acknowledgeByServerAt.present
|
||||||
? acknowledgeByServerAt.value
|
? acknowledgeByServerAt.value
|
||||||
: this.acknowledgeByServerAt,
|
: this.acknowledgeByServerAt,
|
||||||
|
|
@ -4207,6 +4257,9 @@ class MessageRetransmission extends DataClass
|
||||||
encryptedHash: data.encryptedHash.present
|
encryptedHash: data.encryptedHash.present
|
||||||
? data.encryptedHash.value
|
? data.encryptedHash.value
|
||||||
: this.encryptedHash,
|
: this.encryptedHash,
|
||||||
|
retryCount:
|
||||||
|
data.retryCount.present ? data.retryCount.value : this.retryCount,
|
||||||
|
lastRetry: data.lastRetry.present ? data.lastRetry.value : this.lastRetry,
|
||||||
acknowledgeByServerAt: data.acknowledgeByServerAt.present
|
acknowledgeByServerAt: data.acknowledgeByServerAt.present
|
||||||
? data.acknowledgeByServerAt.value
|
? data.acknowledgeByServerAt.value
|
||||||
: this.acknowledgeByServerAt,
|
: this.acknowledgeByServerAt,
|
||||||
|
|
@ -4222,6 +4275,8 @@ class MessageRetransmission extends DataClass
|
||||||
..write('plaintextContent: $plaintextContent, ')
|
..write('plaintextContent: $plaintextContent, ')
|
||||||
..write('pushData: $pushData, ')
|
..write('pushData: $pushData, ')
|
||||||
..write('encryptedHash: $encryptedHash, ')
|
..write('encryptedHash: $encryptedHash, ')
|
||||||
|
..write('retryCount: $retryCount, ')
|
||||||
|
..write('lastRetry: $lastRetry, ')
|
||||||
..write('acknowledgeByServerAt: $acknowledgeByServerAt')
|
..write('acknowledgeByServerAt: $acknowledgeByServerAt')
|
||||||
..write(')'))
|
..write(')'))
|
||||||
.toString();
|
.toString();
|
||||||
|
|
@ -4235,6 +4290,8 @@ class MessageRetransmission extends DataClass
|
||||||
$driftBlobEquality.hash(plaintextContent),
|
$driftBlobEquality.hash(plaintextContent),
|
||||||
$driftBlobEquality.hash(pushData),
|
$driftBlobEquality.hash(pushData),
|
||||||
$driftBlobEquality.hash(encryptedHash),
|
$driftBlobEquality.hash(encryptedHash),
|
||||||
|
retryCount,
|
||||||
|
lastRetry,
|
||||||
acknowledgeByServerAt);
|
acknowledgeByServerAt);
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
|
|
@ -4247,6 +4304,8 @@ class MessageRetransmission extends DataClass
|
||||||
other.plaintextContent, this.plaintextContent) &&
|
other.plaintextContent, this.plaintextContent) &&
|
||||||
$driftBlobEquality.equals(other.pushData, this.pushData) &&
|
$driftBlobEquality.equals(other.pushData, this.pushData) &&
|
||||||
$driftBlobEquality.equals(other.encryptedHash, this.encryptedHash) &&
|
$driftBlobEquality.equals(other.encryptedHash, this.encryptedHash) &&
|
||||||
|
other.retryCount == this.retryCount &&
|
||||||
|
other.lastRetry == this.lastRetry &&
|
||||||
other.acknowledgeByServerAt == this.acknowledgeByServerAt);
|
other.acknowledgeByServerAt == this.acknowledgeByServerAt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -4258,6 +4317,8 @@ class MessageRetransmissionsCompanion
|
||||||
final Value<Uint8List> plaintextContent;
|
final Value<Uint8List> plaintextContent;
|
||||||
final Value<Uint8List?> pushData;
|
final Value<Uint8List?> pushData;
|
||||||
final Value<Uint8List?> encryptedHash;
|
final Value<Uint8List?> encryptedHash;
|
||||||
|
final Value<int> retryCount;
|
||||||
|
final Value<DateTime?> lastRetry;
|
||||||
final Value<DateTime?> acknowledgeByServerAt;
|
final Value<DateTime?> acknowledgeByServerAt;
|
||||||
const MessageRetransmissionsCompanion({
|
const MessageRetransmissionsCompanion({
|
||||||
this.retransmissionId = const Value.absent(),
|
this.retransmissionId = const Value.absent(),
|
||||||
|
|
@ -4266,6 +4327,8 @@ class MessageRetransmissionsCompanion
|
||||||
this.plaintextContent = const Value.absent(),
|
this.plaintextContent = const Value.absent(),
|
||||||
this.pushData = const Value.absent(),
|
this.pushData = const Value.absent(),
|
||||||
this.encryptedHash = const Value.absent(),
|
this.encryptedHash = const Value.absent(),
|
||||||
|
this.retryCount = const Value.absent(),
|
||||||
|
this.lastRetry = const Value.absent(),
|
||||||
this.acknowledgeByServerAt = const Value.absent(),
|
this.acknowledgeByServerAt = const Value.absent(),
|
||||||
});
|
});
|
||||||
MessageRetransmissionsCompanion.insert({
|
MessageRetransmissionsCompanion.insert({
|
||||||
|
|
@ -4275,6 +4338,8 @@ class MessageRetransmissionsCompanion
|
||||||
required Uint8List plaintextContent,
|
required Uint8List plaintextContent,
|
||||||
this.pushData = const Value.absent(),
|
this.pushData = const Value.absent(),
|
||||||
this.encryptedHash = const Value.absent(),
|
this.encryptedHash = const Value.absent(),
|
||||||
|
this.retryCount = const Value.absent(),
|
||||||
|
this.lastRetry = const Value.absent(),
|
||||||
this.acknowledgeByServerAt = const Value.absent(),
|
this.acknowledgeByServerAt = const Value.absent(),
|
||||||
}) : contactId = Value(contactId),
|
}) : contactId = Value(contactId),
|
||||||
plaintextContent = Value(plaintextContent);
|
plaintextContent = Value(plaintextContent);
|
||||||
|
|
@ -4285,6 +4350,8 @@ class MessageRetransmissionsCompanion
|
||||||
Expression<Uint8List>? plaintextContent,
|
Expression<Uint8List>? plaintextContent,
|
||||||
Expression<Uint8List>? pushData,
|
Expression<Uint8List>? pushData,
|
||||||
Expression<Uint8List>? encryptedHash,
|
Expression<Uint8List>? encryptedHash,
|
||||||
|
Expression<int>? retryCount,
|
||||||
|
Expression<DateTime>? lastRetry,
|
||||||
Expression<DateTime>? acknowledgeByServerAt,
|
Expression<DateTime>? acknowledgeByServerAt,
|
||||||
}) {
|
}) {
|
||||||
return RawValuesInsertable({
|
return RawValuesInsertable({
|
||||||
|
|
@ -4294,6 +4361,8 @@ class MessageRetransmissionsCompanion
|
||||||
if (plaintextContent != null) 'plaintext_content': plaintextContent,
|
if (plaintextContent != null) 'plaintext_content': plaintextContent,
|
||||||
if (pushData != null) 'push_data': pushData,
|
if (pushData != null) 'push_data': pushData,
|
||||||
if (encryptedHash != null) 'encrypted_hash': encryptedHash,
|
if (encryptedHash != null) 'encrypted_hash': encryptedHash,
|
||||||
|
if (retryCount != null) 'retry_count': retryCount,
|
||||||
|
if (lastRetry != null) 'last_retry': lastRetry,
|
||||||
if (acknowledgeByServerAt != null)
|
if (acknowledgeByServerAt != null)
|
||||||
'acknowledge_by_server_at': acknowledgeByServerAt,
|
'acknowledge_by_server_at': acknowledgeByServerAt,
|
||||||
});
|
});
|
||||||
|
|
@ -4306,6 +4375,8 @@ class MessageRetransmissionsCompanion
|
||||||
Value<Uint8List>? plaintextContent,
|
Value<Uint8List>? plaintextContent,
|
||||||
Value<Uint8List?>? pushData,
|
Value<Uint8List?>? pushData,
|
||||||
Value<Uint8List?>? encryptedHash,
|
Value<Uint8List?>? encryptedHash,
|
||||||
|
Value<int>? retryCount,
|
||||||
|
Value<DateTime?>? lastRetry,
|
||||||
Value<DateTime?>? acknowledgeByServerAt}) {
|
Value<DateTime?>? acknowledgeByServerAt}) {
|
||||||
return MessageRetransmissionsCompanion(
|
return MessageRetransmissionsCompanion(
|
||||||
retransmissionId: retransmissionId ?? this.retransmissionId,
|
retransmissionId: retransmissionId ?? this.retransmissionId,
|
||||||
|
|
@ -4314,6 +4385,8 @@ class MessageRetransmissionsCompanion
|
||||||
plaintextContent: plaintextContent ?? this.plaintextContent,
|
plaintextContent: plaintextContent ?? this.plaintextContent,
|
||||||
pushData: pushData ?? this.pushData,
|
pushData: pushData ?? this.pushData,
|
||||||
encryptedHash: encryptedHash ?? this.encryptedHash,
|
encryptedHash: encryptedHash ?? this.encryptedHash,
|
||||||
|
retryCount: retryCount ?? this.retryCount,
|
||||||
|
lastRetry: lastRetry ?? this.lastRetry,
|
||||||
acknowledgeByServerAt:
|
acknowledgeByServerAt:
|
||||||
acknowledgeByServerAt ?? this.acknowledgeByServerAt,
|
acknowledgeByServerAt ?? this.acknowledgeByServerAt,
|
||||||
);
|
);
|
||||||
|
|
@ -4340,6 +4413,12 @@ class MessageRetransmissionsCompanion
|
||||||
if (encryptedHash.present) {
|
if (encryptedHash.present) {
|
||||||
map['encrypted_hash'] = Variable<Uint8List>(encryptedHash.value);
|
map['encrypted_hash'] = Variable<Uint8List>(encryptedHash.value);
|
||||||
}
|
}
|
||||||
|
if (retryCount.present) {
|
||||||
|
map['retry_count'] = Variable<int>(retryCount.value);
|
||||||
|
}
|
||||||
|
if (lastRetry.present) {
|
||||||
|
map['last_retry'] = Variable<DateTime>(lastRetry.value);
|
||||||
|
}
|
||||||
if (acknowledgeByServerAt.present) {
|
if (acknowledgeByServerAt.present) {
|
||||||
map['acknowledge_by_server_at'] =
|
map['acknowledge_by_server_at'] =
|
||||||
Variable<DateTime>(acknowledgeByServerAt.value);
|
Variable<DateTime>(acknowledgeByServerAt.value);
|
||||||
|
|
@ -4356,6 +4435,8 @@ class MessageRetransmissionsCompanion
|
||||||
..write('plaintextContent: $plaintextContent, ')
|
..write('plaintextContent: $plaintextContent, ')
|
||||||
..write('pushData: $pushData, ')
|
..write('pushData: $pushData, ')
|
||||||
..write('encryptedHash: $encryptedHash, ')
|
..write('encryptedHash: $encryptedHash, ')
|
||||||
|
..write('retryCount: $retryCount, ')
|
||||||
|
..write('lastRetry: $lastRetry, ')
|
||||||
..write('acknowledgeByServerAt: $acknowledgeByServerAt')
|
..write('acknowledgeByServerAt: $acknowledgeByServerAt')
|
||||||
..write(')'))
|
..write(')'))
|
||||||
.toString();
|
.toString();
|
||||||
|
|
@ -6752,6 +6833,8 @@ typedef $$MessageRetransmissionsTableCreateCompanionBuilder
|
||||||
required Uint8List plaintextContent,
|
required Uint8List plaintextContent,
|
||||||
Value<Uint8List?> pushData,
|
Value<Uint8List?> pushData,
|
||||||
Value<Uint8List?> encryptedHash,
|
Value<Uint8List?> encryptedHash,
|
||||||
|
Value<int> retryCount,
|
||||||
|
Value<DateTime?> lastRetry,
|
||||||
Value<DateTime?> acknowledgeByServerAt,
|
Value<DateTime?> acknowledgeByServerAt,
|
||||||
});
|
});
|
||||||
typedef $$MessageRetransmissionsTableUpdateCompanionBuilder
|
typedef $$MessageRetransmissionsTableUpdateCompanionBuilder
|
||||||
|
|
@ -6762,6 +6845,8 @@ typedef $$MessageRetransmissionsTableUpdateCompanionBuilder
|
||||||
Value<Uint8List> plaintextContent,
|
Value<Uint8List> plaintextContent,
|
||||||
Value<Uint8List?> pushData,
|
Value<Uint8List?> pushData,
|
||||||
Value<Uint8List?> encryptedHash,
|
Value<Uint8List?> encryptedHash,
|
||||||
|
Value<int> retryCount,
|
||||||
|
Value<DateTime?> lastRetry,
|
||||||
Value<DateTime?> acknowledgeByServerAt,
|
Value<DateTime?> acknowledgeByServerAt,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -6824,6 +6909,12 @@ class $$MessageRetransmissionsTableFilterComposer
|
||||||
ColumnFilters<Uint8List> get encryptedHash => $composableBuilder(
|
ColumnFilters<Uint8List> get encryptedHash => $composableBuilder(
|
||||||
column: $table.encryptedHash, builder: (column) => ColumnFilters(column));
|
column: $table.encryptedHash, builder: (column) => ColumnFilters(column));
|
||||||
|
|
||||||
|
ColumnFilters<int> get retryCount => $composableBuilder(
|
||||||
|
column: $table.retryCount, builder: (column) => ColumnFilters(column));
|
||||||
|
|
||||||
|
ColumnFilters<DateTime> get lastRetry => $composableBuilder(
|
||||||
|
column: $table.lastRetry, builder: (column) => ColumnFilters(column));
|
||||||
|
|
||||||
ColumnFilters<DateTime> get acknowledgeByServerAt => $composableBuilder(
|
ColumnFilters<DateTime> get acknowledgeByServerAt => $composableBuilder(
|
||||||
column: $table.acknowledgeByServerAt,
|
column: $table.acknowledgeByServerAt,
|
||||||
builder: (column) => ColumnFilters(column));
|
builder: (column) => ColumnFilters(column));
|
||||||
|
|
@ -6893,6 +6984,12 @@ class $$MessageRetransmissionsTableOrderingComposer
|
||||||
column: $table.encryptedHash,
|
column: $table.encryptedHash,
|
||||||
builder: (column) => ColumnOrderings(column));
|
builder: (column) => ColumnOrderings(column));
|
||||||
|
|
||||||
|
ColumnOrderings<int> get retryCount => $composableBuilder(
|
||||||
|
column: $table.retryCount, builder: (column) => ColumnOrderings(column));
|
||||||
|
|
||||||
|
ColumnOrderings<DateTime> get lastRetry => $composableBuilder(
|
||||||
|
column: $table.lastRetry, builder: (column) => ColumnOrderings(column));
|
||||||
|
|
||||||
ColumnOrderings<DateTime> get acknowledgeByServerAt => $composableBuilder(
|
ColumnOrderings<DateTime> get acknowledgeByServerAt => $composableBuilder(
|
||||||
column: $table.acknowledgeByServerAt,
|
column: $table.acknowledgeByServerAt,
|
||||||
builder: (column) => ColumnOrderings(column));
|
builder: (column) => ColumnOrderings(column));
|
||||||
|
|
@ -6959,6 +7056,12 @@ class $$MessageRetransmissionsTableAnnotationComposer
|
||||||
GeneratedColumn<Uint8List> get encryptedHash => $composableBuilder(
|
GeneratedColumn<Uint8List> get encryptedHash => $composableBuilder(
|
||||||
column: $table.encryptedHash, builder: (column) => column);
|
column: $table.encryptedHash, builder: (column) => column);
|
||||||
|
|
||||||
|
GeneratedColumn<int> get retryCount => $composableBuilder(
|
||||||
|
column: $table.retryCount, builder: (column) => column);
|
||||||
|
|
||||||
|
GeneratedColumn<DateTime> get lastRetry =>
|
||||||
|
$composableBuilder(column: $table.lastRetry, builder: (column) => column);
|
||||||
|
|
||||||
GeneratedColumn<DateTime> get acknowledgeByServerAt => $composableBuilder(
|
GeneratedColumn<DateTime> get acknowledgeByServerAt => $composableBuilder(
|
||||||
column: $table.acknowledgeByServerAt, builder: (column) => column);
|
column: $table.acknowledgeByServerAt, builder: (column) => column);
|
||||||
|
|
||||||
|
|
@ -7036,6 +7139,8 @@ class $$MessageRetransmissionsTableTableManager extends RootTableManager<
|
||||||
Value<Uint8List> plaintextContent = const Value.absent(),
|
Value<Uint8List> plaintextContent = const Value.absent(),
|
||||||
Value<Uint8List?> pushData = const Value.absent(),
|
Value<Uint8List?> pushData = const Value.absent(),
|
||||||
Value<Uint8List?> encryptedHash = const Value.absent(),
|
Value<Uint8List?> encryptedHash = const Value.absent(),
|
||||||
|
Value<int> retryCount = const Value.absent(),
|
||||||
|
Value<DateTime?> lastRetry = const Value.absent(),
|
||||||
Value<DateTime?> acknowledgeByServerAt = const Value.absent(),
|
Value<DateTime?> acknowledgeByServerAt = const Value.absent(),
|
||||||
}) =>
|
}) =>
|
||||||
MessageRetransmissionsCompanion(
|
MessageRetransmissionsCompanion(
|
||||||
|
|
@ -7045,6 +7150,8 @@ class $$MessageRetransmissionsTableTableManager extends RootTableManager<
|
||||||
plaintextContent: plaintextContent,
|
plaintextContent: plaintextContent,
|
||||||
pushData: pushData,
|
pushData: pushData,
|
||||||
encryptedHash: encryptedHash,
|
encryptedHash: encryptedHash,
|
||||||
|
retryCount: retryCount,
|
||||||
|
lastRetry: lastRetry,
|
||||||
acknowledgeByServerAt: acknowledgeByServerAt,
|
acknowledgeByServerAt: acknowledgeByServerAt,
|
||||||
),
|
),
|
||||||
createCompanionCallback: ({
|
createCompanionCallback: ({
|
||||||
|
|
@ -7054,6 +7161,8 @@ class $$MessageRetransmissionsTableTableManager extends RootTableManager<
|
||||||
required Uint8List plaintextContent,
|
required Uint8List plaintextContent,
|
||||||
Value<Uint8List?> pushData = const Value.absent(),
|
Value<Uint8List?> pushData = const Value.absent(),
|
||||||
Value<Uint8List?> encryptedHash = const Value.absent(),
|
Value<Uint8List?> encryptedHash = const Value.absent(),
|
||||||
|
Value<int> retryCount = const Value.absent(),
|
||||||
|
Value<DateTime?> lastRetry = const Value.absent(),
|
||||||
Value<DateTime?> acknowledgeByServerAt = const Value.absent(),
|
Value<DateTime?> acknowledgeByServerAt = const Value.absent(),
|
||||||
}) =>
|
}) =>
|
||||||
MessageRetransmissionsCompanion.insert(
|
MessageRetransmissionsCompanion.insert(
|
||||||
|
|
@ -7063,6 +7172,8 @@ class $$MessageRetransmissionsTableTableManager extends RootTableManager<
|
||||||
plaintextContent: plaintextContent,
|
plaintextContent: plaintextContent,
|
||||||
pushData: pushData,
|
pushData: pushData,
|
||||||
encryptedHash: encryptedHash,
|
encryptedHash: encryptedHash,
|
||||||
|
retryCount: retryCount,
|
||||||
|
lastRetry: lastRetry,
|
||||||
acknowledgeByServerAt: acknowledgeByServerAt,
|
acknowledgeByServerAt: acknowledgeByServerAt,
|
||||||
),
|
),
|
||||||
withReferenceMapper: (p0) => p0
|
withReferenceMapper: (p0) => p0
|
||||||
|
|
|
||||||
|
|
@ -3713,6 +3713,253 @@ final class Schema16 extends i0.VersionedSchema {
|
||||||
alias: null);
|
alias: null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final class Schema17 extends i0.VersionedSchema {
|
||||||
|
Schema17({required super.database}) : super(version: 17);
|
||||||
|
@override
|
||||||
|
late final List<i1.DatabaseSchemaEntity> entities = [
|
||||||
|
contacts,
|
||||||
|
messages,
|
||||||
|
mediaUploads,
|
||||||
|
signalIdentityKeyStores,
|
||||||
|
signalPreKeyStores,
|
||||||
|
signalSenderKeyStores,
|
||||||
|
signalSessionStores,
|
||||||
|
signalContactPreKeys,
|
||||||
|
signalContactSignedPreKeys,
|
||||||
|
messageRetransmissions,
|
||||||
|
];
|
||||||
|
late final Shape13 contacts = Shape13(
|
||||||
|
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_39,
|
||||||
|
_column_53,
|
||||||
|
_column_57,
|
||||||
|
_column_54,
|
||||||
|
_column_40,
|
||||||
|
_column_10,
|
||||||
|
_column_11,
|
||||||
|
_column_12,
|
||||||
|
_column_13,
|
||||||
|
_column_14,
|
||||||
|
_column_55,
|
||||||
|
_column_15,
|
||||||
|
_column_16,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape19 messages = Shape19(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'messages',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [
|
||||||
|
_column_17,
|
||||||
|
_column_18,
|
||||||
|
_column_19,
|
||||||
|
_column_48,
|
||||||
|
_column_49,
|
||||||
|
_column_20,
|
||||||
|
_column_21,
|
||||||
|
_column_22,
|
||||||
|
_column_52,
|
||||||
|
_column_23,
|
||||||
|
_column_24,
|
||||||
|
_column_25,
|
||||||
|
_column_70,
|
||||||
|
_column_26,
|
||||||
|
_column_27,
|
||||||
|
_column_28,
|
||||||
|
_column_29,
|
||||||
|
_column_30,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape20 mediaUploads = Shape20(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'media_uploads',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [
|
||||||
|
_column_41,
|
||||||
|
_column_42,
|
||||||
|
_column_56,
|
||||||
|
_column_44,
|
||||||
|
_column_45,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape2 signalIdentityKeyStores = Shape2(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'signal_identity_key_stores',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [
|
||||||
|
'PRIMARY KEY(device_id, name)',
|
||||||
|
],
|
||||||
|
columns: [
|
||||||
|
_column_31,
|
||||||
|
_column_32,
|
||||||
|
_column_33,
|
||||||
|
_column_10,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape3 signalPreKeyStores = Shape3(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'signal_pre_key_stores',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [
|
||||||
|
'PRIMARY KEY(pre_key_id)',
|
||||||
|
],
|
||||||
|
columns: [
|
||||||
|
_column_34,
|
||||||
|
_column_35,
|
||||||
|
_column_10,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape4 signalSenderKeyStores = Shape4(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'signal_sender_key_stores',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [
|
||||||
|
'PRIMARY KEY(sender_key_name)',
|
||||||
|
],
|
||||||
|
columns: [
|
||||||
|
_column_36,
|
||||||
|
_column_37,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape5 signalSessionStores = Shape5(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'signal_session_stores',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [
|
||||||
|
'PRIMARY KEY(device_id, name)',
|
||||||
|
],
|
||||||
|
columns: [
|
||||||
|
_column_31,
|
||||||
|
_column_32,
|
||||||
|
_column_38,
|
||||||
|
_column_10,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape14 signalContactPreKeys = Shape14(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'signal_contact_pre_keys',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [
|
||||||
|
'PRIMARY KEY(contact_id, pre_key_id)',
|
||||||
|
],
|
||||||
|
columns: [
|
||||||
|
_column_58,
|
||||||
|
_column_34,
|
||||||
|
_column_35,
|
||||||
|
_column_10,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape15 signalContactSignedPreKeys = Shape15(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'signal_contact_signed_pre_keys',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [
|
||||||
|
'PRIMARY KEY(contact_id)',
|
||||||
|
],
|
||||||
|
columns: [
|
||||||
|
_column_58,
|
||||||
|
_column_59,
|
||||||
|
_column_60,
|
||||||
|
_column_61,
|
||||||
|
_column_10,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape21 messageRetransmissions = Shape21(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'message_retransmissions',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [
|
||||||
|
_column_62,
|
||||||
|
_column_63,
|
||||||
|
_column_64,
|
||||||
|
_column_65,
|
||||||
|
_column_66,
|
||||||
|
_column_69,
|
||||||
|
_column_71,
|
||||||
|
_column_72,
|
||||||
|
_column_67,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Shape21 extends i0.VersionedTable {
|
||||||
|
Shape21({required super.source, required super.alias}) : super.aliased();
|
||||||
|
i1.GeneratedColumn<int> get retransmissionId =>
|
||||||
|
columnsByName['retransmission_id']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<int> get contactId =>
|
||||||
|
columnsByName['contact_id']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<int> get messageId =>
|
||||||
|
columnsByName['message_id']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<i2.Uint8List> get plaintextContent =>
|
||||||
|
columnsByName['plaintext_content']! as i1.GeneratedColumn<i2.Uint8List>;
|
||||||
|
i1.GeneratedColumn<i2.Uint8List> get pushData =>
|
||||||
|
columnsByName['push_data']! as i1.GeneratedColumn<i2.Uint8List>;
|
||||||
|
i1.GeneratedColumn<i2.Uint8List> get encryptedHash =>
|
||||||
|
columnsByName['encrypted_hash']! as i1.GeneratedColumn<i2.Uint8List>;
|
||||||
|
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 acknowledgeByServerAt =>
|
||||||
|
columnsByName['acknowledge_by_server_at']!
|
||||||
|
as i1.GeneratedColumn<DateTime>;
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.GeneratedColumn<int> _column_71(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<int>('retry_count', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.int, defaultValue: const CustomExpression('0'));
|
||||||
|
i1.GeneratedColumn<DateTime> _column_72(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<DateTime>('last_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,
|
||||||
|
|
@ -3729,6 +3976,7 @@ i0.MigrationStepWithVersion migrationSteps({
|
||||||
required Future<void> Function(i1.Migrator m, Schema14 schema) from13To14,
|
required Future<void> Function(i1.Migrator m, Schema14 schema) from13To14,
|
||||||
required Future<void> Function(i1.Migrator m, Schema15 schema) from14To15,
|
required Future<void> Function(i1.Migrator m, Schema15 schema) from14To15,
|
||||||
required Future<void> Function(i1.Migrator m, Schema16 schema) from15To16,
|
required Future<void> Function(i1.Migrator m, Schema16 schema) from15To16,
|
||||||
|
required Future<void> Function(i1.Migrator m, Schema17 schema) from16To17,
|
||||||
}) {
|
}) {
|
||||||
return (currentVersion, database) async {
|
return (currentVersion, database) async {
|
||||||
switch (currentVersion) {
|
switch (currentVersion) {
|
||||||
|
|
@ -3807,6 +4055,11 @@ i0.MigrationStepWithVersion migrationSteps({
|
||||||
final migrator = i1.Migrator(database, schema);
|
final migrator = i1.Migrator(database, schema);
|
||||||
await from15To16(migrator, schema);
|
await from15To16(migrator, schema);
|
||||||
return 16;
|
return 16;
|
||||||
|
case 16:
|
||||||
|
final schema = Schema17(database: database);
|
||||||
|
final migrator = i1.Migrator(database, schema);
|
||||||
|
await from16To17(migrator, schema);
|
||||||
|
return 17;
|
||||||
default:
|
default:
|
||||||
throw ArgumentError.value('Unknown migration from $currentVersion');
|
throw ArgumentError.value('Unknown migration from $currentVersion');
|
||||||
}
|
}
|
||||||
|
|
@ -3829,6 +4082,7 @@ i1.OnUpgrade stepByStep({
|
||||||
required Future<void> Function(i1.Migrator m, Schema14 schema) from13To14,
|
required Future<void> Function(i1.Migrator m, Schema14 schema) from13To14,
|
||||||
required Future<void> Function(i1.Migrator m, Schema15 schema) from14To15,
|
required Future<void> Function(i1.Migrator m, Schema15 schema) from14To15,
|
||||||
required Future<void> Function(i1.Migrator m, Schema16 schema) from15To16,
|
required Future<void> Function(i1.Migrator m, Schema16 schema) from15To16,
|
||||||
|
required Future<void> Function(i1.Migrator m, Schema17 schema) from16To17,
|
||||||
}) =>
|
}) =>
|
||||||
i0.VersionedSchema.stepByStepHelper(
|
i0.VersionedSchema.stepByStepHelper(
|
||||||
step: migrationSteps(
|
step: migrationSteps(
|
||||||
|
|
@ -3847,4 +4101,5 @@ i1.OnUpgrade stepByStep({
|
||||||
from13To14: from13To14,
|
from13To14: from13To14,
|
||||||
from14To15: from14To15,
|
from14To15: from14To15,
|
||||||
from15To16: from15To16,
|
from15To16: from15To16,
|
||||||
|
from16To17: from16To17,
|
||||||
));
|
));
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,9 @@ class MessageJson {
|
||||||
messageSenderId: (json['messageSenderId'] as num?)?.toInt(),
|
messageSenderId: (json['messageSenderId'] as num?)?.toInt(),
|
||||||
retransId: (json['retransId'] as num?)?.toInt(),
|
retransId: (json['retransId'] as num?)?.toInt(),
|
||||||
content: MessageContent.fromJson(
|
content: MessageContent.fromJson(
|
||||||
kind, json['content'] as Map<String, dynamic>),
|
kind,
|
||||||
|
json['content'] as Map<String, dynamic>,
|
||||||
|
),
|
||||||
timestamp: DateTime.fromMillisecondsSinceEpoch(json['timestamp'] as int),
|
timestamp: DateTime.fromMillisecondsSinceEpoch(json['timestamp'] as int),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -191,7 +193,8 @@ class TextMessageContent extends MessageContent {
|
||||||
: null,
|
: null,
|
||||||
responseToMessageId: json.containsKey('responseToMessageId')
|
responseToMessageId: json.containsKey('responseToMessageId')
|
||||||
? json['responseToMessageId'] as int?
|
? json['responseToMessageId'] as int?
|
||||||
: null);
|
: null,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -297,10 +300,11 @@ class PushKeyContent extends MessageContent {
|
||||||
}
|
}
|
||||||
|
|
||||||
class FlameSyncContent extends MessageContent {
|
class FlameSyncContent extends MessageContent {
|
||||||
FlameSyncContent(
|
FlameSyncContent({
|
||||||
{required this.flameCounter,
|
required this.flameCounter,
|
||||||
required this.bestFriend,
|
required this.bestFriend,
|
||||||
required this.lastFlameCounterChange});
|
required this.lastFlameCounterChange,
|
||||||
|
});
|
||||||
int flameCounter;
|
int flameCounter;
|
||||||
DateTime lastFlameCounterChange;
|
DateTime lastFlameCounterChange;
|
||||||
bool bestFriend;
|
bool bestFriend;
|
||||||
|
|
@ -310,7 +314,8 @@ class FlameSyncContent extends MessageContent {
|
||||||
flameCounter: json['flameCounter'] as int,
|
flameCounter: json['flameCounter'] as int,
|
||||||
bestFriend: json['bestFriend'] as bool,
|
bestFriend: json['bestFriend'] as bool,
|
||||||
lastFlameCounterChange: DateTime.fromMillisecondsSinceEpoch(
|
lastFlameCounterChange: DateTime.fromMillisecondsSinceEpoch(
|
||||||
json['lastFlameCounterChange'] as int),
|
json['lastFlameCounterChange'] as int,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,9 @@ class UserData {
|
||||||
@JsonKey(defaultValue: 0)
|
@JsonKey(defaultValue: 0)
|
||||||
int avatarCounter = 0;
|
int avatarCounter = 0;
|
||||||
|
|
||||||
|
@JsonKey(defaultValue: false)
|
||||||
|
bool isDeveloper = false;
|
||||||
|
|
||||||
@JsonKey(defaultValue: 0)
|
@JsonKey(defaultValue: 0)
|
||||||
int deviceId = 0;
|
int deviceId = 0;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) => UserData(
|
||||||
..avatarSvg = json['avatarSvg'] as String?
|
..avatarSvg = json['avatarSvg'] as String?
|
||||||
..avatarJson = json['avatarJson'] as String?
|
..avatarJson = json['avatarJson'] as String?
|
||||||
..avatarCounter = (json['avatarCounter'] as num?)?.toInt() ?? 0
|
..avatarCounter = (json['avatarCounter'] as num?)?.toInt() ?? 0
|
||||||
|
..isDeveloper = json['isDeveloper'] as bool? ?? false
|
||||||
..deviceId = (json['deviceId'] as num?)?.toInt() ?? 0
|
..deviceId = (json['deviceId'] as num?)?.toInt() ?? 0
|
||||||
..lastImageSend = json['lastImageSend'] == null
|
..lastImageSend = json['lastImageSend'] == null
|
||||||
? null
|
? null
|
||||||
|
|
@ -79,6 +80,7 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
||||||
'avatarSvg': instance.avatarSvg,
|
'avatarSvg': instance.avatarSvg,
|
||||||
'avatarJson': instance.avatarJson,
|
'avatarJson': instance.avatarJson,
|
||||||
'avatarCounter': instance.avatarCounter,
|
'avatarCounter': instance.avatarCounter,
|
||||||
|
'isDeveloper': instance.isDeveloper,
|
||||||
'deviceId': instance.deviceId,
|
'deviceId': instance.deviceId,
|
||||||
'subscriptionPlan': instance.subscriptionPlan,
|
'subscriptionPlan': instance.subscriptionPlan,
|
||||||
'lastImageSend': instance.lastImageSend?.toIso8601String(),
|
'lastImageSend': instance.lastImageSend?.toIso8601String(),
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,8 @@ class MemoryItem {
|
||||||
var mirrorVideo = false;
|
var mirrorVideo = false;
|
||||||
if (videoPath != null) {
|
if (videoPath != null) {
|
||||||
final content = MediaMessageContent.fromJson(
|
final content = MediaMessageContent.fromJson(
|
||||||
jsonDecode(message.contentJson!) as Map);
|
jsonDecode(message.contentJson!) as Map,
|
||||||
|
);
|
||||||
mirrorVideo = content.mirrorVideo;
|
mirrorVideo = content.mirrorVideo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -81,7 +82,9 @@ class MemoryItem {
|
||||||
mirrorVideo: mirrorVideo,
|
mirrorVideo: mirrorVideo,
|
||||||
thumbnailPath: thumbnailFile,
|
thumbnailPath: thumbnailFile,
|
||||||
imagePath: imagePath,
|
imagePath: imagePath,
|
||||||
videoPath: videoPath))
|
videoPath: videoPath,
|
||||||
|
),
|
||||||
|
)
|
||||||
.messages
|
.messages
|
||||||
.add(message);
|
.add(message);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -119,9 +119,10 @@ class ApiService {
|
||||||
|
|
||||||
Future<void> startReconnectionTimer() async {
|
Future<void> startReconnectionTimer() async {
|
||||||
reconnectionTimer?.cancel();
|
reconnectionTimer?.cancel();
|
||||||
reconnectionTimer ??= Timer(Duration(seconds: _reconnectionDelay), () {
|
reconnectionTimer ??=
|
||||||
|
Timer(Duration(seconds: _reconnectionDelay), () async {
|
||||||
reconnectionTimer = null;
|
reconnectionTimer = null;
|
||||||
connect(force: true);
|
await connect(force: true);
|
||||||
});
|
});
|
||||||
_reconnectionDelay += 5;
|
_reconnectionDelay += 5;
|
||||||
}
|
}
|
||||||
|
|
@ -143,9 +144,9 @@ class ApiService {
|
||||||
}
|
}
|
||||||
connectivitySubscription = Connectivity()
|
connectivitySubscription = Connectivity()
|
||||||
.onConnectivityChanged
|
.onConnectivityChanged
|
||||||
.listen((List<ConnectivityResult> result) {
|
.listen((List<ConnectivityResult> result) async {
|
||||||
if (!result.contains(ConnectivityResult.none)) {
|
if (!result.contains(ConnectivityResult.none)) {
|
||||||
connect(force: true);
|
await connect(force: true);
|
||||||
}
|
}
|
||||||
// Received changes in available connectivity types!
|
// Received changes in available connectivity types!
|
||||||
});
|
});
|
||||||
|
|
@ -186,14 +187,14 @@ class ApiService {
|
||||||
|
|
||||||
bool get isConnected => _channel != null && _channel!.closeCode != null;
|
bool get isConnected => _channel != null && _channel!.closeCode != null;
|
||||||
|
|
||||||
void _onDone() {
|
Future<void> _onDone() async {
|
||||||
Log.info('websocket closed without error');
|
Log.info('websocket closed without error');
|
||||||
onClosed();
|
await onClosed();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onError(dynamic e) {
|
Future<void> _onError(dynamic e) async {
|
||||||
Log.error('websocket error: $e');
|
Log.error('websocket error: $e');
|
||||||
onClosed();
|
await onClosed();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onData(dynamic msgBuffer) async {
|
Future<void> _onData(dynamic msgBuffer) async {
|
||||||
|
|
@ -338,14 +339,20 @@ class ApiService {
|
||||||
}
|
}
|
||||||
if (res.error == ErrorCode.UserIdNotFound && contactId != null) {
|
if (res.error == ErrorCode.UserIdNotFound && contactId != null) {
|
||||||
Log.error('Contact deleted their account $contactId.');
|
Log.error('Contact deleted their account $contactId.');
|
||||||
|
final contact = await twonlyDB.contactsDao
|
||||||
|
.getContactByUserId(contactId)
|
||||||
|
.getSingleOrNull();
|
||||||
|
if (contact != null) {
|
||||||
await twonlyDB.contactsDao.updateContact(
|
await twonlyDB.contactsDao.updateContact(
|
||||||
contactId,
|
contactId,
|
||||||
const ContactsCompanion(
|
ContactsCompanion(
|
||||||
deleted: Value(true),
|
deleted: const Value(true),
|
||||||
|
username: Value('${contact.username} (${contact.userId})'),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -441,7 +448,9 @@ class ApiService {
|
||||||
|
|
||||||
const storage = FlutterSecureStorage();
|
const storage = FlutterSecureStorage();
|
||||||
await storage.write(
|
await storage.write(
|
||||||
key: SecureStorageKeys.apiAuthToken, value: apiAuthTokenB64);
|
key: SecureStorageKeys.apiAuthToken,
|
||||||
|
value: apiAuthTokenB64,
|
||||||
|
);
|
||||||
|
|
||||||
await tryAuthenticateWithToken(userData.userId);
|
await tryAuthenticateWithToken(userData.userId);
|
||||||
}
|
}
|
||||||
|
|
@ -575,7 +584,10 @@ class ApiService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Result> switchToPayedPlan(
|
Future<Result> switchToPayedPlan(
|
||||||
String planId, bool payMonthly, bool autoRenewal) async {
|
String planId,
|
||||||
|
bool payMonthly,
|
||||||
|
bool autoRenewal,
|
||||||
|
) async {
|
||||||
final get = ApplicationData_SwitchToPayedPlan()
|
final get = ApplicationData_SwitchToPayedPlan()
|
||||||
..planId = planId
|
..planId = planId
|
||||||
..payMonthly = payMonthly
|
..payMonthly = payMonthly
|
||||||
|
|
@ -676,7 +688,10 @@ class ApiService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Result> sendTextMessage(
|
Future<Result> sendTextMessage(
|
||||||
int target, Uint8List msg, List<int>? pushData) async {
|
int target,
|
||||||
|
Uint8List msg,
|
||||||
|
List<int>? pushData,
|
||||||
|
) async {
|
||||||
final testMessage = ApplicationData_TextMessage()
|
final testMessage = ApplicationData_TextMessage()
|
||||||
..userId = Int64(target)
|
..userId = Int64(target)
|
||||||
..body = msg;
|
..body = msg;
|
||||||
|
|
|
||||||
|
|
@ -40,8 +40,8 @@ Map<String, List<String>> defaultAutoDownloadOptions = {
|
||||||
ConnectivityResult.mobile.name: [],
|
ConnectivityResult.mobile.name: [],
|
||||||
ConnectivityResult.wifi.name: [
|
ConnectivityResult.wifi.name: [
|
||||||
DownloadMediaTypes.video.name,
|
DownloadMediaTypes.video.name,
|
||||||
DownloadMediaTypes.image.name
|
DownloadMediaTypes.image.name,
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
Future<bool> isAllowedToDownload(bool isVideo) async {
|
Future<bool> isAllowedToDownload(bool isVideo) async {
|
||||||
|
|
@ -121,7 +121,8 @@ Mutex protectDownload = Mutex();
|
||||||
|
|
||||||
Future<void> startDownloadMedia(Message message, bool force) async {
|
Future<void> startDownloadMedia(Message message, bool force) async {
|
||||||
Log.info(
|
Log.info(
|
||||||
'Download blocked for ${message.messageId} because of network state.');
|
'Download blocked for ${message.messageId} because of network state.',
|
||||||
|
);
|
||||||
if (message.contentJson == null) {
|
if (message.contentJson == null) {
|
||||||
Log.error('Content of ${message.messageId} not found.');
|
Log.error('Content of ${message.messageId} not found.');
|
||||||
await handleMediaError(message);
|
await handleMediaError(message);
|
||||||
|
|
@ -147,7 +148,8 @@ Future<void> startDownloadMedia(Message message, bool force) async {
|
||||||
|
|
||||||
if (!force && !await isAllowedToDownload(content.isVideo)) {
|
if (!force && !await isAllowedToDownload(content.isVideo)) {
|
||||||
Log.warn(
|
Log.warn(
|
||||||
'Download blocked for ${message.messageId} because of network state.');
|
'Download blocked for ${message.messageId} because of network state.',
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -160,7 +162,8 @@ Future<void> startDownloadMedia(Message message, bool force) async {
|
||||||
|
|
||||||
if (msg.downloadState != DownloadState.pending) {
|
if (msg.downloadState != DownloadState.pending) {
|
||||||
Log.error(
|
Log.error(
|
||||||
'${message.messageId} is already downloaded or is downloading.');
|
'${message.messageId} is already downloaded or is downloading.',
|
||||||
|
);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -317,7 +320,8 @@ Future<void> handleEncryptedFile(int messageId) async {
|
||||||
await writeMediaFile(msg.messageId, 'png', imageBytes);
|
await writeMediaFile(msg.messageId, 'png', imageBytes);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Log.error(
|
Log.error(
|
||||||
'could not decrypt the media file in the second try. reporting error to user: $e');
|
'could not decrypt the media file in the second try. reporting error to user: $e',
|
||||||
|
);
|
||||||
await handleMediaError(msg);
|
await handleMediaError(msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -461,7 +465,7 @@ Future<void> purgeMediaFiles(Directory directory) async {
|
||||||
if ((message == null) ||
|
if ((message == null) ||
|
||||||
(message.openedAt != null &&
|
(message.openedAt != null &&
|
||||||
!message.mediaStored &&
|
!message.mediaStored &&
|
||||||
message.acknowledgeByServer == true) ||
|
message.acknowledgeByServer) ||
|
||||||
message.errorWhileSending) {
|
message.errorWhileSending) {
|
||||||
file.deleteSync();
|
file.deleteSync();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,16 +75,19 @@ Future<void> initFileDownloader() async {
|
||||||
}
|
}
|
||||||
case TaskProgressUpdate():
|
case TaskProgressUpdate():
|
||||||
Log.info(
|
Log.info(
|
||||||
'Progress update for ${update.task} with progress ${update.progress}');
|
'Progress update for ${update.task} with progress ${update.progress}',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await FileDownloader().start();
|
await FileDownloader().start();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await FileDownloader().configure(androidConfig: [
|
var androidConfig = [];
|
||||||
(Config.bypassTLSCertificateValidation, kDebugMode),
|
if (kDebugMode) {
|
||||||
]);
|
androidConfig = [(Config.bypassTLSCertificateValidation, kDebugMode)];
|
||||||
|
}
|
||||||
|
await FileDownloader().configure(androidConfig: androidConfig);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Log.error(e);
|
Log.error(e);
|
||||||
}
|
}
|
||||||
|
|
@ -201,7 +204,9 @@ Future<bool> addVideoToUpload(int mediaUploadId, File videoFilePath) async {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Uint8List> addOrModifyImageToUpload(
|
Future<Uint8List> addOrModifyImageToUpload(
|
||||||
int mediaUploadId, Uint8List imageBytes) async {
|
int mediaUploadId,
|
||||||
|
Uint8List imageBytes,
|
||||||
|
) async {
|
||||||
Uint8List imageBytesCompressed;
|
Uint8List imageBytesCompressed;
|
||||||
|
|
||||||
final stopwatch = Stopwatch()..start();
|
final stopwatch = Stopwatch()..start();
|
||||||
|
|
@ -236,7 +241,8 @@ Future<Uint8List> addOrModifyImageToUpload(
|
||||||
stopwatch.stop();
|
stopwatch.stop();
|
||||||
|
|
||||||
Log.info(
|
Log.info(
|
||||||
'Compression the image took: ${stopwatch.elapsedMilliseconds} milliseconds');
|
'Compression the image took: ${stopwatch.elapsedMilliseconds} milliseconds',
|
||||||
|
);
|
||||||
Log.info('Raw images size in bytes: ${imageBytesCompressed.length}');
|
Log.info('Raw images size in bytes: ${imageBytesCompressed.length}');
|
||||||
|
|
||||||
// stopwatch.reset();
|
// stopwatch.reset();
|
||||||
|
|
@ -285,7 +291,6 @@ Future<void> encryptMediaFiles(
|
||||||
Future<bool>? videoHandler,
|
Future<bool>? videoHandler,
|
||||||
) async {
|
) async {
|
||||||
Log.info('$mediaUploadId encrypting files');
|
Log.info('$mediaUploadId encrypting files');
|
||||||
// ignore: cast_nullable_to_non_nullable
|
|
||||||
var dataToEncrypt = await imageHandler;
|
var dataToEncrypt = await imageHandler;
|
||||||
|
|
||||||
/// if there is a video wait until it is finished with compression
|
/// if there is a video wait until it is finished with compression
|
||||||
|
|
@ -332,8 +337,14 @@ Future<void> encryptMediaFiles(
|
||||||
unawaited(handleNextMediaUploadSteps(mediaUploadId));
|
unawaited(handleNextMediaUploadSteps(mediaUploadId));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> finalizeUpload(int mediaUploadId, List<int> contactIds,
|
Future<void> finalizeUpload(
|
||||||
bool isRealTwonly, bool isVideo, bool mirrorVideo, int maxShowTime) async {
|
int mediaUploadId,
|
||||||
|
List<int> contactIds,
|
||||||
|
bool isRealTwonly,
|
||||||
|
bool isVideo,
|
||||||
|
bool mirrorVideo,
|
||||||
|
int maxShowTime,
|
||||||
|
) async {
|
||||||
final metadata = MediaUploadMetadata()
|
final metadata = MediaUploadMetadata()
|
||||||
..contactIds = contactIds
|
..contactIds = contactIds
|
||||||
..isRealTwonly = isRealTwonly
|
..isRealTwonly = isRealTwonly
|
||||||
|
|
@ -474,7 +485,8 @@ Future<void> handleUploadStatusUpdate(TaskStatusUpdate update) async {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Log.info(
|
Log.info(
|
||||||
'Status update for ${update.task.taskId} with status ${update.status}');
|
'Status update for ${update.task.taskId} with status ${update.status}',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> handleUploadSuccess(MediaUpload media) async {
|
Future<void> handleUploadSuccess(MediaUpload media) async {
|
||||||
|
|
@ -565,7 +577,8 @@ Future<void> handleMediaUpload(MediaUpload media) async {
|
||||||
|
|
||||||
if (contact == null || contact.deleted) {
|
if (contact == null || contact.deleted) {
|
||||||
Log.warn(
|
Log.warn(
|
||||||
'Contact deleted ${message.contactId} or not found in database.');
|
'Contact deleted ${message.contactId} or not found in database.',
|
||||||
|
);
|
||||||
await twonlyDB.messagesDao.updateMessageByMessageId(
|
await twonlyDB.messagesDao.updateMessageByMessageId(
|
||||||
message.messageId,
|
message.messageId,
|
||||||
const MessagesCompanion(errorWhileSending: Value(true)),
|
const MessagesCompanion(errorWhileSending: Value(true)),
|
||||||
|
|
@ -708,11 +721,13 @@ Future<void> uploadFileFast(
|
||||||
);
|
);
|
||||||
requestMultipart.headers['x-twonly-auth-token'] = apiAuthToken;
|
requestMultipart.headers['x-twonly-auth-token'] = apiAuthToken;
|
||||||
|
|
||||||
requestMultipart.files.add(http.MultipartFile.fromBytes(
|
requestMultipart.files.add(
|
||||||
|
http.MultipartFile.fromBytes(
|
||||||
'file',
|
'file',
|
||||||
uploadRequestFile,
|
uploadRequestFile,
|
||||||
filename: 'upload',
|
filename: 'upload',
|
||||||
));
|
),
|
||||||
|
);
|
||||||
|
|
||||||
final response = await requestMultipart.send();
|
final response = await requestMultipart.send();
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
|
|
@ -790,7 +805,10 @@ Future<Uint8List> readSendMediaFile(int mediaUploadId, String type) async {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<File> writeSendMediaFile(
|
Future<File> writeSendMediaFile(
|
||||||
int mediaUploadId, String type, Uint8List data) async {
|
int mediaUploadId,
|
||||||
|
String type,
|
||||||
|
Uint8List data,
|
||||||
|
) async {
|
||||||
final basePath = await getMediaFilePath(mediaUploadId, 'send');
|
final basePath = await getMediaFilePath(mediaUploadId, 'send');
|
||||||
final file = File('$basePath.$type');
|
final file = File('$basePath.$type');
|
||||||
await file.writeAsBytes(data);
|
await file.writeAsBytes(data);
|
||||||
|
|
@ -838,8 +856,11 @@ List<Uint8List> extractUint8Lists(Uint8List combinedList) {
|
||||||
final byteData = ByteData.sublistView(combinedList);
|
final byteData = ByteData.sublistView(combinedList);
|
||||||
final sizeOfList1 = byteData.getInt32(0);
|
final sizeOfList1 = byteData.getInt32(0);
|
||||||
final list1 = Uint8List.view(combinedList.buffer, 4, sizeOfList1);
|
final list1 = Uint8List.view(combinedList.buffer, 4, sizeOfList1);
|
||||||
final list2 = Uint8List.view(combinedList.buffer, 4 + sizeOfList1,
|
final list2 = Uint8List.view(
|
||||||
combinedList.lengthInBytes - 4 - sizeOfList1);
|
combinedList.buffer,
|
||||||
|
4 + sizeOfList1,
|
||||||
|
combinedList.lengthInBytes - 4 - sizeOfList1,
|
||||||
|
);
|
||||||
return [list1, list2];
|
return [list1, list2];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -853,9 +874,12 @@ String uint8ListToHex(List<int> bytes) {
|
||||||
return bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join();
|
return bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join();
|
||||||
}
|
}
|
||||||
|
|
||||||
Uint8List hexToUint8List(String hex) => Uint8List.fromList(List<int>.generate(
|
Uint8List hexToUint8List(String hex) => Uint8List.fromList(
|
||||||
|
List<int>.generate(
|
||||||
hex.length ~/ 2,
|
hex.length ~/ 2,
|
||||||
(i) => int.parse(hex.substring(i * 2, i * 2 + 2), radix: 16)));
|
(i) => int.parse(hex.substring(i * 2, i * 2 + 2), radix: 16),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
Uint8List createDownloadToken() {
|
Uint8List createDownloadToken() {
|
||||||
final random = Random();
|
final random = Random();
|
||||||
|
|
|
||||||
|
|
@ -52,11 +52,13 @@ Future<void> sendRetransmitMessage(int retransId) async {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final json = MessageJson.fromJson(jsonDecode(
|
final json = MessageJson.fromJson(
|
||||||
|
jsonDecode(
|
||||||
utf8.decode(
|
utf8.decode(
|
||||||
gzip.decode(retrans.plaintextContent),
|
gzip.decode(retrans.plaintextContent),
|
||||||
),
|
),
|
||||||
) as Map<String, dynamic>);
|
) as Map<String, dynamic>,
|
||||||
|
);
|
||||||
|
|
||||||
Log.info('Retransmitting $retransId: ${json.kind} to ${retrans.contactId}');
|
Log.info('Retransmitting $retransId: ${json.kind} to ${retrans.contactId}');
|
||||||
|
|
||||||
|
|
@ -65,6 +67,8 @@ Future<void> sendRetransmitMessage(int retransId) async {
|
||||||
.getSingleOrNull();
|
.getSingleOrNull();
|
||||||
if (contact == null || contact.deleted) {
|
if (contact == null || contact.deleted) {
|
||||||
Log.warn('Contact deleted $retransId or not found in database.');
|
Log.warn('Contact deleted $retransId or not found in database.');
|
||||||
|
await twonlyDB.messageRetransmissionDao
|
||||||
|
.deleteRetransmissionById(retransId);
|
||||||
if (retrans.messageId != null) {
|
if (retrans.messageId != null) {
|
||||||
await twonlyDB.messagesDao.updateMessageByMessageId(
|
await twonlyDB.messagesDao.updateMessageByMessageId(
|
||||||
retrans.messageId!,
|
retrans.messageId!,
|
||||||
|
|
@ -140,6 +144,8 @@ Future<void> sendRetransmitMessage(int retransId) async {
|
||||||
retransId,
|
retransId,
|
||||||
MessageRetransmissionsCompanion(
|
MessageRetransmissionsCompanion(
|
||||||
acknowledgeByServerAt: Value(DateTime.now()),
|
acknowledgeByServerAt: Value(DateTime.now()),
|
||||||
|
retryCount: Value(retrans.retryCount + 1),
|
||||||
|
lastRetry: Value(DateTime.now()),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -185,7 +191,9 @@ Future<void> encryptAndSendMessageAsync(
|
||||||
await twonlyDB.messageRetransmissionDao.updateRetransmission(
|
await twonlyDB.messageRetransmissionDao.updateRetransmission(
|
||||||
retransId,
|
retransId,
|
||||||
MessageRetransmissionsCompanion(
|
MessageRetransmissionsCompanion(
|
||||||
plaintextContent: Value(plaintextContent)));
|
plaintextContent: Value(plaintextContent),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
// this can now be done in the background...
|
// this can now be done in the background...
|
||||||
unawaited(sendRetransmitMessage(retransId));
|
unawaited(sendRetransmitMessage(retransId));
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ Future<void> handleServerMessage(server.ServerToClient msg) async {
|
||||||
final fromUserId = msg.v0.newMessage.fromUserId.toInt();
|
final fromUserId = msg.v0.newMessage.fromUserId.toInt();
|
||||||
response = await handleNewMessage(fromUserId, body);
|
response = await handleNewMessage(fromUserId, body);
|
||||||
} else {
|
} else {
|
||||||
Log.error('Got a new message from the server: $msg');
|
Log.error('Got a unknown message from the server: $msg');
|
||||||
response = client.Response()..error = ErrorCode.InternalError;
|
response = client.Response()..error = ErrorCode.InternalError;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -90,26 +90,10 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
|
||||||
return client.Response()..ok = ok;
|
return client.Response()..ok = ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client.Response? result;
|
||||||
|
|
||||||
Log.info('Got: ${message.kind} from $fromUserId');
|
Log.info('Got: ${message.kind} from $fromUserId');
|
||||||
|
|
||||||
if (messageGetsAck(message.kind) && message.retransId != null) {
|
|
||||||
Log.info('Sending ACK for ${message.kind}');
|
|
||||||
|
|
||||||
/// ACK every message
|
|
||||||
await encryptAndSendMessageAsync(
|
|
||||||
null,
|
|
||||||
fromUserId,
|
|
||||||
MessageJson(
|
|
||||||
kind: MessageKind.ack,
|
|
||||||
content: AckContent(
|
|
||||||
messageIdToAck: message.messageSenderId,
|
|
||||||
retransIdToAck: message.retransId!,
|
|
||||||
),
|
|
||||||
timestamp: DateTime.now(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (message.kind) {
|
switch (message.kind) {
|
||||||
case MessageKind.ack:
|
case MessageKind.ack:
|
||||||
final content = message.content;
|
final content = message.content;
|
||||||
|
|
@ -125,13 +109,13 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
|
||||||
update,
|
update,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await twonlyDB.messageRetransmissionDao
|
await twonlyDB.messageRetransmissionDao
|
||||||
.deleteRetransmissionById(content.retransIdToAck);
|
.deleteRetransmissionById(content.retransIdToAck);
|
||||||
}
|
}
|
||||||
case MessageKind.signalDecryptError:
|
case MessageKind.signalDecryptError:
|
||||||
Log.error(
|
Log.error(
|
||||||
'Got signal decrypt error from other user! Sending all non ACK messages again.');
|
'Got signal decrypt error from other user! Sending it again.',
|
||||||
|
);
|
||||||
|
|
||||||
final content = message.content;
|
final content = message.content;
|
||||||
if (content is SignalDecryptErrorContent) {
|
if (content is SignalDecryptErrorContent) {
|
||||||
|
|
@ -148,7 +132,7 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
|
||||||
}
|
}
|
||||||
|
|
||||||
case MessageKind.contactRequest:
|
case MessageKind.contactRequest:
|
||||||
return handleContactRequest(fromUserId, message);
|
await handleContactRequest(fromUserId, message);
|
||||||
|
|
||||||
case MessageKind.flameSync:
|
case MessageKind.flameSync:
|
||||||
final contact = await twonlyDB.contactsDao
|
final contact = await twonlyDB.contactsDao
|
||||||
|
|
@ -164,7 +148,6 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
|
||||||
isToday(content.lastFlameCounterChange)) {
|
isToday(content.lastFlameCounterChange)) {
|
||||||
if (content.flameCounter > contact.flameCounter) {
|
if (content.flameCounter > contact.flameCounter) {
|
||||||
updates = ContactsCompanion(
|
updates = ContactsCompanion(
|
||||||
alsoBestFriend: Value(content.bestFriend),
|
|
||||||
flameCounter: Value(content.flameCounter),
|
flameCounter: Value(content.flameCounter),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -282,14 +265,49 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
|
||||||
|
|
||||||
// ignore: no_default_cases
|
// ignore: no_default_cases
|
||||||
default:
|
default:
|
||||||
if (message.kind != MessageKind.textMessage &&
|
if (message.messageSenderId == null) {
|
||||||
message.kind != MessageKind.media &&
|
|
||||||
message.kind != MessageKind.storedMediaFile &&
|
|
||||||
message.kind != MessageKind.reopenedMedia) {
|
|
||||||
Log.error('Got unknown MessageKind $message');
|
|
||||||
} else if (message.messageSenderId == null) {
|
|
||||||
Log.error('Messageid not defined $message');
|
Log.error('Messageid not defined $message');
|
||||||
|
} else if ([
|
||||||
|
MessageKind.textMessage,
|
||||||
|
MessageKind.media,
|
||||||
|
MessageKind.storedMediaFile,
|
||||||
|
MessageKind.reopenedMedia,
|
||||||
|
].contains(message.kind)) {
|
||||||
|
result = await handleMediaOrTextMessage(fromUserId, message);
|
||||||
} else {
|
} else {
|
||||||
|
Log.error('Got unknown MessageKind $message');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (messageGetsAck(message.kind) && message.retransId != null) {
|
||||||
|
Log.info('Sending ACK for ${message.kind}');
|
||||||
|
|
||||||
|
/// ACK every message
|
||||||
|
await encryptAndSendMessageAsync(
|
||||||
|
null,
|
||||||
|
fromUserId,
|
||||||
|
MessageJson(
|
||||||
|
kind: MessageKind.ack,
|
||||||
|
content: AckContent(
|
||||||
|
messageIdToAck: message.messageSenderId,
|
||||||
|
retransIdToAck: message.retransId!,
|
||||||
|
),
|
||||||
|
timestamp: DateTime.now(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result != null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
final ok = client.Response_Ok()..none = true;
|
||||||
|
return client.Response()..ok = ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<client.Response?> handleMediaOrTextMessage(
|
||||||
|
int fromUserId,
|
||||||
|
MessageJson message,
|
||||||
|
) async {
|
||||||
if (message.kind == MessageKind.storedMediaFile) {
|
if (message.kind == MessageKind.storedMediaFile) {
|
||||||
if (message.messageReceiverId != null) {
|
if (message.messageReceiverId != null) {
|
||||||
/// stored media file just updates the message
|
/// stored media file just updates the message
|
||||||
|
|
@ -303,11 +321,12 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
|
||||||
);
|
);
|
||||||
final msg = await twonlyDB.messagesDao
|
final msg = await twonlyDB.messagesDao
|
||||||
.getMessageByIdAndContactId(
|
.getMessageByIdAndContactId(
|
||||||
fromUserId, message.messageReceiverId!)
|
fromUserId,
|
||||||
|
message.messageReceiverId!,
|
||||||
|
)
|
||||||
.getSingleOrNull();
|
.getSingleOrNull();
|
||||||
if (msg != null && msg.mediaUploadId != null) {
|
if (msg != null && msg.mediaUploadId != null) {
|
||||||
final filePath =
|
final filePath = await getMediaFilePath(msg.mediaUploadId, 'send');
|
||||||
await getMediaFilePath(msg.mediaUploadId, 'send');
|
|
||||||
if (filePath.contains('mp4')) {
|
if (filePath.contains('mp4')) {
|
||||||
unawaited(createThumbnailsForVideo(File(filePath)));
|
unawaited(createThumbnailsForVideo(File(filePath)));
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -329,7 +348,8 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
|
||||||
.deleteMessagesByMessageId(openedMessage.messageId);
|
.deleteMessagesByMessageId(openedMessage.messageId);
|
||||||
} else {
|
} else {
|
||||||
Log.error(
|
Log.error(
|
||||||
'Got a duplicated message from other user: ${message.messageSenderId!}');
|
'Got a duplicated message from other user: ${message.messageSenderId!}',
|
||||||
|
);
|
||||||
final ok = client.Response_Ok()..none = true;
|
final ok = client.Response_Ok()..none = true;
|
||||||
return client.Response()..ok = ok;
|
return client.Response()..ok = ok;
|
||||||
}
|
}
|
||||||
|
|
@ -351,8 +371,7 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
|
||||||
responseToMessageId = content.responseToMessageId;
|
responseToMessageId = content.responseToMessageId;
|
||||||
responseToOtherMessageId = content.responseToOtherMessageId;
|
responseToOtherMessageId = content.responseToOtherMessageId;
|
||||||
|
|
||||||
if (responseToMessageId != null ||
|
if (responseToMessageId != null || responseToOtherMessageId != null) {
|
||||||
responseToOtherMessageId != null) {
|
|
||||||
// reactions are shown in the notification directly...
|
// reactions are shown in the notification directly...
|
||||||
if (isEmoji(content.text)) {
|
if (isEmoji(content.text)) {
|
||||||
openedAt = DateTime.now();
|
openedAt = DateTime.now();
|
||||||
|
|
@ -387,9 +406,11 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
|
||||||
responseToMessageId: Value(responseToMessageId),
|
responseToMessageId: Value(responseToMessageId),
|
||||||
responseToOtherMessageId: Value(responseToOtherMessageId),
|
responseToOtherMessageId: Value(responseToOtherMessageId),
|
||||||
openedAt: Value(openedAt),
|
openedAt: Value(openedAt),
|
||||||
downloadState: Value(message.kind == MessageKind.media
|
downloadState: Value(
|
||||||
|
message.kind == MessageKind.media
|
||||||
? DownloadState.pending
|
? DownloadState.pending
|
||||||
: DownloadState.downloaded),
|
: DownloadState.downloaded,
|
||||||
|
),
|
||||||
sendAt: Value(message.timestamp),
|
sendAt: Value(message.timestamp),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -429,10 +450,7 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
|
||||||
archived: Value(false),
|
archived: Value(false),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
return null;
|
||||||
}
|
|
||||||
final ok = client.Response_Ok()..none = true;
|
|
||||||
return client.Response()..ok = ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<client.Response> handleRequestNewPreKey() async {
|
Future<client.Response> handleRequestNewPreKey() async {
|
||||||
|
|
@ -440,17 +458,21 @@ Future<client.Response> handleRequestNewPreKey() async {
|
||||||
|
|
||||||
final prekeysList = <client.Response_PreKey>[];
|
final prekeysList = <client.Response_PreKey>[];
|
||||||
for (var i = 0; i < localPreKeys.length; i++) {
|
for (var i = 0; i < localPreKeys.length; i++) {
|
||||||
prekeysList.add(client.Response_PreKey()
|
prekeysList.add(
|
||||||
|
client.Response_PreKey()
|
||||||
..id = Int64(localPreKeys[i].id)
|
..id = Int64(localPreKeys[i].id)
|
||||||
..prekey = localPreKeys[i].getKeyPair().publicKey.serialize());
|
..prekey = localPreKeys[i].getKeyPair().publicKey.serialize(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
final prekeys = client.Response_Prekeys(prekeys: prekeysList);
|
final prekeys = client.Response_Prekeys(prekeys: prekeysList);
|
||||||
final ok = client.Response_Ok()..prekeys = prekeys;
|
final ok = client.Response_Ok()..prekeys = prekeys;
|
||||||
return client.Response()..ok = ok;
|
return client.Response()..ok = ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<client.Response> handleContactRequest(
|
Future<void> handleContactRequest(
|
||||||
int fromUserId, MessageJson message) async {
|
int fromUserId,
|
||||||
|
MessageJson message,
|
||||||
|
) async {
|
||||||
// request the username by the server so an attacker can not
|
// request the username by the server so an attacker can not
|
||||||
// forge the displayed username in the contact request
|
// forge the displayed username in the contact request
|
||||||
final username = await apiService.getUsername(fromUserId);
|
final username = await apiService.getUsername(fromUserId);
|
||||||
|
|
@ -465,6 +487,4 @@ Future<client.Response> handleContactRequest(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
await setupNotificationWithUsers();
|
await setupNotificationWithUsers();
|
||||||
final ok = client.Response_Ok()..none = true;
|
|
||||||
return client.Response()..ok = ok;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,8 @@ ClientToServer createClientToServerFromHandshake(Handshake handshake) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ClientToServer createClientToServerFromApplicationData(
|
ClientToServer createClientToServerFromApplicationData(
|
||||||
ApplicationData applicationData) {
|
ApplicationData applicationData,
|
||||||
|
) {
|
||||||
final v0 = client.V0()
|
final v0 = client.V0()
|
||||||
..seq = Int64()
|
..seq = Int64()
|
||||||
..applicationdata = applicationData;
|
..applicationdata = applicationData;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// ignore_for_file: unreachable_from_main
|
||||||
|
|
||||||
import 'dart:io' show Platform;
|
import 'dart:io' show Platform;
|
||||||
|
|
||||||
import 'package:firebase_core/firebase_core.dart';
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,8 @@ Future<void> syncFlameCounters() async {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final contact in contacts) {
|
for (final contact in contacts) {
|
||||||
if (contact.lastFlameCounterChange == null) continue;
|
if (contact.lastFlameCounterChange == null || contact.deleted) continue;
|
||||||
|
if (!isToday(contact.lastFlameCounterChange!)) continue;
|
||||||
if (contact.lastFlameSync != null) {
|
if (contact.lastFlameSync != null) {
|
||||||
if (isToday(contact.lastFlameSync!)) continue;
|
if (isToday(contact.lastFlameSync!)) continue;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,9 @@ Future<void> handlePushData(String pushDataB64) async {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<PushNotification?> tryDecryptMessage(
|
Future<PushNotification?> tryDecryptMessage(
|
||||||
List<int> key, EncryptedPushNotification push) async {
|
List<int> key,
|
||||||
|
EncryptedPushNotification push,
|
||||||
|
) async {
|
||||||
try {
|
try {
|
||||||
final chacha20 = FlutterChacha20.poly1305Aead();
|
final chacha20 = FlutterChacha20.poly1305Aead();
|
||||||
final secretKeyData = SecretKeyData(key);
|
final secretKeyData = SecretKeyData(key);
|
||||||
|
|
@ -190,7 +192,9 @@ Future<void> showLocalPushNotificationWithoutUserId(
|
||||||
|
|
||||||
const darwinNotificationDetails = DarwinNotificationDetails();
|
const darwinNotificationDetails = DarwinNotificationDetails();
|
||||||
const notificationDetails = NotificationDetails(
|
const notificationDetails = NotificationDetails(
|
||||||
android: androidNotificationDetails, iOS: darwinNotificationDetails);
|
android: androidNotificationDetails,
|
||||||
|
iOS: darwinNotificationDetails,
|
||||||
|
);
|
||||||
|
|
||||||
await flutterLocalNotificationsPlugin.show(
|
await flutterLocalNotificationsPlugin.show(
|
||||||
2,
|
2,
|
||||||
|
|
@ -307,7 +311,9 @@ String getPushNotificationText(PushNotification pushNotification) {
|
||||||
var contentText = pushNotificationText[pushNotification.kind.name] ?? '';
|
var contentText = pushNotificationText[pushNotification.kind.name] ?? '';
|
||||||
if (pushNotification.hasReactionContent()) {
|
if (pushNotification.hasReactionContent()) {
|
||||||
contentText = contentText.replaceAll(
|
contentText = contentText.replaceAll(
|
||||||
'{{reaction}}', pushNotification.reactionContent);
|
'{{reaction}}',
|
||||||
|
pushNotification.reactionContent,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return contentText;
|
return contentText;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,10 @@ import 'package:twonly/src/services/api/messages.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
/// This function must be called after the database is setup
|
/// This function must be called after the database is setup
|
||||||
Future<void> setupNotificationWithUsers(
|
Future<void> setupNotificationWithUsers({
|
||||||
{bool force = false, int? forceContact}) async {
|
bool force = false,
|
||||||
|
int? forceContact,
|
||||||
|
}) async {
|
||||||
var pushUsers = await getPushKeys(SecureStorageKeys.receivingPushKeys);
|
var pushUsers = await getPushKeys(SecureStorageKeys.receivingPushKeys);
|
||||||
|
|
||||||
// HotFIX: Search for user with id 0 if not there remove all
|
// HotFIX: Search for user with id 0 if not there remove all
|
||||||
|
|
@ -29,11 +31,13 @@ Future<void> setupNotificationWithUsers(
|
||||||
Log.info('Clearing push keys');
|
Log.info('Clearing push keys');
|
||||||
await setPushKeys(SecureStorageKeys.receivingPushKeys, []);
|
await setPushKeys(SecureStorageKeys.receivingPushKeys, []);
|
||||||
pushUsers = await getPushKeys(SecureStorageKeys.receivingPushKeys)
|
pushUsers = await getPushKeys(SecureStorageKeys.receivingPushKeys)
|
||||||
..add(PushUser(
|
..add(
|
||||||
|
PushUser(
|
||||||
userId: Int64(),
|
userId: Int64(),
|
||||||
displayName: 'NoUser',
|
displayName: 'NoUser',
|
||||||
pushKeys: [],
|
pushKeys: [],
|
||||||
));
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
var wasChanged = false;
|
var wasChanged = false;
|
||||||
|
|
@ -51,7 +55,8 @@ Future<void> setupNotificationWithUsers(
|
||||||
DateTime.now().subtract(Duration(days: 5 + random.nextInt(5)));
|
DateTime.now().subtract(Duration(days: 5 + random.nextInt(5)));
|
||||||
final lastKey = pushUser.pushKeys.last;
|
final lastKey = pushUser.pushKeys.last;
|
||||||
final createdAt = DateTime.fromMillisecondsSinceEpoch(
|
final createdAt = DateTime.fromMillisecondsSinceEpoch(
|
||||||
lastKey.createdAtUnixTimestamp.toInt());
|
lastKey.createdAtUnixTimestamp.toInt(),
|
||||||
|
);
|
||||||
|
|
||||||
if (force ||
|
if (force ||
|
||||||
(forceContact == contact.userId) ||
|
(forceContact == contact.userId) ||
|
||||||
|
|
@ -82,12 +87,14 @@ Future<void> setupNotificationWithUsers(
|
||||||
createdAtUnixTimestamp: Int64(DateTime.now().millisecondsSinceEpoch),
|
createdAtUnixTimestamp: Int64(DateTime.now().millisecondsSinceEpoch),
|
||||||
);
|
);
|
||||||
await sendNewPushKey(contact.userId, pushKey);
|
await sendNewPushKey(contact.userId, pushKey);
|
||||||
pushUsers.add(PushUser(
|
pushUsers.add(
|
||||||
|
PushUser(
|
||||||
userId: Int64(contact.userId),
|
userId: Int64(contact.userId),
|
||||||
displayName: getContactDisplayName(contact),
|
displayName: getContactDisplayName(contact),
|
||||||
blocked: contact.blocked,
|
blocked: contact.blocked,
|
||||||
pushKeys: [pushKey],
|
pushKeys: [pushKey],
|
||||||
));
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -119,13 +126,15 @@ Future<void> updatePushUser(Contact contact) async {
|
||||||
final pushUser = pushKeys.firstWhereOrNull((x) => x.userId == contact.userId);
|
final pushUser = pushKeys.firstWhereOrNull((x) => x.userId == contact.userId);
|
||||||
|
|
||||||
if (pushUser == null) {
|
if (pushUser == null) {
|
||||||
pushKeys.add(PushUser(
|
pushKeys.add(
|
||||||
|
PushUser(
|
||||||
userId: Int64(contact.userId),
|
userId: Int64(contact.userId),
|
||||||
displayName: getContactDisplayName(contact),
|
displayName: getContactDisplayName(contact),
|
||||||
pushKeys: [],
|
pushKeys: [],
|
||||||
blocked: contact.blocked,
|
blocked: contact.blocked,
|
||||||
lastMessageId: Int64(),
|
lastMessageId: Int64(),
|
||||||
));
|
),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
pushUser
|
pushUser
|
||||||
..displayName = getContactDisplayName(contact)
|
..displayName = getContactDisplayName(contact)
|
||||||
|
|
@ -145,13 +154,15 @@ Future<void> handleNewPushKey(int fromUserId, my.PushKeyContent pushKey) async {
|
||||||
.getContactByUserId(fromUserId)
|
.getContactByUserId(fromUserId)
|
||||||
.getSingleOrNull();
|
.getSingleOrNull();
|
||||||
if (contact == null) return;
|
if (contact == null) return;
|
||||||
pushKeys.add(PushUser(
|
pushKeys.add(
|
||||||
|
PushUser(
|
||||||
userId: Int64(fromUserId),
|
userId: Int64(fromUserId),
|
||||||
displayName: getContactDisplayName(contact),
|
displayName: getContactDisplayName(contact),
|
||||||
pushKeys: [],
|
pushKeys: [],
|
||||||
blocked: contact.blocked,
|
blocked: contact.blocked,
|
||||||
lastMessageId: Int64(),
|
lastMessageId: Int64(),
|
||||||
));
|
),
|
||||||
|
);
|
||||||
pushUser = pushKeys.firstWhereOrNull((x) => x.userId == fromUserId);
|
pushUser = pushKeys.firstWhereOrNull((x) => x.userId == fromUserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// ignore_for_file: unreachable_from_main
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:ui' as ui;
|
import 'dart:ui' as ui;
|
||||||
|
|
@ -18,7 +20,8 @@ void notificationTapBackground(NotificationResponse notificationResponse) {
|
||||||
if (notificationResponse.input?.isNotEmpty ?? false) {
|
if (notificationResponse.input?.isNotEmpty ?? false) {
|
||||||
// ignore: avoid_print
|
// ignore: avoid_print
|
||||||
print(
|
print(
|
||||||
'notification action tapped with input: ${notificationResponse.input}');
|
'notification action tapped with input: ${notificationResponse.input}',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,9 @@ import 'package:twonly/src/utils/misc.dart';
|
||||||
final lockingSignalEncryption = Mutex();
|
final lockingSignalEncryption = Mutex();
|
||||||
|
|
||||||
Future<Uint8List?> signalEncryptMessage(
|
Future<Uint8List?> signalEncryptMessage(
|
||||||
int target, Uint8List plaintextContent) async {
|
int target,
|
||||||
|
Uint8List plaintextContent,
|
||||||
|
) async {
|
||||||
return lockingSignalEncryption.protect<Uint8List?>(() async {
|
return lockingSignalEncryption.protect<Uint8List?>(() async {
|
||||||
try {
|
try {
|
||||||
final signalStore = (await getSignalStore())!;
|
final signalStore = (await getSignalStore())!;
|
||||||
|
|
@ -95,7 +97,9 @@ Future<MessageJson?> signalDecryptMessage(int source, Uint8List msg) async {
|
||||||
final signalStore = (await getSignalStore())!;
|
final signalStore = (await getSignalStore())!;
|
||||||
|
|
||||||
final session = SessionCipher.fromStore(
|
final session = SessionCipher.fromStore(
|
||||||
signalStore, SignalProtocolAddress(source.toString(), defaultDeviceId));
|
signalStore,
|
||||||
|
SignalProtocolAddress(source.toString(), defaultDeviceId),
|
||||||
|
);
|
||||||
|
|
||||||
final msgs = removeLastXBytes(msg, 4);
|
final msgs = removeLastXBytes(msg, 4);
|
||||||
if (msgs == null) {
|
if (msgs == null) {
|
||||||
|
|
@ -115,11 +119,13 @@ Future<MessageJson?> signalDecryptMessage(int source, Uint8List msg) async {
|
||||||
Log.error('Type not known: $type');
|
Log.error('Type not known: $type');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return MessageJson.fromJson(jsonDecode(
|
return MessageJson.fromJson(
|
||||||
|
jsonDecode(
|
||||||
utf8.decode(
|
utf8.decode(
|
||||||
gzip.decode(plaintext),
|
gzip.decode(plaintext),
|
||||||
),
|
),
|
||||||
) as Map<String, dynamic>);
|
) as Map<String, dynamic>,
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Log.error(e.toString());
|
Log.error(e.toString());
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,8 @@ Future<void> requestNewPrekeysForContact(int contactId) async {
|
||||||
final otherKeys = await apiService.getPreKeysByUserId(contactId);
|
final otherKeys = await apiService.getPreKeysByUserId(contactId);
|
||||||
if (otherKeys != null) {
|
if (otherKeys != null) {
|
||||||
Log.info(
|
Log.info(
|
||||||
'got fresh ${otherKeys.preKeys.length} pre keys from other $contactId!');
|
'got fresh ${otherKeys.preKeys.length} pre keys from other $contactId!',
|
||||||
|
);
|
||||||
final preKeys = otherKeys.preKeys
|
final preKeys = otherKeys.preKeys
|
||||||
.map(
|
.map(
|
||||||
(preKey) => SignalContactPreKeysCompanion(
|
(preKey) => SignalContactPreKeysCompanion(
|
||||||
|
|
@ -80,7 +81,8 @@ Future<void> requestNewSignedPreKeyForContact(int contactId) async {
|
||||||
signedPreKeySignature:
|
signedPreKeySignature:
|
||||||
Value(Uint8List.fromList(signedPreKey.signedPrekeySignature)),
|
Value(Uint8List.fromList(signedPreKey.signedPrekeySignature)),
|
||||||
signedPreKeyId: Value(signedPreKey.signedPrekeyId.toInt()),
|
signedPreKeyId: Value(signedPreKey.signedPrekeyId.toInt()),
|
||||||
));
|
),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
Log.error('could not load new signed pre key for user $contactId');
|
Log.error('could not load new signed pre key for user $contactId');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,8 @@ Future<ConnectSignalProtocolStore?> getSignalStore() async {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ConnectSignalProtocolStore> getSignalStoreFromIdentity(
|
Future<ConnectSignalProtocolStore> getSignalStoreFromIdentity(
|
||||||
SignalIdentity signalIdentity) async {
|
SignalIdentity signalIdentity,
|
||||||
|
) async {
|
||||||
final identityKeyPair =
|
final identityKeyPair =
|
||||||
IdentityKeyPair.fromSerialized(signalIdentity.identityKeyPairU8List);
|
IdentityKeyPair.fromSerialized(signalIdentity.identityKeyPairU8List);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ Future<void> performTwonlySafeBackup({bool force = false}) async {
|
||||||
}
|
}
|
||||||
|
|
||||||
final lastUpdateTime = user.twonlySafeBackup!.lastBackupDone;
|
final lastUpdateTime = user.twonlySafeBackup!.lastBackupDone;
|
||||||
if (force != true && lastUpdateTime != null) {
|
if (!force && lastUpdateTime != null) {
|
||||||
if (lastUpdateTime
|
if (lastUpdateTime
|
||||||
.isAfter(DateTime.now().subtract(const Duration(days: 1)))) {
|
.isAfter(DateTime.now().subtract(const Duration(days: 1)))) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -163,7 +163,8 @@ Future<void> performTwonlySafeBackup({bool force = false}) async {
|
||||||
await encryptedBackupBytesFile.writeAsBytes(encryptedBackupBytes);
|
await encryptedBackupBytesFile.writeAsBytes(encryptedBackupBytes);
|
||||||
|
|
||||||
Log.info(
|
Log.info(
|
||||||
'Create twonly Safe backup with a size of ${encryptedBackupBytes.length} bytes.');
|
'Create twonly Safe backup with a size of ${encryptedBackupBytes.length} bytes.',
|
||||||
|
);
|
||||||
|
|
||||||
if (user.backupServer != null) {
|
if (user.backupServer != null) {
|
||||||
if (encryptedBackupBytes.length > user.backupServer!.maxBackupBytes) {
|
if (encryptedBackupBytes.length > user.backupServer!.maxBackupBytes) {
|
||||||
|
|
@ -205,7 +206,8 @@ Future<void> handleBackupStatusUpdate(TaskStatusUpdate update) async {
|
||||||
if (update.status == TaskStatus.failed ||
|
if (update.status == TaskStatus.failed ||
|
||||||
update.status == TaskStatus.canceled) {
|
update.status == TaskStatus.canceled) {
|
||||||
Log.error(
|
Log.error(
|
||||||
'twonly Safe upload failed. ${update.responseStatusCode} ${update.responseBody} ${update.responseHeaders} ${update.exception}');
|
'twonly Safe upload failed. ${update.responseStatusCode} ${update.responseBody} ${update.responseHeaders} ${update.exception}',
|
||||||
|
);
|
||||||
await updateUserdata((user) {
|
await updateUserdata((user) {
|
||||||
if (user.twonlySafeBackup != null) {
|
if (user.twonlySafeBackup != null) {
|
||||||
user.twonlySafeBackup!.backupUploadState = LastBackupUploadState.failed;
|
user.twonlySafeBackup!.backupUploadState = LastBackupUploadState.failed;
|
||||||
|
|
@ -214,7 +216,8 @@ Future<void> handleBackupStatusUpdate(TaskStatusUpdate update) async {
|
||||||
});
|
});
|
||||||
} else if (update.status == TaskStatus.complete) {
|
} else if (update.status == TaskStatus.complete) {
|
||||||
Log.error(
|
Log.error(
|
||||||
'twonly Safe uploaded with status code ${update.responseStatusCode}');
|
'twonly Safe uploaded with status code ${update.responseStatusCode}',
|
||||||
|
);
|
||||||
await updateUserdata((user) {
|
await updateUserdata((user) {
|
||||||
if (user.twonlySafeBackup != null) {
|
if (user.twonlySafeBackup != null) {
|
||||||
user.twonlySafeBackup!.backupUploadState =
|
user.twonlySafeBackup!.backupUploadState =
|
||||||
|
|
|
||||||
|
|
@ -37,9 +37,12 @@ Future<void> recoverTwonlySafe(
|
||||||
late http.Response response;
|
late http.Response response;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
response = await http.get(Uri.parse(backupServerUrl), headers: {
|
response = await http.get(
|
||||||
|
Uri.parse(backupServerUrl),
|
||||||
|
headers: {
|
||||||
HttpHeaders.acceptHeader: 'application/octet-stream',
|
HttpHeaders.acceptHeader: 'application/octet-stream',
|
||||||
});
|
},
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Log.error('Error fetching backup: $e');
|
Log.error('Error fetching backup: $e');
|
||||||
throw Exception('Backup server could not be reached. ($e)');
|
throw Exception('Backup server could not be reached. ($e)');
|
||||||
|
|
@ -110,7 +113,8 @@ Future<void> handleBackupData(
|
||||||
// for each day add 400 message ids
|
// for each day add 400 message ids
|
||||||
final dummyMessagesCounter = (lastMessageSend + 1) * 400;
|
final dummyMessagesCounter = (lastMessageSend + 1) * 400;
|
||||||
Log.info(
|
Log.info(
|
||||||
'Creating $dummyMessagesCounter dummy messages to increase message counter as last message was $lastMessageSend days ago.');
|
'Creating $dummyMessagesCounter dummy messages to increase message counter as last message was $lastMessageSend days ago.',
|
||||||
|
);
|
||||||
for (var i = 0; i < dummyMessagesCounter; i++) {
|
for (var i = 0; i < dummyMessagesCounter; i++) {
|
||||||
await database.messagesDao.insertMessage(
|
await database.messagesDao.insertMessage(
|
||||||
MessagesCompanion(
|
MessagesCompanion(
|
||||||
|
|
@ -130,13 +134,16 @@ Future<void> handleBackupData(
|
||||||
|
|
||||||
await storage.write(
|
await storage.write(
|
||||||
key: SecureStorageKeys.signalIdentity,
|
key: SecureStorageKeys.signalIdentity,
|
||||||
value: secureStorage[SecureStorageKeys.signalIdentity] as String);
|
value: secureStorage[SecureStorageKeys.signalIdentity] as String,
|
||||||
|
);
|
||||||
await storage.write(
|
await storage.write(
|
||||||
key: SecureStorageKeys.signalSignedPreKey,
|
key: SecureStorageKeys.signalSignedPreKey,
|
||||||
value: secureStorage[SecureStorageKeys.signalSignedPreKey] as String);
|
value: secureStorage[SecureStorageKeys.signalSignedPreKey] as String,
|
||||||
|
);
|
||||||
await storage.write(
|
await storage.write(
|
||||||
key: SecureStorageKeys.userData,
|
key: SecureStorageKeys.userData,
|
||||||
value: secureStorage[SecureStorageKeys.userData] as String);
|
value: secureStorage[SecureStorageKeys.userData] as String,
|
||||||
|
);
|
||||||
await updateUserdata((u) {
|
await updateUserdata((u) {
|
||||||
u.deviceId += 1;
|
u.deviceId += 1;
|
||||||
return u;
|
return u;
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,8 @@ void initLogger() {
|
||||||
await _writeLogToFile(record);
|
await _writeLogToFile(record);
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
print(
|
print(
|
||||||
'${record.level.name} [twonly] ${record.loggerName} > ${record.message}');
|
'${record.level.name} [twonly] ${record.loggerName} > ${record.message}',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:drift/drift.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
@ -10,8 +9,6 @@ import 'package:gal/gal.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:local_auth/local_auth.dart';
|
import 'package:local_auth/local_auth.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:twonly/globals.dart';
|
|
||||||
import 'package:twonly/src/database/tables/messages_table.dart';
|
|
||||||
import 'package:twonly/src/database/twonly_database.dart';
|
import 'package:twonly/src/database/twonly_database.dart';
|
||||||
import 'package:twonly/src/localization/generated/app_localizations.dart';
|
import 'package:twonly/src/localization/generated/app_localizations.dart';
|
||||||
import 'package:twonly/src/model/json/message.dart';
|
import 'package:twonly/src/model/json/message.dart';
|
||||||
|
|
@ -133,18 +130,20 @@ Future<Uint8List?> getCompressedImage(Uint8List imageBytes) async {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> authenticateUser(String localizedReason,
|
Future<bool> authenticateUser(
|
||||||
{bool force = true}) async {
|
String localizedReason, {
|
||||||
|
bool force = true,
|
||||||
|
}) async {
|
||||||
try {
|
try {
|
||||||
final auth = LocalAuthentication();
|
final auth = LocalAuthentication();
|
||||||
final didAuthenticate = await auth.authenticate(
|
final didAuthenticate = await auth.authenticate(
|
||||||
localizedReason: localizedReason,
|
localizedReason: localizedReason,
|
||||||
options: const AuthenticationOptions(useErrorDialogs: false));
|
);
|
||||||
if (didAuthenticate) {
|
if (didAuthenticate) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} on PlatformException catch (e) {
|
} on LocalAuthException catch (e) {
|
||||||
debugPrint(e.toString());
|
Log.error(e.toString());
|
||||||
if (!force) {
|
if (!force) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -218,171 +217,6 @@ String truncateString(String input, {int maxLength = 20}) {
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> insertDemoContacts() async {
|
|
||||||
final commonUsernames = <String>[
|
|
||||||
'James',
|
|
||||||
'Mary',
|
|
||||||
'John',
|
|
||||||
'Patricia',
|
|
||||||
'Robert',
|
|
||||||
'Jennifer',
|
|
||||||
'Michael',
|
|
||||||
'Linda',
|
|
||||||
'William',
|
|
||||||
'Elizabeth',
|
|
||||||
'David',
|
|
||||||
'Barbara',
|
|
||||||
'Richard',
|
|
||||||
'Susan',
|
|
||||||
'Joseph',
|
|
||||||
'Jessica',
|
|
||||||
'Charles',
|
|
||||||
'Sarah',
|
|
||||||
'Thomas',
|
|
||||||
'Karen',
|
|
||||||
];
|
|
||||||
final contactConfigs = <Map<String, dynamic>>[
|
|
||||||
{'count': 3, 'requested': true},
|
|
||||||
{'count': 4, 'requested': false, 'accepted': true},
|
|
||||||
{'count': 1, 'accepted': true, 'blocked': true},
|
|
||||||
{'count': 1, 'accepted': true, 'archived': true},
|
|
||||||
{'count': 2, 'accepted': true, 'pinned': true},
|
|
||||||
{'count': 1, 'requested': false},
|
|
||||||
];
|
|
||||||
|
|
||||||
var counter = 0;
|
|
||||||
|
|
||||||
for (final config in contactConfigs) {
|
|
||||||
for (var i = 0; i < (config['count'] as int); i++) {
|
|
||||||
if (counter >= commonUsernames.length) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
final username = commonUsernames[counter];
|
|
||||||
final userId = Random().nextInt(1000000);
|
|
||||||
await twonlyDB.contactsDao.insertContact(
|
|
||||||
ContactsCompanion(
|
|
||||||
username: Value(username),
|
|
||||||
userId: Value(userId),
|
|
||||||
requested: Value(config['requested'] as bool? ?? false),
|
|
||||||
accepted: Value(config['accepted'] as bool? ?? false),
|
|
||||||
blocked: Value(config['blocked'] as bool? ?? false),
|
|
||||||
archived: Value(config['archived'] as bool? ?? false),
|
|
||||||
pinned: Value(config['pinned'] as bool? ?? false),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (config['accepted'] as bool? ?? false) {
|
|
||||||
for (var i = 0; i < 20; i++) {
|
|
||||||
final chatId = Random().nextInt(chatMessages.length);
|
|
||||||
await twonlyDB.messagesDao.insertMessage(
|
|
||||||
MessagesCompanion(
|
|
||||||
contactId: Value(userId),
|
|
||||||
kind: const Value(MessageKind.textMessage),
|
|
||||||
sendAt: Value(chatMessages[chatId][1] as DateTime),
|
|
||||||
acknowledgeByServer: const Value(true),
|
|
||||||
acknowledgeByUser: const Value(true),
|
|
||||||
messageOtherId:
|
|
||||||
Value(Random().nextBool() ? Random().nextInt(10000) : null),
|
|
||||||
// responseToOtherMessageId: Value(content.responseToMessageId),
|
|
||||||
// responseToMessageId: Value(content.responseToOtherMessageId),
|
|
||||||
downloadState: const Value(DownloadState.downloaded),
|
|
||||||
contentJson: Value(
|
|
||||||
jsonEncode(TextMessageContent(
|
|
||||||
text: chatMessages[chatId][0] as String)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
counter++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> createFakeDemoData() async {
|
|
||||||
await insertDemoContacts();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<List<dynamic>> chatMessages = [
|
|
||||||
[
|
|
||||||
'Lorem ipsum dolor sit amet.',
|
|
||||||
DateTime.now().subtract(const Duration(minutes: 20))
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'Consectetur adipiscing elit.',
|
|
||||||
DateTime.now().subtract(const Duration(minutes: 19))
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
|
|
||||||
DateTime.now().subtract(const Duration(minutes: 18))
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'Ut enim ad minim veniam.',
|
|
||||||
DateTime.now().subtract(const Duration(minutes: 17))
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'Quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.',
|
|
||||||
DateTime.now().subtract(const Duration(minutes: 16))
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.',
|
|
||||||
DateTime.now().subtract(const Duration(minutes: 15))
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'Excepteur sint occaecat cupidatat non proident.',
|
|
||||||
DateTime.now().subtract(const Duration(minutes: 14))
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'Sunt in culpa qui officia deserunt mollit anim id est laborum.',
|
|
||||||
DateTime.now().subtract(const Duration(minutes: 13))
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'Curabitur pretium tincidunt lacus.',
|
|
||||||
DateTime.now().subtract(const Duration(minutes: 12))
|
|
||||||
],
|
|
||||||
['Nulla facilisi.', DateTime.now().subtract(const Duration(minutes: 11))],
|
|
||||||
[
|
|
||||||
'Aenean lacinia bibendum nulla sed consectetur.',
|
|
||||||
DateTime.now().subtract(const Duration(minutes: 10))
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'Sed posuere consectetur est at lobortis.',
|
|
||||||
DateTime.now().subtract(const Duration(minutes: 9))
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'Vestibulum id ligula porta felis euismod semper.',
|
|
||||||
DateTime.now().subtract(const Duration(minutes: 8))
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'Cras justo odio, dapibus ac facilisis in, egestas eget quam.',
|
|
||||||
DateTime.now().subtract(const Duration(minutes: 7))
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'Morbi leo risus, porta ac consectetur ac, vestibulum at eros.',
|
|
||||||
DateTime.now().subtract(const Duration(minutes: 6))
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'Praesent commodo cursus magna, vel scelerisque nisl consectetur et.',
|
|
||||||
DateTime.now().subtract(const Duration(minutes: 5))
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'Donec ullamcorper nulla non metus auctor fringilla.',
|
|
||||||
DateTime.now().subtract(const Duration(minutes: 4))
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'Etiam porta sem malesuada magna mollis euismod.',
|
|
||||||
DateTime.now().subtract(const Duration(minutes: 3))
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'Aenean lacinia bibendum nulla sed consectetur.',
|
|
||||||
DateTime.now().subtract(const Duration(minutes: 2))
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'Nullam quis risus eget urna mollis ornare vel eu leo.',
|
|
||||||
DateTime.now().subtract(const Duration(minutes: 1))
|
|
||||||
],
|
|
||||||
['Curabitur blandit tempus porttitor.', DateTime.now()],
|
|
||||||
];
|
|
||||||
|
|
||||||
String formatDateTime(BuildContext context, DateTime? dateTime) {
|
String formatDateTime(BuildContext context, DateTime? dateTime) {
|
||||||
if (dateTime == null) {
|
if (dateTime == null) {
|
||||||
return 'Never';
|
return 'Never';
|
||||||
|
|
@ -426,7 +260,8 @@ MediaMessageContent? getMediaContent(Message message) {
|
||||||
try {
|
try {
|
||||||
if (message.contentJson == null) return null;
|
if (message.contentJson == null) return null;
|
||||||
return MediaMessageContent.fromJson(
|
return MediaMessageContent.fromJson(
|
||||||
jsonDecode(message.contentJson!) as Map);
|
jsonDecode(message.contentJson!) as Map,
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Log.error(e);
|
Log.error(e);
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ class PermissionHandlerViewState extends State<PermissionHandlerView> {
|
||||||
final statuses = await [
|
final statuses = await [
|
||||||
Permission.camera,
|
Permission.camera,
|
||||||
// Permission.microphone,
|
// Permission.microphone,
|
||||||
Permission.notification
|
Permission.notification,
|
||||||
].request();
|
].request();
|
||||||
// } catch (e) {}
|
// } catch (e) {}
|
||||||
// You can request multiple permissions at once.
|
// You can request multiple permissions at once.
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,8 @@ class SaveToGalleryButtonState extends State<SaveToGalleryButton> {
|
||||||
} else {
|
} else {
|
||||||
final random = Random();
|
final random = Random();
|
||||||
final token = uint8ListToHex(
|
final token = uint8ListToHex(
|
||||||
List<int>.generate(32, (i) => random.nextInt(256)));
|
List<int>.generate(32, (i) => random.nextInt(256)),
|
||||||
|
);
|
||||||
memoryPath = join(memoryPath, token);
|
memoryPath = join(memoryPath, token);
|
||||||
}
|
}
|
||||||
final user = await getUser();
|
final user = await getUser();
|
||||||
|
|
@ -113,16 +114,19 @@ class SaveToGalleryButtonState extends State<SaveToGalleryButton> {
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: 12,
|
width: 12,
|
||||||
height: 12,
|
height: 12,
|
||||||
child: CircularProgressIndicator(strokeWidth: 1))
|
child: CircularProgressIndicator(strokeWidth: 1),
|
||||||
|
)
|
||||||
else
|
else
|
||||||
_imageSaved
|
_imageSaved
|
||||||
? const Icon(Icons.check)
|
? const Icon(Icons.check)
|
||||||
: const FaIcon(FontAwesomeIcons.floppyDisk),
|
: const FaIcon(FontAwesomeIcons.floppyDisk),
|
||||||
if (widget.displayButtonLabel) const SizedBox(width: 10),
|
if (widget.displayButtonLabel) const SizedBox(width: 10),
|
||||||
if (widget.displayButtonLabel)
|
if (widget.displayButtonLabel)
|
||||||
Text(_imageSaved
|
Text(
|
||||||
|
_imageSaved
|
||||||
? context.lang.shareImagedEditorSavedImage
|
? context.lang.shareImagedEditorSavedImage
|
||||||
: context.lang.shareImagedEditorSaveImage)
|
: context.lang.shareImagedEditorSaveImage,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -47,11 +47,11 @@ class VideoRecordingTimer extends StatelessWidget {
|
||||||
Shadow(
|
Shadow(
|
||||||
color: Color.fromARGB(122, 0, 0, 0),
|
color: Color.fromARGB(122, 0, 0, 0),
|
||||||
blurRadius: 5,
|
blurRadius: 5,
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
// ignore_for_file: avoid_dynamic_calls
|
// ignore_for_file: avoid_dynamic_calls
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
|
|
@ -45,7 +46,7 @@ class _CameraZoomButtonsState extends State<CameraZoomButtons> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
initAsync();
|
unawaited(initAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initAsync() async {
|
Future<void> initAsync() async {
|
||||||
|
|
@ -139,7 +140,8 @@ class _CameraZoomButtonsState extends State<CameraZoomButtons> {
|
||||||
? '${beautifulZoomScale(widget.scaleFactor)}x'
|
? '${beautifulZoomScale(widget.scaleFactor)}x'
|
||||||
: '1.0x',
|
: '1.0x',
|
||||||
style: zoomTextStyle,
|
style: zoomTextStyle,
|
||||||
)),
|
),
|
||||||
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
style: zoomButtonStyle.copyWith(
|
style: zoomButtonStyle.copyWith(
|
||||||
foregroundColor: WidgetStateProperty.all(
|
foregroundColor: WidgetStateProperty.all(
|
||||||
|
|
@ -152,9 +154,11 @@ class _CameraZoomButtonsState extends State<CameraZoomButtons> {
|
||||||
.toDouble();
|
.toDouble();
|
||||||
widget.updateScaleFactor(level);
|
widget.updateScaleFactor(level);
|
||||||
},
|
},
|
||||||
child: Text('${beautifulZoomScale(maxLevel.toDouble())}x',
|
child: Text(
|
||||||
style: zoomTextStyle),
|
'${beautifulZoomScale(maxLevel.toDouble())}x',
|
||||||
)
|
style: zoomTextStyle,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,10 @@ class CameraPreviewControllerView extends StatelessWidget {
|
||||||
});
|
});
|
||||||
final Contact? sendTo;
|
final Contact? sendTo;
|
||||||
final Future<CameraController?> Function(
|
final Future<CameraController?> Function(
|
||||||
int sCameraId, bool init, bool enableAudio) selectCamera;
|
int sCameraId,
|
||||||
|
bool init,
|
||||||
|
bool enableAudio,
|
||||||
|
) selectCamera;
|
||||||
final CameraController? cameraController;
|
final CameraController? cameraController;
|
||||||
final SelectedCameraDetails selectedCameraDetails;
|
final SelectedCameraDetails selectedCameraDetails;
|
||||||
final ScreenshotController screenshotController;
|
final ScreenshotController screenshotController;
|
||||||
|
|
@ -114,10 +117,12 @@ class CameraPreviewControllerView extends StatelessWidget {
|
||||||
screenshotController: screenshotController,
|
screenshotController: screenshotController,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return PermissionHandlerView(onSuccess: () {
|
return PermissionHandlerView(
|
||||||
|
onSuccess: () {
|
||||||
// setState(() {});
|
// setState(() {});
|
||||||
selectCamera(0, true, false);
|
selectCamera(0, true, false);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Container();
|
return Container();
|
||||||
|
|
@ -138,7 +143,10 @@ class CameraPreviewView extends StatefulWidget {
|
||||||
});
|
});
|
||||||
final Contact? sendTo;
|
final Contact? sendTo;
|
||||||
final Future<CameraController?> Function(
|
final Future<CameraController?> Function(
|
||||||
int sCameraId, bool init, bool enableAudio) selectCamera;
|
int sCameraId,
|
||||||
|
bool init,
|
||||||
|
bool enableAudio,
|
||||||
|
) selectCamera;
|
||||||
final CameraController? cameraController;
|
final CameraController? cameraController;
|
||||||
final SelectedCameraDetails selectedCameraDetails;
|
final SelectedCameraDetails selectedCameraDetails;
|
||||||
final ScreenshotController screenshotController;
|
final ScreenshotController screenshotController;
|
||||||
|
|
@ -212,9 +220,12 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
widget.cameraController == null) {
|
widget.cameraController == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await widget.cameraController?.setZoomLevel(newScale.clamp(
|
await widget.cameraController?.setZoomLevel(
|
||||||
|
newScale.clamp(
|
||||||
widget.selectedCameraDetails.minAvailableZoom,
|
widget.selectedCameraDetails.minAvailableZoom,
|
||||||
widget.selectedCameraDetails.maxAvailableZoom));
|
widget.selectedCameraDetails.maxAvailableZoom,
|
||||||
|
),
|
||||||
|
);
|
||||||
setState(() {
|
setState(() {
|
||||||
widget.selectedCameraDetails.scaleFactor = newScale;
|
widget.selectedCameraDetails.scaleFactor = newScale;
|
||||||
});
|
});
|
||||||
|
|
@ -284,8 +295,10 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> pushMediaEditor(
|
Future<bool> pushMediaEditor(
|
||||||
Future<Uint8List?>? imageBytes, File? videoFilePath,
|
Future<Uint8List?>? imageBytes,
|
||||||
{bool sharedFromGallery = false}) async {
|
File? videoFilePath, {
|
||||||
|
bool sharedFromGallery = false,
|
||||||
|
}) async {
|
||||||
final shouldReturn = await Navigator.push(
|
final shouldReturn = await Navigator.push(
|
||||||
context,
|
context,
|
||||||
PageRouteBuilder(
|
PageRouteBuilder(
|
||||||
|
|
@ -314,7 +327,6 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
if (!mounted) return true;
|
if (!mounted) return true;
|
||||||
// shouldReturn is null when the user used the back button
|
// shouldReturn is null when the user used the back button
|
||||||
if (shouldReturn != null && shouldReturn) {
|
if (shouldReturn != null && shouldReturn) {
|
||||||
// ignore: use_build_context_synchronously
|
|
||||||
if (widget.sendTo == null) {
|
if (widget.sendTo == null) {
|
||||||
globalUpdateOfHomeViewPageIndex(0);
|
globalUpdateOfHomeViewPageIndex(0);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -323,7 +335,10 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
await widget.selectCamera(
|
await widget.selectCamera(
|
||||||
widget.selectedCameraDetails.cameraId, false, false);
|
widget.selectedCameraDetails.cameraId,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -466,7 +481,6 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
Log.error('$e');
|
Log.error('$e');
|
||||||
try {
|
try {
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
// ignore: use_build_context_synchronously
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text('Error: $e'),
|
content: Text('Error: $e'),
|
||||||
|
|
@ -555,7 +569,8 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
await widget.selectCamera(
|
await widget.selectCamera(
|
||||||
(widget.selectedCameraDetails.cameraId + 1) % 2,
|
(widget.selectedCameraDetails.cameraId + 1) % 2,
|
||||||
false,
|
false,
|
||||||
false);
|
false,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ActionButton(
|
ActionButton(
|
||||||
|
|
@ -676,7 +691,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (!isVideoRecording) const SizedBox(width: 80)
|
if (!isVideoRecording) const SizedBox(width: 80),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
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:screenshot/screenshot.dart';
|
||||||
|
|
@ -20,7 +22,7 @@ class CameraSendToViewState extends State<CameraSendToView> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
selectCamera(0, true, false);
|
unawaited(selectCamera(0, true, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -32,9 +34,16 @@ class CameraSendToViewState extends State<CameraSendToView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<CameraController?> selectCamera(
|
Future<CameraController?> selectCamera(
|
||||||
int sCameraId, bool init, bool enableAudio) async {
|
int sCameraId,
|
||||||
|
bool init,
|
||||||
|
bool enableAudio,
|
||||||
|
) async {
|
||||||
final opts = await initializeCameraController(
|
final opts = await initializeCameraController(
|
||||||
selectedCameraDetails, sCameraId, init, enableAudio);
|
selectedCameraDetails,
|
||||||
|
sCameraId,
|
||||||
|
init,
|
||||||
|
enableAudio,
|
||||||
|
);
|
||||||
if (opts != null) {
|
if (opts != null) {
|
||||||
selectedCameraDetails = opts.$1;
|
selectedCameraDetails = opts.$1;
|
||||||
cameraController = opts.$2;
|
cameraController = opts.$2;
|
||||||
|
|
@ -47,7 +56,7 @@ class CameraSendToViewState extends State<CameraSendToView> {
|
||||||
Future<void> toggleSelectedCamera() async {
|
Future<void> toggleSelectedCamera() async {
|
||||||
if (cameraController == null) return;
|
if (cameraController == null) return;
|
||||||
// do not allow switching camera when recording
|
// do not allow switching camera when recording
|
||||||
if (cameraController!.value.isRecordingVideo == true) {
|
if (cameraController!.value.isRecordingVideo) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await cameraController!.dispose();
|
await cameraController!.dispose();
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ class ActionButton extends StatelessWidget {
|
||||||
Shadow(
|
Shadow(
|
||||||
color: Color.fromARGB(122, 0, 0, 0),
|
color: Color.fromARGB(122, 0, 0, 0),
|
||||||
blurRadius: 5,
|
blurRadius: 5,
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class ImageItem {
|
class ImageItem {
|
||||||
ImageItem([dynamic image]) {
|
ImageItem([dynamic image]) {
|
||||||
if (image != null) load(image);
|
if (image != null) unawaited(load(image));
|
||||||
}
|
}
|
||||||
int width = 1;
|
int width = 1;
|
||||||
int height = 1;
|
int height = 1;
|
||||||
|
|
|
||||||
|
|
@ -77,5 +77,12 @@ class DrawLayerData extends Layer {
|
||||||
super.hasCustomActionButtons = true,
|
super.hasCustomActionButtons = true,
|
||||||
super.isEditing = true,
|
super.isEditing = true,
|
||||||
});
|
});
|
||||||
final control = HandSignatureControl();
|
final control = HandSignatureControl(
|
||||||
|
// ignore: prefer_const_constructors
|
||||||
|
setup: () => SignaturePathSetup(
|
||||||
|
args: {
|
||||||
|
'color': null,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:hand_signature/signature.dart';
|
import 'package:hand_signature/signature.dart';
|
||||||
|
// ignore: implementation_imports
|
||||||
|
import 'package:hand_signature/src/utils.dart';
|
||||||
import 'package:screenshot/screenshot.dart';
|
import 'package:screenshot/screenshot.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/views/camera/image_editor/action_button.dart';
|
import 'package:twonly/src/views/camera/image_editor/action_button.dart';
|
||||||
|
|
@ -91,7 +93,7 @@ class _DrawLayerState extends State<DrawLayer> {
|
||||||
controller: screenshotController,
|
controller: screenshotController,
|
||||||
child: HandSignature(
|
child: HandSignature(
|
||||||
control: widget.layerData.control,
|
control: widget.layerData.control,
|
||||||
drawer: LineSignatureDrawer(color: currentColor, width: 7),
|
drawer: CustomSignatureDrawer(color: currentColor, width: 7),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -167,8 +169,10 @@ class _DrawLayerState extends State<DrawLayer> {
|
||||||
begin: Alignment.topCenter,
|
begin: Alignment.topCenter,
|
||||||
end: Alignment.bottomCenter,
|
end: Alignment.bottomCenter,
|
||||||
colors: colors,
|
colors: colors,
|
||||||
stops: List.generate(colors.length,
|
stops: List.generate(
|
||||||
(index) => index / (colors.length - 1)),
|
colors.length,
|
||||||
|
(index) => index / (colors.length - 1),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
),
|
),
|
||||||
|
|
@ -187,12 +191,12 @@ class _DrawLayerState extends State<DrawLayer> {
|
||||||
onChangeStart: (value) => {
|
onChangeStart: (value) => {
|
||||||
setState(() {
|
setState(() {
|
||||||
showMagnifyingGlass = true;
|
showMagnifyingGlass = true;
|
||||||
})
|
}),
|
||||||
},
|
},
|
||||||
onChangeEnd: (value) => {
|
onChangeEnd: (value) => {
|
||||||
setState(() {
|
setState(() {
|
||||||
showMagnifyingGlass = false;
|
showMagnifyingGlass = false;
|
||||||
})
|
}),
|
||||||
},
|
},
|
||||||
divisions: 100,
|
divisions: 100,
|
||||||
),
|
),
|
||||||
|
|
@ -211,7 +215,8 @@ class _DrawLayerState extends State<DrawLayer> {
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: Container(
|
child: Container(
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
))
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -239,3 +244,33 @@ class MagnifyingGlass extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CustomSignatureDrawer extends HandSignatureDrawer {
|
||||||
|
const CustomSignatureDrawer({
|
||||||
|
this.width = 1.0,
|
||||||
|
this.color = Colors.black,
|
||||||
|
});
|
||||||
|
final Color color;
|
||||||
|
final double width;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size, List<CubicPath> paths) {
|
||||||
|
for (final path in paths) {
|
||||||
|
var lineColor = color;
|
||||||
|
if (path.setup.args!['color'] != null) {
|
||||||
|
lineColor = path.setup.args!['color'] as Color;
|
||||||
|
} else {
|
||||||
|
path.setup.args!['color'] = color;
|
||||||
|
}
|
||||||
|
final paint = Paint()
|
||||||
|
..color = lineColor
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeCap = StrokeCap.round
|
||||||
|
..strokeJoin = StrokeJoin.round
|
||||||
|
..strokeWidth = width;
|
||||||
|
if (path.isFilled) {
|
||||||
|
canvas.drawPath(PathUtil.toLinePath(path.lines), paint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,8 @@ class _EmojiLayerState extends State<EmojiLayer> {
|
||||||
setState(() {
|
setState(() {
|
||||||
widget.layerData.offset = Offset(
|
widget.layerData.offset = Offset(
|
||||||
MediaQuery.of(context).size.width / 2 - (153 / 2),
|
MediaQuery.of(context).size.width / 2 - (153 / 2),
|
||||||
MediaQuery.of(context).size.height / 2 - (153 / 2) - 100);
|
MediaQuery.of(context).size.height / 2 - (153 / 2) - 100,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
display = true;
|
display = true;
|
||||||
});
|
});
|
||||||
|
|
@ -87,8 +88,8 @@ class _EmojiLayerState extends State<EmojiLayer> {
|
||||||
|
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
onScaleUpdate: (details) {
|
onScaleUpdate: (details) async {
|
||||||
if (twoPointerWhereDown == true && details.pointerCount != 2) {
|
if (twoPointerWhereDown && details.pointerCount != 2) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final outlineBox =
|
final outlineBox =
|
||||||
|
|
@ -109,7 +110,7 @@ class _EmojiLayerState extends State<EmojiLayer> {
|
||||||
|
|
||||||
if (isAtTheBottom && isInTheCenter) {
|
if (isAtTheBottom && isInTheCenter) {
|
||||||
if (!deleteLayer) {
|
if (!deleteLayer) {
|
||||||
HapticFeedback.heavyImpact();
|
await HapticFeedback.heavyImpact();
|
||||||
}
|
}
|
||||||
deleteLayer = true;
|
deleteLayer = true;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/src/views/camera/image_editor/data/layer.dart';
|
import 'package:twonly/src/views/camera/image_editor/data/layer.dart';
|
||||||
|
|
@ -61,7 +63,7 @@ class FilterText extends StatelessWidget {
|
||||||
Shadow(
|
Shadow(
|
||||||
color: Color.fromARGB(122, 0, 0, 0),
|
color: Color.fromARGB(122, 0, 0, 0),
|
||||||
blurRadius: 5,
|
blurRadius: 5,
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -83,7 +85,7 @@ class _FilterLayerState extends State<FilterLayer> {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
pageController.jumpToPage(1);
|
pageController.jumpToPage(1);
|
||||||
});
|
});
|
||||||
initAsync();
|
unawaited(initAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initAsync() async {
|
Future<void> initAsync() async {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
|
@ -26,7 +27,7 @@ class _LocationFilterState extends State<LocationFilter> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
initAsync();
|
unawaited(initAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initAsync() async {
|
Future<void> initAsync() async {
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,8 @@ class _TextViewState extends State<TextLayer> {
|
||||||
0,
|
0,
|
||||||
MediaQuery.of(context).size.height / 2 -
|
MediaQuery.of(context).size.height / 2 -
|
||||||
150 +
|
150 +
|
||||||
(widget.layerData.textLayersBefore * 40));
|
(widget.layerData.textLayersBefore * 40),
|
||||||
|
);
|
||||||
textController.text = widget.layerData.text;
|
textController.text = widget.layerData.text;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -68,38 +69,37 @@ class _TextViewState extends State<TextLayer> {
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
minLines: 1,
|
minLines: 1,
|
||||||
onEditingComplete: () {
|
onEditingComplete: () async {
|
||||||
setState(() {
|
setState(() {
|
||||||
widget.layerData.isDeleted = textController.text == '';
|
widget.layerData.isDeleted = textController.text == '';
|
||||||
widget.layerData.isEditing = false;
|
widget.layerData.isEditing = false;
|
||||||
widget.layerData.text = textController.text;
|
widget.layerData.text = textController.text;
|
||||||
});
|
});
|
||||||
|
|
||||||
context
|
await context
|
||||||
.read<ImageEditorProvider>()
|
.read<ImageEditorProvider>()
|
||||||
.updateSomeTextViewIsAlreadyEditing(false);
|
.updateSomeTextViewIsAlreadyEditing(false);
|
||||||
if (widget.onUpdate != null) {
|
if (widget.onUpdate != null) {
|
||||||
widget.onUpdate!();
|
widget.onUpdate!();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onTapOutside: (a) {
|
onTapOutside: (a) async {
|
||||||
widget.layerData.text = textController.text;
|
widget.layerData.text = textController.text;
|
||||||
Future.delayed(const Duration(milliseconds: 100), () {
|
Future.delayed(const Duration(milliseconds: 100), () async {
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
setState(() {
|
|
||||||
widget.layerData.isDeleted = textController.text == '';
|
widget.layerData.isDeleted = textController.text == '';
|
||||||
widget.layerData.isEditing = false;
|
widget.layerData.isEditing = false;
|
||||||
context
|
await context
|
||||||
.read<ImageEditorProvider>()
|
.read<ImageEditorProvider>()
|
||||||
.updateSomeTextViewIsAlreadyEditing(false);
|
.updateSomeTextViewIsAlreadyEditing(false);
|
||||||
if (widget.onUpdate != null) {
|
if (widget.onUpdate != null) {
|
||||||
widget.onUpdate!();
|
widget.onUpdate!();
|
||||||
}
|
}
|
||||||
});
|
if (mounted) setState(() {});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
context
|
await context
|
||||||
.read<ImageEditorProvider>()
|
.read<ImageEditorProvider>()
|
||||||
.updateSomeTextViewIsAlreadyEditing(false);
|
.updateSomeTextViewIsAlreadyEditing(false);
|
||||||
},
|
},
|
||||||
|
|
@ -149,25 +149,26 @@ class _TextViewState extends State<TextLayer> {
|
||||||
.watch<ImageEditorProvider>()
|
.watch<ImageEditorProvider>()
|
||||||
.someTextViewIsAlreadyEditing)
|
.someTextViewIsAlreadyEditing)
|
||||||
? null
|
? null
|
||||||
: () {
|
: () async {
|
||||||
setState(() {
|
await context
|
||||||
context
|
|
||||||
.read<ImageEditorProvider>()
|
.read<ImageEditorProvider>()
|
||||||
.updateSomeTextViewIsAlreadyEditing(true);
|
.updateSomeTextViewIsAlreadyEditing(true);
|
||||||
widget.layerData.isEditing = true;
|
widget.layerData.isEditing = true;
|
||||||
});
|
if (mounted) setState(() {});
|
||||||
},
|
},
|
||||||
onScaleUpdate: (detail) {
|
onScaleUpdate: (detail) async {
|
||||||
if (detail.pointerCount == 1) {
|
if (detail.pointerCount == 1) {
|
||||||
widget.layerData.offset = Offset(
|
widget.layerData.offset = Offset(
|
||||||
0, widget.layerData.offset.dy + detail.focalPointDelta.dy);
|
0,
|
||||||
|
widget.layerData.offset.dy + detail.focalPointDelta.dy,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
final renderBox =
|
final renderBox =
|
||||||
_widgetKey.currentContext!.findRenderObject()! as RenderBox;
|
_widgetKey.currentContext!.findRenderObject()! as RenderBox;
|
||||||
|
|
||||||
if (widget.layerData.offset.dy > renderBox.size.height - 80) {
|
if (widget.layerData.offset.dy > renderBox.size.height - 80) {
|
||||||
if (!deleteLayer) {
|
if (!deleteLayer) {
|
||||||
HapticFeedback.heavyImpact();
|
await HapticFeedback.heavyImpact();
|
||||||
}
|
}
|
||||||
deleteLayer = true;
|
deleteLayer = true;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,10 @@ class LayersViewer extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
...layers
|
...layers
|
||||||
.where((layerItem) =>
|
.where(
|
||||||
layerItem is EmojiLayerData || layerItem is DrawLayerData)
|
(layerItem) =>
|
||||||
|
layerItem is EmojiLayerData || layerItem is DrawLayerData,
|
||||||
|
)
|
||||||
.map((layerItem) {
|
.map((layerItem) {
|
||||||
if (layerItem is EmojiLayerData) {
|
if (layerItem is EmojiLayerData) {
|
||||||
return EmojiLayer(
|
return EmojiLayer(
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
import 'package:twonly/src/views/camera/image_editor/data/data.dart';
|
import 'package:twonly/src/views/camera/image_editor/data/data.dart';
|
||||||
|
|
@ -16,7 +18,7 @@ class _EmojisState extends State<Emojis> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
initAsync();
|
unawaited(initAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initAsync() async {
|
Future<void> initAsync() async {
|
||||||
|
|
@ -87,8 +89,8 @@ class _EmojisState extends State<Emojis> {
|
||||||
children: lastUsed.map((String emoji) {
|
children: lastUsed.map((String emoji) {
|
||||||
return GridTile(
|
return GridTile(
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
selectEmojis(emoji);
|
await selectEmojis(emoji);
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
|
|
@ -98,10 +100,11 @@ class _EmojisState extends State<Emojis> {
|
||||||
style: const TextStyle(fontSize: 35),
|
style: const TextStyle(fontSize: 35),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
));
|
),
|
||||||
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -59,8 +59,10 @@ class BestFriendsSelector extends StatelessWidget {
|
||||||
],
|
],
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
child: Text(context.lang.shareImagedSelectAll,
|
child: Text(
|
||||||
style: const TextStyle(fontSize: 10)),
|
context.lang.shareImagedSelectAll,
|
||||||
|
style: const TextStyle(fontSize: 10),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -127,7 +129,8 @@ class UserCheckbox extends StatelessWidget {
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 3), // Padding inside the container
|
horizontal: 3,
|
||||||
|
), // Padding inside the container
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
onChanged(user.userId, !isChecked);
|
onChanged(user.userId, !isChecked);
|
||||||
|
|
@ -172,7 +175,7 @@ class UserCheckbox extends StatelessWidget {
|
||||||
}
|
}
|
||||||
return FlameCounterWidget(user, snapshot.data!);
|
return FlameCounterWidget(user, snapshot.data!);
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Expanded(child: Container()),
|
Expanded(child: Container()),
|
||||||
|
|
@ -184,7 +187,8 @@ class UserCheckbox extends StatelessWidget {
|
||||||
return const BorderSide(width: 0);
|
return const BorderSide(width: 0);
|
||||||
}
|
}
|
||||||
return BorderSide(
|
return BorderSide(
|
||||||
color: Theme.of(context).colorScheme.outline);
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
onChanged: (bool? value) {
|
onChanged: (bool? value) {
|
||||||
|
|
|
||||||
|
|
@ -74,14 +74,14 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
initAsync();
|
unawaited(initAsync());
|
||||||
initMediaFileUpload();
|
unawaited(initMediaFileUpload());
|
||||||
layers.add(FilterLayerData());
|
layers.add(FilterLayerData());
|
||||||
if (widget.sendTo != null) {
|
if (widget.sendTo != null) {
|
||||||
selectedUserIds.add(widget.sendTo!.userId);
|
selectedUserIds.add(widget.sendTo!.userId);
|
||||||
}
|
}
|
||||||
if (widget.imageBytes != null) {
|
if (widget.imageBytes != null) {
|
||||||
loadImage(widget.imageBytes!);
|
unawaited(loadImage(widget.imageBytes!));
|
||||||
} else if (widget.videoFilePath != null) {
|
} else if (widget.videoFilePath != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
sendingOrLoadingImage = false;
|
sendingOrLoadingImage = false;
|
||||||
|
|
@ -89,8 +89,8 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
});
|
});
|
||||||
videoController = VideoPlayerController.file(widget.videoFilePath!);
|
videoController = VideoPlayerController.file(widget.videoFilePath!);
|
||||||
videoController?.setLooping(true);
|
videoController?.setLooping(true);
|
||||||
videoController?.initialize().then((_) {
|
videoController?.initialize().then((_) async {
|
||||||
videoController!.play();
|
await videoController!.play();
|
||||||
setState(() {});
|
setState(() {});
|
||||||
// ignore: invalid_return_type_for_catch_error, argument_type_not_assignable_to_error_handler
|
// ignore: invalid_return_type_for_catch_error, argument_type_not_assignable_to_error_handler
|
||||||
}).catchError(Log.error);
|
}).catchError(Log.error);
|
||||||
|
|
@ -155,9 +155,11 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
if (layers.any((x) => x.isEditing)) return;
|
if (layers.any((x) => x.isEditing)) return;
|
||||||
undoLayers.clear();
|
undoLayers.clear();
|
||||||
removedLayers.clear();
|
removedLayers.clear();
|
||||||
layers.add(TextLayerData(
|
layers.add(
|
||||||
|
TextLayerData(
|
||||||
textLayersBefore: layers.whereType<TextLayerData>().length,
|
textLayersBefore: layers.whereType<TextLayerData>().length,
|
||||||
));
|
),
|
||||||
|
);
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
@ -301,7 +303,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(width: 70)
|
const SizedBox(width: 70),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -329,7 +331,6 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
),
|
),
|
||||||
) as bool?;
|
) as bool?;
|
||||||
if (wasSend != null && wasSend && mounted) {
|
if (wasSend != null && wasSend && mounted) {
|
||||||
// ignore: use_build_context_synchronously
|
|
||||||
Navigator.pop(context, true);
|
Navigator.pop(context, true);
|
||||||
} else {
|
} else {
|
||||||
await videoController?.play();
|
await videoController?.play();
|
||||||
|
|
@ -345,7 +346,8 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
}
|
}
|
||||||
setState(() {});
|
setState(() {});
|
||||||
image = await screenshotController.capture(
|
image = await screenshotController.capture(
|
||||||
pixelRatio: (widget.useHighQuality) ? pixelRatio : 1);
|
pixelRatio: (widget.useHighQuality) ? pixelRatio : 1,
|
||||||
|
);
|
||||||
for (final x in layers) {
|
for (final x in layers) {
|
||||||
x.showCustomButtons = true;
|
x.showCustomButtons = true;
|
||||||
}
|
}
|
||||||
|
|
@ -397,11 +399,16 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
sendingOrLoadingImage = false;
|
sendingOrLoadingImage = false;
|
||||||
});
|
});
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
await Navigator.push(context, MaterialPageRoute(builder: (context) {
|
await Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) {
|
||||||
return SubscriptionView(
|
return SubscriptionView(
|
||||||
redirectError: err,
|
redirectError: err,
|
||||||
);
|
);
|
||||||
}));
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
final imageHandler = addOrModifyImageToUpload(mediaUploadId!, imageBytes);
|
final imageHandler = addOrModifyImageToUpload(mediaUploadId!, imageBytes);
|
||||||
|
|
@ -456,10 +463,12 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
layers = layers.where((x) => !x.isDeleted).toList();
|
layers = layers.where((x) => !x.isDeleted).toList();
|
||||||
undoLayers.clear();
|
undoLayers.clear();
|
||||||
removedLayers.clear();
|
removedLayers.clear();
|
||||||
layers.add(TextLayerData(
|
layers.add(
|
||||||
|
TextLayerData(
|
||||||
offset: Offset(0, tabDownPosition),
|
offset: Offset(0, tabDownPosition),
|
||||||
textLayersBefore: layers.whereType<TextLayerData>().length,
|
textLayersBefore: layers.whereType<TextLayerData>().length,
|
||||||
));
|
),
|
||||||
|
);
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
child: MediaViewSizing(
|
child: MediaViewSizing(
|
||||||
|
|
@ -508,7 +517,9 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
padding: WidgetStateProperty.all<EdgeInsets>(
|
padding: WidgetStateProperty.all<EdgeInsets>(
|
||||||
const EdgeInsets.symmetric(
|
const EdgeInsets.symmetric(
|
||||||
vertical: 10, horizontal: 30),
|
vertical: 10,
|
||||||
|
horizontal: 30,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
label: Text(
|
label: Text(
|
||||||
|
|
|
||||||
|
|
@ -63,14 +63,14 @@ class _ShareImageView extends State<ShareImageView> {
|
||||||
|
|
||||||
final allContacts = twonlyDB.contactsDao.watchContactsForShareView();
|
final allContacts = twonlyDB.contactsDao.watchContactsForShareView();
|
||||||
|
|
||||||
contactSub = allContacts.listen((allContacts) {
|
contactSub = allContacts.listen((allContacts) async {
|
||||||
setState(() {
|
setState(() {
|
||||||
contacts = allContacts;
|
contacts = allContacts;
|
||||||
});
|
});
|
||||||
updateUsers(allContacts.where((x) => !x.archived).toList());
|
await updateUsers(allContacts.where((x) => !x.archived).toList());
|
||||||
});
|
});
|
||||||
|
|
||||||
initAsync();
|
unawaited(initAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initAsync() async {
|
Future<void> initAsync() async {
|
||||||
|
|
@ -80,7 +80,10 @@ class _ShareImageView extends State<ShareImageView> {
|
||||||
addOrModifyImageToUpload(widget.mediaUploadId, imageBytes!);
|
addOrModifyImageToUpload(widget.mediaUploadId, imageBytes!);
|
||||||
// start with the pre upload of the media file...
|
// start with the pre upload of the media file...
|
||||||
await encryptMediaFiles(
|
await encryptMediaFiles(
|
||||||
widget.mediaUploadId, imageHandler, widget.videoUploadHandler);
|
widget.mediaUploadId,
|
||||||
|
imageHandler,
|
||||||
|
widget.videoUploadHandler,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() {});
|
setState(() {});
|
||||||
|
|
@ -88,8 +91,8 @@ class _ShareImageView extends State<ShareImageView> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
unawaited(contactSub.cancel());
|
||||||
super.dispose();
|
super.dispose();
|
||||||
contactSub.cancel();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateUsers(List<Contact> users) async {
|
Future<void> updateUsers(List<Contact> users) async {
|
||||||
|
|
@ -103,7 +106,8 @@ class _ShareImageView extends State<ShareImageView> {
|
||||||
}
|
}
|
||||||
// If flameCounter is the same, compare by totalMediaCounter
|
// If flameCounter is the same, compare by totalMediaCounter
|
||||||
return b.totalMediaCounter.compareTo(
|
return b.totalMediaCounter.compareTo(
|
||||||
a.totalMediaCounter); // Sort by totalMediaCounter in descending order
|
a.totalMediaCounter,
|
||||||
|
); // Sort by totalMediaCounter in descending order
|
||||||
});
|
});
|
||||||
|
|
||||||
// Separate best friends and other users
|
// Separate best friends and other users
|
||||||
|
|
@ -132,18 +136,24 @@ class _ShareImageView extends State<ShareImageView> {
|
||||||
Future<void> _filterUsers(String query) async {
|
Future<void> _filterUsers(String query) async {
|
||||||
lastQuery = query;
|
lastQuery = query;
|
||||||
if (query.isEmpty) {
|
if (query.isEmpty) {
|
||||||
await updateUsers(contacts
|
await updateUsers(
|
||||||
.where((x) =>
|
contacts
|
||||||
|
.where(
|
||||||
|
(x) =>
|
||||||
!x.archived ||
|
!x.archived ||
|
||||||
!hideArchivedUsers ||
|
!hideArchivedUsers ||
|
||||||
widget.selectedUserIds.contains(x.userId))
|
widget.selectedUserIds.contains(x.userId),
|
||||||
.toList());
|
)
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final usersFiltered = contacts
|
final usersFiltered = contacts
|
||||||
.where((user) => getContactDisplayName(user)
|
.where(
|
||||||
|
(user) => getContactDisplayName(user)
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.contains(query.toLowerCase()))
|
.contains(query.toLowerCase()),
|
||||||
|
)
|
||||||
.toList();
|
.toList();
|
||||||
await updateUsers(usersFiltered);
|
await updateUsers(usersFiltered);
|
||||||
}
|
}
|
||||||
|
|
@ -214,21 +224,20 @@ class _ShareImageView extends State<ShareImageView> {
|
||||||
return const BorderSide(width: 0);
|
return const BorderSide(width: 0);
|
||||||
}
|
}
|
||||||
return BorderSide(
|
return BorderSide(
|
||||||
color: Theme.of(context)
|
color:
|
||||||
.colorScheme
|
Theme.of(context).colorScheme.outline,
|
||||||
.outline);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
onChanged: (a) {
|
onChanged: (a) async {
|
||||||
setState(() {
|
|
||||||
hideArchivedUsers = !hideArchivedUsers;
|
hideArchivedUsers = !hideArchivedUsers;
|
||||||
_filterUsers(lastQuery);
|
await _filterUsers(lastQuery);
|
||||||
});
|
if (mounted) setState(() {});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
|
|
@ -238,7 +247,7 @@ class _ShareImageView extends State<ShareImageView> {
|
||||||
isRealTwonly: widget.isRealTwonly,
|
isRealTwonly: widget.isRealTwonly,
|
||||||
updateStatus: updateStatus,
|
updateStatus: updateStatus,
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -270,12 +279,16 @@ class _ShareImageView extends State<ShareImageView> {
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
|
|
||||||
if (err != null) {
|
if (err != null) {
|
||||||
await Navigator.push(context,
|
await Navigator.push(
|
||||||
MaterialPageRoute(builder: (context) {
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) {
|
||||||
return SubscriptionView(
|
return SubscriptionView(
|
||||||
redirectError: err,
|
redirectError: err,
|
||||||
);
|
);
|
||||||
}));
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
setState(() {
|
setState(() {
|
||||||
sendingImage = true;
|
sendingImage = true;
|
||||||
|
|
@ -312,7 +325,8 @@ class _ShareImageView extends State<ShareImageView> {
|
||||||
imageBytes == null || widget.selectedUserIds.isEmpty
|
imageBytes == null || widget.selectedUserIds.isEmpty
|
||||||
? Theme.of(context).colorScheme.secondary
|
? Theme.of(context).colorScheme.secondary
|
||||||
: Theme.of(context).colorScheme.primary,
|
: Theme.of(context).colorScheme.primary,
|
||||||
)),
|
),
|
||||||
|
),
|
||||||
label: Text(
|
label: Text(
|
||||||
context.lang.shareImagedEditorSendImage,
|
context.lang.shareImagedEditorSendImage,
|
||||||
style: const TextStyle(fontSize: 17),
|
style: const TextStyle(fontSize: 17),
|
||||||
|
|
|
||||||
|
|
@ -38,16 +38,16 @@ class _SearchUsernameView extends State<AddNewUserView> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
contactsStream = twonlyDB.contactsDao
|
contactsStream = twonlyDB.contactsDao.watchNotAcceptedContacts().listen(
|
||||||
.watchNotAcceptedContacts()
|
(update) => setState(() {
|
||||||
.listen((update) => setState(() {
|
|
||||||
contacts = update;
|
contacts = update;
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
contactsStream.cancel();
|
unawaited(contactsStream.cancel());
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,8 +69,11 @@ class _SearchUsernameView extends State<AddNewUserView> {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (userdata == null) {
|
if (userdata == null) {
|
||||||
await showAlertDialog(context, context.lang.searchUsernameNotFound,
|
await showAlertDialog(
|
||||||
context.lang.searchUsernameNotFoundBody(searchUserName.text));
|
context,
|
||||||
|
context.lang.searchUsernameNotFound,
|
||||||
|
context.lang.searchUsernameNotFoundBody(searchUserName.text),
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -141,8 +144,8 @@ class _SearchUsernameView extends State<AddNewUserView> {
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||||
child: TextField(
|
child: TextField(
|
||||||
onSubmitted: (_) {
|
onSubmitted: (_) async {
|
||||||
_addNewUser(context);
|
await _addNewUser(context);
|
||||||
},
|
},
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
searchUserName.text = value.toLowerCase();
|
searchUserName.text = value.toLowerCase();
|
||||||
|
|
@ -166,7 +169,7 @@ class _SearchUsernameView extends State<AddNewUserView> {
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ContactsListView(contacts),
|
child: ContactsListView(contacts),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -209,8 +212,10 @@ class ContactsListView extends StatelessWidget {
|
||||||
Tooltip(
|
Tooltip(
|
||||||
message: context.lang.searchUserNameBlockUserTooltip,
|
message: context.lang.searchUserNameBlockUserTooltip,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
icon: const Icon(Icons.person_off_rounded,
|
icon: const Icon(
|
||||||
color: Color.fromARGB(164, 244, 67, 54)),
|
Icons.person_off_rounded,
|
||||||
|
color: Color.fromARGB(164, 244, 67, 54),
|
||||||
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
const update = ContactsCompanion(blocked: Value(true));
|
const update = ContactsCompanion(blocked: Value(true));
|
||||||
await twonlyDB.contactsDao.updateContact(contact.userId, update);
|
await twonlyDB.contactsDao.updateContact(contact.userId, update);
|
||||||
|
|
|
||||||
|
|
@ -95,11 +95,16 @@ class _ChatListViewState extends State<ChatListView> {
|
||||||
// only show changelog to people who already have contacts
|
// only show changelog to people who already have contacts
|
||||||
// this prevents that this is shown directly after the user registered
|
// this prevents that this is shown directly after the user registered
|
||||||
if (_contacts.isNotEmpty) {
|
if (_contacts.isNotEmpty) {
|
||||||
await Navigator.push(context, MaterialPageRoute(builder: (context) {
|
await Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) {
|
||||||
return ChangeLogView(
|
return ChangeLogView(
|
||||||
changeLog: changeLog,
|
changeLog: changeLog,
|
||||||
);
|
);
|
||||||
}));
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -117,13 +122,18 @@ class _ChatListViewState extends State<ChatListView> {
|
||||||
final planId = context.watch<CustomChangeProvider>().plan;
|
final planId = context.watch<CustomChangeProvider>().plan;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Row(children: [
|
title: Row(
|
||||||
|
children: [
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await Navigator.push(context,
|
await Navigator.push(
|
||||||
MaterialPageRoute(builder: (context) {
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) {
|
||||||
return const ProfileView();
|
return const ProfileView();
|
||||||
}));
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
_user = await getUser();
|
_user = await getUser();
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() {});
|
setState(() {});
|
||||||
|
|
@ -139,16 +149,22 @@ class _ChatListViewState extends State<ChatListView> {
|
||||||
if (planId != 'Free')
|
if (planId != 'Free')
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (context) {
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) {
|
||||||
return const SubscriptionView();
|
return const SubscriptionView();
|
||||||
}));
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: context.color.primary,
|
color: context.color.primary,
|
||||||
borderRadius: BorderRadius.circular(15),
|
borderRadius: BorderRadius.circular(15),
|
||||||
),
|
),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 3),
|
padding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 5, vertical: 3),
|
||||||
child: Text(
|
child: Text(
|
||||||
planId,
|
planId,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
|
@ -159,7 +175,8 @@ class _ChatListViewState extends State<ChatListView> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
]),
|
],
|
||||||
|
),
|
||||||
actions: [
|
actions: [
|
||||||
const FeedbackIconButton(),
|
const FeedbackIconButton(),
|
||||||
StreamBuilder(
|
StreamBuilder(
|
||||||
|
|
@ -199,7 +216,7 @@ class _ChatListViewState extends State<ChatListView> {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
icon: const FaIcon(FontAwesomeIcons.gear, size: 19),
|
icon: const FaIcon(FontAwesomeIcons.gear, size: 19),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: Stack(
|
body: Stack(
|
||||||
|
|
@ -225,8 +242,8 @@ class _ChatListViewState extends State<ChatListView> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
label:
|
label: Text(context.lang.chatListViewSearchUserNameBtn),
|
||||||
Text(context.lang.chatListViewSearchUserNameBtn)),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: RefreshIndicator(
|
: RefreshIndicator(
|
||||||
|
|
@ -288,9 +305,11 @@ class _ChatListViewState extends State<ChatListView> {
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(builder: (context) {
|
MaterialPageRoute(
|
||||||
|
builder: (context) {
|
||||||
return const StartNewChatView();
|
return const StartNewChatView();
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: const FaIcon(FontAwesomeIcons.penToSquare),
|
child: const FaIcon(FontAwesomeIcons.penToSquare),
|
||||||
|
|
@ -399,11 +418,14 @@ class _UserListItem extends State<UserListItem> {
|
||||||
|
|
||||||
Future<void> onTap() async {
|
Future<void> onTap() async {
|
||||||
if (currentMessage == null) {
|
if (currentMessage == null) {
|
||||||
await Navigator.push(context, MaterialPageRoute(
|
await Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return CameraSendToView(widget.user);
|
return CameraSendToView(widget.user);
|
||||||
},
|
},
|
||||||
));
|
),
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -417,9 +439,11 @@ class _UserListItem extends State<UserListItem> {
|
||||||
case DownloadState.downloaded:
|
case DownloadState.downloaded:
|
||||||
await Navigator.push(
|
await Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(builder: (context) {
|
MaterialPageRoute(
|
||||||
|
builder: (context) {
|
||||||
return MediaViewerView(widget.user);
|
return MediaViewerView(widget.user);
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
case DownloadState.downloading:
|
case DownloadState.downloading:
|
||||||
|
|
@ -429,9 +453,11 @@ class _UserListItem extends State<UserListItem> {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
await Navigator.push(
|
await Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(builder: (context) {
|
MaterialPageRoute(
|
||||||
|
builder: (context) {
|
||||||
return ChatMessagesView(widget.user);
|
return ChatMessagesView(widget.user);
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -481,7 +507,9 @@ class _UserListItem extends State<UserListItem> {
|
||||||
? null
|
? null
|
||||||
: IconButton(
|
: IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.push(context, MaterialPageRoute(
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
if (hasNonOpenedMediaFile) {
|
if (hasNonOpenedMediaFile) {
|
||||||
return ChatMessagesView(widget.user);
|
return ChatMessagesView(widget.user);
|
||||||
|
|
@ -489,7 +517,8 @@ class _UserListItem extends State<UserListItem> {
|
||||||
return CameraSendToView(widget.user);
|
return CameraSendToView(widget.user);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
));
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
icon: FaIcon(
|
icon: FaIcon(
|
||||||
hasNonOpenedMediaFile
|
hasNonOpenedMediaFile
|
||||||
|
|
@ -500,7 +529,7 @@ class _UserListItem extends State<UserListItem> {
|
||||||
),
|
),
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
|
|
@ -15,8 +17,8 @@ class _BackupNoticeCardState extends State<BackupNoticeCard> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
initAsync();
|
|
||||||
super.initState();
|
super.initState();
|
||||||
|
unawaited(initAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initAsync() async {
|
Future<void> initAsync() async {
|
||||||
|
|
@ -74,8 +76,8 @@ class _BackupNoticeCardState extends State<BackupNoticeCard> {
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
FilledButton(
|
FilledButton(
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
Navigator.push(
|
await Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => const BackupView(),
|
builder: (context) => const BackupView(),
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
||||||
|
|
@ -35,16 +37,18 @@ class _ConnectionInfoWidgetState extends State<ConnectionInfo>
|
||||||
_widthAnim = TweenSequence([
|
_widthAnim = TweenSequence([
|
||||||
TweenSequenceItem(
|
TweenSequenceItem(
|
||||||
tween: Tween<double>(begin: minBarWidth, end: maxBarWidth),
|
tween: Tween<double>(begin: minBarWidth, end: maxBarWidth),
|
||||||
weight: 50),
|
weight: 50,
|
||||||
|
),
|
||||||
TweenSequenceItem(
|
TweenSequenceItem(
|
||||||
tween: Tween<double>(begin: maxBarWidth, end: minBarWidth),
|
tween: Tween<double>(begin: maxBarWidth, end: minBarWidth),
|
||||||
weight: 50),
|
weight: 50,
|
||||||
|
),
|
||||||
]).animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut));
|
]).animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut));
|
||||||
|
|
||||||
// Delay start by 2 seconds
|
// Delay start by 2 seconds
|
||||||
Future.delayed(const Duration(seconds: 2), () {
|
Future.delayed(const Duration(seconds: 2), () {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
_controller.repeat(reverse: true);
|
unawaited(_controller.repeat(reverse: true));
|
||||||
setState(() {
|
setState(() {
|
||||||
showAnimation = true;
|
showAnimation = true;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ class DemoUserCard extends StatelessWidget {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: const Text('Register'),
|
child: const Text('Register'),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
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/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
@ -17,7 +19,7 @@ class _FeedbackIconButtonState extends State<FeedbackIconButton> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
initAsync();
|
unawaited(initAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initAsync() async {
|
Future<void> initAsync() async {
|
||||||
|
|
@ -35,8 +37,8 @@ class _FeedbackIconButtonState extends State<FeedbackIconButton> {
|
||||||
}
|
}
|
||||||
|
|
||||||
return IconButton(
|
return IconButton(
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
Navigator.push(
|
await Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => const ContactUsView(),
|
builder: (context) => const ContactUsView(),
|
||||||
|
|
|
||||||
|
|
@ -102,11 +102,11 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
super.dispose();
|
|
||||||
userSub.cancel();
|
userSub.cancel();
|
||||||
messageSub.cancel();
|
messageSub.cancel();
|
||||||
tutorial?.cancel();
|
tutorial?.cancel();
|
||||||
textFieldFocus.dispose();
|
textFieldFocus.dispose();
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initStreams() async {
|
Future<void> initStreams() async {
|
||||||
|
|
@ -196,10 +196,14 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
||||||
chatItems.add(ChatItem.time(msg.sendAt));
|
chatItems.add(ChatItem.time(msg.sendAt));
|
||||||
lastDate = msg.sendAt;
|
lastDate = msg.sendAt;
|
||||||
}
|
}
|
||||||
chatItems.add(ChatItem.message(ChatMessage(
|
chatItems.add(
|
||||||
|
ChatItem.message(
|
||||||
|
ChatMessage(
|
||||||
message: msg,
|
message: msg,
|
||||||
responseTo: responseTo,
|
responseTo: responseTo,
|
||||||
)));
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -253,7 +257,8 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
||||||
|
|
||||||
Future<void> scrollToMessage(int messageId) async {
|
Future<void> scrollToMessage(int messageId) async {
|
||||||
final index = messages.indexWhere(
|
final index = messages.indexWhere(
|
||||||
(x) => x.isMessage && x.message!.message.messageId == messageId);
|
(x) => x.isMessage && x.message!.message.messageId == messageId,
|
||||||
|
);
|
||||||
if (index == -1) return;
|
if (index == -1) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
focusedScrollItem = index;
|
focusedScrollItem = index;
|
||||||
|
|
@ -279,9 +284,14 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: GestureDetector(
|
title: GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (context) {
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) {
|
||||||
return ContactView(widget.contact.userId);
|
return ContactView(widget.contact.userId);
|
||||||
}));
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -298,7 +308,7 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
||||||
Text(getContactDisplayName(user)),
|
Text(getContactDisplayName(user)),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
if (user.verified)
|
if (user.verified)
|
||||||
VerifiedShield(key: verifyShieldKey, user)
|
VerifiedShield(key: verifyShieldKey, user),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -336,7 +346,8 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
||||||
? -8
|
? -8
|
||||||
: 8
|
: 8
|
||||||
: 0,
|
: 0,
|
||||||
0),
|
0,
|
||||||
|
),
|
||||||
child: Transform.scale(
|
child: Transform.scale(
|
||||||
scale: (focusedScrollItem == i) ? 1.05 : 1,
|
scale: (focusedScrollItem == i) ? 1.05 : 1,
|
||||||
child: ChatListEntry(
|
child: ChatListEntry(
|
||||||
|
|
@ -389,7 +400,7 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
||||||
FontAwesomeIcons.xmark,
|
FontAwesomeIcons.xmark,
|
||||||
size: 16,
|
size: 16,
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -425,7 +436,8 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
||||||
IconButton(
|
IconButton(
|
||||||
padding: const EdgeInsets.all(15),
|
padding: const EdgeInsets.all(15),
|
||||||
icon: const FaIcon(
|
icon: const FaIcon(
|
||||||
FontAwesomeIcons.solidPaperPlane),
|
FontAwesomeIcons.solidPaperPlane,
|
||||||
|
),
|
||||||
onPressed: _sendMessage,
|
onPressed: _sendMessage,
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
|
|
@ -442,7 +454,7 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -42,8 +42,10 @@ class _ChatListEntryState extends State<ChatListEntry> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
final msgContent = MessageContent.fromJson(widget.msg.message.kind,
|
final msgContent = MessageContent.fromJson(
|
||||||
jsonDecode(widget.msg.message.contentJson!) as Map);
|
widget.msg.message.kind,
|
||||||
|
jsonDecode(widget.msg.message.contentJson!) as Map,
|
||||||
|
);
|
||||||
if (msgContent is TextMessageContent) {
|
if (msgContent is TextMessageContent) {
|
||||||
textMessage = msgContent.text;
|
textMessage = msgContent.text;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:drift/drift.dart' show Value;
|
import 'package:drift/drift.dart' show Value;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
|
|
@ -39,7 +41,7 @@ class _ChatMediaEntryState extends State<ChatMediaEntry> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
checkIfTutorialCanBeShown();
|
unawaited(checkIfTutorialCanBeShown());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> checkIfTutorialCanBeShown() async {
|
Future<void> checkIfTutorialCanBeShown() async {
|
||||||
|
|
@ -60,9 +62,9 @@ class _ChatMediaEntryState extends State<ChatMediaEntry> {
|
||||||
canBeReopened = true;
|
canBeReopened = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Future.delayed(const Duration(seconds: 1), () {
|
Future.delayed(const Duration(seconds: 1), () async {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
showReopenMediaFilesTutorial(context, reopenMediaFile);
|
await showReopenMediaFilesTutorial(context, reopenMediaFile);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -101,10 +103,14 @@ class _ChatMediaEntryState extends State<ChatMediaEntry> {
|
||||||
widget.message.openedAt == null) {
|
widget.message.openedAt == null) {
|
||||||
await Navigator.push(
|
await Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(builder: (context) {
|
MaterialPageRoute(
|
||||||
return MediaViewerView(widget.contact,
|
builder: (context) {
|
||||||
initialMessage: widget.message);
|
return MediaViewerView(
|
||||||
}),
|
widget.contact,
|
||||||
|
initialMessage: widget.message,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
await checkIfTutorialCanBeShown();
|
await checkIfTutorialCanBeShown();
|
||||||
} else if (widget.message.downloadState == DownloadState.pending) {
|
} else if (widget.message.downloadState == DownloadState.pending) {
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,9 @@ class _ReactionRowState extends State<ReactionRow> {
|
||||||
var hasOneReopened = false;
|
var hasOneReopened = false;
|
||||||
for (final reaction in widget.otherReactions.reversed) {
|
for (final reaction in widget.otherReactions.reversed) {
|
||||||
final content = MessageContent.fromJson(
|
final content = MessageContent.fromJson(
|
||||||
reaction.kind, jsonDecode(reaction.contentJson!) as Map);
|
reaction.kind,
|
||||||
|
jsonDecode(reaction.contentJson!) as Map,
|
||||||
|
);
|
||||||
|
|
||||||
if (content is ReopenedMediaFileContent) {
|
if (content is ReopenedMediaFileContent) {
|
||||||
if (hasOneReopened) continue;
|
if (hasOneReopened) continue;
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,11 @@ class ChatTextEntry extends StatelessWidget {
|
||||||
maxWidth: MediaQuery.of(context).size.width * 0.8,
|
maxWidth: MediaQuery.of(context).size.width * 0.8,
|
||||||
),
|
),
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
left: 10, top: 4, bottom: 4, right: hasReaction ? 30 : 10),
|
left: 10,
|
||||||
|
top: 4,
|
||||||
|
bottom: 4,
|
||||||
|
right: hasReaction ? 30 : 10,
|
||||||
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: message.responseTo == null
|
color: message.responseTo == null
|
||||||
? getMessageColor(message.message)
|
? getMessageColor(message.message)
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,8 @@ class _InChatMediaViewerState extends State<InChatMediaViewer> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
loadIndexAsync();
|
unawaited(loadIndexAsync());
|
||||||
initStream();
|
unawaited(initStream());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadIndexAsync() async {
|
Future<void> loadIndexAsync() async {
|
||||||
|
|
@ -55,8 +55,10 @@ class _InChatMediaViewerState extends State<InChatMediaViewer> {
|
||||||
|
|
||||||
bool loadIndex() {
|
bool loadIndex() {
|
||||||
if (widget.message.mediaStored) {
|
if (widget.message.mediaStored) {
|
||||||
final index = widget.galleryItems.indexWhere((x) =>
|
final index = widget.galleryItems.indexWhere(
|
||||||
x.id == (widget.message.mediaUploadId ?? widget.message.messageId));
|
(x) =>
|
||||||
|
x.id == (widget.message.mediaUploadId ?? widget.message.messageId),
|
||||||
|
);
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
galleryItemIndex = index;
|
galleryItemIndex = index;
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -122,7 +124,9 @@ class _InChatMediaViewerState extends State<InChatMediaViewer> {
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.symmetric(
|
padding: EdgeInsets.symmetric(
|
||||||
vertical: (widget.canBeReopened) ? 5 : 10.0, horizontal: 4),
|
vertical: (widget.canBeReopened) ? 5 : 10.0,
|
||||||
|
horizontal: 4,
|
||||||
|
),
|
||||||
child: MessageSendStateIcon(
|
child: MessageSendStateIcon(
|
||||||
[widget.message],
|
[widget.message],
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// ignore_for_file: avoid_dynamic_calls, inference_failure_on_function_invocation
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
@ -30,7 +30,7 @@ class _SlidingResponseWidgetState extends State<MessageActions> {
|
||||||
if (_offsetX > 40) {
|
if (_offsetX > 40) {
|
||||||
_offsetX = 40;
|
_offsetX = 40;
|
||||||
if (!gotFeedback) {
|
if (!gotFeedback) {
|
||||||
HapticFeedback.heavyImpact();
|
unawaited(HapticFeedback.heavyImpact());
|
||||||
gotFeedback = true;
|
gotFeedback = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// ignore_for_file: avoid_dynamic_calls, inference_failure_on_function_invocation
|
// ignore_for_file: inference_failure_on_function_invocation
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
@ -30,9 +30,9 @@ class MessageContextMenu extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return PieMenu(
|
return PieMenu(
|
||||||
onPressed: () => (),
|
onPressed: () => (),
|
||||||
onToggle: (menuOpen) {
|
onToggle: (menuOpen) async {
|
||||||
if (menuOpen) {
|
if (menuOpen) {
|
||||||
HapticFeedback.heavyImpact();
|
await HapticFeedback.heavyImpact();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: [
|
actions: [
|
||||||
|
|
@ -53,9 +53,9 @@ class MessageContextMenu extends StatelessWidget {
|
||||||
TextMessageContent(
|
TextMessageContent(
|
||||||
text: layer.text,
|
text: layer.text,
|
||||||
responseToMessageId: message.messageOtherId,
|
responseToMessageId: message.messageOtherId,
|
||||||
responseToOtherMessageId: (message.messageOtherId == null)
|
responseToOtherMessageId:
|
||||||
? message.messageId
|
(message.messageOtherId == null) ? message.messageId : null,
|
||||||
: null),
|
),
|
||||||
(message.messageOtherId != null)
|
(message.messageOtherId != null)
|
||||||
? PushNotification(
|
? PushNotification(
|
||||||
kind: (message.kind == MessageKind.textMessage)
|
kind: (message.kind == MessageKind.textMessage)
|
||||||
|
|
@ -77,10 +77,10 @@ class MessageContextMenu extends StatelessWidget {
|
||||||
),
|
),
|
||||||
PieAction(
|
PieAction(
|
||||||
tooltip: Text(context.lang.copy),
|
tooltip: Text(context.lang.copy),
|
||||||
onSelect: () {
|
onSelect: () async {
|
||||||
final text = getMessageText(message);
|
final text = getMessageText(message);
|
||||||
Clipboard.setData(ClipboardData(text: text));
|
await Clipboard.setData(ClipboardData(text: text));
|
||||||
HapticFeedback.heavyImpact();
|
await HapticFeedback.heavyImpact();
|
||||||
},
|
},
|
||||||
child: const FaIcon(FontAwesomeIcons.solidCopy),
|
child: const FaIcon(FontAwesomeIcons.solidCopy),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -198,8 +198,8 @@ class _MessageSendStateIconState extends State<MessageSendStateIcon> {
|
||||||
|
|
||||||
Transform(
|
Transform(
|
||||||
transform: Matrix4.identity()
|
transform: Matrix4.identity()
|
||||||
..scale(0.7) // Scale to half
|
..scaleByDouble(0.7, 0.7, 0.7, 0.7) // Scale to half
|
||||||
..translate(3.0, 5),
|
..translateByDouble(3, 5, 0, 1),
|
||||||
// Move down by 10 pixels (adjust as needed)
|
// Move down by 10 pixels (adjust as needed)
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: icons[1],
|
child: icons[1],
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
|
@ -126,8 +127,8 @@ class _ResponsePreviewState extends State<ResponsePreview> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
initAsync();
|
|
||||||
super.initState();
|
super.initState();
|
||||||
|
unawaited(initAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initAsync() async {
|
Future<void> initAsync() async {
|
||||||
|
|
@ -145,8 +146,10 @@ class _ResponsePreviewState extends State<ResponsePreview> {
|
||||||
|
|
||||||
if (widget.message.kind == MessageKind.textMessage) {
|
if (widget.message.kind == MessageKind.textMessage) {
|
||||||
if (widget.message.contentJson != null) {
|
if (widget.message.contentJson != null) {
|
||||||
final content = MessageContent.fromJson(MessageKind.textMessage,
|
final content = MessageContent.fromJson(
|
||||||
jsonDecode(widget.message.contentJson!) as Map);
|
MessageKind.textMessage,
|
||||||
|
jsonDecode(widget.message.contentJson!) as Map,
|
||||||
|
);
|
||||||
if (content is TextMessageContent) {
|
if (content is TextMessageContent) {
|
||||||
subtitle = truncateString(content.text);
|
subtitle = truncateString(content.text);
|
||||||
}
|
}
|
||||||
|
|
@ -154,7 +157,9 @@ class _ResponsePreviewState extends State<ResponsePreview> {
|
||||||
}
|
}
|
||||||
if (widget.message.kind == MessageKind.media) {
|
if (widget.message.kind == MessageKind.media) {
|
||||||
final content = MessageContent.fromJson(
|
final content = MessageContent.fromJson(
|
||||||
MessageKind.media, jsonDecode(widget.message.contentJson!) as Map);
|
MessageKind.media,
|
||||||
|
jsonDecode(widget.message.contentJson!) as Map,
|
||||||
|
);
|
||||||
if (content is MediaMessageContent) {
|
if (content is MediaMessageContent) {
|
||||||
subtitle = content.isVideo ? 'Video' : 'Image';
|
subtitle = content.isVideo ? 'Video' : 'Image';
|
||||||
}
|
}
|
||||||
|
|
@ -189,7 +194,7 @@ class _ResponsePreviewState extends State<ResponsePreview> {
|
||||||
username,
|
username,
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
if (subtitle != null) Text(subtitle)
|
if (subtitle != null) Text(subtitle),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -216,7 +221,7 @@ class _ResponsePreviewState extends State<ResponsePreview> {
|
||||||
username,
|
username,
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
if (subtitle != null) Text(subtitle)
|
if (subtitle != null) Text(subtitle),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -224,7 +229,7 @@ class _ResponsePreviewState extends State<ResponsePreview> {
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: widget.showBorder ? 100 : 210,
|
height: widget.showBorder ? 100 : 210,
|
||||||
child: Image.file(thumbnailPath!),
|
child: Image.file(thumbnailPath!),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// ignore_for_file: inference_failure_on_collection_literal, avoid_dynamic_calls
|
// ignore_for_file: avoid_dynamic_calls
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
@ -201,7 +201,9 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> handleNextDownloadedMedia(
|
Future<void> handleNextDownloadedMedia(
|
||||||
Message current, bool showTwonly) async {
|
Message current,
|
||||||
|
bool showTwonly,
|
||||||
|
) async {
|
||||||
final content =
|
final content =
|
||||||
MediaMessageContent.fromJson(jsonDecode(current.contentJson!) as Map);
|
MediaMessageContent.fromJson(jsonDecode(current.contentJson!) as Map);
|
||||||
|
|
||||||
|
|
@ -373,7 +375,8 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: 10,
|
width: 10,
|
||||||
height: 10,
|
height: 10,
|
||||||
child: CircularProgressIndicator(strokeWidth: 1))
|
child: CircularProgressIndicator(strokeWidth: 1),
|
||||||
|
)
|
||||||
else
|
else
|
||||||
imageSaved
|
imageSaved
|
||||||
? const Icon(Icons.check)
|
? const Icon(Icons.check)
|
||||||
|
|
@ -443,11 +446,14 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
||||||
progressTimer?.cancel();
|
progressTimer?.cancel();
|
||||||
await videoController?.pause();
|
await videoController?.pause();
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
await Navigator.push(context, MaterialPageRoute(
|
await Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return CameraSendToView(widget.contact);
|
return CameraSendToView(widget.contact);
|
||||||
},
|
},
|
||||||
));
|
),
|
||||||
|
);
|
||||||
if (mounted && maxShowTime != gMediaShowInfinite) {
|
if (mounted && maxShowTime != gMediaShowInfinite) {
|
||||||
await nextMediaOrExit();
|
await nextMediaOrExit();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -501,8 +507,12 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
||||||
child: Image.memory(
|
child: Image.memory(
|
||||||
imageBytes!,
|
imageBytes!,
|
||||||
fit: BoxFit.contain,
|
fit: BoxFit.contain,
|
||||||
frameBuilder: (context, child, frame,
|
frameBuilder: (
|
||||||
wasSynchronouslyLoaded) {
|
context,
|
||||||
|
child,
|
||||||
|
frame,
|
||||||
|
wasSynchronouslyLoaded,
|
||||||
|
) {
|
||||||
if (wasSynchronouslyLoaded) return child;
|
if (wasSynchronouslyLoaded) return child;
|
||||||
return AnimatedSwitcher(
|
return AnimatedSwitcher(
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: const Duration(milliseconds: 200),
|
||||||
|
|
@ -604,7 +614,7 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
||||||
Shadow(
|
Shadow(
|
||||||
color: Color.fromARGB(122, 0, 0, 0),
|
color: Color.fromARGB(122, 0, 0, 0),
|
||||||
blurRadius: 5,
|
blurRadius: 5,
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -617,7 +627,11 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
||||||
child: Container(
|
child: Container(
|
||||||
color: context.color.surface,
|
color: context.color.surface,
|
||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.only(
|
||||||
bottom: 10, left: 20, right: 20, top: 10),
|
bottom: 10,
|
||||||
|
left: 20,
|
||||||
|
right: 20,
|
||||||
|
top: 10,
|
||||||
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
|
|
@ -664,7 +678,7 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
||||||
showShortReactions = false;
|
showShortReactions = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -765,14 +779,16 @@ class _ReactionButtonsState extends State<ReactionButtons> {
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: secondRowEmojis
|
children: secondRowEmojis
|
||||||
.map((emoji) => EmojiReactionWidget(
|
.map(
|
||||||
|
(emoji) => EmojiReactionWidget(
|
||||||
userId: widget.userId,
|
userId: widget.userId,
|
||||||
responseToMessageId: widget.responseToMessageId,
|
responseToMessageId: widget.responseToMessageId,
|
||||||
hide: widget.hide,
|
hide: widget.hide,
|
||||||
show: widget.show,
|
show: widget.show,
|
||||||
isVideo: widget.isVideo,
|
isVideo: widget.isVideo,
|
||||||
emoji: emoji as String,
|
emoji: emoji as String,
|
||||||
))
|
),
|
||||||
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
),
|
),
|
||||||
if (secondRowEmojis.isNotEmpty) const SizedBox(height: 15),
|
if (secondRowEmojis.isNotEmpty) const SizedBox(height: 15),
|
||||||
|
|
@ -848,10 +864,12 @@ class _EmojiReactionWidgetState extends State<EmojiReactionWidget> {
|
||||||
selectedShortReaction = 0; // Assuming index is 0 for this example
|
selectedShortReaction = 0; // Assuming index is 0 for this example
|
||||||
});
|
});
|
||||||
Future.delayed(const Duration(milliseconds: 300), () {
|
Future.delayed(const Duration(milliseconds: 300), () {
|
||||||
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
widget.hide();
|
widget.hide();
|
||||||
selectedShortReaction = -1;
|
selectedShortReaction = -1;
|
||||||
});
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
child: (selectedShortReaction ==
|
child: (selectedShortReaction ==
|
||||||
|
|
|
||||||
|
|
@ -32,20 +32,21 @@ class _StartNewChatView extends State<StartNewChatView> {
|
||||||
|
|
||||||
final stream = twonlyDB.contactsDao.watchContactsForStartNewChat();
|
final stream = twonlyDB.contactsDao.watchContactsForStartNewChat();
|
||||||
|
|
||||||
contactSub = stream.listen((update) {
|
contactSub = stream.listen((update) async {
|
||||||
update.sort((a, b) =>
|
update.sort(
|
||||||
getContactDisplayName(a).compareTo(getContactDisplayName(b)));
|
(a, b) => getContactDisplayName(a).compareTo(getContactDisplayName(b)),
|
||||||
|
);
|
||||||
setState(() {
|
setState(() {
|
||||||
allContacts = update;
|
allContacts = update;
|
||||||
});
|
});
|
||||||
filterUsers();
|
await filterUsers();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
unawaited(contactSub.cancel());
|
||||||
super.dispose();
|
super.dispose();
|
||||||
contactSub.cancel();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> filterUsers() async {
|
Future<void> filterUsers() async {
|
||||||
|
|
@ -56,9 +57,11 @@ class _StartNewChatView extends State<StartNewChatView> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final usersFiltered = allContacts
|
final usersFiltered = allContacts
|
||||||
.where((user) => getContactDisplayName(user)
|
.where(
|
||||||
|
(user) => getContactDisplayName(user)
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.contains(searchUserName.value.text.toLowerCase()))
|
.contains(searchUserName.value.text.toLowerCase()),
|
||||||
|
)
|
||||||
.toList();
|
.toList();
|
||||||
setState(() {
|
setState(() {
|
||||||
contacts = usersFiltered;
|
contacts = usersFiltered;
|
||||||
|
|
@ -82,8 +85,8 @@ class _StartNewChatView extends State<StartNewChatView> {
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||||
child: TextField(
|
child: TextField(
|
||||||
onChanged: (_) {
|
onChanged: (_) async {
|
||||||
filterUsers();
|
await filterUsers();
|
||||||
},
|
},
|
||||||
controller: searchUserName,
|
controller: searchUserName,
|
||||||
decoration: getInputDecoration(
|
decoration: getInputDecoration(
|
||||||
|
|
@ -97,7 +100,7 @@ class _StartNewChatView extends State<StartNewChatView> {
|
||||||
child: UserList(
|
child: UserList(
|
||||||
contacts,
|
contacts,
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -130,8 +133,8 @@ class UserList extends StatelessWidget {
|
||||||
size: 13,
|
size: 13,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
Navigator.push(
|
await Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => const AddNewUserView(),
|
builder: (context) => const AddNewUserView(),
|
||||||
|
|
@ -160,9 +163,11 @@ class UserList extends StatelessWidget {
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: FaIcon(FontAwesomeIcons.boxOpen,
|
icon: FaIcon(
|
||||||
|
FontAwesomeIcons.boxOpen,
|
||||||
size: 13,
|
size: 13,
|
||||||
color: user.archived ? null : Colors.transparent),
|
color: user.archived ? null : Colors.transparent,
|
||||||
|
),
|
||||||
onPressed: user.archived
|
onPressed: user.archived
|
||||||
? () async {
|
? () async {
|
||||||
const update =
|
const update =
|
||||||
|
|
@ -170,19 +175,22 @@ class UserList extends StatelessWidget {
|
||||||
await twonlyDB.contactsDao
|
await twonlyDB.contactsDao
|
||||||
.updateContact(user.userId, update);
|
.updateContact(user.userId, update);
|
||||||
}
|
}
|
||||||
: null)
|
: null,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
leading: ContactAvatar(
|
leading: ContactAvatar(
|
||||||
contact: user,
|
contact: user,
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
Navigator.pushReplacement(
|
await Navigator.pushReplacement(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(builder: (context) {
|
MaterialPageRoute(
|
||||||
|
builder: (context) {
|
||||||
return ChatMessagesView(user);
|
return ChatMessagesView(user);
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -100,9 +100,12 @@ class _AppOutdatedState extends State<AppOutdated> {
|
||||||
if (Platform.isAndroid) const SizedBox(height: 5),
|
if (Platform.isAndroid) const SizedBox(height: 5),
|
||||||
if (Platform.isAndroid)
|
if (Platform.isAndroid)
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
launchUrl(Uri.parse(
|
await launchUrl(
|
||||||
'https://play.google.com/store/apps/details?id=eu.twonly'));
|
Uri.parse(
|
||||||
|
'https://play.google.com/store/apps/details?id=eu.twonly',
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,15 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
|
||||||
class BetterListTile extends StatelessWidget {
|
class BetterListTile extends StatelessWidget {
|
||||||
const BetterListTile(
|
const BetterListTile({
|
||||||
{required this.icon,
|
required this.icon,
|
||||||
required this.text,
|
required this.text,
|
||||||
required this.onTap,
|
required this.onTap,
|
||||||
super.key,
|
super.key,
|
||||||
this.color,
|
this.color,
|
||||||
this.subtitle,
|
this.subtitle,
|
||||||
this.iconSize = 20});
|
this.iconSize = 20,
|
||||||
|
});
|
||||||
final IconData icon;
|
final IconData icon;
|
||||||
final String text;
|
final String text;
|
||||||
final Widget? subtitle;
|
final Widget? subtitle;
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,8 @@ class BetterText extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
final url = match.group(0);
|
final url = match.group(0);
|
||||||
spans.add(TextSpan(
|
spans.add(
|
||||||
|
TextSpan(
|
||||||
text: url,
|
text: url,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
decoration: TextDecoration.underline,
|
decoration: TextDecoration.underline,
|
||||||
|
|
@ -43,7 +44,8 @@ class BetterText extends StatelessWidget {
|
||||||
Log.error('Could not launch $e');
|
Log.error('Could not launch $e');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
));
|
),
|
||||||
|
);
|
||||||
|
|
||||||
lastMatchEnd = match.end;
|
lastMatchEnd = match.end;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,8 @@ class FlameCounterWidget extends StatelessWidget {
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 15,
|
height: 15,
|
||||||
child: EmojiAnimation(
|
child: EmojiAnimation(
|
||||||
emoji: (globalBestFriendUserId == user.userId) ? '❤️🔥' : '🔥'),
|
emoji: (globalBestFriendUserId == user.userId) ? '❤️🔥' : '🔥',
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class NotificationBadge extends StatelessWidget {
|
class NotificationBadge extends StatelessWidget {
|
||||||
const NotificationBadge(
|
const NotificationBadge({
|
||||||
{required this.count, required this.child, super.key});
|
required this.count,
|
||||||
|
required this.child,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
final String count;
|
final String count;
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
|
||||||
|
|
@ -35,7 +38,7 @@ class NotificationBadge extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// ignore_for_file: deprecated_member_use
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class RadioButton<T> extends StatelessWidget {
|
class RadioButton<T> extends StatelessWidget {
|
||||||
|
|
|
||||||
|
|
@ -27,9 +27,9 @@ class _UserContextMenuState extends State<UserContextMenu> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return PieMenu(
|
return PieMenu(
|
||||||
onPressed: () => (),
|
onPressed: () => (),
|
||||||
onToggle: (menuOpen) {
|
onToggle: (menuOpen) async {
|
||||||
if (menuOpen) {
|
if (menuOpen) {
|
||||||
HapticFeedback.heavyImpact();
|
await HapticFeedback.heavyImpact();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: [
|
actions: [
|
||||||
|
|
@ -59,12 +59,15 @@ class _UserContextMenuState extends State<UserContextMenu> {
|
||||||
),
|
),
|
||||||
PieAction(
|
PieAction(
|
||||||
tooltip: Text(context.lang.contextMenuOpenChat),
|
tooltip: Text(context.lang.contextMenuOpenChat),
|
||||||
onSelect: () {
|
onSelect: () async {
|
||||||
Navigator.push(context, MaterialPageRoute(
|
await Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return ChatMessagesView(widget.contact);
|
return ChatMessagesView(widget.contact);
|
||||||
},
|
},
|
||||||
));
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
child: const FaIcon(FontAwesomeIcons.solidComments),
|
child: const FaIcon(FontAwesomeIcons.solidComments),
|
||||||
),
|
),
|
||||||
|
|
@ -82,9 +85,11 @@ class _UserContextMenuState extends State<UserContextMenu> {
|
||||||
.updateContact(widget.contact.userId, update);
|
.updateContact(widget.contact.userId, update);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: FaIcon(widget.contact.pinned
|
child: FaIcon(
|
||||||
|
widget.contact.pinned
|
||||||
? FontAwesomeIcons.thumbtackSlash
|
? FontAwesomeIcons.thumbtackSlash
|
||||||
: FontAwesomeIcons.thumbtack),
|
: FontAwesomeIcons.thumbtack,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
child: widget.child,
|
child: widget.child,
|
||||||
|
|
@ -137,12 +142,15 @@ class _UserContextMenuBlocked extends State<UserContextMenuBlocked> {
|
||||||
),
|
),
|
||||||
PieAction(
|
PieAction(
|
||||||
tooltip: Text(context.lang.contextMenuUserProfile),
|
tooltip: Text(context.lang.contextMenuUserProfile),
|
||||||
onSelect: () {
|
onSelect: () async {
|
||||||
Navigator.push(context, MaterialPageRoute(
|
await Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return ContactView(widget.contact.userId);
|
return ContactView(widget.contact.userId);
|
||||||
},
|
},
|
||||||
));
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
child: const FaIcon(FontAwesomeIcons.user),
|
child: const FaIcon(FontAwesomeIcons.user),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,15 @@ class VerifiedShield extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
Navigator.push(context, MaterialPageRoute(
|
await Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return ContactVerifyView(contact);
|
return ContactVerifyView(contact);
|
||||||
},
|
},
|
||||||
));
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
child: Tooltip(
|
child: Tooltip(
|
||||||
message: contact.verified
|
message: contact.verified
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:video_player/video_player.dart';
|
import 'package:video_player/video_player.dart';
|
||||||
|
|
||||||
class VideoPlayerWrapper extends StatefulWidget {
|
class VideoPlayerWrapper extends StatefulWidget {
|
||||||
const VideoPlayerWrapper(
|
const VideoPlayerWrapper({
|
||||||
{required this.videoPath, required this.mirrorVideo, super.key});
|
required this.videoPath,
|
||||||
|
required this.mirrorVideo,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
final File videoPath;
|
final File videoPath;
|
||||||
final bool mirrorVideo;
|
final bool mirrorVideo;
|
||||||
|
|
||||||
|
|
@ -19,21 +23,22 @@ class _VideoPlayerWrapperState extends State<VideoPlayerWrapper> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_controller = VideoPlayerController.file(widget.videoPath)
|
_controller = VideoPlayerController.file(widget.videoPath);
|
||||||
..initialize().then((_) {
|
|
||||||
|
unawaited(
|
||||||
|
_controller.initialize().then((_) async {
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
setState(() {
|
await _controller.setLooping(true);
|
||||||
_controller
|
await _controller.play();
|
||||||
..setLooping(true)
|
setState(() {});
|
||||||
..play();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_controller.dispose();
|
unawaited(_controller.dispose());
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,8 @@ class _ContactViewState extends State<ContactView> {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
content: Text(
|
content: Text(
|
||||||
'Es ist ein Fehler aufgetreten. Bitte versuche es später erneut.'),
|
'Es ist ein Fehler aufgetreten. Bitte versuche es später erneut.',
|
||||||
|
),
|
||||||
duration: Duration(seconds: 3),
|
duration: Duration(seconds: 3),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -117,7 +118,8 @@ class _ContactViewState extends State<ContactView> {
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(right: 10),
|
padding: const EdgeInsets.only(right: 10),
|
||||||
child: VerifiedShield(contact)),
|
child: VerifiedShield(contact),
|
||||||
|
),
|
||||||
Text(
|
Text(
|
||||||
getContactDisplayName(contact),
|
getContactDisplayName(contact),
|
||||||
style: const TextStyle(fontSize: 20),
|
style: const TextStyle(fontSize: 20),
|
||||||
|
|
@ -151,12 +153,15 @@ class _ContactViewState extends State<ContactView> {
|
||||||
BetterListTile(
|
BetterListTile(
|
||||||
icon: FontAwesomeIcons.shieldHeart,
|
icon: FontAwesomeIcons.shieldHeart,
|
||||||
text: context.lang.contactVerifyNumberTitle,
|
text: context.lang.contactVerifyNumberTitle,
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
Navigator.push(context, MaterialPageRoute(
|
await Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return ContactVerifyView(contact);
|
return ContactVerifyView(contact);
|
||||||
},
|
},
|
||||||
));
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
BetterListTile(
|
BetterListTile(
|
||||||
|
|
@ -168,7 +173,8 @@ class _ContactViewState extends State<ContactView> {
|
||||||
context,
|
context,
|
||||||
context.lang.deleteAllContactMessages,
|
context.lang.deleteAllContactMessages,
|
||||||
context.lang.deleteAllContactMessagesBody(
|
context.lang.deleteAllContactMessagesBody(
|
||||||
getContactDisplayName(contact)),
|
getContactDisplayName(contact),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
if (block) {
|
if (block) {
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
|
|
@ -204,7 +210,9 @@ class _ContactViewState extends State<ContactView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> showNicknameChangeDialog(
|
Future<String?> showNicknameChangeDialog(
|
||||||
BuildContext context, Contact contact) {
|
BuildContext context,
|
||||||
|
Contact contact,
|
||||||
|
) {
|
||||||
final controller =
|
final controller =
|
||||||
TextEditingController(text: getContactDisplayName(contact));
|
TextEditingController(text: getContactDisplayName(contact));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,12 +39,12 @@ class _ContactVerifyViewState extends State<ContactVerifyView> {
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_contact = widget.contact;
|
_contact = widget.contact;
|
||||||
loadAsync();
|
unawaited(loadAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_contactSub.cancel();
|
unawaited(_contactSub.cancel());
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -86,14 +86,17 @@ class _ContactVerifyViewState extends State<ContactVerifyView> {
|
||||||
|
|
||||||
Future<void> openQrScanner() async {
|
Future<void> openQrScanner() async {
|
||||||
if (_fingerprint == null) return;
|
if (_fingerprint == null) return;
|
||||||
final isValid = await Navigator.push(context, MaterialPageRoute(
|
final isValid = await Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return ContactVerifyQrScanView(
|
return ContactVerifyQrScanView(
|
||||||
widget.contact,
|
widget.contact,
|
||||||
fingerprint: _fingerprint!,
|
fingerprint: _fingerprint!,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
)) as bool?;
|
),
|
||||||
|
) as bool?;
|
||||||
if (isValid == null) {
|
if (isValid == null) {
|
||||||
return; // user just returned...
|
return; // user just returned...
|
||||||
}
|
}
|
||||||
|
|
@ -205,7 +208,8 @@ class _ContactVerifyViewState extends State<ContactVerifyView> {
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 30),
|
padding: const EdgeInsets.symmetric(horizontal: 30),
|
||||||
child: Text(
|
child: Text(
|
||||||
context.lang.contactVerifyNumberLongDesc(
|
context.lang.contactVerifyNumberLongDesc(
|
||||||
getContactDisplayName(_contact)),
|
getContactDisplayName(_contact),
|
||||||
|
),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -213,9 +217,12 @@ class _ContactVerifyViewState extends State<ContactVerifyView> {
|
||||||
padding:
|
padding:
|
||||||
const EdgeInsets.symmetric(horizontal: 30, vertical: 10),
|
const EdgeInsets.symmetric(horizontal: 30, vertical: 10),
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
launchUrl(Uri.parse(
|
await launchUrl(
|
||||||
'https://twonly.eu/en/faq/security/verify-security-number.html'));
|
Uri.parse(
|
||||||
|
'https://twonly.eu/en/faq/security/verify-security-number.html',
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
'Read more.',
|
'Read more.',
|
||||||
|
|
@ -226,7 +233,7 @@ class _ContactVerifyViewState extends State<ContactVerifyView> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
bottomNavigationBar: SafeArea(
|
bottomNavigationBar: SafeArea(
|
||||||
|
|
@ -248,7 +255,7 @@ class _ContactVerifyViewState extends State<ContactVerifyView> {
|
||||||
label: Text(
|
label: Text(
|
||||||
context.lang.contactVerifyNumberMarkAsVerified,
|
context.lang.contactVerifyNumberMarkAsVerified,
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,11 @@ import 'package:twonly/src/database/twonly_database.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
class ContactVerifyQrScanView extends StatefulWidget {
|
class ContactVerifyQrScanView extends StatefulWidget {
|
||||||
const ContactVerifyQrScanView(this.contact,
|
const ContactVerifyQrScanView(
|
||||||
{required this.fingerprint, super.key});
|
this.contact, {
|
||||||
|
required this.fingerprint,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
final Fingerprint fingerprint;
|
final Fingerprint fingerprint;
|
||||||
final Contact contact;
|
final Contact contact;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -72,11 +72,11 @@ class HomeViewState extends State<HomeView> {
|
||||||
}
|
}
|
||||||
if (cameraController == null && !initCameraStarted && offsetRatio < 1) {
|
if (cameraController == null && !initCameraStarted && offsetRatio < 1) {
|
||||||
initCameraStarted = true;
|
initCameraStarted = true;
|
||||||
selectCamera(selectedCameraDetails.cameraId, false, false);
|
unawaited(selectCamera(selectedCameraDetails.cameraId, false, false));
|
||||||
}
|
}
|
||||||
if (offsetRatio == 1) {
|
if (offsetRatio == 1) {
|
||||||
disableCameraTimer = Timer(const Duration(milliseconds: 500), () {
|
disableCameraTimer = Timer(const Duration(milliseconds: 500), () async {
|
||||||
cameraController?.dispose();
|
await cameraController?.dispose();
|
||||||
cameraController = null;
|
cameraController = null;
|
||||||
selectedCameraDetails = SelectedCameraDetails();
|
selectedCameraDetails = SelectedCameraDetails();
|
||||||
disableCameraTimer = null;
|
disableCameraTimer = null;
|
||||||
|
|
@ -99,13 +99,13 @@ class HomeViewState extends State<HomeView> {
|
||||||
.listen((NotificationResponse? response) async {
|
.listen((NotificationResponse? response) async {
|
||||||
globalUpdateOfHomeViewPageIndex(0);
|
globalUpdateOfHomeViewPageIndex(0);
|
||||||
});
|
});
|
||||||
selectCamera(0, true, false);
|
unawaited(selectCamera(0, true, false));
|
||||||
initAsync();
|
unawaited(initAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
selectNotificationStream.close();
|
unawaited(selectNotificationStream.close());
|
||||||
disableCameraTimer?.cancel();
|
disableCameraTimer?.cancel();
|
||||||
cameraController?.dispose();
|
cameraController?.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
|
|
@ -117,7 +117,11 @@ class HomeViewState extends State<HomeView> {
|
||||||
bool enableAudio,
|
bool enableAudio,
|
||||||
) async {
|
) async {
|
||||||
final opts = await initializeCameraController(
|
final opts = await initializeCameraController(
|
||||||
selectedCameraDetails, sCameraId, init, enableAudio);
|
selectedCameraDetails,
|
||||||
|
sCameraId,
|
||||||
|
init,
|
||||||
|
enableAudio,
|
||||||
|
);
|
||||||
if (opts != null) {
|
if (opts != null) {
|
||||||
selectedCameraDetails = opts.$1;
|
selectedCameraDetails = opts.$1;
|
||||||
cameraController = opts.$2;
|
cameraController = opts.$2;
|
||||||
|
|
@ -131,7 +135,7 @@ class HomeViewState extends State<HomeView> {
|
||||||
Future<void> toggleSelectedCamera() async {
|
Future<void> toggleSelectedCamera() async {
|
||||||
if (cameraController == null) return;
|
if (cameraController == null) return;
|
||||||
// do not allow switching camera when recording
|
// do not allow switching camera when recording
|
||||||
if (cameraController!.value.isRecordingVideo == true) {
|
if (cameraController!.value.isRecordingVideo) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await cameraController!.dispose();
|
await cameraController!.dispose();
|
||||||
|
|
@ -199,7 +203,8 @@ class HomeViewState extends State<HomeView> {
|
||||||
selectedCameraDetails: selectedCameraDetails,
|
selectedCameraDetails: selectedCameraDetails,
|
||||||
selectCamera: selectCamera,
|
selectCamera: selectCamera,
|
||||||
),
|
),
|
||||||
)),
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -207,10 +212,11 @@ class HomeViewState extends State<HomeView> {
|
||||||
showSelectedLabels: false,
|
showSelectedLabels: false,
|
||||||
showUnselectedLabels: false,
|
showUnselectedLabels: false,
|
||||||
unselectedIconTheme: IconThemeData(
|
unselectedIconTheme: IconThemeData(
|
||||||
color:
|
color: Theme.of(context).colorScheme.inverseSurface.withAlpha(150),
|
||||||
Theme.of(context).colorScheme.inverseSurface.withAlpha(150)),
|
),
|
||||||
selectedIconTheme: IconThemeData(
|
selectedIconTheme: IconThemeData(
|
||||||
color: Theme.of(context).colorScheme.inverseSurface),
|
color: Theme.of(context).colorScheme.inverseSurface,
|
||||||
|
),
|
||||||
items: const [
|
items: const [
|
||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
icon: FaIcon(FontAwesomeIcons.solidComments),
|
icon: FaIcon(FontAwesomeIcons.solidComments),
|
||||||
|
|
@ -225,15 +231,14 @@ class HomeViewState extends State<HomeView> {
|
||||||
label: '',
|
label: '',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
onTap: (int index) {
|
onTap: (int index) async {
|
||||||
activePageIdx = index;
|
activePageIdx = index;
|
||||||
setState(() {
|
await homeViewPageController.animateToPage(
|
||||||
homeViewPageController.animateToPage(
|
|
||||||
index,
|
index,
|
||||||
duration: const Duration(milliseconds: 100),
|
duration: const Duration(milliseconds: 100),
|
||||||
curve: Curves.bounceIn,
|
curve: Curves.bounceIn,
|
||||||
);
|
);
|
||||||
});
|
if (mounted) setState(() {});
|
||||||
},
|
},
|
||||||
currentIndex: activePageIdx,
|
currentIndex: activePageIdx,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ class MemoriesViewState extends State<MemoriesView> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
initAsync();
|
unawaited(initAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -71,7 +71,8 @@ class MemoriesViewState extends State<MemoriesView> {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
final creationDate = file.lastModifiedSync();
|
final creationDate = file.lastModifiedSync();
|
||||||
items.add(MemoryItem(
|
items.add(
|
||||||
|
MemoryItem(
|
||||||
id: int.parse(fileName.split('.')[0]),
|
id: int.parse(fileName.split('.')[0]),
|
||||||
messages: [],
|
messages: [],
|
||||||
date: creationDate,
|
date: creationDate,
|
||||||
|
|
@ -79,7 +80,8 @@ class MemoriesViewState extends State<MemoriesView> {
|
||||||
thumbnailPath: thumbnailFile,
|
thumbnailPath: thumbnailFile,
|
||||||
imagePath: imagePath,
|
imagePath: imagePath,
|
||||||
videoPath: videoPath,
|
videoPath: videoPath,
|
||||||
));
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -98,8 +100,9 @@ class MemoriesViewState extends State<MemoriesView> {
|
||||||
var lastMonth = '';
|
var lastMonth = '';
|
||||||
galleryItems = await loadMemoriesDirectory();
|
galleryItems = await loadMemoriesDirectory();
|
||||||
for (final item in galleryItems) {
|
for (final item in galleryItems) {
|
||||||
items.remove(item
|
items.remove(
|
||||||
.id); // prefer the stored one and not the saved on in the chat....
|
item.id,
|
||||||
|
); // prefer the stored one and not the saved on in the chat....
|
||||||
}
|
}
|
||||||
galleryItems += items.values.toList();
|
galleryItems += items.values.toList();
|
||||||
galleryItems.sort((a, b) => b.date.compareTo(a.date));
|
galleryItems.sort((a, b) => b.date.compareTo(a.date));
|
||||||
|
|
@ -127,17 +130,18 @@ class MemoriesViewState extends State<MemoriesView> {
|
||||||
child: Text(
|
child: Text(
|
||||||
context.lang.memoriesEmpty,
|
context.lang.memoriesEmpty,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
))
|
),
|
||||||
|
)
|
||||||
: ListView.builder(
|
: ListView.builder(
|
||||||
itemCount: months.length * 2,
|
itemCount: months.length * 2,
|
||||||
itemBuilder: (context, mIndex) {
|
itemBuilder: (context, mIndex) {
|
||||||
if (mIndex.isEven) {
|
if (mIndex.isEven) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
child: Text(months[(mIndex / 2).toInt()]),
|
child: Text(months[(mIndex ~/ 2)]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
final index = ((mIndex - 1) / 2).toInt();
|
final index = (mIndex - 1) ~/ 2;
|
||||||
return GridView.builder(
|
return GridView.builder(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
physics: const ClampingScrollPhysics(),
|
physics: const ClampingScrollPhysics(),
|
||||||
|
|
@ -151,8 +155,8 @@ class MemoriesViewState extends State<MemoriesView> {
|
||||||
final gaIndex = orderedByMonth[months[index]]![gIndex];
|
final gaIndex = orderedByMonth[months[index]]![gIndex];
|
||||||
return MemoriesItemThumbnail(
|
return MemoriesItemThumbnail(
|
||||||
galleryItem: galleryItems[gaIndex],
|
galleryItem: galleryItems[gaIndex],
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
open(context, gaIndex);
|
await open(context, gaIndex);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -126,8 +126,8 @@ class _MemoriesPhotoSliderViewState extends State<MemoriesPhotoSliderView> {
|
||||||
children: [
|
children: [
|
||||||
FilledButton.icon(
|
FilledButton.icon(
|
||||||
icon: const FaIcon(FontAwesomeIcons.solidPaperPlane),
|
icon: const FaIcon(FontAwesomeIcons.solidPaperPlane),
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
Navigator.push(
|
await Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => ShareImageEditorView(
|
builder: (context) => ShareImageEditorView(
|
||||||
|
|
@ -146,11 +146,14 @@ class _MemoriesPhotoSliderViewState extends State<MemoriesPhotoSliderView> {
|
||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
padding: WidgetStateProperty.all<EdgeInsets>(
|
padding: WidgetStateProperty.all<EdgeInsets>(
|
||||||
const EdgeInsets.symmetric(
|
const EdgeInsets.symmetric(
|
||||||
vertical: 10, horizontal: 30),
|
vertical: 10,
|
||||||
|
horizontal: 30,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
backgroundColor: WidgetStateProperty.all<Color>(
|
backgroundColor: WidgetStateProperty.all<Color>(
|
||||||
Theme.of(context).colorScheme.primary,
|
Theme.of(context).colorScheme.primary,
|
||||||
)),
|
),
|
||||||
|
),
|
||||||
label: Text(
|
label: Text(
|
||||||
context.lang.shareImagedEditorSendImage,
|
context.lang.shareImagedEditorSendImage,
|
||||||
style: const TextStyle(fontSize: 17),
|
style: const TextStyle(fontSize: 17),
|
||||||
|
|
@ -173,12 +176,12 @@ class _MemoriesPhotoSliderViewState extends State<MemoriesPhotoSliderView> {
|
||||||
Positioned(
|
Positioned(
|
||||||
right: 5,
|
right: 5,
|
||||||
child: PopupMenuButton<String>(
|
child: PopupMenuButton<String>(
|
||||||
onSelected: (String result) {
|
onSelected: (String result) async {
|
||||||
if (result == 'delete') {
|
if (result == 'delete') {
|
||||||
deleteFile();
|
await deleteFile();
|
||||||
}
|
}
|
||||||
if (result == 'export') {
|
if (result == 'export') {
|
||||||
exportFile();
|
await exportFile();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
itemBuilder: (BuildContext context) =>
|
itemBuilder: (BuildContext context) =>
|
||||||
|
|
@ -197,7 +200,7 @@ class _MemoriesPhotoSliderViewState extends State<MemoriesPhotoSliderView> {
|
||||||
// ),
|
// ),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue