mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-04-19 03:52:54 +00:00
Compare commits
No commits in common. "main" and "v0.0.99" have entirely different histories.
196 changed files with 2089 additions and 14419 deletions
32
CHANGELOG.md
32
CHANGELOG.md
|
|
@ -1,36 +1,6 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## 0.1.5
|
## 0.0.99
|
||||||
|
|
||||||
- Fix: Reupload of media files was not working properly
|
|
||||||
- Fix: Chats were sometimes ordered wrongly
|
|
||||||
- Fix: Typing indicator was not always shown
|
|
||||||
- Fix: Multiple smaller issues
|
|
||||||
|
|
||||||
## 0.1.4
|
|
||||||
|
|
||||||
- New: Typing and chat open indicator
|
|
||||||
- New: Screen lock for twonly (Can be enabled in the settings.)
|
|
||||||
- Improve: Visual indication when connected to the server
|
|
||||||
- Improve: Several minor issues with the user interface
|
|
||||||
- Fix: Poor audio quality and edge distortions in videos sent from Android
|
|
||||||
|
|
||||||
## 0.1.3
|
|
||||||
|
|
||||||
- New: Video stabilization
|
|
||||||
- New: Crop or rotate images before sharing them.
|
|
||||||
- New: Clicking on “Text Notifications” will now open the chat directly (Android only)
|
|
||||||
- New: Developer settings to reduce flames
|
|
||||||
- Improve: Improved troubleshooting for issues with push notifications
|
|
||||||
- Improve: A message appears if someone has deleted their account.
|
|
||||||
- Improve: Make the verification badge more visible.
|
|
||||||
- Fix: Flash not activated when starting a video recording
|
|
||||||
- Fix: Problem sending media when a recipient has deleted their account.
|
|
||||||
- Fix: Receive push notifications without receiving an in-app message (Android)
|
|
||||||
- Fix: Issue with sending GIFs from Memories
|
|
||||||
- Fix: Incorrect processing of messages that have already been fetched from the server causes the UI to freeze
|
|
||||||
|
|
||||||
## 0.1.1
|
|
||||||
|
|
||||||
- New: Groups can now collect flames as well
|
- New: Groups can now collect flames as well
|
||||||
- New: Background execution to pre-load messages
|
- New: Background execution to pre-load messages
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import com.otaliastudios.transcoder.Transcoder
|
import com.otaliastudios.transcoder.Transcoder
|
||||||
import com.otaliastudios.transcoder.TranscoderListener
|
import com.otaliastudios.transcoder.TranscoderListener
|
||||||
|
import com.otaliastudios.transcoder.strategy.DefaultAudioStrategy
|
||||||
import com.otaliastudios.transcoder.strategy.DefaultVideoStrategy
|
import com.otaliastudios.transcoder.strategy.DefaultVideoStrategy
|
||||||
import com.otaliastudios.transcoder.strategy.PassThroughTrackStrategy
|
|
||||||
import com.otaliastudios.transcoder.strategy.TrackStrategy
|
import com.otaliastudios.transcoder.strategy.TrackStrategy
|
||||||
import io.flutter.embedding.engine.FlutterEngine
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
|
@ -18,6 +18,11 @@ object VideoCompressionChannel {
|
||||||
// Compression parameters defined natively (as requested)
|
// Compression parameters defined natively (as requested)
|
||||||
private const val VIDEO_BITRATE = 2_000_000L // 2 Mbps
|
private const val VIDEO_BITRATE = 2_000_000L // 2 Mbps
|
||||||
|
|
||||||
|
// Audio parameters defined natively
|
||||||
|
private const val AUDIO_BITRATE = 128_000L // 128 kbps
|
||||||
|
private const val AUDIO_SAMPLE_RATE = 44_100
|
||||||
|
private const val AUDIO_CHANNELS = 2
|
||||||
|
|
||||||
fun configure(flutterEngine: FlutterEngine, context: Context) {
|
fun configure(flutterEngine: FlutterEngine, context: Context) {
|
||||||
val channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
|
val channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
|
||||||
|
|
||||||
|
|
@ -49,14 +54,6 @@ object VideoCompressionChannel {
|
||||||
val result = if (args != null) method.invoke(baseVideoStrategy, *args) else method.invoke(baseVideoStrategy)
|
val result = if (args != null) method.invoke(baseVideoStrategy, *args) else method.invoke(baseVideoStrategy)
|
||||||
if (method.name == "createOutputFormat" && result is MediaFormat) {
|
if (method.name == "createOutputFormat" && result is MediaFormat) {
|
||||||
result.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_HEVC)
|
result.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_HEVC)
|
||||||
|
|
||||||
if (result.containsKey(MediaFormat.KEY_WIDTH) && result.containsKey(MediaFormat.KEY_HEIGHT)) {
|
|
||||||
val width = result.getInteger(MediaFormat.KEY_WIDTH)
|
|
||||||
val height = result.getInteger(MediaFormat.KEY_HEIGHT)
|
|
||||||
// Align dimensions to a multiple of 16 to prevent edge artifacts (green lines/distortions)
|
|
||||||
result.setInteger(MediaFormat.KEY_WIDTH, width - (width % 16))
|
|
||||||
result.setInteger(MediaFormat.KEY_HEIGHT, height - (height % 16))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
} as TrackStrategy
|
} as TrackStrategy
|
||||||
|
|
@ -64,7 +61,13 @@ object VideoCompressionChannel {
|
||||||
Transcoder.into(outputPath)
|
Transcoder.into(outputPath)
|
||||||
.addDataSource(inputPath)
|
.addDataSource(inputPath)
|
||||||
.setVideoTrackStrategy(hevcStrategy)
|
.setVideoTrackStrategy(hevcStrategy)
|
||||||
.setAudioTrackStrategy(PassThroughTrackStrategy())
|
.setAudioTrackStrategy(
|
||||||
|
DefaultAudioStrategy.builder()
|
||||||
|
.channels(AUDIO_CHANNELS)
|
||||||
|
.sampleRate(AUDIO_SAMPLE_RATE)
|
||||||
|
.bitRate(AUDIO_BITRATE)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
.setListener(object : TranscoderListener {
|
.setListener(object : TranscoderListener {
|
||||||
override fun onTranscodeProgress(progress: Double) {
|
override fun onTranscodeProgress(progress: Double) {
|
||||||
mainHandler.post {
|
mainHandler.post {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#57CC99" class="bi bi-patch-check-fill" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#57CC99" class="bi bi-patch-check" viewBox="0 0 16 16">
|
||||||
<path d="M10.067.87a2.89 2.89 0 0 0-4.134 0l-.622.638-.89-.011a2.89 2.89 0 0 0-2.924 2.924l.01.89-.636.622a2.89 2.89 0 0 0 0 4.134l.637.622-.011.89a2.89 2.89 0 0 0 2.924 2.924l.89-.01.622.636a2.89 2.89 0 0 0 4.134 0l.622-.637.89.011a2.89 2.89 0 0 0 2.924-2.924l-.01-.89.636-.622a2.89 2.89 0 0 0 0-4.134l-.637-.622.011-.89a2.89 2.89 0 0 0-2.924-2.924l-.89.01zm.287 5.984-3 3a.5.5 0 0 1-.708 0l-1.5-1.5a.5.5 0 1 1 .708-.708L7 8.793l2.646-2.647a.5.5 0 0 1 .708.708"/>
|
<path fill-rule="evenodd" d="M10.354 6.146a.5.5 0 0 1 0 .708l-3 3a.5.5 0 0 1-.708 0l-1.5-1.5a.5.5 0 1 1 .708-.708L7 8.793l2.646-2.647a.5.5 0 0 1 .708 0"/>
|
||||||
|
<path d="m10.273 2.513-.921-.944.715-.698.622.637.89-.011a2.89 2.89 0 0 1 2.924 2.924l-.01.89.636.622a2.89 2.89 0 0 1 0 4.134l-.637.622.011.89a2.89 2.89 0 0 1-2.924 2.924l-.89-.01-.622.636a2.89 2.89 0 0 1-4.134 0l-.622-.637-.89.011a2.89 2.89 0 0 1-2.924-2.924l.01-.89-.636-.622a2.89 2.89 0 0 1 0-4.134l.637-.622-.011-.89a2.89 2.89 0 0 1 2.924-2.924l.89.01.622-.636a2.89 2.89 0 0 1 4.134 0l-.715.698a1.89 1.89 0 0 0-2.704 0l-.92.944-1.32-.016a1.89 1.89 0 0 0-1.911 1.912l.016 1.318-.944.921a1.89 1.89 0 0 0 0 2.704l.944.92-.016 1.32a1.89 1.89 0 0 0 1.912 1.911l1.318-.016.921.944a1.89 1.89 0 0 0 2.704 0l.92-.944 1.32.016a1.89 1.89 0 0 0 1.911-1.912l-.016-1.318.944-.921a1.89 1.89 0 0 0 0-2.704l-.944-.92.016-1.32a1.89 1.89 0 0 0-1.912-1.911z"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 604 B After Width: | Height: | Size: 1 KiB |
|
|
@ -1,3 +1,4 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#ff0000" class="bi bi-patch-exclamation-fill" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#ff0000" class="bi bi-patch-check" viewBox="0 0 16 16">
|
||||||
<path d="M10.067.87a2.89 2.89 0 0 0-4.134 0l-.622.638-.89-.011a2.89 2.89 0 0 0-2.924 2.924l.01.89-.636.622a2.89 2.89 0 0 0 0 4.134l.637.622-.011.89a2.89 2.89 0 0 0 2.924 2.924l.89-.01.622.636a2.89 2.89 0 0 0 4.134 0l.622-.637.89.011a2.89 2.89 0 0 0 2.924-2.924l-.01-.89.636-.622a2.89 2.89 0 0 0 0-4.134l-.637-.622.011-.89a2.89 2.89 0 0 0-2.924-2.924l-.89.01zM8 4c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995A.905.905 0 0 1 8 4m.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2"/>
|
<path d="M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0z"/>
|
||||||
|
<path d="m10.273 2.513-.921-.944.715-.698.622.637.89-.011a2.89 2.89 0 0 1 2.924 2.924l-.01.89.636.622a2.89 2.89 0 0 1 0 4.134l-.637.622.011.89a2.89 2.89 0 0 1-2.924 2.924l-.89-.01-.622.636a2.89 2.89 0 0 1-4.134 0l-.622-.637-.89.011a2.89 2.89 0 0 1-2.924-2.924l.01-.89-.636-.622a2.89 2.89 0 0 1 0-4.134l.637-.622-.011-.89a2.89 2.89 0 0 1 2.924-2.924l.89.01.622-.636a2.89 2.89 0 0 1 4.134 0l-.715.698a1.89 1.89 0 0 0-2.704 0l-.92.944-1.32-.016a1.89 1.89 0 0 0-1.911 1.912l.016 1.318-.944.921a1.89 1.89 0 0 0 0 2.704l.944.92-.016 1.32a1.89 1.89 0 0 0 1.912 1.911l1.318-.016.921.944a1.89 1.89 0 0 0 2.704 0l.92-.944 1.32.016a1.89 1.89 0 0 0 1.911-1.912l-.016-1.318.944-.921a1.89 1.89 0 0 0 0-2.704l-.944-.92.016-1.32a1.89 1.89 0 0 0-1.912-1.911z"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 629 B After Width: | Height: | Size: 995 B |
|
|
@ -8,11 +8,7 @@
|
||||||
// For information on using the generated types, please see the documentation:
|
// For information on using the generated types, please see the documentation:
|
||||||
// https://github.com/apple/swift-protobuf/
|
// https://github.com/apple/swift-protobuf/
|
||||||
|
|
||||||
#if canImport(FoundationEssentials)
|
|
||||||
import FoundationEssentials
|
|
||||||
#else
|
|
||||||
import Foundation
|
import Foundation
|
||||||
#endif
|
|
||||||
import SwiftProtobuf
|
import SwiftProtobuf
|
||||||
|
|
||||||
// If the compiler emits an error on this type, it is because this file
|
// If the compiler emits an error on this type, it is because this file
|
||||||
|
|
|
||||||
|
|
@ -56,20 +56,13 @@ PODS:
|
||||||
- FirebaseAnalytics (~> 12.9.0)
|
- FirebaseAnalytics (~> 12.9.0)
|
||||||
- Firebase/CoreOnly (12.9.0):
|
- Firebase/CoreOnly (12.9.0):
|
||||||
- FirebaseCore (~> 12.9.0)
|
- FirebaseCore (~> 12.9.0)
|
||||||
- Firebase/Installations (12.9.0):
|
|
||||||
- Firebase/CoreOnly
|
|
||||||
- FirebaseInstallations (~> 12.9.0)
|
|
||||||
- Firebase/Messaging (12.9.0):
|
- Firebase/Messaging (12.9.0):
|
||||||
- Firebase/CoreOnly
|
- Firebase/CoreOnly
|
||||||
- FirebaseMessaging (~> 12.9.0)
|
- FirebaseMessaging (~> 12.9.0)
|
||||||
- firebase_app_installations (0.4.1):
|
- firebase_core (4.5.0):
|
||||||
- Firebase/Installations (= 12.9.0)
|
|
||||||
- firebase_core
|
|
||||||
- Flutter
|
|
||||||
- firebase_core (4.6.0):
|
|
||||||
- Firebase/CoreOnly (= 12.9.0)
|
- Firebase/CoreOnly (= 12.9.0)
|
||||||
- Flutter
|
- Flutter
|
||||||
- firebase_messaging (16.1.3):
|
- firebase_messaging (16.1.2):
|
||||||
- Firebase/Messaging (= 12.9.0)
|
- Firebase/Messaging (= 12.9.0)
|
||||||
- firebase_core
|
- firebase_core
|
||||||
- Flutter
|
- Flutter
|
||||||
|
|
@ -285,17 +278,17 @@ PODS:
|
||||||
- PromisesObjC (2.4.0)
|
- PromisesObjC (2.4.0)
|
||||||
- restart_app (1.7.3):
|
- restart_app (1.7.3):
|
||||||
- Flutter
|
- Flutter
|
||||||
- SDWebImage (5.21.7):
|
- SDWebImage (5.21.6):
|
||||||
- SDWebImage/Core (= 5.21.7)
|
- SDWebImage/Core (= 5.21.6)
|
||||||
- SDWebImage/Core (5.21.7)
|
- SDWebImage/Core (5.21.6)
|
||||||
- SDWebImageWebPCoder (0.15.0):
|
- SDWebImageWebPCoder (0.15.0):
|
||||||
- libwebp (~> 1.0)
|
- libwebp (~> 1.0)
|
||||||
- SDWebImage/Core (~> 5.17)
|
- SDWebImage/Core (~> 5.17)
|
||||||
- Sentry/HybridSDK (8.58.0)
|
- Sentry/HybridSDK (8.56.2)
|
||||||
- sentry_flutter (9.16.0):
|
- sentry_flutter (9.14.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- Sentry/HybridSDK (= 8.58.0)
|
- Sentry/HybridSDK (= 8.56.2)
|
||||||
- share_plus (0.0.1):
|
- share_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- shared_preferences_foundation (0.0.1):
|
- shared_preferences_foundation (0.0.1):
|
||||||
|
|
@ -304,32 +297,32 @@ PODS:
|
||||||
- sqflite_darwin (0.0.4):
|
- sqflite_darwin (0.0.4):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- sqlite3 (3.52.0):
|
- sqlite3 (3.51.1):
|
||||||
- sqlite3/common (= 3.52.0)
|
- sqlite3/common (= 3.51.1)
|
||||||
- sqlite3/common (3.52.0)
|
- sqlite3/common (3.51.1)
|
||||||
- sqlite3/dbstatvtab (3.52.0):
|
- sqlite3/dbstatvtab (3.51.1):
|
||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
- sqlite3/fts5 (3.52.0):
|
- sqlite3/fts5 (3.51.1):
|
||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
- sqlite3/math (3.52.0):
|
- sqlite3/math (3.51.1):
|
||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
- sqlite3/perf-threadsafe (3.52.0):
|
- sqlite3/perf-threadsafe (3.51.1):
|
||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
- sqlite3/rtree (3.52.0):
|
- sqlite3/rtree (3.51.1):
|
||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
- sqlite3/session (3.52.0):
|
- sqlite3/session (3.51.1):
|
||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
- sqlite3_flutter_libs (0.0.1):
|
- sqlite3_flutter_libs (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- sqlite3 (~> 3.52.0)
|
- sqlite3 (~> 3.51.1)
|
||||||
- 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.36.1)
|
- SwiftProtobuf (1.34.1)
|
||||||
- SwiftyGif (5.4.5)
|
- SwiftyGif (5.4.5)
|
||||||
- url_launcher_ios (0.0.1):
|
- url_launcher_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
|
@ -350,7 +343,6 @@ DEPENDENCIES:
|
||||||
- emoji_picker_flutter (from `.symlinks/plugins/emoji_picker_flutter/ios`)
|
- emoji_picker_flutter (from `.symlinks/plugins/emoji_picker_flutter/ios`)
|
||||||
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
||||||
- Firebase
|
- Firebase
|
||||||
- firebase_app_installations (from `.symlinks/plugins/firebase_app_installations/ios`)
|
|
||||||
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
|
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
|
||||||
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
|
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
|
||||||
- FirebaseCore
|
- FirebaseCore
|
||||||
|
|
@ -438,8 +430,6 @@ EXTERNAL SOURCES:
|
||||||
:path: ".symlinks/plugins/emoji_picker_flutter/ios"
|
:path: ".symlinks/plugins/emoji_picker_flutter/ios"
|
||||||
file_picker:
|
file_picker:
|
||||||
:path: ".symlinks/plugins/file_picker/ios"
|
:path: ".symlinks/plugins/file_picker/ios"
|
||||||
firebase_app_installations:
|
|
||||||
:path: ".symlinks/plugins/firebase_app_installations/ios"
|
|
||||||
firebase_core:
|
firebase_core:
|
||||||
:path: ".symlinks/plugins/firebase_core/ios"
|
:path: ".symlinks/plugins/firebase_core/ios"
|
||||||
firebase_messaging:
|
firebase_messaging:
|
||||||
|
|
@ -503,7 +493,7 @@ SPEC CHECKSUMS:
|
||||||
app_links: a754cbec3c255bd4bbb4d236ecc06f28cd9a7ce8
|
app_links: a754cbec3c255bd4bbb4d236ecc06f28cd9a7ce8
|
||||||
audio_waveforms: a6dde7fe7c0ea05f06ffbdb0f7c1b2b2ba6cedcf
|
audio_waveforms: a6dde7fe7c0ea05f06ffbdb0f7c1b2b2ba6cedcf
|
||||||
background_downloader: 50e91d979067b82081aba359d7d916b3ba5fadad
|
background_downloader: 50e91d979067b82081aba359d7d916b3ba5fadad
|
||||||
camera_avfoundation: 968a9a5323c79a99c166ad9d7866bfd2047b5a9b
|
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
|
||||||
|
|
@ -512,9 +502,8 @@ SPEC CHECKSUMS:
|
||||||
emoji_picker_flutter: ece213fc274bdddefb77d502d33080dc54e616cc
|
emoji_picker_flutter: ece213fc274bdddefb77d502d33080dc54e616cc
|
||||||
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
|
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
|
||||||
Firebase: 065f2bb395062046623036d8e6dc857bc2521d56
|
Firebase: 065f2bb395062046623036d8e6dc857bc2521d56
|
||||||
firebase_app_installations: 1abd8d071ea2022d7888f7a9713710c37136ff91
|
firebase_core: afac1aac13c931e0401c7e74ed1276112030efab
|
||||||
firebase_core: 8e6f58412ca227827c366b92e7cee047a2148c60
|
firebase_messaging: 7cb2727feb789751fc6936bcc8e08408970e2820
|
||||||
firebase_messaging: c3aa897e0d40109cfb7927c40dc0dea799863f3b
|
|
||||||
FirebaseAnalytics: cd7d01d352f3c237c9a0e31552c257cd0b0c0352
|
FirebaseAnalytics: cd7d01d352f3c237c9a0e31552c257cd0b0c0352
|
||||||
FirebaseCore: 428912f751178b06bef0a1793effeb4a5e09a9b8
|
FirebaseCore: 428912f751178b06bef0a1793effeb4a5e09a9b8
|
||||||
FirebaseCoreInternal: b321eafae5362113bc182956fafc9922cfc77b72
|
FirebaseCoreInternal: b321eafae5362113bc182956fafc9922cfc77b72
|
||||||
|
|
@ -555,16 +544,16 @@ SPEC CHECKSUMS:
|
||||||
pro_video_editor: 44ef9a6d48dbd757ed428cf35396dd05f35c7830
|
pro_video_editor: 44ef9a6d48dbd757ed428cf35396dd05f35c7830
|
||||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||||
restart_app: 0714144901e260eae68f7afc2fc4aacc1a323ad2
|
restart_app: 0714144901e260eae68f7afc2fc4aacc1a323ad2
|
||||||
SDWebImage: e9fc87c1aab89a8ab1bbd74eba378c6f53be8abf
|
SDWebImage: 1bb6a1b84b6fe87b972a102bdc77dd589df33477
|
||||||
SDWebImageWebPCoder: 0e06e365080397465cc73a7a9b472d8a3bd0f377
|
SDWebImageWebPCoder: 0e06e365080397465cc73a7a9b472d8a3bd0f377
|
||||||
Sentry: d587a8fe91ca13503ecd69a1905f3e8a0fcf61be
|
Sentry: b53951377b78e21a734f5dc8318e333dbfc682d7
|
||||||
sentry_flutter: 31101687061fb85211ebab09ce6eb8db4e9ba74f
|
sentry_flutter: 841fa2fe08dc72eb95e2320b76e3f751f3400cf5
|
||||||
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
|
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
|
||||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||||
sqlite3: a51c07cf16e023d6c48abd5e5791a61a47354921
|
sqlite3: 8d708bc63e9f4ce48f0ad9d6269e478c5ced1d9b
|
||||||
sqlite3_flutter_libs: b3e120efe9a82017e5552a620f696589ed4f62ab
|
sqlite3_flutter_libs: d13b8b3003f18f596e542bcb9482d105577eff41
|
||||||
SwiftProtobuf: 9e106a71456f4d3f6a3b0c8fd87ef0be085efc38
|
SwiftProtobuf: c901f00a3e125dc33cac9b16824da85682ee47da
|
||||||
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
||||||
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
|
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
|
||||||
video_player_avfoundation: dd410b52df6d2466a42d28550e33e4146928280a
|
video_player_avfoundation: dd410b52df6d2466a42d28550e33e4146928280a
|
||||||
|
|
|
||||||
26
lib/app.dart
26
lib/app.dart
|
|
@ -19,7 +19,6 @@ import 'package:twonly/src/views/home.view.dart';
|
||||||
import 'package:twonly/src/views/onboarding/onboarding.view.dart';
|
import 'package:twonly/src/views/onboarding/onboarding.view.dart';
|
||||||
import 'package:twonly/src/views/onboarding/register.view.dart';
|
import 'package:twonly/src/views/onboarding/register.view.dart';
|
||||||
import 'package:twonly/src/views/settings/backup/setup_backup.view.dart';
|
import 'package:twonly/src/views/settings/backup/setup_backup.view.dart';
|
||||||
import 'package:twonly/src/views/unlock_twonly.view.dart';
|
|
||||||
|
|
||||||
class App extends StatefulWidget {
|
class App extends StatefulWidget {
|
||||||
const App({super.key});
|
const App({super.key});
|
||||||
|
|
@ -37,9 +36,9 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||||
WidgetsBinding.instance.addObserver(this);
|
WidgetsBinding.instance.addObserver(this);
|
||||||
|
|
||||||
globalCallbackConnectionState = ({required isConnected}) async {
|
globalCallbackConnectionState = ({required isConnected}) async {
|
||||||
await context.read<CustomChangeProvider>().updateConnectionState(
|
await context
|
||||||
isConnected,
|
.read<CustomChangeProvider>()
|
||||||
);
|
.updateConnectionState(isConnected);
|
||||||
await setUserPlan();
|
await setUserPlan();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -135,7 +134,6 @@ class _AppMainWidgetState extends State<AppMainWidget> {
|
||||||
bool _showOnboarding = true;
|
bool _showOnboarding = true;
|
||||||
bool _isLoaded = false;
|
bool _isLoaded = false;
|
||||||
bool _skipBackup = false;
|
bool _skipBackup = false;
|
||||||
bool _isTwonlyLocked = true;
|
|
||||||
int _initialPage = 0;
|
int _initialPage = 0;
|
||||||
|
|
||||||
(Future<int>?, bool) _proofOfWork = (null, false);
|
(Future<int>?, bool) _proofOfWork = (null, false);
|
||||||
|
|
@ -151,10 +149,6 @@ class _AppMainWidgetState extends State<AppMainWidget> {
|
||||||
_isUserCreated = await isUserCreated();
|
_isUserCreated = await isUserCreated();
|
||||||
|
|
||||||
if (_isUserCreated) {
|
if (_isUserCreated) {
|
||||||
if (_isTwonlyLocked) {
|
|
||||||
// do not change in case twonly was already unlocked at some point
|
|
||||||
_isTwonlyLocked = gUser.screenLockEnabled;
|
|
||||||
}
|
|
||||||
if (gUser.appVersion < 62) {
|
if (gUser.appVersion < 62) {
|
||||||
_showDatabaseMigration = true;
|
_showDatabaseMigration = true;
|
||||||
}
|
}
|
||||||
|
|
@ -170,10 +164,8 @@ class _AppMainWidgetState extends State<AppMainWidget> {
|
||||||
if (proof != null) {
|
if (proof != null) {
|
||||||
Log.info('Starting with proof of work calculation.');
|
Log.info('Starting with proof of work calculation.');
|
||||||
// Starting with the proof of work.
|
// Starting with the proof of work.
|
||||||
_proofOfWork = (
|
_proofOfWork =
|
||||||
calculatePoW(proof.prefix, proof.difficulty.toInt()),
|
(calculatePoW(proof.prefix, proof.difficulty.toInt()), false);
|
||||||
false,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
_proofOfWork = (null, disabled);
|
_proofOfWork = (null, disabled);
|
||||||
}
|
}
|
||||||
|
|
@ -195,13 +187,7 @@ class _AppMainWidgetState extends State<AppMainWidget> {
|
||||||
if (_showDatabaseMigration) {
|
if (_showDatabaseMigration) {
|
||||||
child = const Center(child: Text('Please reinstall twonly.'));
|
child = const Center(child: Text('Please reinstall twonly.'));
|
||||||
} else if (_isUserCreated) {
|
} else if (_isUserCreated) {
|
||||||
if (_isTwonlyLocked) {
|
if (gUser.twonlySafeBackup == null && !_skipBackup) {
|
||||||
child = UnlockTwonlyView(
|
|
||||||
callbackOnSuccess: () => setState(() {
|
|
||||||
_isTwonlyLocked = false;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
} else if (gUser.twonlySafeBackup == null && !_skipBackup) {
|
|
||||||
child = SetupBackupView(
|
child = SetupBackupView(
|
||||||
callBack: () {
|
callBack: () {
|
||||||
_skipBackup = true;
|
_skipBackup = true;
|
||||||
|
|
|
||||||
|
|
@ -65,4 +65,5 @@ class DefaultFirebaseOptions {
|
||||||
storageBucket: 'twonly-ff605.firebasestorage.app',
|
storageBucket: 'twonly-ff605.firebasestorage.app',
|
||||||
iosBundleId: 'eu.twonly',
|
iosBundleId: 'eu.twonly',
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -21,8 +21,7 @@ late UserData gUser;
|
||||||
// App widget.
|
// App widget.
|
||||||
|
|
||||||
// This callback called by the apiProvider
|
// This callback called by the apiProvider
|
||||||
void Function({required bool isConnected}) globalCallbackConnectionState =
|
void Function({required bool isConnected}) globalCallbackConnectionState = ({
|
||||||
({
|
|
||||||
required isConnected,
|
required isConnected,
|
||||||
}) {};
|
}) {};
|
||||||
void Function() globalCallbackAppIsOutdated = () {};
|
void Function() globalCallbackAppIsOutdated = () {};
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,7 @@ class Routes {
|
||||||
static const String chatsStartNewChat = '/chats/start_new_chat';
|
static const String chatsStartNewChat = '/chats/start_new_chat';
|
||||||
static const String chatsCameraSendTo = '/chats/camera_send_to';
|
static const String chatsCameraSendTo = '/chats/camera_send_to';
|
||||||
static const String chatsMediaViewer = '/chats/media_viewer';
|
static const String chatsMediaViewer = '/chats/media_viewer';
|
||||||
|
static const String chatsMessages = '/chats/messages';
|
||||||
static String chatsMessages(String groupId) => '/chats/messages/$groupId';
|
|
||||||
|
|
||||||
static String groupCreateSelectMember(String? groupId) =>
|
static String groupCreateSelectMember(String? groupId) =>
|
||||||
'/group/create/select_member${groupId == null ? '' : '/$groupId'}';
|
'/group/create/select_member${groupId == null ? '' : '/$groupId'}';
|
||||||
|
|
@ -54,7 +53,5 @@ class Routes {
|
||||||
'/settings/developer/retransmission_database';
|
'/settings/developer/retransmission_database';
|
||||||
static const String settingsDeveloperAutomatedTesting =
|
static const String settingsDeveloperAutomatedTesting =
|
||||||
'/settings/developer/automated_testing';
|
'/settings/developer/automated_testing';
|
||||||
static const String settingsDeveloperReduceFlames =
|
|
||||||
'/settings/developer/reduce_flames';
|
|
||||||
static const String settingsInvite = '/settings/invite';
|
static const String settingsInvite = '/settings/invite';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,16 +37,16 @@ class ContactsDao extends DatabaseAccessor<TwonlyDB> with _$ContactsDaoMixin {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Contact?> getContactById(int userId) async {
|
Future<Contact?> getContactById(int userId) async {
|
||||||
return (select(
|
return (select(contacts)..where((t) => t.userId.equals(userId)))
|
||||||
contacts,
|
.getSingleOrNull();
|
||||||
)..where((t) => t.userId.equals(userId))).getSingleOrNull();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Contact>> getContactsByUsername(
|
Future<List<Contact>> getContactsByUsername(
|
||||||
String username, {
|
String username, {
|
||||||
String username2 = '_______',
|
String username2 = '_______',
|
||||||
}) async {
|
}) async {
|
||||||
return (select(contacts)..where(
|
return (select(contacts)
|
||||||
|
..where(
|
||||||
(t) => t.username.equals(username) | t.username.equals(username2),
|
(t) => t.username.equals(username) | t.username.equals(username2),
|
||||||
))
|
))
|
||||||
.get();
|
.get();
|
||||||
|
|
@ -60,9 +60,8 @@ class ContactsDao extends DatabaseAccessor<TwonlyDB> with _$ContactsDaoMixin {
|
||||||
int userId,
|
int userId,
|
||||||
ContactsCompanion updatedValues,
|
ContactsCompanion updatedValues,
|
||||||
) async {
|
) async {
|
||||||
await (update(
|
await (update(contacts)..where((c) => c.userId.equals(userId)))
|
||||||
contacts,
|
.write(updatedValues);
|
||||||
)..where((c) => c.userId.equals(userId))).write(updatedValues);
|
|
||||||
if (updatedValues.blocked.present ||
|
if (updatedValues.blocked.present ||
|
||||||
updatedValues.displayName.present ||
|
updatedValues.displayName.present ||
|
||||||
updatedValues.nickName.present ||
|
updatedValues.nickName.present ||
|
||||||
|
|
@ -84,7 +83,8 @@ class ContactsDao extends DatabaseAccessor<TwonlyDB> with _$ContactsDaoMixin {
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<List<Contact>> watchNotAcceptedContacts() {
|
Stream<List<Contact>> watchNotAcceptedContacts() {
|
||||||
return (select(contacts)..where(
|
return (select(contacts)
|
||||||
|
..where(
|
||||||
(t) =>
|
(t) =>
|
||||||
t.accepted.equals(false) &
|
t.accepted.equals(false) &
|
||||||
t.blocked.equals(false) &
|
t.blocked.equals(false) &
|
||||||
|
|
@ -94,9 +94,8 @@ class ContactsDao extends DatabaseAccessor<TwonlyDB> with _$ContactsDaoMixin {
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<Contact?> watchContact(int userid) {
|
Stream<Contact?> watchContact(int userid) {
|
||||||
return (select(
|
return (select(contacts)..where((t) => t.userId.equals(userid)))
|
||||||
contacts,
|
.watchSingleOrNull();
|
||||||
)..where((t) => t.userId.equals(userid))).watchSingleOrNull();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Contact>> getAllContacts() {
|
Future<List<Contact>> getAllContacts() {
|
||||||
|
|
@ -125,7 +124,8 @@ class ContactsDao extends DatabaseAccessor<TwonlyDB> with _$ContactsDaoMixin {
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<List<Contact>> watchAllAcceptedContacts() {
|
Stream<List<Contact>> watchAllAcceptedContacts() {
|
||||||
return (select(contacts)..where(
|
return (select(contacts)
|
||||||
|
..where(
|
||||||
(t) =>
|
(t) =>
|
||||||
t.blocked.equals(false) &
|
t.blocked.equals(false) &
|
||||||
t.accepted.equals(true) &
|
t.accepted.equals(true) &
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
|
||||||
// ignore: matching_super_parameters
|
// ignore: matching_super_parameters
|
||||||
MediaFilesDao(super.db);
|
MediaFilesDao(super.db);
|
||||||
|
|
||||||
Future<MediaFile?> insertOrUpdateMedia(MediaFilesCompanion mediaFile) async {
|
Future<MediaFile?> insertMedia(MediaFilesCompanion mediaFile) async {
|
||||||
try {
|
try {
|
||||||
var insertMediaFile = mediaFile;
|
var insertMediaFile = mediaFile;
|
||||||
|
|
||||||
|
|
@ -24,13 +24,10 @@ class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final rowId = await into(
|
final rowId = await into(mediaFiles).insert(insertMediaFile);
|
||||||
mediaFiles,
|
|
||||||
).insertOnConflictUpdate(insertMediaFile);
|
|
||||||
|
|
||||||
return await (select(
|
return await (select(mediaFiles)..where((t) => t.rowId.equals(rowId)))
|
||||||
mediaFiles,
|
.getSingle();
|
||||||
)..where((t) => t.rowId.equals(rowId))).getSingle();
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Log.error('Could not insert media file: $e');
|
Log.error('Could not insert media file: $e');
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -38,7 +35,8 @@ class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteMediaFile(String mediaId) async {
|
Future<void> deleteMediaFile(String mediaId) async {
|
||||||
await (delete(mediaFiles)..where(
|
await (delete(mediaFiles)
|
||||||
|
..where(
|
||||||
(t) => t.mediaId.equals(mediaId),
|
(t) => t.mediaId.equals(mediaId),
|
||||||
))
|
))
|
||||||
.go();
|
.go();
|
||||||
|
|
@ -48,9 +46,8 @@ class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
|
||||||
String mediaId,
|
String mediaId,
|
||||||
MediaFilesCompanion updates,
|
MediaFilesCompanion updates,
|
||||||
) async {
|
) async {
|
||||||
await (update(
|
await (update(mediaFiles)..where((c) => c.mediaId.equals(mediaId)))
|
||||||
mediaFiles,
|
.write(updates);
|
||||||
)..where((c) => c.mediaId.equals(mediaId))).write(updates);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateAllMediaFiles(
|
Future<void> updateAllMediaFiles(
|
||||||
|
|
@ -60,15 +57,14 @@ class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<MediaFile?> getMediaFileById(String mediaId) async {
|
Future<MediaFile?> getMediaFileById(String mediaId) async {
|
||||||
return (select(
|
return (select(mediaFiles)..where((t) => t.mediaId.equals(mediaId)))
|
||||||
mediaFiles,
|
.getSingleOrNull();
|
||||||
)..where((t) => t.mediaId.equals(mediaId))).getSingleOrNull();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<MediaFile?> getDraftMediaFile() async {
|
Future<MediaFile?> getDraftMediaFile() async {
|
||||||
final medias = await (select(
|
final medias = await (select(mediaFiles)
|
||||||
mediaFiles,
|
..where((t) => t.isDraftMedia.equals(true)))
|
||||||
)..where((t) => t.isDraftMedia.equals(true))).get();
|
.get();
|
||||||
if (medias.isEmpty) {
|
if (medias.isEmpty) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -76,13 +72,13 @@ class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<MediaFile?> watchMedia(String mediaId) {
|
Stream<MediaFile?> watchMedia(String mediaId) {
|
||||||
return (select(
|
return (select(mediaFiles)..where((t) => t.mediaId.equals(mediaId)))
|
||||||
mediaFiles,
|
.watchSingleOrNull();
|
||||||
)..where((t) => t.mediaId.equals(mediaId))).watchSingleOrNull();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> resetPendingDownloadState() async {
|
Future<void> resetPendingDownloadState() async {
|
||||||
await (update(mediaFiles)..where(
|
await (update(mediaFiles)
|
||||||
|
..where(
|
||||||
(c) => c.downloadState.equals(
|
(c) => c.downloadState.equals(
|
||||||
DownloadState.downloading.name,
|
DownloadState.downloading.name,
|
||||||
),
|
),
|
||||||
|
|
@ -95,7 +91,8 @@ class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<MediaFile>> getAllMediaFilesPendingDownload() async {
|
Future<List<MediaFile>> getAllMediaFilesPendingDownload() async {
|
||||||
return (select(mediaFiles)..where(
|
return (select(mediaFiles)
|
||||||
|
..where(
|
||||||
(t) =>
|
(t) =>
|
||||||
t.downloadState.equals(DownloadState.pending.name) |
|
t.downloadState.equals(DownloadState.pending.name) |
|
||||||
t.downloadState.equals(DownloadState.downloading.name),
|
t.downloadState.equals(DownloadState.downloading.name),
|
||||||
|
|
@ -104,23 +101,25 @@ class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<MediaFile>> getAllMediaFilesReuploadRequested() async {
|
Future<List<MediaFile>> getAllMediaFilesReuploadRequested() async {
|
||||||
return (select(mediaFiles)..where(
|
return (select(mediaFiles)
|
||||||
|
..where(
|
||||||
(t) => t.downloadState.equals(DownloadState.reuploadRequested.name),
|
(t) => t.downloadState.equals(DownloadState.reuploadRequested.name),
|
||||||
))
|
))
|
||||||
.get();
|
.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<MediaFile>> getAllNonHashedStoredMediaFiles() async {
|
Future<List<MediaFile>> getAllNonHashedStoredMediaFiles() async {
|
||||||
return (select(mediaFiles)..where(
|
return (select(mediaFiles)
|
||||||
|
..where(
|
||||||
(t) => t.stored.equals(true) & t.storedFileHash.isNull(),
|
(t) => t.stored.equals(true) & t.storedFileHash.isNull(),
|
||||||
))
|
))
|
||||||
.get();
|
.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<MediaFile>> getAllMediaFilesPendingUpload() async {
|
Future<List<MediaFile>> getAllMediaFilesPendingUpload() async {
|
||||||
return (select(mediaFiles)..where(
|
return (select(mediaFiles)
|
||||||
(t) =>
|
..where(
|
||||||
(t.uploadState.equals(UploadState.initialized.name) |
|
(t) => (t.uploadState.equals(UploadState.initialized.name) |
|
||||||
t.uploadState.equals(UploadState.uploadLimitReached.name) |
|
t.uploadState.equals(UploadState.uploadLimitReached.name) |
|
||||||
t.uploadState.equals(UploadState.uploading.name) |
|
t.uploadState.equals(UploadState.uploading.name) |
|
||||||
t.uploadState.equals(UploadState.preprocessing.name)),
|
t.uploadState.equals(UploadState.preprocessing.name)),
|
||||||
|
|
@ -129,8 +128,8 @@ class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<List<MediaFile>> watchAllStoredMediaFiles() {
|
Stream<List<MediaFile>> watchAllStoredMediaFiles() {
|
||||||
final query =
|
final query = (select(mediaFiles)..where((t) => t.stored.equals(true)))
|
||||||
(select(mediaFiles)..where((t) => t.stored.equals(true))).join([])
|
.join([])
|
||||||
..groupBy([mediaFiles.storedFileHash]);
|
..groupBy([mediaFiles.storedFileHash]);
|
||||||
return query.map((row) => row.readTable(mediaFiles)).watch();
|
return query.map((row) => row.readTable(mediaFiles)).watch();
|
||||||
}
|
}
|
||||||
|
|
@ -143,7 +142,8 @@ class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateAllRetransmissionUploadingState() async {
|
Future<void> updateAllRetransmissionUploadingState() async {
|
||||||
await (update(mediaFiles)..where(
|
await (update(mediaFiles)
|
||||||
|
..where(
|
||||||
(t) =>
|
(t) =>
|
||||||
t.uploadState.equals(UploadState.uploading.name) &
|
t.uploadState.equals(UploadState.uploading.name) &
|
||||||
t.reuploadRequestedBy.isNotNull(),
|
t.reuploadRequestedBy.isNotNull(),
|
||||||
|
|
|
||||||
|
|
@ -318,7 +318,7 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final rowId = await into(messages).insertOnConflictUpdate(insertMessage);
|
final rowId = await into(messages).insert(insertMessage);
|
||||||
|
|
||||||
await twonlyDB.groupsDao.updateGroup(
|
await twonlyDB.groupsDao.updateGroup(
|
||||||
message.groupId.value,
|
message.groupId.value,
|
||||||
|
|
|
||||||
|
|
@ -26,14 +26,14 @@ class ReactionsDao extends DatabaseAccessor<TwonlyDB> with _$ReactionsDaoMixin {
|
||||||
Log.error('Did not update reaction as it is not an emoji!');
|
Log.error('Did not update reaction as it is not an emoji!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final msg = await twonlyDB.messagesDao
|
final msg =
|
||||||
.getMessageById(messageId)
|
await twonlyDB.messagesDao.getMessageById(messageId).getSingleOrNull();
|
||||||
.getSingleOrNull();
|
|
||||||
if (msg == null || msg.groupId != groupId) return;
|
if (msg == null || msg.groupId != groupId) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (remove) {
|
if (remove) {
|
||||||
await (delete(reactions)..where(
|
await (delete(reactions)
|
||||||
|
..where(
|
||||||
(t) =>
|
(t) =>
|
||||||
t.senderId.equals(contactId) &
|
t.senderId.equals(contactId) &
|
||||||
t.messageId.equals(messageId) &
|
t.messageId.equals(messageId) &
|
||||||
|
|
@ -63,13 +63,13 @@ class ReactionsDao extends DatabaseAccessor<TwonlyDB> with _$ReactionsDaoMixin {
|
||||||
Log.error('Did not update reaction as it is not an emoji!');
|
Log.error('Did not update reaction as it is not an emoji!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final msg = await twonlyDB.messagesDao
|
final msg =
|
||||||
.getMessageById(messageId)
|
await twonlyDB.messagesDao.getMessageById(messageId).getSingleOrNull();
|
||||||
.getSingleOrNull();
|
|
||||||
if (msg == null) return;
|
if (msg == null) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await (delete(reactions)..where(
|
await (delete(reactions)
|
||||||
|
..where(
|
||||||
(t) =>
|
(t) =>
|
||||||
t.senderId.isNull() &
|
t.senderId.isNull() &
|
||||||
t.messageId.equals(messageId) &
|
t.messageId.equals(messageId) &
|
||||||
|
|
@ -98,8 +98,9 @@ class ReactionsDao extends DatabaseAccessor<TwonlyDB> with _$ReactionsDaoMixin {
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<Reaction?> watchLastReactions(String groupId) {
|
Stream<Reaction?> watchLastReactions(String groupId) {
|
||||||
final query =
|
final query = (select(reactions)
|
||||||
(select(reactions)).join(
|
..orderBy([(t) => OrderingTerm.desc(t.createdAt)]))
|
||||||
|
.join(
|
||||||
[
|
[
|
||||||
innerJoin(
|
innerJoin(
|
||||||
messages,
|
messages,
|
||||||
|
|
@ -109,7 +110,7 @@ class ReactionsDao extends DatabaseAccessor<TwonlyDB> with _$ReactionsDaoMixin {
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
..where(messages.groupId.equals(groupId))
|
..where(messages.groupId.equals(groupId))
|
||||||
..orderBy([OrderingTerm.desc(messages.createdAt)])
|
// ..orderBy([(t) => OrderingTerm.asc(t.createdAt)]))
|
||||||
..limit(1);
|
..limit(1);
|
||||||
return query.map((row) => row.readTable(reactions)).watchSingleOrNull();
|
return query.map((row) => row.readTable(reactions)).watchSingleOrNull();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
|
||||||
if (receipt == null) return;
|
if (receipt == null) return;
|
||||||
|
|
||||||
if (receipt.messageId != null) {
|
if (receipt.messageId != null) {
|
||||||
await into(messageActions).insertOnConflictUpdate(
|
await into(messageActions).insert(
|
||||||
MessageActionsCompanion(
|
MessageActionsCompanion(
|
||||||
messageId: Value(receipt.messageId!),
|
messageId: Value(receipt.messageId!),
|
||||||
contactId: Value(fromUserId),
|
contactId: Value(fromUserId),
|
||||||
|
|
@ -54,13 +54,6 @@ class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
|
||||||
.go();
|
.go();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteReceiptForUser(int contactId) async {
|
|
||||||
await (delete(receipts)..where(
|
|
||||||
(t) => t.contactId.equals(contactId),
|
|
||||||
))
|
|
||||||
.go();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> purgeReceivedReceipts() async {
|
Future<void> purgeReceivedReceipts() async {
|
||||||
await (delete(receivedReceipts)..where(
|
await (delete(receivedReceipts)..where(
|
||||||
(t) => (t.createdAt.isSmallerThanValue(
|
(t) => (t.createdAt.isSmallerThanValue(
|
||||||
|
|
@ -113,16 +106,6 @@ class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Receipt>> getReceiptsByContactAndMessageId(
|
|
||||||
int contactId,
|
|
||||||
String messageId,
|
|
||||||
) async {
|
|
||||||
return (select(receipts)..where(
|
|
||||||
(t) => t.contactId.equals(contactId) & t.messageId.equals(messageId),
|
|
||||||
))
|
|
||||||
.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<Receipt>> getReceiptsForRetransmission() async {
|
Future<List<Receipt>> getReceiptsForRetransmission() async {
|
||||||
final markedRetriesTime = clock.now().subtract(
|
final markedRetriesTime = clock.now().subtract(
|
||||||
const Duration(
|
const Duration(
|
||||||
|
|
@ -142,38 +125,10 @@ class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
|
||||||
.get();
|
.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Receipt>> getReceiptsForMediaRetransmissions() async {
|
|
||||||
final markedRetriesTime = clock.now().subtract(
|
|
||||||
const Duration(
|
|
||||||
// give the server time to transmit all messages to the client
|
|
||||||
seconds: 20,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return (select(receipts)..where(
|
|
||||||
(t) =>
|
|
||||||
(t.markForRetry.isSmallerThanValue(markedRetriesTime) |
|
|
||||||
t.markForRetryAfterAccepted.isSmallerThanValue(
|
|
||||||
markedRetriesTime,
|
|
||||||
)) &
|
|
||||||
t.willBeRetriedByMediaUpload.equals(true),
|
|
||||||
))
|
|
||||||
.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream<List<Receipt>> watchAll() {
|
Stream<List<Receipt>> watchAll() {
|
||||||
return select(receipts).watch();
|
return select(receipts).watch();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> getReceiptCountForContact(int contactId) {
|
|
||||||
final countExp = countAll();
|
|
||||||
|
|
||||||
final query = selectOnly(receipts)
|
|
||||||
..addColumns([countExp])
|
|
||||||
..where(receipts.contactId.equals(contactId));
|
|
||||||
|
|
||||||
return query.map((row) => row.read(countExp)!).getSingle();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> updateReceipt(
|
Future<void> updateReceipt(
|
||||||
String receiptId,
|
String receiptId,
|
||||||
ReceiptsCompanion updates,
|
ReceiptsCompanion updates,
|
||||||
|
|
@ -183,19 +138,6 @@ class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
|
||||||
)..where((c) => c.receiptId.equals(receiptId))).write(updates);
|
)..where((c) => c.receiptId.equals(receiptId))).write(updates);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateReceiptByContactAndMessageId(
|
|
||||||
int contactId,
|
|
||||||
String messageId,
|
|
||||||
ReceiptsCompanion updates,
|
|
||||||
) async {
|
|
||||||
await (update(
|
|
||||||
receipts,
|
|
||||||
)..where(
|
|
||||||
(c) => c.contactId.equals(contactId) & c.messageId.equals(messageId),
|
|
||||||
))
|
|
||||||
.write(updates);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> updateReceiptWidthUserId(
|
Future<void> updateReceiptWidthUserId(
|
||||||
int fromUserId,
|
int fromUserId,
|
||||||
String receiptId,
|
String receiptId,
|
||||||
|
|
@ -209,7 +151,9 @@ class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
|
||||||
|
|
||||||
Future<void> markMessagesForRetry(int contactId) async {
|
Future<void> markMessagesForRetry(int contactId) async {
|
||||||
await (update(receipts)..where(
|
await (update(receipts)..where(
|
||||||
(c) => c.contactId.equals(contactId) & c.markForRetry.isNull(),
|
(c) =>
|
||||||
|
c.contactId.equals(contactId) &
|
||||||
|
c.willBeRetriedByMediaUpload.equals(false),
|
||||||
))
|
))
|
||||||
.write(
|
.write(
|
||||||
ReceiptsCompanion(
|
ReceiptsCompanion(
|
||||||
|
|
|
||||||
|
|
@ -19,13 +19,9 @@ class SignalDaoManager {
|
||||||
$$ContactsTableTableManager(_db.attachedDatabase, _db.contacts);
|
$$ContactsTableTableManager(_db.attachedDatabase, _db.contacts);
|
||||||
$$SignalContactPreKeysTableTableManager get signalContactPreKeys =>
|
$$SignalContactPreKeysTableTableManager get signalContactPreKeys =>
|
||||||
$$SignalContactPreKeysTableTableManager(
|
$$SignalContactPreKeysTableTableManager(
|
||||||
_db.attachedDatabase,
|
_db.attachedDatabase, _db.signalContactPreKeys);
|
||||||
_db.signalContactPreKeys,
|
|
||||||
);
|
|
||||||
$$SignalContactSignedPreKeysTableTableManager
|
$$SignalContactSignedPreKeysTableTableManager
|
||||||
get signalContactSignedPreKeys =>
|
get signalContactSignedPreKeys =>
|
||||||
$$SignalContactSignedPreKeysTableTableManager(
|
$$SignalContactSignedPreKeysTableTableManager(
|
||||||
_db.attachedDatabase,
|
_db.attachedDatabase, _db.signalContactSignedPreKeys);
|
||||||
_db.signalContactSignedPreKeys,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -12,8 +12,8 @@ class ConnectIdentityKeyStore extends IdentityKeyStore {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<IdentityKey?> getIdentity(SignalProtocolAddress address) async {
|
Future<IdentityKey?> getIdentity(SignalProtocolAddress address) async {
|
||||||
final identity =
|
final identity = await (twonlyDB.select(twonlyDB.signalIdentityKeyStores)
|
||||||
await (twonlyDB.select(twonlyDB.signalIdentityKeyStores)..where(
|
..where(
|
||||||
(t) =>
|
(t) =>
|
||||||
t.deviceId.equals(address.getDeviceId()) &
|
t.deviceId.equals(address.getDeviceId()) &
|
||||||
t.name.equals(address.getName()),
|
t.name.equals(address.getName()),
|
||||||
|
|
@ -40,10 +40,8 @@ class ConnectIdentityKeyStore extends IdentityKeyStore {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return trusted == null ||
|
return trusted == null ||
|
||||||
const ListEquality<dynamic>().equals(
|
const ListEquality<dynamic>()
|
||||||
trusted.serialize(),
|
.equals(trusted.serialize(), identityKey.serialize());
|
||||||
identityKey.serialize(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -55,9 +53,7 @@ class ConnectIdentityKeyStore extends IdentityKeyStore {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (await getIdentity(address) == null) {
|
if (await getIdentity(address) == null) {
|
||||||
await twonlyDB
|
await twonlyDB.into(twonlyDB.signalIdentityKeyStores).insert(
|
||||||
.into(twonlyDB.signalIdentityKeyStores)
|
|
||||||
.insert(
|
|
||||||
SignalIdentityKeyStoresCompanion(
|
SignalIdentityKeyStoresCompanion(
|
||||||
deviceId: Value(address.getDeviceId()),
|
deviceId: Value(address.getDeviceId()),
|
||||||
name: Value(address.getName()),
|
name: Value(address.getName()),
|
||||||
|
|
@ -65,7 +61,8 @@ class ConnectIdentityKeyStore extends IdentityKeyStore {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
await (twonlyDB.update(twonlyDB.signalIdentityKeyStores)..where(
|
await (twonlyDB.update(twonlyDB.signalIdentityKeyStores)
|
||||||
|
..where(
|
||||||
(t) =>
|
(t) =>
|
||||||
t.deviceId.equals(address.getDeviceId()) &
|
t.deviceId.equals(address.getDeviceId()) &
|
||||||
t.name.equals(address.getName()),
|
t.name.equals(address.getName()),
|
||||||
|
|
|
||||||
|
|
@ -7,17 +7,17 @@ import 'package:twonly/src/utils/log.dart';
|
||||||
class ConnectPreKeyStore extends PreKeyStore {
|
class ConnectPreKeyStore extends PreKeyStore {
|
||||||
@override
|
@override
|
||||||
Future<bool> containsPreKey(int preKeyId) async {
|
Future<bool> containsPreKey(int preKeyId) async {
|
||||||
final preKeyRecord = await (twonlyDB.select(
|
final preKeyRecord = await (twonlyDB.select(twonlyDB.signalPreKeyStores)
|
||||||
twonlyDB.signalPreKeyStores,
|
..where((tbl) => tbl.preKeyId.equals(preKeyId)))
|
||||||
)..where((tbl) => tbl.preKeyId.equals(preKeyId))).get();
|
.get();
|
||||||
return preKeyRecord.isNotEmpty;
|
return preKeyRecord.isNotEmpty;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<PreKeyRecord> loadPreKey(int preKeyId) async {
|
Future<PreKeyRecord> loadPreKey(int preKeyId) async {
|
||||||
final preKeyRecord = await (twonlyDB.select(
|
final preKeyRecord = await (twonlyDB.select(twonlyDB.signalPreKeyStores)
|
||||||
twonlyDB.signalPreKeyStores,
|
..where((tbl) => tbl.preKeyId.equals(preKeyId)))
|
||||||
)..where((tbl) => tbl.preKeyId.equals(preKeyId))).get();
|
.get();
|
||||||
if (preKeyRecord.isEmpty) {
|
if (preKeyRecord.isEmpty) {
|
||||||
throw InvalidKeyIdException(
|
throw InvalidKeyIdException(
|
||||||
'[PREKEY] No such preKey record!',
|
'[PREKEY] No such preKey record!',
|
||||||
|
|
@ -29,9 +29,9 @@ class ConnectPreKeyStore extends PreKeyStore {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> removePreKey(int preKeyId) async {
|
Future<void> removePreKey(int preKeyId) async {
|
||||||
await (twonlyDB.delete(
|
await (twonlyDB.delete(twonlyDB.signalPreKeyStores)
|
||||||
twonlyDB.signalPreKeyStores,
|
..where((tbl) => tbl.preKeyId.equals(preKeyId)))
|
||||||
)..where((tbl) => tbl.preKeyId.equals(preKeyId))).go();
|
.go();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,7 @@ import 'package:twonly/src/database/twonly.db.dart';
|
||||||
class ConnectSenderKeyStore extends SenderKeyStore {
|
class ConnectSenderKeyStore extends SenderKeyStore {
|
||||||
@override
|
@override
|
||||||
Future<SenderKeyRecord> loadSenderKey(SenderKeyName senderKeyName) async {
|
Future<SenderKeyRecord> loadSenderKey(SenderKeyName senderKeyName) async {
|
||||||
final identity =
|
final identity = await (twonlyDB.select(twonlyDB.signalSenderKeyStores)
|
||||||
await (twonlyDB.select(twonlyDB.signalSenderKeyStores)
|
|
||||||
..where((t) => t.senderKeyName.equals(senderKeyName.serialize())))
|
..where((t) => t.senderKeyName.equals(senderKeyName.serialize())))
|
||||||
.getSingleOrNull();
|
.getSingleOrNull();
|
||||||
if (identity == null) {
|
if (identity == null) {
|
||||||
|
|
@ -23,9 +22,7 @@ class ConnectSenderKeyStore extends SenderKeyStore {
|
||||||
SenderKeyName senderKeyName,
|
SenderKeyName senderKeyName,
|
||||||
SenderKeyRecord record,
|
SenderKeyRecord record,
|
||||||
) async {
|
) async {
|
||||||
await twonlyDB
|
await twonlyDB.into(twonlyDB.signalSenderKeyStores).insert(
|
||||||
.into(twonlyDB.signalSenderKeyStores)
|
|
||||||
.insert(
|
|
||||||
SignalSenderKeyStoresCompanion(
|
SignalSenderKeyStoresCompanion(
|
||||||
senderKey: Value(record.serialize()),
|
senderKey: Value(record.serialize()),
|
||||||
senderKeyName: Value(senderKeyName.serialize()),
|
senderKeyName: Value(senderKeyName.serialize()),
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ import 'package:twonly/src/database/twonly.db.dart';
|
||||||
class ConnectSessionStore extends SessionStore {
|
class ConnectSessionStore extends SessionStore {
|
||||||
@override
|
@override
|
||||||
Future<bool> containsSession(SignalProtocolAddress address) async {
|
Future<bool> containsSession(SignalProtocolAddress address) async {
|
||||||
final sessions =
|
final sessions = await (twonlyDB.select(twonlyDB.signalSessionStores)
|
||||||
await (twonlyDB.select(twonlyDB.signalSessionStores)..where(
|
..where(
|
||||||
(tbl) =>
|
(tbl) =>
|
||||||
tbl.deviceId.equals(address.getDeviceId()) &
|
tbl.deviceId.equals(address.getDeviceId()) &
|
||||||
tbl.name.equals(address.getName()),
|
tbl.name.equals(address.getName()),
|
||||||
|
|
@ -18,14 +18,15 @@ class ConnectSessionStore extends SessionStore {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> deleteAllSessions(String name) async {
|
Future<void> deleteAllSessions(String name) async {
|
||||||
await (twonlyDB.delete(
|
await (twonlyDB.delete(twonlyDB.signalSessionStores)
|
||||||
twonlyDB.signalSessionStores,
|
..where((tbl) => tbl.name.equals(name)))
|
||||||
)..where((tbl) => tbl.name.equals(name))).go();
|
.go();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> deleteSession(SignalProtocolAddress address) async {
|
Future<void> deleteSession(SignalProtocolAddress address) async {
|
||||||
await (twonlyDB.delete(twonlyDB.signalSessionStores)..where(
|
await (twonlyDB.delete(twonlyDB.signalSessionStores)
|
||||||
|
..where(
|
||||||
(tbl) =>
|
(tbl) =>
|
||||||
tbl.deviceId.equals(address.getDeviceId()) &
|
tbl.deviceId.equals(address.getDeviceId()) &
|
||||||
tbl.name.equals(address.getName()),
|
tbl.name.equals(address.getName()),
|
||||||
|
|
@ -35,8 +36,8 @@ class ConnectSessionStore extends SessionStore {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<int>> getSubDeviceSessions(String name) async {
|
Future<List<int>> getSubDeviceSessions(String name) async {
|
||||||
final deviceIds =
|
final deviceIds = await (twonlyDB.select(twonlyDB.signalSessionStores)
|
||||||
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();
|
||||||
|
|
@ -45,8 +46,8 @@ class ConnectSessionStore extends SessionStore {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<SessionRecord> loadSession(SignalProtocolAddress address) async {
|
Future<SessionRecord> loadSession(SignalProtocolAddress address) async {
|
||||||
final dbSession =
|
final dbSession = await (twonlyDB.select(twonlyDB.signalSessionStores)
|
||||||
await (twonlyDB.select(twonlyDB.signalSessionStores)..where(
|
..where(
|
||||||
(tbl) =>
|
(tbl) =>
|
||||||
tbl.deviceId.equals(address.getDeviceId()) &
|
tbl.deviceId.equals(address.getDeviceId()) &
|
||||||
tbl.name.equals(address.getName()),
|
tbl.name.equals(address.getName()),
|
||||||
|
|
@ -76,7 +77,8 @@ class ConnectSessionStore extends SessionStore {
|
||||||
.into(twonlyDB.signalSessionStores)
|
.into(twonlyDB.signalSessionStores)
|
||||||
.insert(sessionCompanion);
|
.insert(sessionCompanion);
|
||||||
} else {
|
} else {
|
||||||
await (twonlyDB.update(twonlyDB.signalSessionStores)..where(
|
await (twonlyDB.update(twonlyDB.signalSessionStores)
|
||||||
|
..where(
|
||||||
(tbl) =>
|
(tbl) =>
|
||||||
tbl.deviceId.equals(address.getDeviceId()) &
|
tbl.deviceId.equals(address.getDeviceId()) &
|
||||||
tbl.name.equals(address.getName()),
|
tbl.name.equals(address.getName()),
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,8 @@ class ConnectSignalProtocolStore implements SignalProtocolStore {
|
||||||
IdentityKeyPair identityKeyPair,
|
IdentityKeyPair identityKeyPair,
|
||||||
int registrationId,
|
int registrationId,
|
||||||
) {
|
) {
|
||||||
_identityKeyStore = ConnectIdentityKeyStore(
|
_identityKeyStore =
|
||||||
identityKeyPair,
|
ConnectIdentityKeyStore(identityKeyPair, registrationId);
|
||||||
registrationId,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final preKeyStore = ConnectPreKeyStore();
|
final preKeyStore = ConnectPreKeyStore();
|
||||||
|
|
@ -33,7 +31,8 @@ class ConnectSignalProtocolStore implements SignalProtocolStore {
|
||||||
Future<bool> saveIdentity(
|
Future<bool> saveIdentity(
|
||||||
SignalProtocolAddress address,
|
SignalProtocolAddress address,
|
||||||
IdentityKey? identityKey,
|
IdentityKey? identityKey,
|
||||||
) async => _identityKeyStore.saveIdentity(address, identityKey);
|
) async =>
|
||||||
|
_identityKeyStore.saveIdentity(address, identityKey);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> isTrustedIdentity(
|
Future<bool> isTrustedIdentity(
|
||||||
|
|
|
||||||
|
|
@ -30,9 +30,8 @@ class Groups extends Table {
|
||||||
BoolColumn get alsoBestFriend =>
|
BoolColumn get alsoBestFriend =>
|
||||||
boolean().withDefault(const Constant(false))();
|
boolean().withDefault(const Constant(false))();
|
||||||
|
|
||||||
IntColumn get deleteMessagesAfterMilliseconds => integer().withDefault(
|
IntColumn get deleteMessagesAfterMilliseconds => integer()
|
||||||
const Constant(defaultDeleteMessagesAfterMilliseconds),
|
.withDefault(const Constant(defaultDeleteMessagesAfterMilliseconds))();
|
||||||
)();
|
|
||||||
|
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
||||||
|
|
||||||
|
|
@ -64,9 +63,6 @@ class GroupMembers extends Table {
|
||||||
TextColumn get memberState => textEnum<MemberState>().nullable()();
|
TextColumn get memberState => textEnum<MemberState>().nullable()();
|
||||||
BlobColumn get groupPublicKey => blob().nullable()();
|
BlobColumn get groupPublicKey => blob().nullable()();
|
||||||
|
|
||||||
DateTimeColumn get lastChatOpened => dateTime().nullable()();
|
|
||||||
DateTimeColumn get lastTypeIndicator => dateTime().nullable()();
|
|
||||||
|
|
||||||
DateTimeColumn get lastMessage => dateTime().nullable()();
|
DateTimeColumn get lastMessage => dateTime().nullable()();
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ enum DownloadState {
|
||||||
downloading,
|
downloading,
|
||||||
downloaded,
|
downloaded,
|
||||||
ready,
|
ready,
|
||||||
reuploadRequested,
|
reuploadRequested
|
||||||
}
|
}
|
||||||
|
|
||||||
@DataClassName('MediaFile')
|
@DataClassName('MediaFile')
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,9 @@ class Messages extends Table {
|
||||||
TextColumn get type => text()();
|
TextColumn get type => text()();
|
||||||
|
|
||||||
TextColumn get content => text().nullable()();
|
TextColumn get content => text().nullable()();
|
||||||
TextColumn get mediaId => text().nullable().references(
|
TextColumn get mediaId => text()
|
||||||
MediaFiles,
|
.nullable()
|
||||||
#mediaId,
|
.references(MediaFiles, #mediaId, onDelete: KeyAction.setNull)();
|
||||||
onDelete: KeyAction.setNull,
|
|
||||||
)();
|
|
||||||
|
|
||||||
BlobColumn get additionalMessageData => blob().nullable()();
|
BlobColumn get additionalMessageData => blob().nullable()();
|
||||||
|
|
||||||
|
|
@ -77,11 +75,9 @@ class MessageHistories extends Table {
|
||||||
TextColumn get messageId =>
|
TextColumn get messageId =>
|
||||||
text().references(Messages, #messageId, onDelete: KeyAction.cascade)();
|
text().references(Messages, #messageId, onDelete: KeyAction.cascade)();
|
||||||
|
|
||||||
IntColumn get contactId => integer().nullable().references(
|
IntColumn get contactId => integer()
|
||||||
Contacts,
|
.nullable()
|
||||||
#userId,
|
.references(Contacts, #userId, onDelete: KeyAction.cascade)();
|
||||||
onDelete: KeyAction.cascade,
|
|
||||||
)();
|
|
||||||
|
|
||||||
TextColumn get content => text().nullable()();
|
TextColumn get content => text().nullable()();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,9 @@ class Reactions extends Table {
|
||||||
TextColumn get emoji => text()();
|
TextColumn get emoji => text()();
|
||||||
|
|
||||||
// in case senderId is null, it was send by user itself
|
// in case senderId is null, it was send by user itself
|
||||||
IntColumn get senderId => integer().nullable().references(
|
IntColumn get senderId => integer()
|
||||||
Contacts,
|
.nullable()
|
||||||
#userId,
|
.references(Contacts, #userId, onDelete: KeyAction.cascade)();
|
||||||
onDelete: KeyAction.cascade,
|
|
||||||
)();
|
|
||||||
|
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,9 @@ class Receipts extends Table {
|
||||||
integer().references(Contacts, #userId, onDelete: KeyAction.cascade)();
|
integer().references(Contacts, #userId, onDelete: KeyAction.cascade)();
|
||||||
|
|
||||||
// in case a message is deleted, it should be also deleted from the receipts table
|
// in case a message is deleted, it should be also deleted from the receipts table
|
||||||
TextColumn get messageId => text().nullable().references(
|
TextColumn get messageId => text()
|
||||||
Messages,
|
.nullable()
|
||||||
#messageId,
|
.references(Messages, #messageId, onDelete: KeyAction.cascade)();
|
||||||
onDelete: KeyAction.cascade,
|
|
||||||
)();
|
|
||||||
|
|
||||||
/// This is the protobuf 'Message'
|
/// This is the protobuf 'Message'
|
||||||
BlobColumn get message => blob()();
|
BlobColumn get message => blob()();
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ class TwonlyDB extends _$TwonlyDB {
|
||||||
TwonlyDB.forTesting(DatabaseConnection super.connection);
|
TwonlyDB.forTesting(DatabaseConnection super.connection);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get schemaVersion => 11;
|
int get schemaVersion => 10;
|
||||||
|
|
||||||
static QueryExecutor _openConnection() {
|
static QueryExecutor _openConnection() {
|
||||||
return driftDatabase(
|
return driftDatabase(
|
||||||
|
|
@ -143,16 +143,6 @@ class TwonlyDB extends _$TwonlyDB {
|
||||||
schema.receipts.willBeRetriedByMediaUpload,
|
schema.receipts.willBeRetriedByMediaUpload,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
from10To11: (m, schema) async {
|
|
||||||
await m.addColumn(
|
|
||||||
schema.groupMembers,
|
|
||||||
schema.groupMembers.lastChatOpened,
|
|
||||||
);
|
|
||||||
await m.addColumn(
|
|
||||||
schema.groupMembers,
|
|
||||||
schema.groupMembers.lastTypeIndicator,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
)(m, from, to);
|
)(m, from, to);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -5198,30 +5198,6 @@ class $GroupMembersTable extends GroupMembers
|
||||||
type: DriftSqlType.blob,
|
type: DriftSqlType.blob,
|
||||||
requiredDuringInsert: false,
|
requiredDuringInsert: false,
|
||||||
);
|
);
|
||||||
static const VerificationMeta _lastChatOpenedMeta = const VerificationMeta(
|
|
||||||
'lastChatOpened',
|
|
||||||
);
|
|
||||||
@override
|
|
||||||
late final GeneratedColumn<DateTime> lastChatOpened =
|
|
||||||
GeneratedColumn<DateTime>(
|
|
||||||
'last_chat_opened',
|
|
||||||
aliasedName,
|
|
||||||
true,
|
|
||||||
type: DriftSqlType.dateTime,
|
|
||||||
requiredDuringInsert: false,
|
|
||||||
);
|
|
||||||
static const VerificationMeta _lastTypeIndicatorMeta = const VerificationMeta(
|
|
||||||
'lastTypeIndicator',
|
|
||||||
);
|
|
||||||
@override
|
|
||||||
late final GeneratedColumn<DateTime> lastTypeIndicator =
|
|
||||||
GeneratedColumn<DateTime>(
|
|
||||||
'last_type_indicator',
|
|
||||||
aliasedName,
|
|
||||||
true,
|
|
||||||
type: DriftSqlType.dateTime,
|
|
||||||
requiredDuringInsert: false,
|
|
||||||
);
|
|
||||||
static const VerificationMeta _lastMessageMeta = const VerificationMeta(
|
static const VerificationMeta _lastMessageMeta = const VerificationMeta(
|
||||||
'lastMessage',
|
'lastMessage',
|
||||||
);
|
);
|
||||||
|
|
@ -5251,8 +5227,6 @@ class $GroupMembersTable extends GroupMembers
|
||||||
contactId,
|
contactId,
|
||||||
memberState,
|
memberState,
|
||||||
groupPublicKey,
|
groupPublicKey,
|
||||||
lastChatOpened,
|
|
||||||
lastTypeIndicator,
|
|
||||||
lastMessage,
|
lastMessage,
|
||||||
createdAt,
|
createdAt,
|
||||||
];
|
];
|
||||||
|
|
@ -5293,24 +5267,6 @@ class $GroupMembersTable extends GroupMembers
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (data.containsKey('last_chat_opened')) {
|
|
||||||
context.handle(
|
|
||||||
_lastChatOpenedMeta,
|
|
||||||
lastChatOpened.isAcceptableOrUnknown(
|
|
||||||
data['last_chat_opened']!,
|
|
||||||
_lastChatOpenedMeta,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (data.containsKey('last_type_indicator')) {
|
|
||||||
context.handle(
|
|
||||||
_lastTypeIndicatorMeta,
|
|
||||||
lastTypeIndicator.isAcceptableOrUnknown(
|
|
||||||
data['last_type_indicator']!,
|
|
||||||
_lastTypeIndicatorMeta,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (data.containsKey('last_message')) {
|
if (data.containsKey('last_message')) {
|
||||||
context.handle(
|
context.handle(
|
||||||
_lastMessageMeta,
|
_lastMessageMeta,
|
||||||
|
|
@ -5353,14 +5309,6 @@ class $GroupMembersTable extends GroupMembers
|
||||||
DriftSqlType.blob,
|
DriftSqlType.blob,
|
||||||
data['${effectivePrefix}group_public_key'],
|
data['${effectivePrefix}group_public_key'],
|
||||||
),
|
),
|
||||||
lastChatOpened: attachedDatabase.typeMapping.read(
|
|
||||||
DriftSqlType.dateTime,
|
|
||||||
data['${effectivePrefix}last_chat_opened'],
|
|
||||||
),
|
|
||||||
lastTypeIndicator: attachedDatabase.typeMapping.read(
|
|
||||||
DriftSqlType.dateTime,
|
|
||||||
data['${effectivePrefix}last_type_indicator'],
|
|
||||||
),
|
|
||||||
lastMessage: attachedDatabase.typeMapping.read(
|
lastMessage: attachedDatabase.typeMapping.read(
|
||||||
DriftSqlType.dateTime,
|
DriftSqlType.dateTime,
|
||||||
data['${effectivePrefix}last_message'],
|
data['${effectivePrefix}last_message'],
|
||||||
|
|
@ -5388,8 +5336,6 @@ class GroupMember extends DataClass implements Insertable<GroupMember> {
|
||||||
final int contactId;
|
final int contactId;
|
||||||
final MemberState? memberState;
|
final MemberState? memberState;
|
||||||
final Uint8List? groupPublicKey;
|
final Uint8List? groupPublicKey;
|
||||||
final DateTime? lastChatOpened;
|
|
||||||
final DateTime? lastTypeIndicator;
|
|
||||||
final DateTime? lastMessage;
|
final DateTime? lastMessage;
|
||||||
final DateTime createdAt;
|
final DateTime createdAt;
|
||||||
const GroupMember({
|
const GroupMember({
|
||||||
|
|
@ -5397,8 +5343,6 @@ class GroupMember extends DataClass implements Insertable<GroupMember> {
|
||||||
required this.contactId,
|
required this.contactId,
|
||||||
this.memberState,
|
this.memberState,
|
||||||
this.groupPublicKey,
|
this.groupPublicKey,
|
||||||
this.lastChatOpened,
|
|
||||||
this.lastTypeIndicator,
|
|
||||||
this.lastMessage,
|
this.lastMessage,
|
||||||
required this.createdAt,
|
required this.createdAt,
|
||||||
});
|
});
|
||||||
|
|
@ -5415,12 +5359,6 @@ class GroupMember extends DataClass implements Insertable<GroupMember> {
|
||||||
if (!nullToAbsent || groupPublicKey != null) {
|
if (!nullToAbsent || groupPublicKey != null) {
|
||||||
map['group_public_key'] = Variable<Uint8List>(groupPublicKey);
|
map['group_public_key'] = Variable<Uint8List>(groupPublicKey);
|
||||||
}
|
}
|
||||||
if (!nullToAbsent || lastChatOpened != null) {
|
|
||||||
map['last_chat_opened'] = Variable<DateTime>(lastChatOpened);
|
|
||||||
}
|
|
||||||
if (!nullToAbsent || lastTypeIndicator != null) {
|
|
||||||
map['last_type_indicator'] = Variable<DateTime>(lastTypeIndicator);
|
|
||||||
}
|
|
||||||
if (!nullToAbsent || lastMessage != null) {
|
if (!nullToAbsent || lastMessage != null) {
|
||||||
map['last_message'] = Variable<DateTime>(lastMessage);
|
map['last_message'] = Variable<DateTime>(lastMessage);
|
||||||
}
|
}
|
||||||
|
|
@ -5438,12 +5376,6 @@ class GroupMember extends DataClass implements Insertable<GroupMember> {
|
||||||
groupPublicKey: groupPublicKey == null && nullToAbsent
|
groupPublicKey: groupPublicKey == null && nullToAbsent
|
||||||
? const Value.absent()
|
? const Value.absent()
|
||||||
: Value(groupPublicKey),
|
: Value(groupPublicKey),
|
||||||
lastChatOpened: lastChatOpened == null && nullToAbsent
|
|
||||||
? const Value.absent()
|
|
||||||
: Value(lastChatOpened),
|
|
||||||
lastTypeIndicator: lastTypeIndicator == null && nullToAbsent
|
|
||||||
? const Value.absent()
|
|
||||||
: Value(lastTypeIndicator),
|
|
||||||
lastMessage: lastMessage == null && nullToAbsent
|
lastMessage: lastMessage == null && nullToAbsent
|
||||||
? const Value.absent()
|
? const Value.absent()
|
||||||
: Value(lastMessage),
|
: Value(lastMessage),
|
||||||
|
|
@ -5463,10 +5395,6 @@ class GroupMember extends DataClass implements Insertable<GroupMember> {
|
||||||
serializer.fromJson<String?>(json['memberState']),
|
serializer.fromJson<String?>(json['memberState']),
|
||||||
),
|
),
|
||||||
groupPublicKey: serializer.fromJson<Uint8List?>(json['groupPublicKey']),
|
groupPublicKey: serializer.fromJson<Uint8List?>(json['groupPublicKey']),
|
||||||
lastChatOpened: serializer.fromJson<DateTime?>(json['lastChatOpened']),
|
|
||||||
lastTypeIndicator: serializer.fromJson<DateTime?>(
|
|
||||||
json['lastTypeIndicator'],
|
|
||||||
),
|
|
||||||
lastMessage: serializer.fromJson<DateTime?>(json['lastMessage']),
|
lastMessage: serializer.fromJson<DateTime?>(json['lastMessage']),
|
||||||
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
|
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
|
||||||
);
|
);
|
||||||
|
|
@ -5481,8 +5409,6 @@ class GroupMember extends DataClass implements Insertable<GroupMember> {
|
||||||
$GroupMembersTable.$convertermemberStaten.toJson(memberState),
|
$GroupMembersTable.$convertermemberStaten.toJson(memberState),
|
||||||
),
|
),
|
||||||
'groupPublicKey': serializer.toJson<Uint8List?>(groupPublicKey),
|
'groupPublicKey': serializer.toJson<Uint8List?>(groupPublicKey),
|
||||||
'lastChatOpened': serializer.toJson<DateTime?>(lastChatOpened),
|
|
||||||
'lastTypeIndicator': serializer.toJson<DateTime?>(lastTypeIndicator),
|
|
||||||
'lastMessage': serializer.toJson<DateTime?>(lastMessage),
|
'lastMessage': serializer.toJson<DateTime?>(lastMessage),
|
||||||
'createdAt': serializer.toJson<DateTime>(createdAt),
|
'createdAt': serializer.toJson<DateTime>(createdAt),
|
||||||
};
|
};
|
||||||
|
|
@ -5493,8 +5419,6 @@ class GroupMember extends DataClass implements Insertable<GroupMember> {
|
||||||
int? contactId,
|
int? contactId,
|
||||||
Value<MemberState?> memberState = const Value.absent(),
|
Value<MemberState?> memberState = const Value.absent(),
|
||||||
Value<Uint8List?> groupPublicKey = const Value.absent(),
|
Value<Uint8List?> groupPublicKey = const Value.absent(),
|
||||||
Value<DateTime?> lastChatOpened = const Value.absent(),
|
|
||||||
Value<DateTime?> lastTypeIndicator = const Value.absent(),
|
|
||||||
Value<DateTime?> lastMessage = const Value.absent(),
|
Value<DateTime?> lastMessage = const Value.absent(),
|
||||||
DateTime? createdAt,
|
DateTime? createdAt,
|
||||||
}) => GroupMember(
|
}) => GroupMember(
|
||||||
|
|
@ -5504,12 +5428,6 @@ class GroupMember extends DataClass implements Insertable<GroupMember> {
|
||||||
groupPublicKey: groupPublicKey.present
|
groupPublicKey: groupPublicKey.present
|
||||||
? groupPublicKey.value
|
? groupPublicKey.value
|
||||||
: this.groupPublicKey,
|
: this.groupPublicKey,
|
||||||
lastChatOpened: lastChatOpened.present
|
|
||||||
? lastChatOpened.value
|
|
||||||
: this.lastChatOpened,
|
|
||||||
lastTypeIndicator: lastTypeIndicator.present
|
|
||||||
? lastTypeIndicator.value
|
|
||||||
: this.lastTypeIndicator,
|
|
||||||
lastMessage: lastMessage.present ? lastMessage.value : this.lastMessage,
|
lastMessage: lastMessage.present ? lastMessage.value : this.lastMessage,
|
||||||
createdAt: createdAt ?? this.createdAt,
|
createdAt: createdAt ?? this.createdAt,
|
||||||
);
|
);
|
||||||
|
|
@ -5523,12 +5441,6 @@ class GroupMember extends DataClass implements Insertable<GroupMember> {
|
||||||
groupPublicKey: data.groupPublicKey.present
|
groupPublicKey: data.groupPublicKey.present
|
||||||
? data.groupPublicKey.value
|
? data.groupPublicKey.value
|
||||||
: this.groupPublicKey,
|
: this.groupPublicKey,
|
||||||
lastChatOpened: data.lastChatOpened.present
|
|
||||||
? data.lastChatOpened.value
|
|
||||||
: this.lastChatOpened,
|
|
||||||
lastTypeIndicator: data.lastTypeIndicator.present
|
|
||||||
? data.lastTypeIndicator.value
|
|
||||||
: this.lastTypeIndicator,
|
|
||||||
lastMessage: data.lastMessage.present
|
lastMessage: data.lastMessage.present
|
||||||
? data.lastMessage.value
|
? data.lastMessage.value
|
||||||
: this.lastMessage,
|
: this.lastMessage,
|
||||||
|
|
@ -5543,8 +5455,6 @@ class GroupMember extends DataClass implements Insertable<GroupMember> {
|
||||||
..write('contactId: $contactId, ')
|
..write('contactId: $contactId, ')
|
||||||
..write('memberState: $memberState, ')
|
..write('memberState: $memberState, ')
|
||||||
..write('groupPublicKey: $groupPublicKey, ')
|
..write('groupPublicKey: $groupPublicKey, ')
|
||||||
..write('lastChatOpened: $lastChatOpened, ')
|
|
||||||
..write('lastTypeIndicator: $lastTypeIndicator, ')
|
|
||||||
..write('lastMessage: $lastMessage, ')
|
..write('lastMessage: $lastMessage, ')
|
||||||
..write('createdAt: $createdAt')
|
..write('createdAt: $createdAt')
|
||||||
..write(')'))
|
..write(')'))
|
||||||
|
|
@ -5557,8 +5467,6 @@ class GroupMember extends DataClass implements Insertable<GroupMember> {
|
||||||
contactId,
|
contactId,
|
||||||
memberState,
|
memberState,
|
||||||
$driftBlobEquality.hash(groupPublicKey),
|
$driftBlobEquality.hash(groupPublicKey),
|
||||||
lastChatOpened,
|
|
||||||
lastTypeIndicator,
|
|
||||||
lastMessage,
|
lastMessage,
|
||||||
createdAt,
|
createdAt,
|
||||||
);
|
);
|
||||||
|
|
@ -5573,8 +5481,6 @@ class GroupMember extends DataClass implements Insertable<GroupMember> {
|
||||||
other.groupPublicKey,
|
other.groupPublicKey,
|
||||||
this.groupPublicKey,
|
this.groupPublicKey,
|
||||||
) &&
|
) &&
|
||||||
other.lastChatOpened == this.lastChatOpened &&
|
|
||||||
other.lastTypeIndicator == this.lastTypeIndicator &&
|
|
||||||
other.lastMessage == this.lastMessage &&
|
other.lastMessage == this.lastMessage &&
|
||||||
other.createdAt == this.createdAt);
|
other.createdAt == this.createdAt);
|
||||||
}
|
}
|
||||||
|
|
@ -5584,8 +5490,6 @@ class GroupMembersCompanion extends UpdateCompanion<GroupMember> {
|
||||||
final Value<int> contactId;
|
final Value<int> contactId;
|
||||||
final Value<MemberState?> memberState;
|
final Value<MemberState?> memberState;
|
||||||
final Value<Uint8List?> groupPublicKey;
|
final Value<Uint8List?> groupPublicKey;
|
||||||
final Value<DateTime?> lastChatOpened;
|
|
||||||
final Value<DateTime?> lastTypeIndicator;
|
|
||||||
final Value<DateTime?> lastMessage;
|
final Value<DateTime?> lastMessage;
|
||||||
final Value<DateTime> createdAt;
|
final Value<DateTime> createdAt;
|
||||||
final Value<int> rowid;
|
final Value<int> rowid;
|
||||||
|
|
@ -5594,8 +5498,6 @@ class GroupMembersCompanion extends UpdateCompanion<GroupMember> {
|
||||||
this.contactId = const Value.absent(),
|
this.contactId = const Value.absent(),
|
||||||
this.memberState = const Value.absent(),
|
this.memberState = const Value.absent(),
|
||||||
this.groupPublicKey = const Value.absent(),
|
this.groupPublicKey = const Value.absent(),
|
||||||
this.lastChatOpened = const Value.absent(),
|
|
||||||
this.lastTypeIndicator = const Value.absent(),
|
|
||||||
this.lastMessage = const Value.absent(),
|
this.lastMessage = const Value.absent(),
|
||||||
this.createdAt = const Value.absent(),
|
this.createdAt = const Value.absent(),
|
||||||
this.rowid = const Value.absent(),
|
this.rowid = const Value.absent(),
|
||||||
|
|
@ -5605,8 +5507,6 @@ class GroupMembersCompanion extends UpdateCompanion<GroupMember> {
|
||||||
required int contactId,
|
required int contactId,
|
||||||
this.memberState = const Value.absent(),
|
this.memberState = const Value.absent(),
|
||||||
this.groupPublicKey = const Value.absent(),
|
this.groupPublicKey = const Value.absent(),
|
||||||
this.lastChatOpened = const Value.absent(),
|
|
||||||
this.lastTypeIndicator = const Value.absent(),
|
|
||||||
this.lastMessage = const Value.absent(),
|
this.lastMessage = const Value.absent(),
|
||||||
this.createdAt = const Value.absent(),
|
this.createdAt = const Value.absent(),
|
||||||
this.rowid = const Value.absent(),
|
this.rowid = const Value.absent(),
|
||||||
|
|
@ -5617,8 +5517,6 @@ class GroupMembersCompanion extends UpdateCompanion<GroupMember> {
|
||||||
Expression<int>? contactId,
|
Expression<int>? contactId,
|
||||||
Expression<String>? memberState,
|
Expression<String>? memberState,
|
||||||
Expression<Uint8List>? groupPublicKey,
|
Expression<Uint8List>? groupPublicKey,
|
||||||
Expression<DateTime>? lastChatOpened,
|
|
||||||
Expression<DateTime>? lastTypeIndicator,
|
|
||||||
Expression<DateTime>? lastMessage,
|
Expression<DateTime>? lastMessage,
|
||||||
Expression<DateTime>? createdAt,
|
Expression<DateTime>? createdAt,
|
||||||
Expression<int>? rowid,
|
Expression<int>? rowid,
|
||||||
|
|
@ -5628,8 +5526,6 @@ class GroupMembersCompanion extends UpdateCompanion<GroupMember> {
|
||||||
if (contactId != null) 'contact_id': contactId,
|
if (contactId != null) 'contact_id': contactId,
|
||||||
if (memberState != null) 'member_state': memberState,
|
if (memberState != null) 'member_state': memberState,
|
||||||
if (groupPublicKey != null) 'group_public_key': groupPublicKey,
|
if (groupPublicKey != null) 'group_public_key': groupPublicKey,
|
||||||
if (lastChatOpened != null) 'last_chat_opened': lastChatOpened,
|
|
||||||
if (lastTypeIndicator != null) 'last_type_indicator': lastTypeIndicator,
|
|
||||||
if (lastMessage != null) 'last_message': lastMessage,
|
if (lastMessage != null) 'last_message': lastMessage,
|
||||||
if (createdAt != null) 'created_at': createdAt,
|
if (createdAt != null) 'created_at': createdAt,
|
||||||
if (rowid != null) 'rowid': rowid,
|
if (rowid != null) 'rowid': rowid,
|
||||||
|
|
@ -5641,8 +5537,6 @@ class GroupMembersCompanion extends UpdateCompanion<GroupMember> {
|
||||||
Value<int>? contactId,
|
Value<int>? contactId,
|
||||||
Value<MemberState?>? memberState,
|
Value<MemberState?>? memberState,
|
||||||
Value<Uint8List?>? groupPublicKey,
|
Value<Uint8List?>? groupPublicKey,
|
||||||
Value<DateTime?>? lastChatOpened,
|
|
||||||
Value<DateTime?>? lastTypeIndicator,
|
|
||||||
Value<DateTime?>? lastMessage,
|
Value<DateTime?>? lastMessage,
|
||||||
Value<DateTime>? createdAt,
|
Value<DateTime>? createdAt,
|
||||||
Value<int>? rowid,
|
Value<int>? rowid,
|
||||||
|
|
@ -5652,8 +5546,6 @@ class GroupMembersCompanion extends UpdateCompanion<GroupMember> {
|
||||||
contactId: contactId ?? this.contactId,
|
contactId: contactId ?? this.contactId,
|
||||||
memberState: memberState ?? this.memberState,
|
memberState: memberState ?? this.memberState,
|
||||||
groupPublicKey: groupPublicKey ?? this.groupPublicKey,
|
groupPublicKey: groupPublicKey ?? this.groupPublicKey,
|
||||||
lastChatOpened: lastChatOpened ?? this.lastChatOpened,
|
|
||||||
lastTypeIndicator: lastTypeIndicator ?? this.lastTypeIndicator,
|
|
||||||
lastMessage: lastMessage ?? this.lastMessage,
|
lastMessage: lastMessage ?? this.lastMessage,
|
||||||
createdAt: createdAt ?? this.createdAt,
|
createdAt: createdAt ?? this.createdAt,
|
||||||
rowid: rowid ?? this.rowid,
|
rowid: rowid ?? this.rowid,
|
||||||
|
|
@ -5677,12 +5569,6 @@ class GroupMembersCompanion extends UpdateCompanion<GroupMember> {
|
||||||
if (groupPublicKey.present) {
|
if (groupPublicKey.present) {
|
||||||
map['group_public_key'] = Variable<Uint8List>(groupPublicKey.value);
|
map['group_public_key'] = Variable<Uint8List>(groupPublicKey.value);
|
||||||
}
|
}
|
||||||
if (lastChatOpened.present) {
|
|
||||||
map['last_chat_opened'] = Variable<DateTime>(lastChatOpened.value);
|
|
||||||
}
|
|
||||||
if (lastTypeIndicator.present) {
|
|
||||||
map['last_type_indicator'] = Variable<DateTime>(lastTypeIndicator.value);
|
|
||||||
}
|
|
||||||
if (lastMessage.present) {
|
if (lastMessage.present) {
|
||||||
map['last_message'] = Variable<DateTime>(lastMessage.value);
|
map['last_message'] = Variable<DateTime>(lastMessage.value);
|
||||||
}
|
}
|
||||||
|
|
@ -5702,8 +5588,6 @@ class GroupMembersCompanion extends UpdateCompanion<GroupMember> {
|
||||||
..write('contactId: $contactId, ')
|
..write('contactId: $contactId, ')
|
||||||
..write('memberState: $memberState, ')
|
..write('memberState: $memberState, ')
|
||||||
..write('groupPublicKey: $groupPublicKey, ')
|
..write('groupPublicKey: $groupPublicKey, ')
|
||||||
..write('lastChatOpened: $lastChatOpened, ')
|
|
||||||
..write('lastTypeIndicator: $lastTypeIndicator, ')
|
|
||||||
..write('lastMessage: $lastMessage, ')
|
..write('lastMessage: $lastMessage, ')
|
||||||
..write('createdAt: $createdAt, ')
|
..write('createdAt: $createdAt, ')
|
||||||
..write('rowid: $rowid')
|
..write('rowid: $rowid')
|
||||||
|
|
@ -13442,8 +13326,6 @@ typedef $$GroupMembersTableCreateCompanionBuilder =
|
||||||
required int contactId,
|
required int contactId,
|
||||||
Value<MemberState?> memberState,
|
Value<MemberState?> memberState,
|
||||||
Value<Uint8List?> groupPublicKey,
|
Value<Uint8List?> groupPublicKey,
|
||||||
Value<DateTime?> lastChatOpened,
|
|
||||||
Value<DateTime?> lastTypeIndicator,
|
|
||||||
Value<DateTime?> lastMessage,
|
Value<DateTime?> lastMessage,
|
||||||
Value<DateTime> createdAt,
|
Value<DateTime> createdAt,
|
||||||
Value<int> rowid,
|
Value<int> rowid,
|
||||||
|
|
@ -13454,8 +13336,6 @@ typedef $$GroupMembersTableUpdateCompanionBuilder =
|
||||||
Value<int> contactId,
|
Value<int> contactId,
|
||||||
Value<MemberState?> memberState,
|
Value<MemberState?> memberState,
|
||||||
Value<Uint8List?> groupPublicKey,
|
Value<Uint8List?> groupPublicKey,
|
||||||
Value<DateTime?> lastChatOpened,
|
|
||||||
Value<DateTime?> lastTypeIndicator,
|
|
||||||
Value<DateTime?> lastMessage,
|
Value<DateTime?> lastMessage,
|
||||||
Value<DateTime> createdAt,
|
Value<DateTime> createdAt,
|
||||||
Value<int> rowid,
|
Value<int> rowid,
|
||||||
|
|
@ -13523,16 +13403,6 @@ class $$GroupMembersTableFilterComposer
|
||||||
builder: (column) => ColumnFilters(column),
|
builder: (column) => ColumnFilters(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
ColumnFilters<DateTime> get lastChatOpened => $composableBuilder(
|
|
||||||
column: $table.lastChatOpened,
|
|
||||||
builder: (column) => ColumnFilters(column),
|
|
||||||
);
|
|
||||||
|
|
||||||
ColumnFilters<DateTime> get lastTypeIndicator => $composableBuilder(
|
|
||||||
column: $table.lastTypeIndicator,
|
|
||||||
builder: (column) => ColumnFilters(column),
|
|
||||||
);
|
|
||||||
|
|
||||||
ColumnFilters<DateTime> get lastMessage => $composableBuilder(
|
ColumnFilters<DateTime> get lastMessage => $composableBuilder(
|
||||||
column: $table.lastMessage,
|
column: $table.lastMessage,
|
||||||
builder: (column) => ColumnFilters(column),
|
builder: (column) => ColumnFilters(column),
|
||||||
|
|
@ -13609,16 +13479,6 @@ class $$GroupMembersTableOrderingComposer
|
||||||
builder: (column) => ColumnOrderings(column),
|
builder: (column) => ColumnOrderings(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
ColumnOrderings<DateTime> get lastChatOpened => $composableBuilder(
|
|
||||||
column: $table.lastChatOpened,
|
|
||||||
builder: (column) => ColumnOrderings(column),
|
|
||||||
);
|
|
||||||
|
|
||||||
ColumnOrderings<DateTime> get lastTypeIndicator => $composableBuilder(
|
|
||||||
column: $table.lastTypeIndicator,
|
|
||||||
builder: (column) => ColumnOrderings(column),
|
|
||||||
);
|
|
||||||
|
|
||||||
ColumnOrderings<DateTime> get lastMessage => $composableBuilder(
|
ColumnOrderings<DateTime> get lastMessage => $composableBuilder(
|
||||||
column: $table.lastMessage,
|
column: $table.lastMessage,
|
||||||
builder: (column) => ColumnOrderings(column),
|
builder: (column) => ColumnOrderings(column),
|
||||||
|
|
@ -13696,16 +13556,6 @@ class $$GroupMembersTableAnnotationComposer
|
||||||
builder: (column) => column,
|
builder: (column) => column,
|
||||||
);
|
);
|
||||||
|
|
||||||
GeneratedColumn<DateTime> get lastChatOpened => $composableBuilder(
|
|
||||||
column: $table.lastChatOpened,
|
|
||||||
builder: (column) => column,
|
|
||||||
);
|
|
||||||
|
|
||||||
GeneratedColumn<DateTime> get lastTypeIndicator => $composableBuilder(
|
|
||||||
column: $table.lastTypeIndicator,
|
|
||||||
builder: (column) => column,
|
|
||||||
);
|
|
||||||
|
|
||||||
GeneratedColumn<DateTime> get lastMessage => $composableBuilder(
|
GeneratedColumn<DateTime> get lastMessage => $composableBuilder(
|
||||||
column: $table.lastMessage,
|
column: $table.lastMessage,
|
||||||
builder: (column) => column,
|
builder: (column) => column,
|
||||||
|
|
@ -13793,8 +13643,6 @@ class $$GroupMembersTableTableManager
|
||||||
Value<int> contactId = const Value.absent(),
|
Value<int> contactId = const Value.absent(),
|
||||||
Value<MemberState?> memberState = const Value.absent(),
|
Value<MemberState?> memberState = const Value.absent(),
|
||||||
Value<Uint8List?> groupPublicKey = const Value.absent(),
|
Value<Uint8List?> groupPublicKey = const Value.absent(),
|
||||||
Value<DateTime?> lastChatOpened = const Value.absent(),
|
|
||||||
Value<DateTime?> lastTypeIndicator = const Value.absent(),
|
|
||||||
Value<DateTime?> lastMessage = const Value.absent(),
|
Value<DateTime?> lastMessage = const Value.absent(),
|
||||||
Value<DateTime> createdAt = const Value.absent(),
|
Value<DateTime> createdAt = const Value.absent(),
|
||||||
Value<int> rowid = const Value.absent(),
|
Value<int> rowid = const Value.absent(),
|
||||||
|
|
@ -13803,8 +13651,6 @@ class $$GroupMembersTableTableManager
|
||||||
contactId: contactId,
|
contactId: contactId,
|
||||||
memberState: memberState,
|
memberState: memberState,
|
||||||
groupPublicKey: groupPublicKey,
|
groupPublicKey: groupPublicKey,
|
||||||
lastChatOpened: lastChatOpened,
|
|
||||||
lastTypeIndicator: lastTypeIndicator,
|
|
||||||
lastMessage: lastMessage,
|
lastMessage: lastMessage,
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
rowid: rowid,
|
rowid: rowid,
|
||||||
|
|
@ -13815,8 +13661,6 @@ class $$GroupMembersTableTableManager
|
||||||
required int contactId,
|
required int contactId,
|
||||||
Value<MemberState?> memberState = const Value.absent(),
|
Value<MemberState?> memberState = const Value.absent(),
|
||||||
Value<Uint8List?> groupPublicKey = const Value.absent(),
|
Value<Uint8List?> groupPublicKey = const Value.absent(),
|
||||||
Value<DateTime?> lastChatOpened = const Value.absent(),
|
|
||||||
Value<DateTime?> lastTypeIndicator = const Value.absent(),
|
|
||||||
Value<DateTime?> lastMessage = const Value.absent(),
|
Value<DateTime?> lastMessage = const Value.absent(),
|
||||||
Value<DateTime> createdAt = const Value.absent(),
|
Value<DateTime> createdAt = const Value.absent(),
|
||||||
Value<int> rowid = const Value.absent(),
|
Value<int> rowid = const Value.absent(),
|
||||||
|
|
@ -13825,8 +13669,6 @@ class $$GroupMembersTableTableManager
|
||||||
contactId: contactId,
|
contactId: contactId,
|
||||||
memberState: memberState,
|
memberState: memberState,
|
||||||
groupPublicKey: groupPublicKey,
|
groupPublicKey: groupPublicKey,
|
||||||
lastChatOpened: lastChatOpened,
|
|
||||||
lastTypeIndicator: lastTypeIndicator,
|
|
||||||
lastMessage: lastMessage,
|
lastMessage: lastMessage,
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
rowid: rowid,
|
rowid: rowid,
|
||||||
|
|
|
||||||
|
|
@ -5484,345 +5484,6 @@ i1.GeneratedColumn<int> _column_208(
|
||||||
'NOT NULL DEFAULT 0 CHECK (will_be_retried_by_media_upload IN (0, 1))',
|
'NOT NULL DEFAULT 0 CHECK (will_be_retried_by_media_upload IN (0, 1))',
|
||||||
defaultValue: const i1.CustomExpression('0'),
|
defaultValue: const i1.CustomExpression('0'),
|
||||||
);
|
);
|
||||||
|
|
||||||
final class Schema11 extends i0.VersionedSchema {
|
|
||||||
Schema11({required super.database}) : super(version: 11);
|
|
||||||
@override
|
|
||||||
late final List<i1.DatabaseSchemaEntity> entities = [
|
|
||||||
contacts,
|
|
||||||
groups,
|
|
||||||
mediaFiles,
|
|
||||||
messages,
|
|
||||||
messageHistories,
|
|
||||||
reactions,
|
|
||||||
groupMembers,
|
|
||||||
receipts,
|
|
||||||
receivedReceipts,
|
|
||||||
signalIdentityKeyStores,
|
|
||||||
signalPreKeyStores,
|
|
||||||
signalSenderKeyStores,
|
|
||||||
signalSessionStores,
|
|
||||||
messageActions,
|
|
||||||
groupHistories,
|
|
||||||
];
|
|
||||||
late final Shape22 contacts = Shape22(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'contacts',
|
|
||||||
withoutRowId: false,
|
|
||||||
isStrict: false,
|
|
||||||
tableConstraints: ['PRIMARY KEY(user_id)'],
|
|
||||||
columns: [
|
|
||||||
_column_106,
|
|
||||||
_column_107,
|
|
||||||
_column_108,
|
|
||||||
_column_109,
|
|
||||||
_column_110,
|
|
||||||
_column_111,
|
|
||||||
_column_112,
|
|
||||||
_column_113,
|
|
||||||
_column_114,
|
|
||||||
_column_115,
|
|
||||||
_column_116,
|
|
||||||
_column_117,
|
|
||||||
_column_118,
|
|
||||||
],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape23 groups = Shape23(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'groups',
|
|
||||||
withoutRowId: false,
|
|
||||||
isStrict: false,
|
|
||||||
tableConstraints: ['PRIMARY KEY(group_id)'],
|
|
||||||
columns: [
|
|
||||||
_column_119,
|
|
||||||
_column_120,
|
|
||||||
_column_121,
|
|
||||||
_column_122,
|
|
||||||
_column_123,
|
|
||||||
_column_124,
|
|
||||||
_column_125,
|
|
||||||
_column_126,
|
|
||||||
_column_127,
|
|
||||||
_column_128,
|
|
||||||
_column_129,
|
|
||||||
_column_130,
|
|
||||||
_column_131,
|
|
||||||
_column_132,
|
|
||||||
_column_133,
|
|
||||||
_column_134,
|
|
||||||
_column_118,
|
|
||||||
_column_135,
|
|
||||||
_column_136,
|
|
||||||
_column_137,
|
|
||||||
_column_138,
|
|
||||||
_column_139,
|
|
||||||
_column_140,
|
|
||||||
_column_141,
|
|
||||||
_column_142,
|
|
||||||
],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape36 mediaFiles = Shape36(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'media_files',
|
|
||||||
withoutRowId: false,
|
|
||||||
isStrict: false,
|
|
||||||
tableConstraints: ['PRIMARY KEY(media_id)'],
|
|
||||||
columns: [
|
|
||||||
_column_143,
|
|
||||||
_column_144,
|
|
||||||
_column_145,
|
|
||||||
_column_146,
|
|
||||||
_column_147,
|
|
||||||
_column_148,
|
|
||||||
_column_149,
|
|
||||||
_column_207,
|
|
||||||
_column_150,
|
|
||||||
_column_151,
|
|
||||||
_column_152,
|
|
||||||
_column_153,
|
|
||||||
_column_154,
|
|
||||||
_column_155,
|
|
||||||
_column_156,
|
|
||||||
_column_157,
|
|
||||||
_column_118,
|
|
||||||
],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape25 messages = Shape25(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'messages',
|
|
||||||
withoutRowId: false,
|
|
||||||
isStrict: false,
|
|
||||||
tableConstraints: ['PRIMARY KEY(message_id)'],
|
|
||||||
columns: [
|
|
||||||
_column_158,
|
|
||||||
_column_159,
|
|
||||||
_column_160,
|
|
||||||
_column_144,
|
|
||||||
_column_161,
|
|
||||||
_column_162,
|
|
||||||
_column_163,
|
|
||||||
_column_164,
|
|
||||||
_column_165,
|
|
||||||
_column_153,
|
|
||||||
_column_166,
|
|
||||||
_column_167,
|
|
||||||
_column_168,
|
|
||||||
_column_169,
|
|
||||||
_column_118,
|
|
||||||
_column_170,
|
|
||||||
_column_171,
|
|
||||||
_column_172,
|
|
||||||
],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape26 messageHistories = Shape26(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'message_histories',
|
|
||||||
withoutRowId: false,
|
|
||||||
isStrict: false,
|
|
||||||
tableConstraints: [],
|
|
||||||
columns: [
|
|
||||||
_column_173,
|
|
||||||
_column_174,
|
|
||||||
_column_175,
|
|
||||||
_column_161,
|
|
||||||
_column_118,
|
|
||||||
],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape27 reactions = Shape27(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'reactions',
|
|
||||||
withoutRowId: false,
|
|
||||||
isStrict: false,
|
|
||||||
tableConstraints: ['PRIMARY KEY(message_id, sender_id, emoji)'],
|
|
||||||
columns: [_column_174, _column_176, _column_177, _column_118],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape38 groupMembers = Shape38(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'group_members',
|
|
||||||
withoutRowId: false,
|
|
||||||
isStrict: false,
|
|
||||||
tableConstraints: ['PRIMARY KEY(group_id, contact_id)'],
|
|
||||||
columns: [
|
|
||||||
_column_158,
|
|
||||||
_column_178,
|
|
||||||
_column_179,
|
|
||||||
_column_180,
|
|
||||||
_column_209,
|
|
||||||
_column_210,
|
|
||||||
_column_181,
|
|
||||||
_column_118,
|
|
||||||
],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape37 receipts = Shape37(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'receipts',
|
|
||||||
withoutRowId: false,
|
|
||||||
isStrict: false,
|
|
||||||
tableConstraints: ['PRIMARY KEY(receipt_id)'],
|
|
||||||
columns: [
|
|
||||||
_column_182,
|
|
||||||
_column_183,
|
|
||||||
_column_184,
|
|
||||||
_column_185,
|
|
||||||
_column_186,
|
|
||||||
_column_208,
|
|
||||||
_column_187,
|
|
||||||
_column_188,
|
|
||||||
_column_189,
|
|
||||||
_column_190,
|
|
||||||
_column_191,
|
|
||||||
_column_118,
|
|
||||||
],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape30 receivedReceipts = Shape30(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'received_receipts',
|
|
||||||
withoutRowId: false,
|
|
||||||
isStrict: false,
|
|
||||||
tableConstraints: ['PRIMARY KEY(receipt_id)'],
|
|
||||||
columns: [_column_182, _column_118],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape31 signalIdentityKeyStores = Shape31(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'signal_identity_key_stores',
|
|
||||||
withoutRowId: false,
|
|
||||||
isStrict: false,
|
|
||||||
tableConstraints: ['PRIMARY KEY(device_id, name)'],
|
|
||||||
columns: [_column_192, _column_193, _column_194, _column_118],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape32 signalPreKeyStores = Shape32(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'signal_pre_key_stores',
|
|
||||||
withoutRowId: false,
|
|
||||||
isStrict: false,
|
|
||||||
tableConstraints: ['PRIMARY KEY(pre_key_id)'],
|
|
||||||
columns: [_column_195, _column_196, _column_118],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape11 signalSenderKeyStores = Shape11(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'signal_sender_key_stores',
|
|
||||||
withoutRowId: false,
|
|
||||||
isStrict: false,
|
|
||||||
tableConstraints: ['PRIMARY KEY(sender_key_name)'],
|
|
||||||
columns: [_column_197, _column_198],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape33 signalSessionStores = Shape33(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'signal_session_stores',
|
|
||||||
withoutRowId: false,
|
|
||||||
isStrict: false,
|
|
||||||
tableConstraints: ['PRIMARY KEY(device_id, name)'],
|
|
||||||
columns: [_column_192, _column_193, _column_199, _column_118],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape34 messageActions = Shape34(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'message_actions',
|
|
||||||
withoutRowId: false,
|
|
||||||
isStrict: false,
|
|
||||||
tableConstraints: ['PRIMARY KEY(message_id, contact_id, type)'],
|
|
||||||
columns: [_column_174, _column_183, _column_144, _column_200],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape35 groupHistories = Shape35(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'group_histories',
|
|
||||||
withoutRowId: false,
|
|
||||||
isStrict: false,
|
|
||||||
tableConstraints: ['PRIMARY KEY(group_history_id)'],
|
|
||||||
columns: [
|
|
||||||
_column_201,
|
|
||||||
_column_158,
|
|
||||||
_column_202,
|
|
||||||
_column_203,
|
|
||||||
_column_204,
|
|
||||||
_column_205,
|
|
||||||
_column_206,
|
|
||||||
_column_144,
|
|
||||||
_column_200,
|
|
||||||
],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class Shape38 extends i0.VersionedTable {
|
|
||||||
Shape38({required super.source, required super.alias}) : super.aliased();
|
|
||||||
i1.GeneratedColumn<String> get groupId =>
|
|
||||||
columnsByName['group_id']! as i1.GeneratedColumn<String>;
|
|
||||||
i1.GeneratedColumn<int> get contactId =>
|
|
||||||
columnsByName['contact_id']! as i1.GeneratedColumn<int>;
|
|
||||||
i1.GeneratedColumn<String> get memberState =>
|
|
||||||
columnsByName['member_state']! as i1.GeneratedColumn<String>;
|
|
||||||
i1.GeneratedColumn<i2.Uint8List> get groupPublicKey =>
|
|
||||||
columnsByName['group_public_key']! as i1.GeneratedColumn<i2.Uint8List>;
|
|
||||||
i1.GeneratedColumn<int> get lastChatOpened =>
|
|
||||||
columnsByName['last_chat_opened']! as i1.GeneratedColumn<int>;
|
|
||||||
i1.GeneratedColumn<int> get lastTypeIndicator =>
|
|
||||||
columnsByName['last_type_indicator']! as i1.GeneratedColumn<int>;
|
|
||||||
i1.GeneratedColumn<int> get lastMessage =>
|
|
||||||
columnsByName['last_message']! as i1.GeneratedColumn<int>;
|
|
||||||
i1.GeneratedColumn<int> get createdAt =>
|
|
||||||
columnsByName['created_at']! as i1.GeneratedColumn<int>;
|
|
||||||
}
|
|
||||||
|
|
||||||
i1.GeneratedColumn<int> _column_209(String aliasedName) =>
|
|
||||||
i1.GeneratedColumn<int>(
|
|
||||||
'last_chat_opened',
|
|
||||||
aliasedName,
|
|
||||||
true,
|
|
||||||
type: i1.DriftSqlType.int,
|
|
||||||
$customConstraints: 'NULL',
|
|
||||||
);
|
|
||||||
i1.GeneratedColumn<int> _column_210(String aliasedName) =>
|
|
||||||
i1.GeneratedColumn<int>(
|
|
||||||
'last_type_indicator',
|
|
||||||
aliasedName,
|
|
||||||
true,
|
|
||||||
type: i1.DriftSqlType.int,
|
|
||||||
$customConstraints: 'NULL',
|
|
||||||
);
|
|
||||||
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,
|
||||||
|
|
@ -5833,7 +5494,6 @@ i0.MigrationStepWithVersion migrationSteps({
|
||||||
required Future<void> Function(i1.Migrator m, Schema8 schema) from7To8,
|
required Future<void> Function(i1.Migrator m, Schema8 schema) from7To8,
|
||||||
required Future<void> Function(i1.Migrator m, Schema9 schema) from8To9,
|
required Future<void> Function(i1.Migrator m, Schema9 schema) from8To9,
|
||||||
required Future<void> Function(i1.Migrator m, Schema10 schema) from9To10,
|
required Future<void> Function(i1.Migrator m, Schema10 schema) from9To10,
|
||||||
required Future<void> Function(i1.Migrator m, Schema11 schema) from10To11,
|
|
||||||
}) {
|
}) {
|
||||||
return (currentVersion, database) async {
|
return (currentVersion, database) async {
|
||||||
switch (currentVersion) {
|
switch (currentVersion) {
|
||||||
|
|
@ -5882,11 +5542,6 @@ i0.MigrationStepWithVersion migrationSteps({
|
||||||
final migrator = i1.Migrator(database, schema);
|
final migrator = i1.Migrator(database, schema);
|
||||||
await from9To10(migrator, schema);
|
await from9To10(migrator, schema);
|
||||||
return 10;
|
return 10;
|
||||||
case 10:
|
|
||||||
final schema = Schema11(database: database);
|
|
||||||
final migrator = i1.Migrator(database, schema);
|
|
||||||
await from10To11(migrator, schema);
|
|
||||||
return 11;
|
|
||||||
default:
|
default:
|
||||||
throw ArgumentError.value('Unknown migration from $currentVersion');
|
throw ArgumentError.value('Unknown migration from $currentVersion');
|
||||||
}
|
}
|
||||||
|
|
@ -5903,7 +5558,6 @@ i1.OnUpgrade stepByStep({
|
||||||
required Future<void> Function(i1.Migrator m, Schema8 schema) from7To8,
|
required Future<void> Function(i1.Migrator m, Schema8 schema) from7To8,
|
||||||
required Future<void> Function(i1.Migrator m, Schema9 schema) from8To9,
|
required Future<void> Function(i1.Migrator m, Schema9 schema) from8To9,
|
||||||
required Future<void> Function(i1.Migrator m, Schema10 schema) from9To10,
|
required Future<void> Function(i1.Migrator m, Schema10 schema) from9To10,
|
||||||
required Future<void> Function(i1.Migrator m, Schema11 schema) from10To11,
|
|
||||||
}) => i0.VersionedSchema.stepByStepHelper(
|
}) => i0.VersionedSchema.stepByStepHelper(
|
||||||
step: migrationSteps(
|
step: migrationSteps(
|
||||||
from1To2: from1To2,
|
from1To2: from1To2,
|
||||||
|
|
@ -5915,6 +5569,5 @@ i1.OnUpgrade stepByStep({
|
||||||
from7To8: from7To8,
|
from7To8: from7To8,
|
||||||
from8To9: from8To9,
|
from8To9: from8To9,
|
||||||
from9To10: from9To10,
|
from9To10: from9To10,
|
||||||
from10To11: from10To11,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -376,11 +376,11 @@ abstract class AppLocalizations {
|
||||||
/// **'Username'**
|
/// **'Username'**
|
||||||
String get searchUsernameInput;
|
String get searchUsernameInput;
|
||||||
|
|
||||||
/// No description provided for @addFriendTitle.
|
/// No description provided for @searchUsernameTitle.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Add friends'**
|
/// **'Search username'**
|
||||||
String get addFriendTitle;
|
String get searchUsernameTitle;
|
||||||
|
|
||||||
/// No description provided for @searchUserNamePreview.
|
/// No description provided for @searchUserNamePreview.
|
||||||
///
|
///
|
||||||
|
|
@ -733,33 +733,9 @@ abstract class AppLocalizations {
|
||||||
/// No description provided for @settingsNotifyTroubleshootingNoProblemDesc.
|
/// No description provided for @settingsNotifyTroubleshootingNoProblemDesc.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Press OK to receive a test notification. If you do not receive the test notification, please click on the new menu item that appears after you click “OK”.'**
|
/// **'Press OK to receive a test notification. When you receive no message even after waiting for 10 minutes, please send us your debug log in Settings > Help > Debug log, so we can look at that issue.'**
|
||||||
String get settingsNotifyTroubleshootingNoProblemDesc;
|
String get settingsNotifyTroubleshootingNoProblemDesc;
|
||||||
|
|
||||||
/// No description provided for @settingsNotifyResetTitle.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'Didn\'t receive a test notification?'**
|
|
||||||
String get settingsNotifyResetTitle;
|
|
||||||
|
|
||||||
/// No description provided for @settingsNotifyResetTitleSubtitle.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'If you haven\'t received any test notifications, click here to reset your notification tokens.'**
|
|
||||||
String get settingsNotifyResetTitleSubtitle;
|
|
||||||
|
|
||||||
/// No description provided for @settingsNotifyResetTitleReset.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'Your notification tokens have been reset.'**
|
|
||||||
String get settingsNotifyResetTitleReset;
|
|
||||||
|
|
||||||
/// No description provided for @settingsNotifyResetTitleResetDesc.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'If the problem persists, please send us your debug log via Settings > Help so we can investigate the issue.'**
|
|
||||||
String get settingsNotifyResetTitleResetDesc;
|
|
||||||
|
|
||||||
/// No description provided for @settingsHelp.
|
/// No description provided for @settingsHelp.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|
@ -2842,12 +2818,6 @@ abstract class AppLocalizations {
|
||||||
/// **'Scan other profile'**
|
/// **'Scan other profile'**
|
||||||
String get scanOtherProfile;
|
String get scanOtherProfile;
|
||||||
|
|
||||||
/// No description provided for @openYourOwnQRcode.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'Open your own QR code'**
|
|
||||||
String get openYourOwnQRcode;
|
|
||||||
|
|
||||||
/// No description provided for @skipForNow.
|
/// No description provided for @skipForNow.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|
@ -3087,60 +3057,6 @@ abstract class AppLocalizations {
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Your QR code'**
|
/// **'Your QR code'**
|
||||||
String get profileYourQrCode;
|
String get profileYourQrCode;
|
||||||
|
|
||||||
/// No description provided for @settingsScreenLock.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'Screen lock'**
|
|
||||||
String get settingsScreenLock;
|
|
||||||
|
|
||||||
/// No description provided for @settingsScreenLockSubtitle.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'To open twonly, you\'ll need to use your smartphone\'s unlock feature.'**
|
|
||||||
String get settingsScreenLockSubtitle;
|
|
||||||
|
|
||||||
/// No description provided for @settingsScreenLockAuthMessageEnable.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'Use the screen lock from twonly.'**
|
|
||||||
String get settingsScreenLockAuthMessageEnable;
|
|
||||||
|
|
||||||
/// No description provided for @settingsScreenLockAuthMessageDisable.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'Disable the screen lock from twonly.'**
|
|
||||||
String get settingsScreenLockAuthMessageDisable;
|
|
||||||
|
|
||||||
/// No description provided for @unlockTwonly.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'Unlock twonly'**
|
|
||||||
String get unlockTwonly;
|
|
||||||
|
|
||||||
/// No description provided for @unlockTwonlyTryAgain.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'Try again'**
|
|
||||||
String get unlockTwonlyTryAgain;
|
|
||||||
|
|
||||||
/// No description provided for @unlockTwonlyDesc.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'Use your phone\'s unlock settings to unlock twonly'**
|
|
||||||
String get unlockTwonlyDesc;
|
|
||||||
|
|
||||||
/// No description provided for @settingsTypingIndication.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'Typing Indicators'**
|
|
||||||
String get settingsTypingIndication;
|
|
||||||
|
|
||||||
/// No description provided for @settingsTypingIndicationSubtitle.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'When the typing indicator is turned off, you can\'t see when others are typing a message.'**
|
|
||||||
String get settingsTypingIndicationSubtitle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppLocalizationsDelegate
|
class _AppLocalizationsDelegate
|
||||||
|
|
|
||||||
|
|
@ -162,7 +162,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
String get searchUsernameInput => 'Benutzername';
|
String get searchUsernameInput => 'Benutzername';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get addFriendTitle => 'Freunde hinzufügen';
|
String get searchUsernameTitle => 'Benutzernamen suchen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get searchUserNamePreview =>
|
String get searchUserNamePreview =>
|
||||||
|
|
@ -356,22 +356,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get settingsNotifyTroubleshootingNoProblemDesc =>
|
String get settingsNotifyTroubleshootingNoProblemDesc =>
|
||||||
'Um eine Testbenachrichtigung zu erhalten, klicke auf OK. Falls du die Testbenachrichtigung nicht erhältst, klicke bitte auf den neuen Menüpunkt, der nach dem Klicken auf „OK“ angezeigt wird.';
|
'Klicke auf OK, um eine Testbenachrichtigung zu erhalten. Wenn du auch nach 10 Minuten warten keine Nachricht erhältst, sende uns bitte dein Diagnoseprotokoll unter Einstellungen > Hilfe > Diagnoseprotokoll, damit wir uns das Problem ansehen können.';
|
||||||
|
|
||||||
@override
|
|
||||||
String get settingsNotifyResetTitle => 'Keine Testbenachrichtigung erhalten?';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get settingsNotifyResetTitleSubtitle =>
|
|
||||||
'Falls du keine Testbenachrichtigungen erhalten hast, klicke hier, um deine Benachrichtigungstoken zurückzusetzen.';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get settingsNotifyResetTitleReset =>
|
|
||||||
'Deine Benachrichtigungstoken wurden zurückgesetzt.';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get settingsNotifyResetTitleResetDesc =>
|
|
||||||
'Sollte das Problem weiterhin bestehen, sende uns bitte dein Debug-Protokoll über „Einstellungen“ > „Hilfe“, damit wir das Problem untersuchen können.';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get settingsHelp => 'Hilfe';
|
String get settingsHelp => 'Hilfe';
|
||||||
|
|
@ -1568,9 +1553,6 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get scanOtherProfile => 'Scanne ein anderes Profil';
|
String get scanOtherProfile => 'Scanne ein anderes Profil';
|
||||||
|
|
||||||
@override
|
|
||||||
String get openYourOwnQRcode => 'Eigenen QR-Code öffnen';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get skipForNow => 'Vorerst überspringen';
|
String get skipForNow => 'Vorerst überspringen';
|
||||||
|
|
||||||
|
|
@ -1729,36 +1711,4 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get profileYourQrCode => 'Dein QR-Code';
|
String get profileYourQrCode => 'Dein QR-Code';
|
||||||
|
|
||||||
@override
|
|
||||||
String get settingsScreenLock => 'Bildschirmsperre';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get settingsScreenLockSubtitle =>
|
|
||||||
'Um twonly zu öffnen, wird die Entsperrfunktion deines Smartphones verwenden.';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get settingsScreenLockAuthMessageEnable =>
|
|
||||||
'Bildschirmsperre von twonly verwenden';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get settingsScreenLockAuthMessageDisable =>
|
|
||||||
'Bildschirmsperre von twonly deaktivieren.';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get unlockTwonly => 'twonly entsperren';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get unlockTwonlyTryAgain => 'Erneut versuchen';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get unlockTwonlyDesc =>
|
|
||||||
'Entsperre twonly über die Sperreinstellungen deines Handys';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get settingsTypingIndication => 'Tipp-Indikatoren';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get settingsTypingIndicationSubtitle =>
|
|
||||||
'Bei deaktivierten Tipp-Indikatoren kannst du nicht sehen, wenn andere gerade eine Nachricht tippen.';
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -161,7 +161,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
String get searchUsernameInput => 'Username';
|
String get searchUsernameInput => 'Username';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get addFriendTitle => 'Add friends';
|
String get searchUsernameTitle => 'Search username';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get searchUserNamePreview =>
|
String get searchUserNamePreview =>
|
||||||
|
|
@ -351,22 +351,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get settingsNotifyTroubleshootingNoProblemDesc =>
|
String get settingsNotifyTroubleshootingNoProblemDesc =>
|
||||||
'Press OK to receive a test notification. If you do not receive the test notification, please click on the new menu item that appears after you click “OK”.';
|
'Press OK to receive a test notification. When you receive no message even after waiting for 10 minutes, please send us your debug log in Settings > Help > Debug log, so we can look at that issue.';
|
||||||
|
|
||||||
@override
|
|
||||||
String get settingsNotifyResetTitle => 'Didn\'t receive a test notification?';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get settingsNotifyResetTitleSubtitle =>
|
|
||||||
'If you haven\'t received any test notifications, click here to reset your notification tokens.';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get settingsNotifyResetTitleReset =>
|
|
||||||
'Your notification tokens have been reset.';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get settingsNotifyResetTitleResetDesc =>
|
|
||||||
'If the problem persists, please send us your debug log via Settings > Help so we can investigate the issue.';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get settingsHelp => 'Help';
|
String get settingsHelp => 'Help';
|
||||||
|
|
@ -1558,9 +1543,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get scanOtherProfile => 'Scan other profile';
|
String get scanOtherProfile => 'Scan other profile';
|
||||||
|
|
||||||
@override
|
|
||||||
String get openYourOwnQRcode => 'Open your own QR code';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get skipForNow => 'Skip for now';
|
String get skipForNow => 'Skip for now';
|
||||||
|
|
||||||
|
|
@ -1717,36 +1699,4 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get profileYourQrCode => 'Your QR code';
|
String get profileYourQrCode => 'Your QR code';
|
||||||
|
|
||||||
@override
|
|
||||||
String get settingsScreenLock => 'Screen lock';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get settingsScreenLockSubtitle =>
|
|
||||||
'To open twonly, you\'ll need to use your smartphone\'s unlock feature.';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get settingsScreenLockAuthMessageEnable =>
|
|
||||||
'Use the screen lock from twonly.';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get settingsScreenLockAuthMessageDisable =>
|
|
||||||
'Disable the screen lock from twonly.';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get unlockTwonly => 'Unlock twonly';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get unlockTwonlyTryAgain => 'Try again';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get unlockTwonlyDesc =>
|
|
||||||
'Use your phone\'s unlock settings to unlock twonly';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get settingsTypingIndication => 'Typing Indicators';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get settingsTypingIndicationSubtitle =>
|
|
||||||
'When the typing indicator is turned off, you can\'t see when others are typing a message.';
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -161,7 +161,7 @@ class AppLocalizationsSv extends AppLocalizations {
|
||||||
String get searchUsernameInput => 'Username';
|
String get searchUsernameInput => 'Username';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get addFriendTitle => 'Add friends';
|
String get searchUsernameTitle => 'Search username';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get searchUserNamePreview =>
|
String get searchUserNamePreview =>
|
||||||
|
|
@ -351,22 +351,7 @@ class AppLocalizationsSv extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get settingsNotifyTroubleshootingNoProblemDesc =>
|
String get settingsNotifyTroubleshootingNoProblemDesc =>
|
||||||
'Press OK to receive a test notification. If you do not receive the test notification, please click on the new menu item that appears after you click “OK”.';
|
'Press OK to receive a test notification. When you receive no message even after waiting for 10 minutes, please send us your debug log in Settings > Help > Debug log, so we can look at that issue.';
|
||||||
|
|
||||||
@override
|
|
||||||
String get settingsNotifyResetTitle => 'Didn\'t receive a test notification?';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get settingsNotifyResetTitleSubtitle =>
|
|
||||||
'If you haven\'t received any test notifications, click here to reset your notification tokens.';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get settingsNotifyResetTitleReset =>
|
|
||||||
'Your notification tokens have been reset.';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get settingsNotifyResetTitleResetDesc =>
|
|
||||||
'If the problem persists, please send us your debug log via Settings > Help so we can investigate the issue.';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get settingsHelp => 'Help';
|
String get settingsHelp => 'Help';
|
||||||
|
|
@ -1558,9 +1543,6 @@ class AppLocalizationsSv extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get scanOtherProfile => 'Scan other profile';
|
String get scanOtherProfile => 'Scan other profile';
|
||||||
|
|
||||||
@override
|
|
||||||
String get openYourOwnQRcode => 'Open your own QR code';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get skipForNow => 'Skip for now';
|
String get skipForNow => 'Skip for now';
|
||||||
|
|
||||||
|
|
@ -1717,36 +1699,4 @@ class AppLocalizationsSv extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get profileYourQrCode => 'Your QR code';
|
String get profileYourQrCode => 'Your QR code';
|
||||||
|
|
||||||
@override
|
|
||||||
String get settingsScreenLock => 'Screen lock';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get settingsScreenLockSubtitle =>
|
|
||||||
'To open twonly, you\'ll need to use your smartphone\'s unlock feature.';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get settingsScreenLockAuthMessageEnable =>
|
|
||||||
'Use the screen lock from twonly.';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get settingsScreenLockAuthMessageDisable =>
|
|
||||||
'Disable the screen lock from twonly.';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get unlockTwonly => 'Unlock twonly';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get unlockTwonlyTryAgain => 'Try again';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get unlockTwonlyDesc =>
|
|
||||||
'Use your phone\'s unlock settings to unlock twonly';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get settingsTypingIndication => 'Typing Indicators';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get settingsTypingIndicationSubtitle =>
|
|
||||||
'When the typing indicator is turned off, you can\'t see when others are typing a message.';
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 93f2b3daddd98dbb022c34e7c5976a76c3143236
|
Subproject commit 284c602b507e77addc8f21c4fc8a321f237cac1b
|
||||||
|
|
@ -53,9 +53,6 @@ class UserData {
|
||||||
@JsonKey(defaultValue: false)
|
@JsonKey(defaultValue: false)
|
||||||
bool requestedAudioPermission = false;
|
bool requestedAudioPermission = false;
|
||||||
|
|
||||||
@JsonKey(defaultValue: true)
|
|
||||||
bool videoStabilizationEnabled = true;
|
|
||||||
|
|
||||||
@JsonKey(defaultValue: true)
|
@JsonKey(defaultValue: true)
|
||||||
bool showFeedbackShortcut = true;
|
bool showFeedbackShortcut = true;
|
||||||
|
|
||||||
|
|
@ -75,9 +72,6 @@ class UserData {
|
||||||
@JsonKey(defaultValue: false)
|
@JsonKey(defaultValue: false)
|
||||||
bool autoStoreAllSendUnlimitedMediaFiles = false;
|
bool autoStoreAllSendUnlimitedMediaFiles = false;
|
||||||
|
|
||||||
@JsonKey(defaultValue: true)
|
|
||||||
bool typingIndicators = true;
|
|
||||||
|
|
||||||
String? lastPlanBallance;
|
String? lastPlanBallance;
|
||||||
String? additionalUserInvites;
|
String? additionalUserInvites;
|
||||||
|
|
||||||
|
|
@ -90,9 +84,6 @@ class UserData {
|
||||||
@JsonKey(defaultValue: false)
|
@JsonKey(defaultValue: false)
|
||||||
bool allowErrorTrackingViaSentry = false;
|
bool allowErrorTrackingViaSentry = false;
|
||||||
|
|
||||||
@JsonKey(defaultValue: false)
|
|
||||||
bool screenLockEnabled = false;
|
|
||||||
|
|
||||||
// -- Custom DATA --
|
// -- Custom DATA --
|
||||||
|
|
||||||
@JsonKey(defaultValue: 100_000)
|
@JsonKey(defaultValue: 100_000)
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,6 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) =>
|
||||||
..defaultShowTime = (json['defaultShowTime'] as num?)?.toInt()
|
..defaultShowTime = (json['defaultShowTime'] as num?)?.toInt()
|
||||||
..requestedAudioPermission =
|
..requestedAudioPermission =
|
||||||
json['requestedAudioPermission'] as bool? ?? false
|
json['requestedAudioPermission'] as bool? ?? false
|
||||||
..videoStabilizationEnabled =
|
|
||||||
json['videoStabilizationEnabled'] as bool? ?? true
|
|
||||||
..showFeedbackShortcut = json['showFeedbackShortcut'] as bool? ?? true
|
..showFeedbackShortcut = json['showFeedbackShortcut'] as bool? ?? true
|
||||||
..showShowImagePreviewWhenSending =
|
..showShowImagePreviewWhenSending =
|
||||||
json['showShowImagePreviewWhenSending'] as bool? ?? false
|
json['showShowImagePreviewWhenSending'] as bool? ?? false
|
||||||
|
|
@ -50,7 +48,6 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) =>
|
||||||
json['storeMediaFilesInGallery'] as bool? ?? false
|
json['storeMediaFilesInGallery'] as bool? ?? false
|
||||||
..autoStoreAllSendUnlimitedMediaFiles =
|
..autoStoreAllSendUnlimitedMediaFiles =
|
||||||
json['autoStoreAllSendUnlimitedMediaFiles'] as bool? ?? false
|
json['autoStoreAllSendUnlimitedMediaFiles'] as bool? ?? false
|
||||||
..typingIndicators = json['typingIndicators'] as bool? ?? true
|
|
||||||
..lastPlanBallance = json['lastPlanBallance'] as String?
|
..lastPlanBallance = json['lastPlanBallance'] as String?
|
||||||
..additionalUserInvites = json['additionalUserInvites'] as String?
|
..additionalUserInvites = json['additionalUserInvites'] as String?
|
||||||
..tutorialDisplayed = (json['tutorialDisplayed'] as List<dynamic>?)
|
..tutorialDisplayed = (json['tutorialDisplayed'] as List<dynamic>?)
|
||||||
|
|
@ -63,7 +60,6 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) =>
|
||||||
: DateTime.parse(json['signalLastSignedPreKeyUpdated'] as String)
|
: DateTime.parse(json['signalLastSignedPreKeyUpdated'] as String)
|
||||||
..allowErrorTrackingViaSentry =
|
..allowErrorTrackingViaSentry =
|
||||||
json['allowErrorTrackingViaSentry'] as bool? ?? false
|
json['allowErrorTrackingViaSentry'] as bool? ?? false
|
||||||
..screenLockEnabled = json['screenLockEnabled'] as bool? ?? false
|
|
||||||
..currentPreKeyIndexStart =
|
..currentPreKeyIndexStart =
|
||||||
(json['currentPreKeyIndexStart'] as num?)?.toInt() ?? 100000
|
(json['currentPreKeyIndexStart'] as num?)?.toInt() ?? 100000
|
||||||
..currentSignedPreKeyIndexStart =
|
..currentSignedPreKeyIndexStart =
|
||||||
|
|
@ -109,7 +105,6 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
||||||
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
|
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
|
||||||
'defaultShowTime': instance.defaultShowTime,
|
'defaultShowTime': instance.defaultShowTime,
|
||||||
'requestedAudioPermission': instance.requestedAudioPermission,
|
'requestedAudioPermission': instance.requestedAudioPermission,
|
||||||
'videoStabilizationEnabled': instance.videoStabilizationEnabled,
|
|
||||||
'showFeedbackShortcut': instance.showFeedbackShortcut,
|
'showFeedbackShortcut': instance.showFeedbackShortcut,
|
||||||
'showShowImagePreviewWhenSending': instance.showShowImagePreviewWhenSending,
|
'showShowImagePreviewWhenSending': instance.showShowImagePreviewWhenSending,
|
||||||
'startWithCameraOpen': instance.startWithCameraOpen,
|
'startWithCameraOpen': instance.startWithCameraOpen,
|
||||||
|
|
@ -118,7 +113,6 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
||||||
'storeMediaFilesInGallery': instance.storeMediaFilesInGallery,
|
'storeMediaFilesInGallery': instance.storeMediaFilesInGallery,
|
||||||
'autoStoreAllSendUnlimitedMediaFiles':
|
'autoStoreAllSendUnlimitedMediaFiles':
|
||||||
instance.autoStoreAllSendUnlimitedMediaFiles,
|
instance.autoStoreAllSendUnlimitedMediaFiles,
|
||||||
'typingIndicators': instance.typingIndicators,
|
|
||||||
'lastPlanBallance': instance.lastPlanBallance,
|
'lastPlanBallance': instance.lastPlanBallance,
|
||||||
'additionalUserInvites': instance.additionalUserInvites,
|
'additionalUserInvites': instance.additionalUserInvites,
|
||||||
'tutorialDisplayed': instance.tutorialDisplayed,
|
'tutorialDisplayed': instance.tutorialDisplayed,
|
||||||
|
|
@ -126,7 +120,6 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
||||||
'signalLastSignedPreKeyUpdated': instance.signalLastSignedPreKeyUpdated
|
'signalLastSignedPreKeyUpdated': instance.signalLastSignedPreKeyUpdated
|
||||||
?.toIso8601String(),
|
?.toIso8601String(),
|
||||||
'allowErrorTrackingViaSentry': instance.allowErrorTrackingViaSentry,
|
'allowErrorTrackingViaSentry': instance.allowErrorTrackingViaSentry,
|
||||||
'screenLockEnabled': instance.screenLockEnabled,
|
|
||||||
'currentPreKeyIndexStart': instance.currentPreKeyIndexStart,
|
'currentPreKeyIndexStart': instance.currentPreKeyIndexStart,
|
||||||
'currentSignedPreKeyIndexStart': instance.currentSignedPreKeyIndexStart,
|
'currentSignedPreKeyIndexStart': instance.currentSignedPreKeyIndexStart,
|
||||||
'lastChangeLogHash': instance.lastChangeLogHash,
|
'lastChangeLogHash': instance.lastChangeLogHash,
|
||||||
|
|
|
||||||
|
|
@ -11,3 +11,4 @@
|
||||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||||
|
|
||||||
export 'http_requests.pb.dart';
|
export 'http_requests.pb.dart';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,3 +11,4 @@
|
||||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||||
|
|
||||||
export 'client_to_server.pb.dart';
|
export 'client_to_server.pb.dart';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,3 +11,4 @@
|
||||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||||
|
|
||||||
export 'error.pb.dart';
|
export 'error.pb.dart';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,3 +11,4 @@
|
||||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||||
|
|
||||||
export 'server_to_client.pb.dart';
|
export 'server_to_client.pb.dart';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,3 +11,4 @@
|
||||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||||
|
|
||||||
export 'backup.pb.dart';
|
export 'backup.pb.dart';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,3 +11,4 @@
|
||||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||||
|
|
||||||
export 'groups.pb.dart';
|
export 'groups.pb.dart';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1692,79 +1692,6 @@ class EncryptedContent_FlameSync extends $pb.GeneratedMessage {
|
||||||
void clearForceUpdate() => $_clearField(4);
|
void clearForceUpdate() => $_clearField(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
class EncryptedContent_TypingIndicator extends $pb.GeneratedMessage {
|
|
||||||
factory EncryptedContent_TypingIndicator({
|
|
||||||
$core.bool? isTyping,
|
|
||||||
$fixnum.Int64? createdAt,
|
|
||||||
}) {
|
|
||||||
final result = create();
|
|
||||||
if (isTyping != null) result.isTyping = isTyping;
|
|
||||||
if (createdAt != null) result.createdAt = createdAt;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
EncryptedContent_TypingIndicator._();
|
|
||||||
|
|
||||||
factory EncryptedContent_TypingIndicator.fromBuffer(
|
|
||||||
$core.List<$core.int> data,
|
|
||||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
|
||||||
create()..mergeFromBuffer(data, registry);
|
|
||||||
factory EncryptedContent_TypingIndicator.fromJson($core.String json,
|
|
||||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
|
||||||
create()..mergeFromJson(json, registry);
|
|
||||||
|
|
||||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
|
||||||
_omitMessageNames ? '' : 'EncryptedContent.TypingIndicator',
|
|
||||||
createEmptyInstance: create)
|
|
||||||
..aOB(1, _omitFieldNames ? '' : 'isTyping')
|
|
||||||
..aInt64(2, _omitFieldNames ? '' : 'createdAt')
|
|
||||||
..hasRequiredFields = false;
|
|
||||||
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
|
||||||
EncryptedContent_TypingIndicator clone() =>
|
|
||||||
EncryptedContent_TypingIndicator()..mergeFromMessage(this);
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
|
||||||
EncryptedContent_TypingIndicator copyWith(
|
|
||||||
void Function(EncryptedContent_TypingIndicator) updates) =>
|
|
||||||
super.copyWith(
|
|
||||||
(message) => updates(message as EncryptedContent_TypingIndicator))
|
|
||||||
as EncryptedContent_TypingIndicator;
|
|
||||||
|
|
||||||
@$core.override
|
|
||||||
$pb.BuilderInfo get info_ => _i;
|
|
||||||
|
|
||||||
@$core.pragma('dart2js:noInline')
|
|
||||||
static EncryptedContent_TypingIndicator create() =>
|
|
||||||
EncryptedContent_TypingIndicator._();
|
|
||||||
@$core.override
|
|
||||||
EncryptedContent_TypingIndicator createEmptyInstance() => create();
|
|
||||||
static $pb.PbList<EncryptedContent_TypingIndicator> createRepeated() =>
|
|
||||||
$pb.PbList<EncryptedContent_TypingIndicator>();
|
|
||||||
@$core.pragma('dart2js:noInline')
|
|
||||||
static EncryptedContent_TypingIndicator getDefault() => _defaultInstance ??=
|
|
||||||
$pb.GeneratedMessage.$_defaultFor<EncryptedContent_TypingIndicator>(
|
|
||||||
create);
|
|
||||||
static EncryptedContent_TypingIndicator? _defaultInstance;
|
|
||||||
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
$core.bool get isTyping => $_getBF(0);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
set isTyping($core.bool value) => $_setBool(0, value);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
$core.bool hasIsTyping() => $_has(0);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
void clearIsTyping() => $_clearField(1);
|
|
||||||
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
$fixnum.Int64 get createdAt => $_getI64(1);
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
set createdAt($fixnum.Int64 value) => $_setInt64(1, value);
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
$core.bool hasCreatedAt() => $_has(1);
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
void clearCreatedAt() => $_clearField(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
class EncryptedContent extends $pb.GeneratedMessage {
|
class EncryptedContent extends $pb.GeneratedMessage {
|
||||||
factory EncryptedContent({
|
factory EncryptedContent({
|
||||||
$core.String? groupId,
|
$core.String? groupId,
|
||||||
|
|
@ -1785,7 +1712,6 @@ class EncryptedContent extends $pb.GeneratedMessage {
|
||||||
EncryptedContent_ResendGroupPublicKey? resendGroupPublicKey,
|
EncryptedContent_ResendGroupPublicKey? resendGroupPublicKey,
|
||||||
EncryptedContent_ErrorMessages? errorMessages,
|
EncryptedContent_ErrorMessages? errorMessages,
|
||||||
EncryptedContent_AdditionalDataMessage? additionalDataMessage,
|
EncryptedContent_AdditionalDataMessage? additionalDataMessage,
|
||||||
EncryptedContent_TypingIndicator? typingIndicator,
|
|
||||||
}) {
|
}) {
|
||||||
final result = create();
|
final result = create();
|
||||||
if (groupId != null) result.groupId = groupId;
|
if (groupId != null) result.groupId = groupId;
|
||||||
|
|
@ -1809,7 +1735,6 @@ class EncryptedContent extends $pb.GeneratedMessage {
|
||||||
if (errorMessages != null) result.errorMessages = errorMessages;
|
if (errorMessages != null) result.errorMessages = errorMessages;
|
||||||
if (additionalDataMessage != null)
|
if (additionalDataMessage != null)
|
||||||
result.additionalDataMessage = additionalDataMessage;
|
result.additionalDataMessage = additionalDataMessage;
|
||||||
if (typingIndicator != null) result.typingIndicator = typingIndicator;
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1876,9 +1801,6 @@ class EncryptedContent extends $pb.GeneratedMessage {
|
||||||
..aOM<EncryptedContent_AdditionalDataMessage>(
|
..aOM<EncryptedContent_AdditionalDataMessage>(
|
||||||
19, _omitFieldNames ? '' : 'additionalDataMessage',
|
19, _omitFieldNames ? '' : 'additionalDataMessage',
|
||||||
subBuilder: EncryptedContent_AdditionalDataMessage.create)
|
subBuilder: EncryptedContent_AdditionalDataMessage.create)
|
||||||
..aOM<EncryptedContent_TypingIndicator>(
|
|
||||||
20, _omitFieldNames ? '' : 'typingIndicator',
|
|
||||||
subBuilder: EncryptedContent_TypingIndicator.create)
|
|
||||||
..hasRequiredFields = false;
|
..hasRequiredFields = false;
|
||||||
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
|
@ -2103,18 +2025,6 @@ class EncryptedContent extends $pb.GeneratedMessage {
|
||||||
@$pb.TagNumber(19)
|
@$pb.TagNumber(19)
|
||||||
EncryptedContent_AdditionalDataMessage ensureAdditionalDataMessage() =>
|
EncryptedContent_AdditionalDataMessage ensureAdditionalDataMessage() =>
|
||||||
$_ensure(17);
|
$_ensure(17);
|
||||||
|
|
||||||
@$pb.TagNumber(20)
|
|
||||||
EncryptedContent_TypingIndicator get typingIndicator => $_getN(18);
|
|
||||||
@$pb.TagNumber(20)
|
|
||||||
set typingIndicator(EncryptedContent_TypingIndicator value) =>
|
|
||||||
$_setField(20, value);
|
|
||||||
@$pb.TagNumber(20)
|
|
||||||
$core.bool hasTypingIndicator() => $_has(18);
|
|
||||||
@$pb.TagNumber(20)
|
|
||||||
void clearTypingIndicator() => $_clearField(20);
|
|
||||||
@$pb.TagNumber(20)
|
|
||||||
EncryptedContent_TypingIndicator ensureTypingIndicator() => $_ensure(18);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const $core.bool _omitFieldNames =
|
const $core.bool _omitFieldNames =
|
||||||
|
|
|
||||||
|
|
@ -326,16 +326,6 @@ const EncryptedContent$json = {
|
||||||
'10': 'additionalDataMessage',
|
'10': 'additionalDataMessage',
|
||||||
'17': true
|
'17': true
|
||||||
},
|
},
|
||||||
{
|
|
||||||
'1': 'typing_indicator',
|
|
||||||
'3': 20,
|
|
||||||
'4': 1,
|
|
||||||
'5': 11,
|
|
||||||
'6': '.EncryptedContent.TypingIndicator',
|
|
||||||
'9': 18,
|
|
||||||
'10': 'typingIndicator',
|
|
||||||
'17': true
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
'3': [
|
'3': [
|
||||||
EncryptedContent_ErrorMessages$json,
|
EncryptedContent_ErrorMessages$json,
|
||||||
|
|
@ -352,8 +342,7 @@ const EncryptedContent$json = {
|
||||||
EncryptedContent_ContactRequest$json,
|
EncryptedContent_ContactRequest$json,
|
||||||
EncryptedContent_ContactUpdate$json,
|
EncryptedContent_ContactUpdate$json,
|
||||||
EncryptedContent_PushKeys$json,
|
EncryptedContent_PushKeys$json,
|
||||||
EncryptedContent_FlameSync$json,
|
EncryptedContent_FlameSync$json
|
||||||
EncryptedContent_TypingIndicator$json
|
|
||||||
],
|
],
|
||||||
'8': [
|
'8': [
|
||||||
{'1': '_groupId'},
|
{'1': '_groupId'},
|
||||||
|
|
@ -374,7 +363,6 @@ const EncryptedContent$json = {
|
||||||
{'1': '_resendGroupPublicKey'},
|
{'1': '_resendGroupPublicKey'},
|
||||||
{'1': '_error_messages'},
|
{'1': '_error_messages'},
|
||||||
{'1': '_additional_data_message'},
|
{'1': '_additional_data_message'},
|
||||||
{'1': '_typing_indicator'},
|
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -852,15 +840,6 @@ const EncryptedContent_FlameSync$json = {
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
@$core.Deprecated('Use encryptedContentDescriptor instead')
|
|
||||||
const EncryptedContent_TypingIndicator$json = {
|
|
||||||
'1': 'TypingIndicator',
|
|
||||||
'2': [
|
|
||||||
{'1': 'is_typing', '3': 1, '4': 1, '5': 8, '10': 'isTyping'},
|
|
||||||
{'1': 'created_at', '3': 2, '4': 1, '5': 3, '10': 'createdAt'},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `EncryptedContent`. Decode as a `google.protobuf.DescriptorProto`.
|
/// Descriptor for `EncryptedContent`. Decode as a `google.protobuf.DescriptorProto`.
|
||||||
final $typed_data.Uint8List encryptedContentDescriptor = $convert.base64Decode(
|
final $typed_data.Uint8List encryptedContentDescriptor = $convert.base64Decode(
|
||||||
'ChBFbmNyeXB0ZWRDb250ZW50Eh0KB2dyb3VwSWQYAiABKAlIAFIHZ3JvdXBJZIgBARInCgxpc0'
|
'ChBFbmNyeXB0ZWRDb250ZW50Eh0KB2dyb3VwSWQYAiABKAlIAFIHZ3JvdXBJZIgBARInCgxpc0'
|
||||||
|
|
@ -885,71 +864,68 @@ final $typed_data.Uint8List encryptedContentDescriptor = $convert.base64Decode(
|
||||||
'gBARJLCg5lcnJvcl9tZXNzYWdlcxgSIAEoCzIfLkVuY3J5cHRlZENvbnRlbnQuRXJyb3JNZXNz'
|
'gBARJLCg5lcnJvcl9tZXNzYWdlcxgSIAEoCzIfLkVuY3J5cHRlZENvbnRlbnQuRXJyb3JNZXNz'
|
||||||
'YWdlc0gQUg1lcnJvck1lc3NhZ2VziAEBEmQKF2FkZGl0aW9uYWxfZGF0YV9tZXNzYWdlGBMgAS'
|
'YWdlc0gQUg1lcnJvck1lc3NhZ2VziAEBEmQKF2FkZGl0aW9uYWxfZGF0YV9tZXNzYWdlGBMgAS'
|
||||||
'gLMicuRW5jcnlwdGVkQ29udGVudC5BZGRpdGlvbmFsRGF0YU1lc3NhZ2VIEVIVYWRkaXRpb25h'
|
'gLMicuRW5jcnlwdGVkQ29udGVudC5BZGRpdGlvbmFsRGF0YU1lc3NhZ2VIEVIVYWRkaXRpb25h'
|
||||||
'bERhdGFNZXNzYWdliAEBElEKEHR5cGluZ19pbmRpY2F0b3IYFCABKAsyIS5FbmNyeXB0ZWRDb2'
|
'bERhdGFNZXNzYWdliAEBGvABCg1FcnJvck1lc3NhZ2VzEjgKBHR5cGUYASABKA4yJC5FbmNyeX'
|
||||||
'50ZW50LlR5cGluZ0luZGljYXRvckgSUg90eXBpbmdJbmRpY2F0b3KIAQEa8AEKDUVycm9yTWVz'
|
'B0ZWRDb250ZW50LkVycm9yTWVzc2FnZXMuVHlwZVIEdHlwZRIsChJyZWxhdGVkX3JlY2VpcHRf'
|
||||||
'c2FnZXMSOAoEdHlwZRgBIAEoDjIkLkVuY3J5cHRlZENvbnRlbnQuRXJyb3JNZXNzYWdlcy5UeX'
|
'aWQYAiABKAlSEHJlbGF0ZWRSZWNlaXB0SWQidwoEVHlwZRI8CjhFUlJPUl9QUk9DRVNTSU5HX0'
|
||||||
'BlUgR0eXBlEiwKEnJlbGF0ZWRfcmVjZWlwdF9pZBgCIAEoCVIQcmVsYXRlZFJlY2VpcHRJZCJ3'
|
'1FU1NBR0VfQ1JFQVRFRF9BQ0NPVU5UX1JFUVVFU1RfSU5TVEVBRBAAEhgKFFVOS05PV05fTUVT'
|
||||||
'CgRUeXBlEjwKOEVSUk9SX1BST0NFU1NJTkdfTUVTU0FHRV9DUkVBVEVEX0FDQ09VTlRfUkVRVU'
|
'U0FHRV9UWVBFEAISFwoTU0VTU0lPTl9PVVRfT0ZfU1lOQxADGlEKC0dyb3VwQ3JlYXRlEhoKCH'
|
||||||
'VTVF9JTlNURUFEEAASGAoUVU5LTk9XTl9NRVNTQUdFX1RZUEUQAhIXChNTRVNTSU9OX09VVF9P'
|
'N0YXRlS2V5GAMgASgMUghzdGF0ZUtleRImCg5ncm91cFB1YmxpY0tleRgEIAEoDFIOZ3JvdXBQ'
|
||||||
'Rl9TWU5DEAMaUQoLR3JvdXBDcmVhdGUSGgoIc3RhdGVLZXkYAyABKAxSCHN0YXRlS2V5EiYKDm'
|
'dWJsaWNLZXkaMwoJR3JvdXBKb2luEiYKDmdyb3VwUHVibGljS2V5GAEgASgMUg5ncm91cFB1Ym'
|
||||||
'dyb3VwUHVibGljS2V5GAQgASgMUg5ncm91cFB1YmxpY0tleRozCglHcm91cEpvaW4SJgoOZ3Jv'
|
'xpY0tleRoWChRSZXNlbmRHcm91cFB1YmxpY0tleRq2AgoLR3JvdXBVcGRhdGUSKAoPZ3JvdXBB'
|
||||||
'dXBQdWJsaWNLZXkYASABKAxSDmdyb3VwUHVibGljS2V5GhYKFFJlc2VuZEdyb3VwUHVibGljS2'
|
'Y3Rpb25UeXBlGAEgASgJUg9ncm91cEFjdGlvblR5cGUSMQoRYWZmZWN0ZWRDb250YWN0SWQYAi'
|
||||||
'V5GrYCCgtHcm91cFVwZGF0ZRIoCg9ncm91cEFjdGlvblR5cGUYASABKAlSD2dyb3VwQWN0aW9u'
|
'ABKANIAFIRYWZmZWN0ZWRDb250YWN0SWSIAQESJwoMbmV3R3JvdXBOYW1lGAMgASgJSAFSDG5l'
|
||||||
'VHlwZRIxChFhZmZlY3RlZENvbnRhY3RJZBgCIAEoA0gAUhFhZmZlY3RlZENvbnRhY3RJZIgBAR'
|
'd0dyb3VwTmFtZYgBARJTCiJuZXdEZWxldGVNZXNzYWdlc0FmdGVyTWlsbGlzZWNvbmRzGAQgAS'
|
||||||
'InCgxuZXdHcm91cE5hbWUYAyABKAlIAVIMbmV3R3JvdXBOYW1liAEBElMKIm5ld0RlbGV0ZU1l'
|
'gDSAJSIm5ld0RlbGV0ZU1lc3NhZ2VzQWZ0ZXJNaWxsaXNlY29uZHOIAQFCFAoSX2FmZmVjdGVk'
|
||||||
'c3NhZ2VzQWZ0ZXJNaWxsaXNlY29uZHMYBCABKANIAlIibmV3RGVsZXRlTWVzc2FnZXNBZnRlck'
|
'Q29udGFjdElkQg8KDV9uZXdHcm91cE5hbWVCJQojX25ld0RlbGV0ZU1lc3NhZ2VzQWZ0ZXJNaW'
|
||||||
'1pbGxpc2Vjb25kc4gBAUIUChJfYWZmZWN0ZWRDb250YWN0SWRCDwoNX25ld0dyb3VwTmFtZUIl'
|
'xsaXNlY29uZHMaqQEKC1RleHRNZXNzYWdlEigKD3NlbmRlck1lc3NhZ2VJZBgBIAEoCVIPc2Vu'
|
||||||
'CiNfbmV3RGVsZXRlTWVzc2FnZXNBZnRlck1pbGxpc2Vjb25kcxqpAQoLVGV4dE1lc3NhZ2USKA'
|
'ZGVyTWVzc2FnZUlkEhIKBHRleHQYAiABKAlSBHRleHQSHAoJdGltZXN0YW1wGAMgASgDUgl0aW'
|
||||||
'oPc2VuZGVyTWVzc2FnZUlkGAEgASgJUg9zZW5kZXJNZXNzYWdlSWQSEgoEdGV4dBgCIAEoCVIE'
|
'1lc3RhbXASKwoOcXVvdGVNZXNzYWdlSWQYBCABKAlIAFIOcXVvdGVNZXNzYWdlSWSIAQFCEQoP'
|
||||||
'dGV4dBIcCgl0aW1lc3RhbXAYAyABKANSCXRpbWVzdGFtcBIrCg5xdW90ZU1lc3NhZ2VJZBgEIA'
|
'X3F1b3RlTWVzc2FnZUlkGs4BChVBZGRpdGlvbmFsRGF0YU1lc3NhZ2USKgoRc2VuZGVyX21lc3'
|
||||||
'EoCUgAUg5xdW90ZU1lc3NhZ2VJZIgBAUIRCg9fcXVvdGVNZXNzYWdlSWQazgEKFUFkZGl0aW9u'
|
'NhZ2VfaWQYASABKAlSD3NlbmRlck1lc3NhZ2VJZBIcCgl0aW1lc3RhbXAYAiABKANSCXRpbWVz'
|
||||||
'YWxEYXRhTWVzc2FnZRIqChFzZW5kZXJfbWVzc2FnZV9pZBgBIAEoCVIPc2VuZGVyTWVzc2FnZU'
|
'dGFtcBISCgR0eXBlGAMgASgJUgR0eXBlEjsKF2FkZGl0aW9uYWxfbWVzc2FnZV9kYXRhGAQgAS'
|
||||||
'lkEhwKCXRpbWVzdGFtcBgCIAEoA1IJdGltZXN0YW1wEhIKBHR5cGUYAyABKAlSBHR5cGUSOwoX'
|
'gMSABSFWFkZGl0aW9uYWxNZXNzYWdlRGF0YYgBAUIaChhfYWRkaXRpb25hbF9tZXNzYWdlX2Rh'
|
||||||
'YWRkaXRpb25hbF9tZXNzYWdlX2RhdGEYBCABKAxIAFIVYWRkaXRpb25hbE1lc3NhZ2VEYXRhiA'
|
'dGEaYgoIUmVhY3Rpb24SKAoPdGFyZ2V0TWVzc2FnZUlkGAEgASgJUg90YXJnZXRNZXNzYWdlSW'
|
||||||
'EBQhoKGF9hZGRpdGlvbmFsX21lc3NhZ2VfZGF0YRpiCghSZWFjdGlvbhIoCg90YXJnZXRNZXNz'
|
'QSFAoFZW1vamkYAiABKAlSBWVtb2ppEhYKBnJlbW92ZRgDIAEoCFIGcmVtb3ZlGrcCCg1NZXNz'
|
||||||
'YWdlSWQYASABKAlSD3RhcmdldE1lc3NhZ2VJZBIUCgVlbW9qaRgCIAEoCVIFZW1vamkSFgoGcm'
|
'YWdlVXBkYXRlEjgKBHR5cGUYASABKA4yJC5FbmNyeXB0ZWRDb250ZW50Lk1lc3NhZ2VVcGRhdG'
|
||||||
'Vtb3ZlGAMgASgIUgZyZW1vdmUatwIKDU1lc3NhZ2VVcGRhdGUSOAoEdHlwZRgBIAEoDjIkLkVu'
|
'UuVHlwZVIEdHlwZRItCg9zZW5kZXJNZXNzYWdlSWQYAiABKAlIAFIPc2VuZGVyTWVzc2FnZUlk'
|
||||||
'Y3J5cHRlZENvbnRlbnQuTWVzc2FnZVVwZGF0ZS5UeXBlUgR0eXBlEi0KD3NlbmRlck1lc3NhZ2'
|
'iAEBEjoKGG11bHRpcGxlVGFyZ2V0TWVzc2FnZUlkcxgDIAMoCVIYbXVsdGlwbGVUYXJnZXRNZX'
|
||||||
'VJZBgCIAEoCUgAUg9zZW5kZXJNZXNzYWdlSWSIAQESOgoYbXVsdGlwbGVUYXJnZXRNZXNzYWdl'
|
'NzYWdlSWRzEhcKBHRleHQYBCABKAlIAVIEdGV4dIgBARIcCgl0aW1lc3RhbXAYBSABKANSCXRp'
|
||||||
'SWRzGAMgAygJUhhtdWx0aXBsZVRhcmdldE1lc3NhZ2VJZHMSFwoEdGV4dBgEIAEoCUgBUgR0ZX'
|
'bWVzdGFtcCItCgRUeXBlEgoKBkRFTEVURRAAEg0KCUVESVRfVEVYVBABEgoKBk9QRU5FRBACQh'
|
||||||
'h0iAEBEhwKCXRpbWVzdGFtcBgFIAEoA1IJdGltZXN0YW1wIi0KBFR5cGUSCgoGREVMRVRFEAAS'
|
'IKEF9zZW5kZXJNZXNzYWdlSWRCBwoFX3RleHQa8AUKBU1lZGlhEigKD3NlbmRlck1lc3NhZ2VJ'
|
||||||
'DQoJRURJVF9URVhUEAESCgoGT1BFTkVEEAJCEgoQX3NlbmRlck1lc3NhZ2VJZEIHCgVfdGV4dB'
|
'ZBgBIAEoCVIPc2VuZGVyTWVzc2FnZUlkEjAKBHR5cGUYAiABKA4yHC5FbmNyeXB0ZWRDb250ZW'
|
||||||
'rwBQoFTWVkaWESKAoPc2VuZGVyTWVzc2FnZUlkGAEgASgJUg9zZW5kZXJNZXNzYWdlSWQSMAoE'
|
'50Lk1lZGlhLlR5cGVSBHR5cGUSQwoaZGlzcGxheUxpbWl0SW5NaWxsaXNlY29uZHMYAyABKANI'
|
||||||
'dHlwZRgCIAEoDjIcLkVuY3J5cHRlZENvbnRlbnQuTWVkaWEuVHlwZVIEdHlwZRJDChpkaXNwbG'
|
'AFIaZGlzcGxheUxpbWl0SW5NaWxsaXNlY29uZHOIAQESNgoWcmVxdWlyZXNBdXRoZW50aWNhdG'
|
||||||
'F5TGltaXRJbk1pbGxpc2Vjb25kcxgDIAEoA0gAUhpkaXNwbGF5TGltaXRJbk1pbGxpc2Vjb25k'
|
'lvbhgEIAEoCFIWcmVxdWlyZXNBdXRoZW50aWNhdGlvbhIcCgl0aW1lc3RhbXAYBSABKANSCXRp'
|
||||||
'c4gBARI2ChZyZXF1aXJlc0F1dGhlbnRpY2F0aW9uGAQgASgIUhZyZXF1aXJlc0F1dGhlbnRpY2'
|
'bWVzdGFtcBIrCg5xdW90ZU1lc3NhZ2VJZBgGIAEoCUgBUg5xdW90ZU1lc3NhZ2VJZIgBARIpCg'
|
||||||
'F0aW9uEhwKCXRpbWVzdGFtcBgFIAEoA1IJdGltZXN0YW1wEisKDnF1b3RlTWVzc2FnZUlkGAYg'
|
'1kb3dubG9hZFRva2VuGAcgASgMSAJSDWRvd25sb2FkVG9rZW6IAQESKQoNZW5jcnlwdGlvbktl'
|
||||||
'ASgJSAFSDnF1b3RlTWVzc2FnZUlkiAEBEikKDWRvd25sb2FkVG9rZW4YByABKAxIAlINZG93bm'
|
'eRgIIAEoDEgDUg1lbmNyeXB0aW9uS2V5iAEBEikKDWVuY3J5cHRpb25NYWMYCSABKAxIBFINZW'
|
||||||
'xvYWRUb2tlbogBARIpCg1lbmNyeXB0aW9uS2V5GAggASgMSANSDWVuY3J5cHRpb25LZXmIAQES'
|
'5jcnlwdGlvbk1hY4gBARItCg9lbmNyeXB0aW9uTm9uY2UYCiABKAxIBVIPZW5jcnlwdGlvbk5v'
|
||||||
'KQoNZW5jcnlwdGlvbk1hYxgJIAEoDEgEUg1lbmNyeXB0aW9uTWFjiAEBEi0KD2VuY3J5cHRpb2'
|
'bmNliAEBEjsKF2FkZGl0aW9uYWxfbWVzc2FnZV9kYXRhGAsgASgMSAZSFWFkZGl0aW9uYWxNZX'
|
||||||
'5Ob25jZRgKIAEoDEgFUg9lbmNyeXB0aW9uTm9uY2WIAQESOwoXYWRkaXRpb25hbF9tZXNzYWdl'
|
'NzYWdlRGF0YYgBASI+CgRUeXBlEgwKCFJFVVBMT0FEEAASCQoFSU1BR0UQARIJCgVWSURFTxAC'
|
||||||
'X2RhdGEYCyABKAxIBlIVYWRkaXRpb25hbE1lc3NhZ2VEYXRhiAEBIj4KBFR5cGUSDAoIUkVVUE'
|
'EgcKA0dJRhADEgkKBUFVRElPEARCHQobX2Rpc3BsYXlMaW1pdEluTWlsbGlzZWNvbmRzQhEKD1'
|
||||||
'xPQUQQABIJCgVJTUFHRRABEgkKBVZJREVPEAISBwoDR0lGEAMSCQoFQVVESU8QBEIdChtfZGlz'
|
'9xdW90ZU1lc3NhZ2VJZEIQCg5fZG93bmxvYWRUb2tlbkIQCg5fZW5jcnlwdGlvbktleUIQCg5f'
|
||||||
'cGxheUxpbWl0SW5NaWxsaXNlY29uZHNCEQoPX3F1b3RlTWVzc2FnZUlkQhAKDl9kb3dubG9hZF'
|
'ZW5jcnlwdGlvbk1hY0ISChBfZW5jcnlwdGlvbk5vbmNlQhoKGF9hZGRpdGlvbmFsX21lc3NhZ2'
|
||||||
'Rva2VuQhAKDl9lbmNyeXB0aW9uS2V5QhAKDl9lbmNyeXB0aW9uTWFjQhIKEF9lbmNyeXB0aW9u'
|
'VfZGF0YRqnAQoLTWVkaWFVcGRhdGUSNgoEdHlwZRgBIAEoDjIiLkVuY3J5cHRlZENvbnRlbnQu'
|
||||||
'Tm9uY2VCGgoYX2FkZGl0aW9uYWxfbWVzc2FnZV9kYXRhGqcBCgtNZWRpYVVwZGF0ZRI2CgR0eX'
|
'TWVkaWFVcGRhdGUuVHlwZVIEdHlwZRIoCg90YXJnZXRNZXNzYWdlSWQYAiABKAlSD3RhcmdldE'
|
||||||
'BlGAEgASgOMiIuRW5jcnlwdGVkQ29udGVudC5NZWRpYVVwZGF0ZS5UeXBlUgR0eXBlEigKD3Rh'
|
'1lc3NhZ2VJZCI2CgRUeXBlEgwKCFJFT1BFTkVEEAASCgoGU1RPUkVEEAESFAoQREVDUllQVElP'
|
||||||
'cmdldE1lc3NhZ2VJZBgCIAEoCVIPdGFyZ2V0TWVzc2FnZUlkIjYKBFR5cGUSDAoIUkVPUEVORU'
|
'Tl9FUlJPUhACGngKDkNvbnRhY3RSZXF1ZXN0EjkKBHR5cGUYASABKA4yJS5FbmNyeXB0ZWRDb2'
|
||||||
'QQABIKCgZTVE9SRUQQARIUChBERUNSWVBUSU9OX0VSUk9SEAIaeAoOQ29udGFjdFJlcXVlc3QS'
|
'50ZW50LkNvbnRhY3RSZXF1ZXN0LlR5cGVSBHR5cGUiKwoEVHlwZRILCgdSRVFVRVNUEAASCgoG'
|
||||||
'OQoEdHlwZRgBIAEoDjIlLkVuY3J5cHRlZENvbnRlbnQuQ29udGFjdFJlcXVlc3QuVHlwZVIEdH'
|
'UkVKRUNUEAESCgoGQUNDRVBUEAIangIKDUNvbnRhY3RVcGRhdGUSOAoEdHlwZRgBIAEoDjIkLk'
|
||||||
'lwZSIrCgRUeXBlEgsKB1JFUVVFU1QQABIKCgZSRUpFQ1QQARIKCgZBQ0NFUFQQAhqeAgoNQ29u'
|
'VuY3J5cHRlZENvbnRlbnQuQ29udGFjdFVwZGF0ZS5UeXBlUgR0eXBlEjUKE2F2YXRhclN2Z0Nv'
|
||||||
'dGFjdFVwZGF0ZRI4CgR0eXBlGAEgASgOMiQuRW5jcnlwdGVkQ29udGVudC5Db250YWN0VXBkYX'
|
'bXByZXNzZWQYAiABKAxIAFITYXZhdGFyU3ZnQ29tcHJlc3NlZIgBARIfCgh1c2VybmFtZRgDIA'
|
||||||
'RlLlR5cGVSBHR5cGUSNQoTYXZhdGFyU3ZnQ29tcHJlc3NlZBgCIAEoDEgAUhNhdmF0YXJTdmdD'
|
'EoCUgBUgh1c2VybmFtZYgBARIlCgtkaXNwbGF5TmFtZRgEIAEoCUgCUgtkaXNwbGF5TmFtZYgB'
|
||||||
'b21wcmVzc2VkiAEBEh8KCHVzZXJuYW1lGAMgASgJSAFSCHVzZXJuYW1liAEBEiUKC2Rpc3BsYX'
|
'ASIfCgRUeXBlEgsKB1JFUVVFU1QQABIKCgZVUERBVEUQAUIWChRfYXZhdGFyU3ZnQ29tcHJlc3'
|
||||||
'lOYW1lGAQgASgJSAJSC2Rpc3BsYXlOYW1liAEBIh8KBFR5cGUSCwoHUkVRVUVTVBAAEgoKBlVQ'
|
'NlZEILCglfdXNlcm5hbWVCDgoMX2Rpc3BsYXlOYW1lGtUBCghQdXNoS2V5cxIzCgR0eXBlGAEg'
|
||||||
'REFURRABQhYKFF9hdmF0YXJTdmdDb21wcmVzc2VkQgsKCV91c2VybmFtZUIOCgxfZGlzcGxheU'
|
'ASgOMh8uRW5jcnlwdGVkQ29udGVudC5QdXNoS2V5cy5UeXBlUgR0eXBlEhkKBWtleUlkGAIgAS'
|
||||||
'5hbWUa1QEKCFB1c2hLZXlzEjMKBHR5cGUYASABKA4yHy5FbmNyeXB0ZWRDb250ZW50LlB1c2hL'
|
'gDSABSBWtleUlkiAEBEhUKA2tleRgDIAEoDEgBUgNrZXmIAQESIQoJY3JlYXRlZEF0GAQgASgD'
|
||||||
'ZXlzLlR5cGVSBHR5cGUSGQoFa2V5SWQYAiABKANIAFIFa2V5SWSIAQESFQoDa2V5GAMgASgMSA'
|
'SAJSCWNyZWF0ZWRBdIgBASIfCgRUeXBlEgsKB1JFUVVFU1QQABIKCgZVUERBVEUQAUIICgZfa2'
|
||||||
'FSA2tleYgBARIhCgljcmVhdGVkQXQYBCABKANIAlIJY3JlYXRlZEF0iAEBIh8KBFR5cGUSCwoH'
|
'V5SWRCBgoEX2tleUIMCgpfY3JlYXRlZEF0GqkBCglGbGFtZVN5bmMSIgoMZmxhbWVDb3VudGVy'
|
||||||
'UkVRVUVTVBAAEgoKBlVQREFURRABQggKBl9rZXlJZEIGCgRfa2V5QgwKCl9jcmVhdGVkQXQaqQ'
|
'GAEgASgDUgxmbGFtZUNvdW50ZXISNgoWbGFzdEZsYW1lQ291bnRlckNoYW5nZRgCIAEoA1IWbG'
|
||||||
'EKCUZsYW1lU3luYxIiCgxmbGFtZUNvdW50ZXIYASABKANSDGZsYW1lQ291bnRlchI2ChZsYXN0'
|
'FzdEZsYW1lQ291bnRlckNoYW5nZRIeCgpiZXN0RnJpZW5kGAMgASgIUgpiZXN0RnJpZW5kEiAK'
|
||||||
'RmxhbWVDb3VudGVyQ2hhbmdlGAIgASgDUhZsYXN0RmxhbWVDb3VudGVyQ2hhbmdlEh4KCmJlc3'
|
'C2ZvcmNlVXBkYXRlGAQgASgIUgtmb3JjZVVwZGF0ZUIKCghfZ3JvdXBJZEIPCg1faXNEaXJlY3'
|
||||||
'RGcmllbmQYAyABKAhSCmJlc3RGcmllbmQSIAoLZm9yY2VVcGRhdGUYBCABKAhSC2ZvcmNlVXBk'
|
'RDaGF0QhcKFV9zZW5kZXJQcm9maWxlQ291bnRlckIQCg5fbWVzc2FnZVVwZGF0ZUIICgZfbWVk'
|
||||||
'YXRlGk0KD1R5cGluZ0luZGljYXRvchIbCglpc190eXBpbmcYASABKAhSCGlzVHlwaW5nEh0KCm'
|
'aWFCDgoMX21lZGlhVXBkYXRlQhAKDl9jb250YWN0VXBkYXRlQhEKD19jb250YWN0UmVxdWVzdE'
|
||||||
'NyZWF0ZWRfYXQYAiABKANSCWNyZWF0ZWRBdEIKCghfZ3JvdXBJZEIPCg1faXNEaXJlY3RDaGF0'
|
'IMCgpfZmxhbWVTeW5jQgsKCV9wdXNoS2V5c0ILCglfcmVhY3Rpb25CDgoMX3RleHRNZXNzYWdl'
|
||||||
'QhcKFV9zZW5kZXJQcm9maWxlQ291bnRlckIQCg5fbWVzc2FnZVVwZGF0ZUIICgZfbWVkaWFCDg'
|
'Qg4KDF9ncm91cENyZWF0ZUIMCgpfZ3JvdXBKb2luQg4KDF9ncm91cFVwZGF0ZUIXChVfcmVzZW'
|
||||||
'oMX21lZGlhVXBkYXRlQhAKDl9jb250YWN0VXBkYXRlQhEKD19jb250YWN0UmVxdWVzdEIMCgpf'
|
'5kR3JvdXBQdWJsaWNLZXlCEQoPX2Vycm9yX21lc3NhZ2VzQhoKGF9hZGRpdGlvbmFsX2RhdGFf'
|
||||||
'ZmxhbWVTeW5jQgsKCV9wdXNoS2V5c0ILCglfcmVhY3Rpb25CDgoMX3RleHRNZXNzYWdlQg4KDF'
|
'bWVzc2FnZQ==');
|
||||||
'9ncm91cENyZWF0ZUIMCgpfZ3JvdXBKb2luQg4KDF9ncm91cFVwZGF0ZUIXChVfcmVzZW5kR3Jv'
|
|
||||||
'dXBQdWJsaWNLZXlCEQoPX2Vycm9yX21lc3NhZ2VzQhoKGF9hZGRpdGlvbmFsX2RhdGFfbWVzc2'
|
|
||||||
'FnZUITChFfdHlwaW5nX2luZGljYXRvcg==');
|
|
||||||
|
|
|
||||||
|
|
@ -11,3 +11,4 @@
|
||||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||||
|
|
||||||
export 'messages.pb.dart';
|
export 'messages.pb.dart';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,3 +11,4 @@
|
||||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||||
|
|
||||||
export 'push_notification.pb.dart';
|
export 'push_notification.pb.dart';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,6 @@ message EncryptedContent {
|
||||||
optional ResendGroupPublicKey resendGroupPublicKey = 17;
|
optional ResendGroupPublicKey resendGroupPublicKey = 17;
|
||||||
optional ErrorMessages error_messages = 18;
|
optional ErrorMessages error_messages = 18;
|
||||||
optional AdditionalDataMessage additional_data_message = 19;
|
optional AdditionalDataMessage additional_data_message = 19;
|
||||||
optional TypingIndicator typing_indicator = 20;
|
|
||||||
|
|
||||||
message ErrorMessages {
|
message ErrorMessages {
|
||||||
enum Type {
|
enum Type {
|
||||||
|
|
@ -195,9 +194,4 @@ message EncryptedContent {
|
||||||
bool forceUpdate = 4;
|
bool forceUpdate = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message TypingIndicator {
|
|
||||||
bool is_typing = 1;
|
|
||||||
int64 created_at = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -133,10 +133,8 @@ class PurchasesProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
||||||
final jsonData = base64Decode(b64Data);
|
final jsonData = base64Decode(b64Data);
|
||||||
final data = jsonDecode(utf8.decode(jsonData)) as Map<String, dynamic>;
|
final data = jsonDecode(utf8.decode(jsonData)) as Map<String, dynamic>;
|
||||||
final expiresDate = data['expiresDate'] as int;
|
final expiresDate = data['expiresDate'] as int;
|
||||||
final dt = DateTime.fromMillisecondsSinceEpoch(
|
final dt =
|
||||||
expiresDate,
|
DateTime.fromMillisecondsSinceEpoch(expiresDate, isUtc: true);
|
||||||
isUtc: true,
|
|
||||||
);
|
|
||||||
if (dt.isBefore(DateTime.now())) {
|
if (dt.isBefore(DateTime.now())) {
|
||||||
Log.warn('ExpiresDate is in the past: $dt');
|
Log.warn('ExpiresDate is in the past: $dt');
|
||||||
if (_userTriggeredBuyButton && Platform.isIOS) {
|
if (_userTriggeredBuyButton && Platform.isIOS) {
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ import 'package:twonly/src/views/settings/data_and_storage/export_media.view.dar
|
||||||
import 'package:twonly/src/views/settings/data_and_storage/import_media.view.dart';
|
import 'package:twonly/src/views/settings/data_and_storage/import_media.view.dart';
|
||||||
import 'package:twonly/src/views/settings/developer/automated_testing.view.dart';
|
import 'package:twonly/src/views/settings/developer/automated_testing.view.dart';
|
||||||
import 'package:twonly/src/views/settings/developer/developer.view.dart';
|
import 'package:twonly/src/views/settings/developer/developer.view.dart';
|
||||||
import 'package:twonly/src/views/settings/developer/reduce_flames.view.dart';
|
|
||||||
import 'package:twonly/src/views/settings/developer/retransmission_data.view.dart';
|
import 'package:twonly/src/views/settings/developer/retransmission_data.view.dart';
|
||||||
import 'package:twonly/src/views/settings/help/changelog.view.dart';
|
import 'package:twonly/src/views/settings/help/changelog.view.dart';
|
||||||
import 'package:twonly/src/views/settings/help/contact_us.view.dart';
|
import 'package:twonly/src/views/settings/help/contact_us.view.dart';
|
||||||
|
|
@ -85,10 +84,10 @@ final routerProvider = GoRouter(
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: 'messages/:groupId',
|
path: 'messages',
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final groupId = state.pathParameters['groupId']!;
|
final group = state.extra! as Group;
|
||||||
return ChatMessagesView(groupId);
|
return ChatMessagesView(group);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -281,10 +280,6 @@ final routerProvider = GoRouter(
|
||||||
path: 'automated_testing',
|
path: 'automated_testing',
|
||||||
builder: (context, state) => const AutomatedTestingView(),
|
builder: (context, state) => const AutomatedTestingView(),
|
||||||
),
|
),
|
||||||
GoRoute(
|
|
||||||
path: 'reduce_flames',
|
|
||||||
builder: (context, state) => const ReduceFlamesView(),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
|
|
|
||||||
|
|
@ -92,14 +92,12 @@ class ApiService {
|
||||||
|
|
||||||
if (globalIsInBackgroundTask) {
|
if (globalIsInBackgroundTask) {
|
||||||
await retransmitRawBytes();
|
await retransmitRawBytes();
|
||||||
await retransmitAllMessages();
|
await tryTransmitMessages();
|
||||||
await reuploadMediaFiles();
|
|
||||||
await tryDownloadAllMediaFiles();
|
await tryDownloadAllMediaFiles();
|
||||||
} else if (!globalIsAppInBackground) {
|
} else if (!globalIsAppInBackground) {
|
||||||
unawaited(retransmitRawBytes());
|
unawaited(retransmitRawBytes());
|
||||||
unawaited(retransmitAllMessages());
|
unawaited(tryTransmitMessages());
|
||||||
unawaited(tryDownloadAllMediaFiles());
|
unawaited(tryDownloadAllMediaFiles());
|
||||||
unawaited(reuploadMediaFiles());
|
|
||||||
twonlyDB.markUpdated();
|
twonlyDB.markUpdated();
|
||||||
unawaited(syncFlameCounters());
|
unawaited(syncFlameCounters());
|
||||||
unawaited(setupNotificationWithUsers());
|
unawaited(setupNotificationWithUsers());
|
||||||
|
|
@ -135,12 +133,11 @@ class ApiService {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
reconnectionTimer?.cancel();
|
reconnectionTimer?.cancel();
|
||||||
|
Log.info('Starting reconnection timer with $_reconnectionDelay s delay');
|
||||||
reconnectionTimer = Timer(Duration(seconds: _reconnectionDelay), () async {
|
reconnectionTimer = Timer(Duration(seconds: _reconnectionDelay), () async {
|
||||||
|
Log.info('Reconnection timer triggered');
|
||||||
reconnectionTimer = null;
|
reconnectionTimer = null;
|
||||||
// only try to reconnect in case the app is in the foreground
|
|
||||||
if (!globalIsAppInBackground) {
|
|
||||||
await connect();
|
await connect();
|
||||||
}
|
|
||||||
});
|
});
|
||||||
_reconnectionDelay = 3;
|
_reconnectionDelay = 3;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,8 @@ Future<void> handleAdditionalDataMessage(
|
||||||
senderId: Value(fromUserId),
|
senderId: Value(fromUserId),
|
||||||
groupId: Value(groupId),
|
groupId: Value(groupId),
|
||||||
type: Value(message.type),
|
type: Value(message.type),
|
||||||
additionalMessageData: Value(
|
additionalMessageData:
|
||||||
Uint8List.fromList(message.additionalMessageData),
|
Value(Uint8List.fromList(message.additionalMessageData)),
|
||||||
),
|
|
||||||
createdAt: Value(fromTimestamp(message.timestamp)),
|
createdAt: Value(fromTimestamp(message.timestamp)),
|
||||||
ackByServer: Value(clock.now()),
|
ackByServer: Value(clock.now()),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -124,15 +124,14 @@ Future<void> handleContactUpdate(
|
||||||
await twonlyDB.contactsDao.updateContact(
|
await twonlyDB.contactsDao.updateContact(
|
||||||
fromUserId,
|
fromUserId,
|
||||||
ContactsCompanion(
|
ContactsCompanion(
|
||||||
avatarSvgCompressed: Value(
|
avatarSvgCompressed:
|
||||||
Uint8List.fromList(contactUpdate.avatarSvgCompressed),
|
Value(Uint8List.fromList(contactUpdate.avatarSvgCompressed)),
|
||||||
),
|
|
||||||
displayName: Value(contactUpdate.displayName),
|
displayName: Value(contactUpdate.displayName),
|
||||||
username: Value(contactUpdate.username),
|
username: Value(contactUpdate.username),
|
||||||
senderProfileCounter: Value(senderProfileCounter),
|
senderProfileCounter: Value(senderProfileCounter),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
unawaited(createPushAvatars(forceForUserId: fromUserId));
|
unawaited(createPushAvatars());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import 'package:twonly/src/database/tables/groups.table.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
|
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
|
||||||
import 'package:twonly/src/services/api/messages.dart';
|
import 'package:twonly/src/services/api/messages.dart';
|
||||||
import 'package:twonly/src/services/api/utils.dart';
|
|
||||||
import 'package:twonly/src/services/group.services.dart';
|
import 'package:twonly/src/services/group.services.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
|
|
@ -138,9 +137,8 @@ Future<void> handleGroupUpdate(
|
||||||
GroupHistoriesCompanion(
|
GroupHistoriesCompanion(
|
||||||
groupId: Value(groupId),
|
groupId: Value(groupId),
|
||||||
type: Value(actionType),
|
type: Value(actionType),
|
||||||
newDeleteMessagesAfterMilliseconds: Value(
|
newDeleteMessagesAfterMilliseconds:
|
||||||
update.newDeleteMessagesAfterMilliseconds.toInt(),
|
Value(update.newDeleteMessagesAfterMilliseconds.toInt()),
|
||||||
),
|
|
||||||
contactId: Value(fromUserId),
|
contactId: Value(fromUserId),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -148,9 +146,8 @@ Future<void> handleGroupUpdate(
|
||||||
await twonlyDB.groupsDao.updateGroup(
|
await twonlyDB.groupsDao.updateGroup(
|
||||||
group.groupId,
|
group.groupId,
|
||||||
GroupsCompanion(
|
GroupsCompanion(
|
||||||
deleteMessagesAfterMilliseconds: Value(
|
deleteMessagesAfterMilliseconds:
|
||||||
update.newDeleteMessagesAfterMilliseconds.toInt(),
|
Value(update.newDeleteMessagesAfterMilliseconds.toInt()),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -224,24 +221,3 @@ Future<void> handleResendGroupPublicKey(
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> handleTypingIndicator(
|
|
||||||
int fromUserId,
|
|
||||||
String groupId,
|
|
||||||
EncryptedContent_TypingIndicator indicator,
|
|
||||||
) async {
|
|
||||||
var lastTypeIndicator = const Value<DateTime?>.absent();
|
|
||||||
|
|
||||||
if (indicator.isTyping) {
|
|
||||||
lastTypeIndicator = Value(fromTimestamp(indicator.createdAt));
|
|
||||||
}
|
|
||||||
|
|
||||||
await twonlyDB.groupsDao.updateMember(
|
|
||||||
groupId,
|
|
||||||
fromUserId,
|
|
||||||
GroupMembersCompanion(
|
|
||||||
lastChatOpened: Value(fromTimestamp(indicator.createdAt)),
|
|
||||||
lastTypeIndicator: lastTypeIndicator,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -73,39 +73,13 @@ Future<void> handleMedia(
|
||||||
mediaType = MediaType.audio;
|
mediaType = MediaType.audio;
|
||||||
}
|
}
|
||||||
|
|
||||||
var mediaIdValue = const Value<String>.absent();
|
|
||||||
|
|
||||||
final messageTmp = await twonlyDB.messagesDao
|
final messageTmp = await twonlyDB.messagesDao
|
||||||
.getMessageById(media.senderMessageId)
|
.getMessageById(media.senderMessageId)
|
||||||
.getSingleOrNull();
|
.getSingleOrNull();
|
||||||
if (messageTmp != null) {
|
if (messageTmp != null) {
|
||||||
if (messageTmp.senderId != fromUserId) {
|
Log.warn('This message already exit. Message is dropped.');
|
||||||
Log.warn(
|
|
||||||
'$fromUserId tried to modify the message from ${messageTmp.senderId}.',
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (messageTmp.mediaId == null) {
|
|
||||||
Log.warn(
|
|
||||||
'This message already exit without a mediaId. Message is dropped.',
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final mediaFile = await twonlyDB.mediaFilesDao.getMediaFileById(
|
|
||||||
messageTmp.mediaId!,
|
|
||||||
);
|
|
||||||
if (mediaFile?.downloadState != DownloadState.reuploadRequested) {
|
|
||||||
Log.warn(
|
|
||||||
'This message and media file already exit and was not requested again. Dropping it.',
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mediaFile != null) {
|
|
||||||
// media file is reuploaded use the same mediaId
|
|
||||||
mediaIdValue = Value(mediaFile.mediaId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int? displayLimitInMilliseconds;
|
int? displayLimitInMilliseconds;
|
||||||
if (media.hasDisplayLimitInMilliseconds()) {
|
if (media.hasDisplayLimitInMilliseconds()) {
|
||||||
|
|
@ -121,9 +95,8 @@ Future<void> handleMedia(
|
||||||
late Message? message;
|
late Message? message;
|
||||||
|
|
||||||
await twonlyDB.transaction(() async {
|
await twonlyDB.transaction(() async {
|
||||||
mediaFile = await twonlyDB.mediaFilesDao.insertOrUpdateMedia(
|
mediaFile = await twonlyDB.mediaFilesDao.insertMedia(
|
||||||
MediaFilesCompanion(
|
MediaFilesCompanion(
|
||||||
mediaId: mediaIdValue,
|
|
||||||
downloadState: const Value(DownloadState.pending),
|
downloadState: const Value(DownloadState.pending),
|
||||||
type: Value(mediaType),
|
type: Value(mediaType),
|
||||||
requiresAuthentication: Value(media.requiresAuthentication),
|
requiresAuthentication: Value(media.requiresAuthentication),
|
||||||
|
|
@ -232,6 +205,23 @@ Future<void> handleMediaUpdate(
|
||||||
|
|
||||||
case EncryptedContent_MediaUpdate_Type.DECRYPTION_ERROR:
|
case EncryptedContent_MediaUpdate_Type.DECRYPTION_ERROR:
|
||||||
Log.info('Got media file decryption error ${mediaFile.mediaId}');
|
Log.info('Got media file decryption error ${mediaFile.mediaId}');
|
||||||
await reuploadMediaFile(fromUserId, mediaFile, message.messageId);
|
final reuploadRequestedBy = mediaFile.reuploadRequestedBy ?? [];
|
||||||
|
reuploadRequestedBy.add(fromUserId);
|
||||||
|
await twonlyDB.mediaFilesDao.updateMedia(
|
||||||
|
mediaFile.mediaId,
|
||||||
|
MediaFilesCompanion(
|
||||||
|
uploadState: const Value(UploadState.preprocessing),
|
||||||
|
reuploadRequestedBy: Value(reuploadRequestedBy),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
final mediaFileUpdated = await MediaFileService.fromMediaId(
|
||||||
|
mediaFile.mediaId,
|
||||||
|
);
|
||||||
|
if (mediaFileUpdated != null) {
|
||||||
|
if (mediaFileUpdated.uploadRequestPath.existsSync()) {
|
||||||
|
mediaFileUpdated.uploadRequestPath.deleteSync();
|
||||||
|
}
|
||||||
|
unawaited(startBackgroundMediaUpload(mediaFileUpdated));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,9 +54,8 @@ Future<void> handleMessageUpdate(
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> isSender(int fromUserId, String messageId) async {
|
Future<bool> isSender(int fromUserId, String messageId) async {
|
||||||
final message = await twonlyDB.messagesDao
|
final message =
|
||||||
.getMessageById(messageId)
|
await twonlyDB.messagesDao.getMessageById(messageId).getSingleOrNull();
|
||||||
.getSingleOrNull();
|
|
||||||
if (message == null) return false;
|
if (message == null) return false;
|
||||||
if (message.senderId == fromUserId) {
|
if (message.senderId == fromUserId) {
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,8 @@ Future<void> handlePushKey(
|
||||||
switch (pushKeys.type) {
|
switch (pushKeys.type) {
|
||||||
case EncryptedContent_PushKeys_Type.REQUEST:
|
case EncryptedContent_PushKeys_Type.REQUEST:
|
||||||
Log.info('Got a pushkey request from $contactId');
|
Log.info('Got a pushkey request from $contactId');
|
||||||
if (lastPushKeyRequest.isBefore(
|
if (lastPushKeyRequest
|
||||||
clock.now().subtract(const Duration(seconds: 60)),
|
.isBefore(clock.now().subtract(const Duration(seconds: 60)))) {
|
||||||
)) {
|
|
||||||
lastPushKeyRequest = clock.now();
|
lastPushKeyRequest = clock.now();
|
||||||
unawaited(setupNotificationWithUsers(forceContact: contactId));
|
unawaited(setupNotificationWithUsers(forceContact: contactId));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,155 +26,14 @@ import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:workmanager/workmanager.dart' hide TaskStatus;
|
import 'package:workmanager/workmanager.dart' hide TaskStatus;
|
||||||
|
|
||||||
final lockRetransmission = Mutex();
|
|
||||||
|
|
||||||
Future<void> reuploadMediaFiles() async {
|
|
||||||
return lockRetransmission.protect(() async {
|
|
||||||
final receipts = await twonlyDB.receiptsDao
|
|
||||||
.getReceiptsForMediaRetransmissions();
|
|
||||||
|
|
||||||
if (receipts.isEmpty) return;
|
|
||||||
|
|
||||||
Log.info('Reuploading ${receipts.length} media files to the server.');
|
|
||||||
|
|
||||||
final contacts = <int, Contact>{};
|
|
||||||
|
|
||||||
for (final receipt in receipts) {
|
|
||||||
if (receipt.retryCount > 1 && receipt.lastRetry != null) {
|
|
||||||
final twentyFourHoursAgo = DateTime.now().subtract(
|
|
||||||
const Duration(hours: 24),
|
|
||||||
);
|
|
||||||
if (receipt.lastRetry!.isAfter(twentyFourHoursAgo)) {
|
|
||||||
Log.info(
|
|
||||||
'Ignoring ${receipt.receiptId} as it was retried in the last 24h',
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var messageId = receipt.messageId;
|
|
||||||
if (receipt.messageId == null) {
|
|
||||||
Log.info('Message not in receipt. Loading it from the content.');
|
|
||||||
try {
|
|
||||||
final content = EncryptedContent.fromBuffer(receipt.message);
|
|
||||||
if (content.hasMedia()) {
|
|
||||||
messageId = content.media.senderMessageId;
|
|
||||||
await twonlyDB.receiptsDao.updateReceipt(
|
|
||||||
receipt.receiptId,
|
|
||||||
ReceiptsCompanion(
|
|
||||||
messageId: Value(messageId),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
Log.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (messageId == null) {
|
|
||||||
Log.error('MessageId is empty for media file receipts');
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (receipt.markForRetryAfterAccepted != null) {
|
|
||||||
if (!contacts.containsKey(receipt.contactId)) {
|
|
||||||
final contact = await twonlyDB.contactsDao
|
|
||||||
.getContactByUserId(receipt.contactId)
|
|
||||||
.getSingleOrNull();
|
|
||||||
if (contact == null) {
|
|
||||||
Log.error(
|
|
||||||
'Contact does not exists, but has a record in receipts, this should not be possible, because of the DELETE CASCADE relation.',
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
contacts[receipt.contactId] = contact;
|
|
||||||
}
|
|
||||||
if (!(contacts[receipt.contactId]?.accepted ?? true)) {
|
|
||||||
Log.warn(
|
|
||||||
'Could not send message as contact has still not yet accepted.',
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (receipt.ackByServerAt == null) {
|
|
||||||
// media file must be reuploaded again in case the media files
|
|
||||||
// was deleted by the server, the receiver will request a new media reupload
|
|
||||||
|
|
||||||
final message = await twonlyDB.messagesDao
|
|
||||||
.getMessageById(messageId)
|
|
||||||
.getSingleOrNull();
|
|
||||||
if (message == null || message.mediaId == null) {
|
|
||||||
Log.error(
|
|
||||||
'Message not found for reupload of the receipt (${message == null} - ${message?.mediaId}).',
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
final mediaFile = await twonlyDB.mediaFilesDao.getMediaFileById(
|
|
||||||
message.mediaId!,
|
|
||||||
);
|
|
||||||
if (mediaFile == null) {
|
|
||||||
Log.error(
|
|
||||||
'Mediafile not found for reupload of the receipt (${message.messageId} - ${message.mediaId}).',
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
await reuploadMediaFile(
|
|
||||||
receipt.contactId,
|
|
||||||
mediaFile,
|
|
||||||
message.messageId,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
Log.info('Reuploading media file $messageId');
|
|
||||||
// the media file should be still on the server, so it should be enough
|
|
||||||
// to just resend the message containing the download token.
|
|
||||||
await tryToSendCompleteMessage(receipt: receipt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> reuploadMediaFile(
|
|
||||||
int contactId,
|
|
||||||
MediaFile mediaFile,
|
|
||||||
String messageId,
|
|
||||||
) async {
|
|
||||||
Log.info('Reuploading media file: ${mediaFile.mediaId}');
|
|
||||||
|
|
||||||
await twonlyDB.receiptsDao.updateReceiptByContactAndMessageId(
|
|
||||||
contactId,
|
|
||||||
messageId,
|
|
||||||
const ReceiptsCompanion(
|
|
||||||
markForRetry: Value(null),
|
|
||||||
markForRetryAfterAccepted: Value(null),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
final reuploadRequestedBy = (mediaFile.reuploadRequestedBy ?? [])
|
|
||||||
..add(contactId);
|
|
||||||
await twonlyDB.mediaFilesDao.updateMedia(
|
|
||||||
mediaFile.mediaId,
|
|
||||||
MediaFilesCompanion(
|
|
||||||
uploadState: const Value(UploadState.preprocessing),
|
|
||||||
reuploadRequestedBy: Value(reuploadRequestedBy),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
final mediaFileUpdated = await MediaFileService.fromMediaId(
|
|
||||||
mediaFile.mediaId,
|
|
||||||
);
|
|
||||||
if (mediaFileUpdated != null) {
|
|
||||||
if (mediaFileUpdated.uploadRequestPath.existsSync()) {
|
|
||||||
mediaFileUpdated.uploadRequestPath.deleteSync();
|
|
||||||
}
|
|
||||||
unawaited(startBackgroundMediaUpload(mediaFileUpdated));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> finishStartedPreprocessing() async {
|
Future<void> finishStartedPreprocessing() async {
|
||||||
final mediaFiles = await twonlyDB.mediaFilesDao
|
final mediaFiles = await twonlyDB.mediaFilesDao
|
||||||
.getAllMediaFilesPendingUpload();
|
.getAllMediaFilesPendingUpload();
|
||||||
|
|
||||||
|
Log.info('There are ${mediaFiles.length} media files pending');
|
||||||
|
|
||||||
for (final mediaFile in mediaFiles) {
|
for (final mediaFile in mediaFiles) {
|
||||||
if (mediaFile.isDraftMedia) {
|
if (mediaFile.isDraftMedia) {
|
||||||
Log.info('Ignoring media files as it is a draft');
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
|
@ -192,9 +51,6 @@ Future<void> finishStartedPreprocessing() async {
|
||||||
await twonlyDB.mediaFilesDao.deleteMediaFile(mediaFile.mediaId);
|
await twonlyDB.mediaFilesDao.deleteMediaFile(mediaFile.mediaId);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Log.info(
|
|
||||||
'Finishing started preprocessing of ${mediaFile.mediaId} in state ${mediaFile.uploadState}.',
|
|
||||||
);
|
|
||||||
await startBackgroundMediaUpload(service);
|
await startBackgroundMediaUpload(service);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Log.warn(e);
|
Log.warn(e);
|
||||||
|
|
@ -204,7 +60,7 @@ Future<void> finishStartedPreprocessing() async {
|
||||||
|
|
||||||
/// It can happen, that a media files is uploaded but not yet marked for been uploaded.
|
/// It can happen, that a media files is uploaded but not yet marked for been uploaded.
|
||||||
/// For example because the background_downloader plugin has not yet reported the finished upload.
|
/// For example because the background_downloader plugin has not yet reported the finished upload.
|
||||||
/// In case the message receipts or a reaction was received, mark the media file as been uploaded.
|
/// In case the the message receipts or a reaction was received, mark the media file as been uploaded.
|
||||||
Future<void> handleMediaRelatedResponseFromReceiver(String messageId) async {
|
Future<void> handleMediaRelatedResponseFromReceiver(String messageId) async {
|
||||||
final message = await twonlyDB.messagesDao
|
final message = await twonlyDB.messagesDao
|
||||||
.getMessageById(messageId)
|
.getMessageById(messageId)
|
||||||
|
|
@ -242,16 +98,6 @@ Future<void> markUploadAsSuccessful(MediaFile media) async {
|
||||||
message.messageId,
|
message.messageId,
|
||||||
clock.now(),
|
clock.now(),
|
||||||
);
|
);
|
||||||
await twonlyDB.receiptsDao.updateReceiptByContactAndMessageId(
|
|
||||||
contact.contactId,
|
|
||||||
message.messageId,
|
|
||||||
ReceiptsCompanion(
|
|
||||||
ackByServerAt: Value(clock.now()),
|
|
||||||
retryCount: const Value(1),
|
|
||||||
lastRetry: Value(clock.now()),
|
|
||||||
markForRetry: const Value(null),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -274,7 +120,7 @@ Future<MediaFileService?> initializeMediaUpload(
|
||||||
const MediaFilesCompanion(isDraftMedia: Value(false)),
|
const MediaFilesCompanion(isDraftMedia: Value(false)),
|
||||||
);
|
);
|
||||||
|
|
||||||
final mediaFile = await twonlyDB.mediaFilesDao.insertOrUpdateMedia(
|
final mediaFile = await twonlyDB.mediaFilesDao.insertMedia(
|
||||||
MediaFilesCompanion(
|
MediaFilesCompanion(
|
||||||
uploadState: const Value(UploadState.initialized),
|
uploadState: const Value(UploadState.initialized),
|
||||||
displayLimitInMilliseconds: Value(displayLimitInMilliseconds),
|
displayLimitInMilliseconds: Value(displayLimitInMilliseconds),
|
||||||
|
|
@ -299,16 +145,6 @@ Future<void> insertMediaFileInMessagesTable(
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
for (final groupId in groupIds) {
|
for (final groupId in groupIds) {
|
||||||
final groupMembers = await twonlyDB.groupsDao.getGroupContact(groupId);
|
|
||||||
if (groupMembers.length == 1) {
|
|
||||||
if (groupMembers.first.accountDeleted) {
|
|
||||||
Log.warn(
|
|
||||||
'Did not send media file to $groupId because the only account has deleted his account.',
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final message = await twonlyDB.messagesDao.insertMessage(
|
final message = await twonlyDB.messagesDao.insertMessage(
|
||||||
MessagesCompanion(
|
MessagesCompanion(
|
||||||
groupId: Value(groupId),
|
groupId: Value(groupId),
|
||||||
|
|
@ -442,14 +278,6 @@ Future<void> _createUploadRequest(MediaFileService media) async {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final contact = await twonlyDB.contactsDao.getContactById(
|
|
||||||
groupMember.contactId,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (contact == null || contact.accountDeleted) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
final downloadToken = getRandomUint8List(32);
|
final downloadToken = getRandomUint8List(32);
|
||||||
|
|
||||||
late EncryptedContent_Media_Type type;
|
late EncryptedContent_Media_Type type;
|
||||||
|
|
@ -465,8 +293,7 @@ Future<void> _createUploadRequest(MediaFileService media) async {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (media.mediaFile.reuploadRequestedBy != null) {
|
if (media.mediaFile.reuploadRequestedBy != null) {
|
||||||
// not used any more... Receiver detects automatically if it is an reupload...
|
type = EncryptedContent_Media_Type.REUPLOAD;
|
||||||
// type = EncryptedContent_Media_Type.REUPLOAD;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final notEncryptedContent = EncryptedContent(
|
final notEncryptedContent = EncryptedContent(
|
||||||
|
|
@ -493,7 +320,6 @@ Future<void> _createUploadRequest(MediaFileService media) async {
|
||||||
final cipherText = await sendCipherText(
|
final cipherText = await sendCipherText(
|
||||||
groupMember.contactId,
|
groupMember.contactId,
|
||||||
notEncryptedContent,
|
notEncryptedContent,
|
||||||
messageId: message.messageId,
|
|
||||||
onlyReturnEncryptedData: true,
|
onlyReturnEncryptedData: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -501,11 +327,10 @@ Future<void> _createUploadRequest(MediaFileService media) async {
|
||||||
Log.error(
|
Log.error(
|
||||||
'Could not generate ciphertext message for ${groupMember.contactId}',
|
'Could not generate ciphertext message for ${groupMember.contactId}',
|
||||||
);
|
);
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final messageOnSuccess = TextMessage()
|
final messageOnSuccess = TextMessage()
|
||||||
..body = cipherText.$1
|
..body = cipherText!.$1
|
||||||
..userId = Int64(groupMember.contactId);
|
..userId = Int64(groupMember.contactId);
|
||||||
|
|
||||||
if (cipherText.$2 != null) {
|
if (cipherText.$2 != null) {
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
||||||
final lockRetransmission = Mutex();
|
final lockRetransmission = Mutex();
|
||||||
|
|
||||||
Future<void> retransmitAllMessages() async {
|
Future<void> tryTransmitMessages() async {
|
||||||
return lockRetransmission.protect(() async {
|
return lockRetransmission.protect(() async {
|
||||||
final receipts = await twonlyDB.receiptsDao.getReceiptsForRetransmission();
|
final receipts = await twonlyDB.receiptsDao.getReceiptsForRetransmission();
|
||||||
|
|
||||||
|
|
@ -95,6 +95,8 @@ Future<(Uint8List, Uint8List?)?> tryToSendCompleteMessage({
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.info('Uploading $receiptId');
|
||||||
|
|
||||||
final message = pb.Message.fromBuffer(receipt.message)
|
final message = pb.Message.fromBuffer(receipt.message)
|
||||||
..receiptId = receiptId;
|
..receiptId = receiptId;
|
||||||
|
|
||||||
|
|
@ -108,11 +110,9 @@ Future<(Uint8List, Uint8List?)?> tryToSendCompleteMessage({
|
||||||
encryptedContent,
|
encryptedContent,
|
||||||
);
|
);
|
||||||
|
|
||||||
Log.info('Uploading $receiptId. (${pushNotification?.kind})');
|
|
||||||
|
|
||||||
Uint8List? pushData;
|
Uint8List? pushData;
|
||||||
if (pushNotification != null && receipt.retryCount <= 1) {
|
if (pushNotification != null && receipt.retryCount <= 3) {
|
||||||
// Only show the push notification the first two time.
|
/// In case the message has to be resend more than three times, do not show a notification again...
|
||||||
pushData = await encryptPushNotification(
|
pushData = await encryptPushNotification(
|
||||||
receipt.contactId,
|
receipt.contactId,
|
||||||
pushNotification,
|
pushNotification,
|
||||||
|
|
@ -300,17 +300,10 @@ Future<void> sendCipherTextToGroup(
|
||||||
String groupId,
|
String groupId,
|
||||||
pb.EncryptedContent encryptedContent, {
|
pb.EncryptedContent encryptedContent, {
|
||||||
String? messageId,
|
String? messageId,
|
||||||
bool onlySendIfNoReceiptsAreOpen = false,
|
|
||||||
}) async {
|
}) async {
|
||||||
final groupMembers = await twonlyDB.groupsDao.getGroupNonLeftMembers(groupId);
|
final groupMembers = await twonlyDB.groupsDao.getGroupNonLeftMembers(groupId);
|
||||||
|
|
||||||
if (messageId != null ||
|
|
||||||
encryptedContent.hasReaction() ||
|
|
||||||
encryptedContent.hasMedia() ||
|
|
||||||
encryptedContent.hasTextMessage()) {
|
|
||||||
// only update the counter in case this is a actual message
|
|
||||||
await twonlyDB.groupsDao.increaseLastMessageExchange(groupId, clock.now());
|
await twonlyDB.groupsDao.increaseLastMessageExchange(groupId, clock.now());
|
||||||
}
|
|
||||||
|
|
||||||
encryptedContent.groupId = groupId;
|
encryptedContent.groupId = groupId;
|
||||||
|
|
||||||
|
|
@ -320,7 +313,6 @@ Future<void> sendCipherTextToGroup(
|
||||||
encryptedContent,
|
encryptedContent,
|
||||||
messageId: messageId,
|
messageId: messageId,
|
||||||
blocking: false,
|
blocking: false,
|
||||||
onlySendIfNoReceiptsAreOpen: onlySendIfNoReceiptsAreOpen,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -331,48 +323,19 @@ Future<(Uint8List, Uint8List?)?> sendCipherText(
|
||||||
bool onlyReturnEncryptedData = false,
|
bool onlyReturnEncryptedData = false,
|
||||||
bool blocking = true,
|
bool blocking = true,
|
||||||
String? messageId,
|
String? messageId,
|
||||||
bool onlySendIfNoReceiptsAreOpen = false,
|
|
||||||
}) async {
|
}) async {
|
||||||
if (onlySendIfNoReceiptsAreOpen) {
|
|
||||||
final openReceipts = await twonlyDB.receiptsDao.getReceiptCountForContact(
|
|
||||||
contactId,
|
|
||||||
);
|
|
||||||
if (openReceipts > 2) {
|
|
||||||
// this prevents that these types of messages are send in case the receiver is offline
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
encryptedContent.senderProfileCounter = Int64(gUser.avatarCounter);
|
encryptedContent.senderProfileCounter = Int64(gUser.avatarCounter);
|
||||||
|
|
||||||
final response = pb.Message()
|
final response = pb.Message()
|
||||||
..type = pb.Message_Type.CIPHERTEXT
|
..type = pb.Message_Type.CIPHERTEXT
|
||||||
..encryptedContent = encryptedContent.writeToBuffer();
|
..encryptedContent = encryptedContent.writeToBuffer();
|
||||||
|
|
||||||
var retryCounter = 0;
|
|
||||||
DateTime? lastRetry;
|
|
||||||
|
|
||||||
if (messageId != null) {
|
|
||||||
final receipts = await twonlyDB.receiptsDao
|
|
||||||
.getReceiptsByContactAndMessageId(contactId, messageId);
|
|
||||||
|
|
||||||
for (final receipt in receipts) {
|
|
||||||
if (receipt.lastRetry != null) {
|
|
||||||
lastRetry = receipt.lastRetry;
|
|
||||||
}
|
|
||||||
retryCounter += 1;
|
|
||||||
Log.info('Removing duplicated receipt for message $messageId');
|
|
||||||
await twonlyDB.receiptsDao.deleteReceipt(receipt.receiptId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final receipt = await twonlyDB.receiptsDao.insertReceipt(
|
final receipt = await twonlyDB.receiptsDao.insertReceipt(
|
||||||
ReceiptsCompanion(
|
ReceiptsCompanion(
|
||||||
contactId: Value(contactId),
|
contactId: Value(contactId),
|
||||||
message: Value(response.writeToBuffer()),
|
message: Value(response.writeToBuffer()),
|
||||||
messageId: Value(messageId),
|
messageId: Value(messageId),
|
||||||
willBeRetriedByMediaUpload: Value(onlyReturnEncryptedData),
|
willBeRetriedByMediaUpload: Value(onlyReturnEncryptedData),
|
||||||
retryCount: Value(retryCounter),
|
|
||||||
lastRetry: Value(lastRetry),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -390,20 +353,6 @@ Future<(Uint8List, Uint8List?)?> sendCipherText(
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> sendTypingIndication(String groupId, bool isTyping) async {
|
|
||||||
if (!gUser.typingIndicators) return;
|
|
||||||
await sendCipherTextToGroup(
|
|
||||||
groupId,
|
|
||||||
pb.EncryptedContent(
|
|
||||||
typingIndicator: pb.EncryptedContent_TypingIndicator(
|
|
||||||
isTyping: isTyping,
|
|
||||||
createdAt: Int64(clock.now().millisecondsSinceEpoch),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onlySendIfNoReceiptsAreOpen: true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> notifyContactAboutOpeningMessage(
|
Future<void> notifyContactAboutOpeningMessage(
|
||||||
int contactId,
|
int contactId,
|
||||||
List<String> messageOtherIds,
|
List<String> messageOtherIds,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
|
||||||
import 'package:clock/clock.dart';
|
import 'package:clock/clock.dart';
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:hashlib/random.dart';
|
import 'package:hashlib/random.dart';
|
||||||
|
|
@ -26,7 +25,6 @@ import 'package:twonly/src/services/api/client2client/reaction.c2c.dart';
|
||||||
import 'package:twonly/src/services/api/client2client/text_message.c2c.dart';
|
import 'package:twonly/src/services/api/client2client/text_message.c2c.dart';
|
||||||
import 'package:twonly/src/services/api/messages.dart';
|
import 'package:twonly/src/services/api/messages.dart';
|
||||||
import 'package:twonly/src/services/group.services.dart';
|
import 'package:twonly/src/services/group.services.dart';
|
||||||
import 'package:twonly/src/services/notifications/background.notifications.dart';
|
|
||||||
import 'package:twonly/src/services/signal/encryption.signal.dart';
|
import 'package:twonly/src/services/signal/encryption.signal.dart';
|
||||||
import 'package:twonly/src/services/signal/session.signal.dart';
|
import 'package:twonly/src/services/signal/session.signal.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
@ -81,17 +79,13 @@ Future<void> handleClient2ClientMessage(NewMessage newMessage) async {
|
||||||
final message = Message.fromBuffer(body);
|
final message = Message.fromBuffer(body);
|
||||||
final receiptId = message.receiptId;
|
final receiptId = message.receiptId;
|
||||||
|
|
||||||
final isDuplicated = await protectReceiptCheck.protect(() async {
|
await protectReceiptCheck.protect(() async {
|
||||||
if (await twonlyDB.receiptsDao.isDuplicated(receiptId)) {
|
if (await twonlyDB.receiptsDao.isDuplicated(receiptId)) {
|
||||||
return true;
|
Log.warn('Got duplicated message from the server.');
|
||||||
}
|
|
||||||
await twonlyDB.receiptsDao.gotReceipt(receiptId);
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isDuplicated) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
await twonlyDB.receiptsDao.gotReceipt(receiptId);
|
||||||
|
});
|
||||||
|
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
case Message_Type.SENDER_DELIVERY_RECEIPT:
|
case Message_Type.SENDER_DELIVERY_RECEIPT:
|
||||||
|
|
@ -137,9 +131,8 @@ Future<void> handleClient2ClientMessage(NewMessage newMessage) async {
|
||||||
if (message.hasEncryptedContent()) {
|
if (message.hasEncryptedContent()) {
|
||||||
Value<String>? receiptIdDB;
|
Value<String>? receiptIdDB;
|
||||||
|
|
||||||
final encryptedContentRaw = Uint8List.fromList(
|
final encryptedContentRaw =
|
||||||
message.encryptedContent,
|
Uint8List.fromList(message.encryptedContent);
|
||||||
);
|
|
||||||
|
|
||||||
Message? response;
|
Message? response;
|
||||||
|
|
||||||
|
|
@ -162,10 +155,8 @@ Future<void> handleClient2ClientMessage(NewMessage newMessage) async {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response == null) {
|
if (response == null) {
|
||||||
final (
|
final (encryptedContent, plainTextContent) =
|
||||||
encryptedContent,
|
await handleEncryptedMessage(
|
||||||
plainTextContent,
|
|
||||||
) = await handleEncryptedMessageRaw(
|
|
||||||
fromUserId,
|
fromUserId,
|
||||||
encryptedContentRaw,
|
encryptedContentRaw,
|
||||||
message.type,
|
message.type,
|
||||||
|
|
@ -183,9 +174,6 @@ Future<void> handleClient2ClientMessage(NewMessage newMessage) async {
|
||||||
encryptedContent: encryptedContent.writeToBuffer(),
|
encryptedContent: encryptedContent.writeToBuffer(),
|
||||||
);
|
);
|
||||||
receiptIdDB = const Value.absent();
|
receiptIdDB = const Value.absent();
|
||||||
} else {
|
|
||||||
// Message was successful processed
|
|
||||||
//
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -210,48 +198,27 @@ Future<void> handleClient2ClientMessage(NewMessage newMessage) async {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessageRaw(
|
Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage(
|
||||||
int fromUserId,
|
int fromUserId,
|
||||||
Uint8List encryptedContentRaw,
|
Uint8List encryptedContentRaw,
|
||||||
Message_Type messageType,
|
Message_Type messageType,
|
||||||
String receiptId,
|
String receiptId,
|
||||||
) async {
|
) async {
|
||||||
final (encryptedContent, decryptionErrorType) = await signalDecryptMessage(
|
final (content, decryptionErrorType) = await signalDecryptMessage(
|
||||||
fromUserId,
|
fromUserId,
|
||||||
encryptedContentRaw,
|
encryptedContentRaw,
|
||||||
messageType.value,
|
messageType.value,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (encryptedContent == null) {
|
if (content == null) {
|
||||||
return (
|
return (
|
||||||
null,
|
null,
|
||||||
PlaintextContent()
|
PlaintextContent()
|
||||||
..decryptionErrorMessage = (PlaintextContent_DecryptionErrorMessage()
|
..decryptionErrorMessage = (PlaintextContent_DecryptionErrorMessage()
|
||||||
..type = decryptionErrorType!),
|
..type = decryptionErrorType!)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final (a, b) = await handleEncryptedMessage(
|
|
||||||
fromUserId,
|
|
||||||
encryptedContent,
|
|
||||||
messageType,
|
|
||||||
receiptId,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (Platform.isAndroid && a == null && b == null) {
|
|
||||||
// Message was handled without any error -> Show push notification to the user.
|
|
||||||
await showPushNotificationFromServerMessages(fromUserId, encryptedContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage(
|
|
||||||
int fromUserId,
|
|
||||||
EncryptedContent content,
|
|
||||||
Message_Type messageType,
|
|
||||||
String receiptId,
|
|
||||||
) async {
|
|
||||||
// We got a valid message fromUserId, so mark all messages which where
|
// We got a valid message fromUserId, so mark all messages which where
|
||||||
// send to the user but not yet ACK for retransmission. All marked messages
|
// send to the user but not yet ACK for retransmission. All marked messages
|
||||||
// will be either transmitted again after a new server connection (minimum 20 seconds).
|
// will be either transmitted again after a new server connection (minimum 20 seconds).
|
||||||
|
|
@ -268,7 +235,7 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage(
|
||||||
return (
|
return (
|
||||||
null,
|
null,
|
||||||
PlaintextContent()
|
PlaintextContent()
|
||||||
..retryControlError = PlaintextContent_RetryErrorMessage(),
|
..retryControlError = PlaintextContent_RetryErrorMessage()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (null, null);
|
return (null, null);
|
||||||
|
|
@ -345,7 +312,7 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage(
|
||||||
relatedReceiptId: receiptId,
|
relatedReceiptId: receiptId,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
null,
|
null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Log.info(
|
Log.info(
|
||||||
|
|
@ -366,7 +333,7 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage(
|
||||||
return (
|
return (
|
||||||
null,
|
null,
|
||||||
PlaintextContent()
|
PlaintextContent()
|
||||||
..retryControlError = PlaintextContent_RetryErrorMessage(),
|
..retryControlError = PlaintextContent_RetryErrorMessage()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -398,7 +365,7 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage(
|
||||||
return (
|
return (
|
||||||
null,
|
null,
|
||||||
PlaintextContent()
|
PlaintextContent()
|
||||||
..retryControlError = PlaintextContent_RetryErrorMessage(),
|
..retryControlError = PlaintextContent_RetryErrorMessage()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (null, null);
|
return (null, null);
|
||||||
|
|
@ -449,13 +416,5 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage(
|
||||||
return (null, null);
|
return (null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (content.hasTypingIndicator()) {
|
|
||||||
await handleTypingIndicator(
|
|
||||||
fromUserId,
|
|
||||||
content.groupId,
|
|
||||||
content.typingIndicator,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (null, null);
|
return (null, null);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -65,9 +65,8 @@ Future<void> handleMediaError(MediaFile media) async {
|
||||||
downloadState: Value(DownloadState.reuploadRequested),
|
downloadState: Value(DownloadState.reuploadRequested),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
final messages = await twonlyDB.messagesDao.getMessagesByMediaId(
|
final messages =
|
||||||
media.mediaId,
|
await twonlyDB.messagesDao.getMessagesByMediaId(media.mediaId);
|
||||||
);
|
|
||||||
if (messages.length != 1) return;
|
if (messages.length != 1) return;
|
||||||
final message = messages.first;
|
final message = messages.first;
|
||||||
if (message.senderId == null) return;
|
if (message.senderId == null) return;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:mutex/mutex.dart';
|
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
|
|
@ -7,7 +6,6 @@ import 'package:twonly/src/constants/keyvalue.keys.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/services/api.service.dart';
|
import 'package:twonly/src/services/api.service.dart';
|
||||||
import 'package:twonly/src/services/api/mediafiles/upload.service.dart';
|
import 'package:twonly/src/services/api/mediafiles/upload.service.dart';
|
||||||
import 'package:twonly/src/utils/exclusive_access.dart';
|
|
||||||
import 'package:twonly/src/utils/keyvalue.dart';
|
import 'package:twonly/src/utils/keyvalue.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
|
|
@ -16,14 +14,12 @@ import 'package:workmanager/workmanager.dart';
|
||||||
// ignore: unreachable_from_main
|
// ignore: unreachable_from_main
|
||||||
Future<void> initializeBackgroundTaskManager() async {
|
Future<void> initializeBackgroundTaskManager() async {
|
||||||
await Workmanager().initialize(callbackDispatcher);
|
await Workmanager().initialize(callbackDispatcher);
|
||||||
await Workmanager().cancelByUniqueName('fetch_data_from_server');
|
|
||||||
|
|
||||||
await Workmanager().registerPeriodicTask(
|
await Workmanager().registerPeriodicTask(
|
||||||
'fetch_data_from_server',
|
'fetch_data_from_server',
|
||||||
'eu.twonly.periodic_task',
|
'eu.twonly.periodic_task',
|
||||||
frequency: const Duration(minutes: 20),
|
frequency: const Duration(minutes: 20),
|
||||||
initialDelay: const Duration(minutes: 5),
|
initialDelay: const Duration(minutes: 5),
|
||||||
existingWorkPolicy: ExistingPeriodicWorkPolicy.update,
|
|
||||||
constraints: Constraints(
|
constraints: Constraints(
|
||||||
networkType: NetworkType.connected,
|
networkType: NetworkType.connected,
|
||||||
),
|
),
|
||||||
|
|
@ -49,67 +45,45 @@ void callbackDispatcher() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _isInitialized = false;
|
|
||||||
|
|
||||||
Future<bool> initBackgroundExecution() async {
|
Future<bool> initBackgroundExecution() async {
|
||||||
if (_isInitialized) {
|
|
||||||
// Reload the users, as on Android the background isolate can
|
|
||||||
// stay alive for multiple hours between task executions
|
|
||||||
final user = await getUser();
|
|
||||||
if (user == null) return false;
|
|
||||||
gUser = user;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
SentryWidgetsFlutterBinding.ensureInitialized();
|
SentryWidgetsFlutterBinding.ensureInitialized();
|
||||||
globalApplicationCacheDirectory = (await getApplicationCacheDirectory()).path;
|
|
||||||
globalApplicationSupportDirectory =
|
|
||||||
(await getApplicationSupportDirectory()).path;
|
|
||||||
|
|
||||||
initLogger();
|
initLogger();
|
||||||
|
|
||||||
final user = await getUser();
|
final user = await getUser();
|
||||||
if (user == null) return false;
|
if (user == null) return false;
|
||||||
gUser = user;
|
gUser = user;
|
||||||
|
|
||||||
|
globalApplicationCacheDirectory = (await getApplicationCacheDirectory()).path;
|
||||||
|
globalApplicationSupportDirectory =
|
||||||
|
(await getApplicationSupportDirectory()).path;
|
||||||
|
|
||||||
twonlyDB = TwonlyDB();
|
twonlyDB = TwonlyDB();
|
||||||
apiService = ApiService();
|
apiService = ApiService();
|
||||||
globalIsInBackgroundTask = true;
|
globalIsInBackgroundTask = true;
|
||||||
|
|
||||||
_isInitialized = true;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Mutex _keyValueMutex = Mutex();
|
Future<bool> handlePeriodicTask() async {
|
||||||
|
final lastExecution =
|
||||||
Future<void> handlePeriodicTask({int lastExecutionInSecondsLimit = 120}) async {
|
await KeyValueStore.get(KeyValueKeys.lastPeriodicTaskExecution);
|
||||||
final shouldBeExecuted = await exclusiveAccess(
|
if (lastExecution == null || !lastExecution.containsKey('timestamp')) {
|
||||||
lockName: 'periodic_task',
|
final lastExecutionTime = lastExecution?['timestamp'] as int?;
|
||||||
mutex: _keyValueMutex,
|
|
||||||
action: () async {
|
|
||||||
final lastExecution = await KeyValueStore.get(
|
|
||||||
KeyValueKeys.lastPeriodicTaskExecution,
|
|
||||||
);
|
|
||||||
if (lastExecution != null && lastExecution.containsKey('timestamp')) {
|
|
||||||
final lastExecutionTime = lastExecution['timestamp'] as int?;
|
|
||||||
if (lastExecutionTime != null) {
|
if (lastExecutionTime != null) {
|
||||||
final lastExecutionDate = DateTime.fromMillisecondsSinceEpoch(
|
final lastExecution =
|
||||||
lastExecutionTime,
|
DateTime.fromMillisecondsSinceEpoch(lastExecutionTime);
|
||||||
|
if (DateTime.now().difference(lastExecution).inMinutes < 2) {
|
||||||
|
Log.info(
|
||||||
|
'eu.twonly.periodic_task not executed as last execution was within the last two minutes.',
|
||||||
);
|
);
|
||||||
if (DateTime.now().difference(lastExecutionDate).inSeconds <
|
return true;
|
||||||
lastExecutionInSecondsLimit) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await KeyValueStore.put(KeyValueKeys.lastPeriodicTaskExecution, {
|
await KeyValueStore.put(KeyValueKeys.lastPeriodicTaskExecution, {
|
||||||
'timestamp': DateTime.now().millisecondsSinceEpoch,
|
'timestamp': DateTime.now().millisecondsSinceEpoch,
|
||||||
});
|
});
|
||||||
return true;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!shouldBeExecuted) return;
|
|
||||||
|
|
||||||
Log.info('eu.twonly.periodic_task was called.');
|
Log.info('eu.twonly.periodic_task was called.');
|
||||||
|
|
||||||
|
|
@ -117,12 +91,12 @@ Future<void> handlePeriodicTask({int lastExecutionInSecondsLimit = 120}) async {
|
||||||
|
|
||||||
if (!await apiService.connect()) {
|
if (!await apiService.connect()) {
|
||||||
Log.info('Could not connect to the api. Returning early.');
|
Log.info('Could not connect to the api. Returning early.');
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!apiService.isAuthenticated) {
|
if (!apiService.isAuthenticated) {
|
||||||
Log.info('Api is not authenticated. Returning early.');
|
Log.info('Api is not authenticated. Returning early.');
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (!globalGotMessageFromServer) {
|
while (!globalGotMessageFromServer) {
|
||||||
|
|
@ -145,7 +119,7 @@ Future<void> handlePeriodicTask({int lastExecutionInSecondsLimit = 120}) async {
|
||||||
stopwatch.stop();
|
stopwatch.stop();
|
||||||
|
|
||||||
Log.info('eu.twonly.periodic_task finished after ${stopwatch.elapsed}.');
|
Log.info('eu.twonly.periodic_task finished after ${stopwatch.elapsed}.');
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> handleProcessingTask() async {
|
Future<void> handleProcessingTask() async {
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,8 @@ import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
|
|
||||||
Future<void> enableTwonlySafe(String password) async {
|
Future<void> enableTwonlySafe(String password) async {
|
||||||
final (backupId, encryptionKey) = await getMasterKey(
|
final (backupId, encryptionKey) =
|
||||||
password,
|
await getMasterKey(password, gUser.username);
|
||||||
gUser.username,
|
|
||||||
);
|
|
||||||
|
|
||||||
await updateUserdata((user) {
|
await updateUserdata((user) {
|
||||||
user.twonlySafeBackup = TwonlySafeBackup(
|
user.twonlySafeBackup = TwonlySafeBackup(
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,8 @@ Future<void> syncFlameCounters({String? forceForGroup}) async {
|
||||||
final groups = await twonlyDB.groupsDao.getAllGroups();
|
final groups = await twonlyDB.groupsDao.getAllGroups();
|
||||||
if (groups.isEmpty) return;
|
if (groups.isEmpty) return;
|
||||||
final maxMessageCounter = groups.map((x) => x.totalMediaCounter).max;
|
final maxMessageCounter = groups.map((x) => x.totalMediaCounter).max;
|
||||||
final bestFriend = groups.firstWhere(
|
final bestFriend =
|
||||||
(x) => x.totalMediaCounter == maxMessageCounter,
|
groups.firstWhere((x) => x.totalMediaCounter == maxMessageCounter);
|
||||||
);
|
|
||||||
|
|
||||||
if (gUser.myBestFriendGroupId != bestFriend.groupId) {
|
if (gUser.myBestFriendGroupId != bestFriend.groupId) {
|
||||||
await updateUserdata((user) {
|
await updateUserdata((user) {
|
||||||
|
|
@ -43,9 +42,8 @@ Future<void> syncFlameCounters({String? forceForGroup}) async {
|
||||||
EncryptedContent(
|
EncryptedContent(
|
||||||
flameSync: EncryptedContent_FlameSync(
|
flameSync: EncryptedContent_FlameSync(
|
||||||
flameCounter: Int64(flameCounter),
|
flameCounter: Int64(flameCounter),
|
||||||
lastFlameCounterChange: Int64(
|
lastFlameCounterChange:
|
||||||
group.lastFlameCounterChange!.millisecondsSinceEpoch,
|
Int64(group.lastFlameCounterChange!.millisecondsSinceEpoch),
|
||||||
),
|
|
||||||
bestFriend: group.groupId == bestFriend.groupId,
|
bestFriend: group.groupId == bestFriend.groupId,
|
||||||
forceUpdate: group.groupId == forceForGroup,
|
forceUpdate: group.groupId == forceForGroup,
|
||||||
),
|
),
|
||||||
|
|
@ -136,9 +134,8 @@ Future<void> incFlameCounter(
|
||||||
// Overwrite max flame counter either the current is bigger or the the max flame counter is older then 4 days
|
// Overwrite max flame counter either the current is bigger or the the max flame counter is older then 4 days
|
||||||
if (flameCounter >= maxFlameCounter ||
|
if (flameCounter >= maxFlameCounter ||
|
||||||
maxFlameCounterFrom == null ||
|
maxFlameCounterFrom == null ||
|
||||||
maxFlameCounterFrom.isBefore(
|
maxFlameCounterFrom
|
||||||
clock.now().subtract(const Duration(days: 5)),
|
.isBefore(clock.now().subtract(const Duration(days: 5)))) {
|
||||||
)) {
|
|
||||||
maxFlameCounter = flameCounter;
|
maxFlameCounter = flameCounter;
|
||||||
maxFlameCounterFrom = clock.now();
|
maxFlameCounterFrom = clock.now();
|
||||||
}
|
}
|
||||||
|
|
@ -175,7 +172,6 @@ bool isItPossibleToRestoreFlames(Group group) {
|
||||||
final flameCounter = getFlameCounterFromGroup(group);
|
final flameCounter = getFlameCounterFromGroup(group);
|
||||||
return group.maxFlameCounter > 2 &&
|
return group.maxFlameCounter > 2 &&
|
||||||
flameCounter < group.maxFlameCounter &&
|
flameCounter < group.maxFlameCounter &&
|
||||||
group.maxFlameCounterFrom!.isAfter(
|
group.maxFlameCounterFrom!
|
||||||
clock.now().subtract(const Duration(days: 7)),
|
.isAfter(clock.now().subtract(const Duration(days: 5)));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,9 +45,8 @@ Future<bool> createNewGroup(String groupName, List<Contact> members) async {
|
||||||
memberIds: [Int64(gUser.userId)] + memberIds,
|
memberIds: [Int64(gUser.userId)] + memberIds,
|
||||||
adminIds: [Int64(gUser.userId)],
|
adminIds: [Int64(gUser.userId)],
|
||||||
groupName: groupName,
|
groupName: groupName,
|
||||||
deleteMessagesAfterMilliseconds: Int64(
|
deleteMessagesAfterMilliseconds:
|
||||||
defaultDeleteMessagesAfterMilliseconds,
|
Int64(defaultDeleteMessagesAfterMilliseconds),
|
||||||
),
|
|
||||||
padding: List<int>.generate(Random().nextInt(80), (_) => 0),
|
padding: List<int>.generate(Random().nextInt(80), (_) => 0),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -159,9 +158,8 @@ Future<void> fetchMissingGroupPublicKey() async {
|
||||||
for (final member in members) {
|
for (final member in members) {
|
||||||
if (member.lastMessage == null) continue;
|
if (member.lastMessage == null) continue;
|
||||||
// only request if the users has send a message in the last two days.
|
// only request if the users has send a message in the last two days.
|
||||||
if (member.lastMessage!.isAfter(
|
if (member.lastMessage!
|
||||||
clock.now().subtract(const Duration(days: 2)),
|
.isAfter(clock.now().subtract(const Duration(days: 2)))) {
|
||||||
)) {
|
|
||||||
await sendCipherText(
|
await sendCipherText(
|
||||||
member.contactId,
|
member.contactId,
|
||||||
EncryptedContent(
|
EncryptedContent(
|
||||||
|
|
@ -229,15 +227,12 @@ Future<(int, EncryptedGroupState)?> fetchGroupState(Group group) async {
|
||||||
|
|
||||||
final groupStateServer = GroupState.fromBuffer(response.bodyBytes);
|
final groupStateServer = GroupState.fromBuffer(response.bodyBytes);
|
||||||
|
|
||||||
final encryptedStateRaw = await _decryptEnvelop(
|
final encryptedStateRaw =
|
||||||
group,
|
await _decryptEnvelop(group, groupStateServer.encryptedGroupState);
|
||||||
groupStateServer.encryptedGroupState,
|
|
||||||
);
|
|
||||||
if (encryptedStateRaw == null) return null;
|
if (encryptedStateRaw == null) return null;
|
||||||
|
|
||||||
final encryptedGroupState = EncryptedGroupState.fromBuffer(
|
final encryptedGroupState =
|
||||||
encryptedStateRaw,
|
EncryptedGroupState.fromBuffer(encryptedStateRaw);
|
||||||
);
|
|
||||||
|
|
||||||
if (group.stateVersionId >= groupStateServer.versionId.toInt()) {
|
if (group.stateVersionId >= groupStateServer.versionId.toInt()) {
|
||||||
Log.info(
|
Log.info(
|
||||||
|
|
@ -271,28 +266,24 @@ Future<(int, EncryptedGroupState)?> fetchGroupState(Group group) async {
|
||||||
);
|
);
|
||||||
if (encryptedStateRaw == null) continue;
|
if (encryptedStateRaw == null) continue;
|
||||||
|
|
||||||
final appended = EncryptedAppendedGroupState.fromBuffer(
|
final appended =
|
||||||
encryptedStateRaw,
|
EncryptedAppendedGroupState.fromBuffer(encryptedStateRaw);
|
||||||
);
|
|
||||||
if (appended.type == EncryptedAppendedGroupState_Type.LEFT_GROUP) {
|
if (appended.type == EncryptedAppendedGroupState_Type.LEFT_GROUP) {
|
||||||
final keyPair = IdentityKeyPair.fromSerialized(
|
final keyPair =
|
||||||
group.myGroupPrivateKey!,
|
IdentityKeyPair.fromSerialized(group.myGroupPrivateKey!);
|
||||||
);
|
|
||||||
|
|
||||||
final appendedPubKey = appendedState.appendTBS.publicKey;
|
final appendedPubKey = appendedState.appendTBS.publicKey;
|
||||||
final myPubKey = keyPair.getPublicKey().serialize().toList();
|
final myPubKey = keyPair.getPublicKey().serialize().toList();
|
||||||
|
|
||||||
if (listEquals(appendedPubKey, myPubKey)) {
|
if (listEquals(appendedPubKey, myPubKey)) {
|
||||||
adminIds.remove(Int64(gUser.userId));
|
adminIds.remove(Int64(gUser.userId));
|
||||||
memberIds.remove(
|
memberIds
|
||||||
Int64(gUser.userId),
|
.remove(Int64(gUser.userId)); // -> Will remove the user later...
|
||||||
); // -> Will remove the user later...
|
|
||||||
} else {
|
} else {
|
||||||
Log.info('A non admin left the group!!!');
|
Log.info('A non admin left the group!!!');
|
||||||
|
|
||||||
final member = await twonlyDB.groupsDao.getGroupMemberByPublicKey(
|
final member = await twonlyDB.groupsDao
|
||||||
Uint8List.fromList(appendedPubKey),
|
.getGroupMemberByPublicKey(Uint8List.fromList(appendedPubKey));
|
||||||
);
|
|
||||||
if (member == null) {
|
if (member == null) {
|
||||||
Log.error('Member is already not in this group...');
|
Log.error('Member is already not in this group...');
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -362,9 +353,8 @@ Future<(int, EncryptedGroupState)?> fetchGroupState(Group group) async {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
var currentGroupMembers = await twonlyDB.groupsDao.getGroupNonLeftMembers(
|
var currentGroupMembers =
|
||||||
group.groupId,
|
await twonlyDB.groupsDao.getGroupNonLeftMembers(group.groupId);
|
||||||
);
|
|
||||||
|
|
||||||
// First find and insert NEW members
|
// First find and insert NEW members
|
||||||
for (final memberId in memberIds) {
|
for (final memberId in memberIds) {
|
||||||
|
|
@ -401,9 +391,8 @@ Future<(int, EncryptedGroupState)?> fetchGroupState(Group group) async {
|
||||||
|
|
||||||
// Send the new user my public group key
|
// Send the new user my public group key
|
||||||
if (group.myGroupPrivateKey != null) {
|
if (group.myGroupPrivateKey != null) {
|
||||||
final keyPair = IdentityKeyPair.fromSerialized(
|
final keyPair =
|
||||||
group.myGroupPrivateKey!,
|
IdentityKeyPair.fromSerialized(group.myGroupPrivateKey!);
|
||||||
);
|
|
||||||
await sendCipherText(
|
await sendCipherText(
|
||||||
memberId.toInt(),
|
memberId.toInt(),
|
||||||
EncryptedContent(
|
EncryptedContent(
|
||||||
|
|
@ -418,9 +407,8 @@ Future<(int, EncryptedGroupState)?> fetchGroupState(Group group) async {
|
||||||
// check if there is a member which is not in the server list...
|
// check if there is a member which is not in the server list...
|
||||||
|
|
||||||
// update the current members list
|
// update the current members list
|
||||||
currentGroupMembers = await twonlyDB.groupsDao.getGroupNonLeftMembers(
|
currentGroupMembers =
|
||||||
group.groupId,
|
await twonlyDB.groupsDao.getGroupNonLeftMembers(group.groupId);
|
||||||
);
|
|
||||||
|
|
||||||
for (final member in currentGroupMembers) {
|
for (final member in currentGroupMembers) {
|
||||||
// Member is not any more in the members list
|
// Member is not any more in the members list
|
||||||
|
|
@ -480,9 +468,8 @@ Future<bool> addNewHiddenContact(int contactId) async {
|
||||||
ContactsCompanion(
|
ContactsCompanion(
|
||||||
username: Value(utf8.decode(userData.username)),
|
username: Value(utf8.decode(userData.username)),
|
||||||
userId: Value(contactId),
|
userId: Value(contactId),
|
||||||
deletedByUser: const Value(
|
deletedByUser:
|
||||||
true,
|
const Value(true), // this will hide the contact in the contact list
|
||||||
), // this will hide the contact in the contact list
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
await processSignalUserData(userData);
|
await processSignalUserData(userData);
|
||||||
|
|
@ -607,9 +594,8 @@ Future<bool> manageAdminState(
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final groupActionType = remove
|
final groupActionType =
|
||||||
? GroupActionType.demoteToMember
|
remove ? GroupActionType.demoteToMember : GroupActionType.promoteToAdmin;
|
||||||
: GroupActionType.promoteToAdmin;
|
|
||||||
|
|
||||||
await sendCipherTextToGroup(
|
await sendCipherTextToGroup(
|
||||||
group.groupId,
|
group.groupId,
|
||||||
|
|
@ -678,9 +664,8 @@ Future<bool> updateChatDeletionTime(
|
||||||
if (currentState == null) return false;
|
if (currentState == null) return false;
|
||||||
final (versionId, state) = currentState;
|
final (versionId, state) = currentState;
|
||||||
|
|
||||||
state.deleteMessagesAfterMilliseconds = Int64(
|
state.deleteMessagesAfterMilliseconds =
|
||||||
deleteMessagesAfterMilliseconds,
|
Int64(deleteMessagesAfterMilliseconds);
|
||||||
);
|
|
||||||
|
|
||||||
// send new state to the server
|
// send new state to the server
|
||||||
if (!await _updateGroupState(group, state)) {
|
if (!await _updateGroupState(group, state)) {
|
||||||
|
|
@ -703,9 +688,8 @@ Future<bool> updateChatDeletionTime(
|
||||||
GroupHistoriesCompanion(
|
GroupHistoriesCompanion(
|
||||||
groupId: Value(group.groupId),
|
groupId: Value(group.groupId),
|
||||||
type: const Value(GroupActionType.changeDisplayMaxTime),
|
type: const Value(GroupActionType.changeDisplayMaxTime),
|
||||||
newDeleteMessagesAfterMilliseconds: Value(
|
newDeleteMessagesAfterMilliseconds:
|
||||||
deleteMessagesAfterMilliseconds,
|
Value(deleteMessagesAfterMilliseconds),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -72,19 +72,13 @@ Future<void> compressAndOverlayVideo(MediaFileService media) async {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final task = VideoRenderData(
|
final task = VideoRenderData(
|
||||||
videoSegments: [
|
video: EditorVideo.file(media.originalPath),
|
||||||
VideoSegment(video: EditorVideo.file(media.originalPath)),
|
imageBytes: media.overlayImagePath.readAsBytesSync(),
|
||||||
],
|
|
||||||
imageLayers: [
|
|
||||||
ImageLayer(image: EditorLayerImage.file(media.overlayImagePath)),
|
|
||||||
],
|
|
||||||
enableAudio: !media.removeAudio,
|
enableAudio: !media.removeAudio,
|
||||||
);
|
);
|
||||||
|
|
||||||
await ProVideoEditor.instance.renderVideoToFile(
|
await ProVideoEditor.instance
|
||||||
media.ffmpegOutputPath.path,
|
.renderVideoToFile(media.ffmpegOutputPath.path, task);
|
||||||
task,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (Platform.isIOS ||
|
if (Platform.isIOS ||
|
||||||
media.ffmpegOutputPath.statSync().size >= 10_000_000 ||
|
media.ffmpegOutputPath.statSync().size >= 10_000_000 ||
|
||||||
|
|
@ -121,8 +115,8 @@ Future<void> compressAndOverlayVideo(MediaFileService media) async {
|
||||||
|
|
||||||
final sizeFrom = (media.ffmpegOutputPath.statSync().size / 1024 / 1024)
|
final sizeFrom = (media.ffmpegOutputPath.statSync().size / 1024 / 1024)
|
||||||
.toStringAsFixed(2);
|
.toStringAsFixed(2);
|
||||||
final sizeTo = (media.tempPath.statSync().size / 1024 / 1024)
|
final sizeTo =
|
||||||
.toStringAsFixed(2);
|
(media.tempPath.statSync().size / 1024 / 1024).toStringAsFixed(2);
|
||||||
|
|
||||||
Log.info(
|
Log.info(
|
||||||
'It took ${stopwatch.elapsedMilliseconds}ms to compress the video. Reduced from $sizeFrom to $sizeTo bytes.',
|
'It took ${stopwatch.elapsedMilliseconds}ms to compress the video. Reduced from $sizeFrom to $sizeTo bytes.',
|
||||||
|
|
|
||||||
|
|
@ -44,9 +44,8 @@ class MediaFileService {
|
||||||
delete = false;
|
delete = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final messages = await twonlyDB.messagesDao.getMessagesByMediaId(
|
final messages =
|
||||||
mediaId,
|
await twonlyDB.messagesDao.getMessagesByMediaId(mediaId);
|
||||||
);
|
|
||||||
|
|
||||||
// in case messages in empty the file will be deleted, as delete is true by default
|
// in case messages in empty the file will be deleted, as delete is true by default
|
||||||
|
|
||||||
|
|
@ -64,18 +63,16 @@ class MediaFileService {
|
||||||
// This branch will prevent to reach the next if condition, with would otherwise store the image for two days
|
// This branch will prevent to reach the next if condition, with would otherwise store the image for two days
|
||||||
// delete = true; // do not overwrite a previous delete = false
|
// delete = true; // do not overwrite a previous delete = false
|
||||||
// this is just to make it easier to understand :)
|
// this is just to make it easier to understand :)
|
||||||
} else if (message.openedAt!.isAfter(
|
} else if (message.openedAt!
|
||||||
clock.now().subtract(const Duration(days: 2)),
|
.isAfter(clock.now().subtract(const Duration(days: 2)))) {
|
||||||
)) {
|
|
||||||
// In case the image was opened, but send with unlimited time or no authentication.
|
// In case the image was opened, but send with unlimited time or no authentication.
|
||||||
if (message.senderId == null) {
|
if (message.senderId == null) {
|
||||||
delete = false;
|
delete = false;
|
||||||
} else {
|
} else {
|
||||||
// Check weather the image was send in a group. Then the images is preserved for two days in case another person stores the image.
|
// Check weather the image was send in a group. Then the images is preserved for two days in case another person stores the image.
|
||||||
// This also allows to reopen this image for two days.
|
// This also allows to reopen this image for two days.
|
||||||
final group = await twonlyDB.groupsDao.getGroup(
|
final group =
|
||||||
message.groupId,
|
await twonlyDB.groupsDao.getGroup(message.groupId);
|
||||||
);
|
|
||||||
if (group != null && !group.isDirectChat) {
|
if (group != null && !group.isDirectChat) {
|
||||||
delete = false;
|
delete = false;
|
||||||
}
|
}
|
||||||
|
|
@ -96,9 +93,8 @@ class MediaFileService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateFromDB() async {
|
Future<void> updateFromDB() async {
|
||||||
final updated = await twonlyDB.mediaFilesDao.getMediaFileById(
|
final updated =
|
||||||
mediaFile.mediaId,
|
await twonlyDB.mediaFilesDao.getMediaFileById(mediaFile.mediaId);
|
||||||
);
|
|
||||||
if (updated != null) {
|
if (updated != null) {
|
||||||
mediaFile = updated;
|
mediaFile = updated;
|
||||||
}
|
}
|
||||||
|
|
@ -155,9 +151,8 @@ class MediaFileService {
|
||||||
mediaFile.mediaId,
|
mediaFile.mediaId,
|
||||||
MediaFilesCompanion(
|
MediaFilesCompanion(
|
||||||
requiresAuthentication: Value(requiresAuthentication),
|
requiresAuthentication: Value(requiresAuthentication),
|
||||||
displayLimitInMilliseconds: requiresAuthentication
|
displayLimitInMilliseconds:
|
||||||
? const Value(12000)
|
requiresAuthentication ? const Value(12000) : const Value.absent(),
|
||||||
: const Value.absent(),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
await updateFromDB();
|
await updateFromDB();
|
||||||
|
|
@ -213,13 +208,6 @@ class MediaFileService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Media was send with unlimited display limit time and without auth required
|
|
||||||
// and the temp media file still exists, then the media file can be reopened again...
|
|
||||||
bool get canBeOpenedAgain =>
|
|
||||||
!mediaFile.requiresAuthentication &&
|
|
||||||
mediaFile.displayLimitInMilliseconds == null &&
|
|
||||||
tempPath.existsSync();
|
|
||||||
|
|
||||||
bool get imagePreviewAvailable =>
|
bool get imagePreviewAvailable =>
|
||||||
thumbnailPath.existsSync() || storedPath.existsSync();
|
thumbnailPath.existsSync() || storedPath.existsSync();
|
||||||
|
|
||||||
|
|
@ -305,10 +293,8 @@ class MediaFileService {
|
||||||
extension = 'm4a';
|
extension = 'm4a';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final mediaBaseDir = buildDirectoryPath(
|
final mediaBaseDir =
|
||||||
directory,
|
buildDirectoryPath(directory, globalApplicationSupportDirectory);
|
||||||
globalApplicationSupportDirectory,
|
|
||||||
);
|
|
||||||
return File(
|
return File(
|
||||||
join(mediaBaseDir.path, '${mediaFile.mediaId}$namePrefix.$extension'),
|
join(mediaBaseDir.path, '${mediaFile.mediaId}$namePrefix.$extension'),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,10 @@ import 'package:cryptography_flutter_plus/cryptography_flutter_plus.dart';
|
||||||
import 'package:cryptography_plus/cryptography_plus.dart';
|
import 'package:cryptography_plus/cryptography_plus.dart';
|
||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:twonly/src/constants/routes.keys.dart';
|
|
||||||
import 'package:twonly/src/constants/secure_storage_keys.dart';
|
import 'package:twonly/src/constants/secure_storage_keys.dart';
|
||||||
import 'package:twonly/src/localization/generated/app_localizations.dart';
|
import 'package:twonly/src/localization/generated/app_localizations.dart';
|
||||||
import 'package:twonly/src/localization/generated/app_localizations_de.dart';
|
import 'package:twonly/src/localization/generated/app_localizations_de.dart';
|
||||||
import 'package:twonly/src/localization/generated/app_localizations_en.dart';
|
import 'package:twonly/src/localization/generated/app_localizations_en.dart';
|
||||||
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
|
|
||||||
import 'package:twonly/src/model/protobuf/client/generated/push_notification.pb.dart';
|
import 'package:twonly/src/model/protobuf/client/generated/push_notification.pb.dart';
|
||||||
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
|
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
@ -47,34 +45,10 @@ Future<void> customLocalPushNotification(String title, String msg) async {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> showPushNotificationFromServerMessages(
|
|
||||||
int fromUserId,
|
|
||||||
EncryptedContent encryptedContent,
|
|
||||||
) async {
|
|
||||||
final pushData = await getPushNotificationFromEncryptedContent(
|
|
||||||
null, // this is the toUserID which must be null as this means that the targetMessageId was send from this user.
|
|
||||||
null,
|
|
||||||
encryptedContent,
|
|
||||||
);
|
|
||||||
if (pushData != null) {
|
|
||||||
final pushUsers = await getPushKeys(SecureStorageKeys.receivingPushKeys);
|
|
||||||
for (final pushUser in pushUsers) {
|
|
||||||
if (pushUser.userId.toInt() == fromUserId) {
|
|
||||||
String? groupId;
|
|
||||||
if (encryptedContent.hasGroupId()) {
|
|
||||||
groupId = encryptedContent.groupId;
|
|
||||||
}
|
|
||||||
return showLocalPushNotification(pushUser, pushData, groupId: groupId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> handlePushData(String pushDataB64) async {
|
Future<void> handlePushData(String pushDataB64) async {
|
||||||
try {
|
try {
|
||||||
final pushData = EncryptedPushNotification.fromBuffer(
|
final pushData =
|
||||||
base64.decode(pushDataB64),
|
EncryptedPushNotification.fromBuffer(base64.decode(pushDataB64));
|
||||||
);
|
|
||||||
|
|
||||||
PushNotification? pushNotification;
|
PushNotification? pushNotification;
|
||||||
PushUser? foundPushUser;
|
PushUser? foundPushUser;
|
||||||
|
|
@ -147,10 +121,8 @@ Future<PushNotification?> tryDecryptMessage(
|
||||||
mac: Mac(push.mac),
|
mac: Mac(push.mac),
|
||||||
);
|
);
|
||||||
|
|
||||||
final plaintext = await chacha20.decrypt(
|
final plaintext =
|
||||||
secretBox,
|
await chacha20.decrypt(secretBox, secretKey: secretKeyData);
|
||||||
secretKey: secretKeyData,
|
|
||||||
);
|
|
||||||
return PushNotification.fromBuffer(plaintext);
|
return PushNotification.fromBuffer(plaintext);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// this error is allowed to happen...
|
// this error is allowed to happen...
|
||||||
|
|
@ -160,9 +132,8 @@ Future<PushNotification?> tryDecryptMessage(
|
||||||
|
|
||||||
Future<void> showLocalPushNotification(
|
Future<void> showLocalPushNotification(
|
||||||
PushUser pushUser,
|
PushUser pushUser,
|
||||||
PushNotification pushNotification, {
|
PushNotification pushNotification,
|
||||||
String? groupId,
|
) async {
|
||||||
}) async {
|
|
||||||
String? title;
|
String? title;
|
||||||
String? body;
|
String? body;
|
||||||
|
|
||||||
|
|
@ -203,26 +174,13 @@ Future<void> showLocalPushNotification(
|
||||||
iOS: darwinNotificationDetails,
|
iOS: darwinNotificationDetails,
|
||||||
);
|
);
|
||||||
|
|
||||||
String? payload;
|
|
||||||
|
|
||||||
if (groupId != null &&
|
|
||||||
(pushNotification.kind == PushKind.text ||
|
|
||||||
pushNotification.kind == PushKind.response ||
|
|
||||||
pushNotification.kind == PushKind.reactionToAudio ||
|
|
||||||
pushNotification.kind == PushKind.storedMediaFile ||
|
|
||||||
pushNotification.kind == PushKind.reactionToImage ||
|
|
||||||
pushNotification.kind == PushKind.reactionToText ||
|
|
||||||
pushNotification.kind == PushKind.reactionToAudio)) {
|
|
||||||
payload = Routes.chatsMessages(groupId);
|
|
||||||
}
|
|
||||||
|
|
||||||
await flutterLocalNotificationsPlugin.show(
|
await flutterLocalNotificationsPlugin.show(
|
||||||
// Invalid argument (id): must fit within the size of a 32-bit integer
|
pushUser.userId.toInt() %
|
||||||
pushUser.userId.toInt() % 2147483647,
|
2147483647, // Invalid argument (id): must fit within the size of a 32-bit integer
|
||||||
title,
|
title,
|
||||||
body,
|
body,
|
||||||
notificationDetails,
|
notificationDetails,
|
||||||
payload: payload,
|
// payload: pushNotification.kind.name,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -301,22 +259,17 @@ String getPushNotificationText(PushNotification pushNotification) {
|
||||||
PushKind.storedMediaFile.name: lang.notificationStoredMediaFile,
|
PushKind.storedMediaFile.name: lang.notificationStoredMediaFile,
|
||||||
PushKind.reaction.name: lang.notificationReaction,
|
PushKind.reaction.name: lang.notificationReaction,
|
||||||
PushKind.reopenedMedia.name: lang.notificationReopenedMedia,
|
PushKind.reopenedMedia.name: lang.notificationReopenedMedia,
|
||||||
PushKind.reactionToVideo.name: lang.notificationReactionToVideo(
|
PushKind.reactionToVideo.name:
|
||||||
pushNotification.additionalContent,
|
lang.notificationReactionToVideo(pushNotification.additionalContent),
|
||||||
),
|
PushKind.reactionToAudio.name:
|
||||||
PushKind.reactionToAudio.name: lang.notificationReactionToAudio(
|
lang.notificationReactionToAudio(pushNotification.additionalContent),
|
||||||
pushNotification.additionalContent,
|
PushKind.reactionToText.name:
|
||||||
),
|
lang.notificationReactionToText(pushNotification.additionalContent),
|
||||||
PushKind.reactionToText.name: lang.notificationReactionToText(
|
PushKind.reactionToImage.name:
|
||||||
pushNotification.additionalContent,
|
lang.notificationReactionToImage(pushNotification.additionalContent),
|
||||||
),
|
|
||||||
PushKind.reactionToImage.name: lang.notificationReactionToImage(
|
|
||||||
pushNotification.additionalContent,
|
|
||||||
),
|
|
||||||
PushKind.response.name: lang.notificationResponse(inGroup),
|
PushKind.response.name: lang.notificationResponse(inGroup),
|
||||||
PushKind.addedToGroup.name: lang.notificationAddedToGroup(
|
PushKind.addedToGroup.name:
|
||||||
pushNotification.additionalContent,
|
lang.notificationAddedToGroup(pushNotification.additionalContent),
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return pushNotificationText[pushNotification.kind.name] ?? '';
|
return pushNotificationText[pushNotification.kind.name] ?? '';
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io' show Platform;
|
import 'dart:io' show Platform;
|
||||||
|
|
||||||
import 'package:firebase_app_installations/firebase_app_installations.dart';
|
|
||||||
import 'package:firebase_core/firebase_core.dart';
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
|
|
@ -42,31 +41,22 @@ Future<void> checkForTokenUpdates() async {
|
||||||
Log.error('Could not get fcm token');
|
Log.error('Could not get fcm token');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Log.info('Loaded fcm token');
|
||||||
Log.info('Loaded FCM token.');
|
|
||||||
|
|
||||||
if (storedToken == null || fcmToken != storedToken) {
|
if (storedToken == null || fcmToken != storedToken) {
|
||||||
Log.info('Got new FCM TOKEN.');
|
|
||||||
await storage.write(key: SecureStorageKeys.googleFcm, value: fcmToken);
|
|
||||||
await updateUserdata((u) {
|
await updateUserdata((u) {
|
||||||
u.updateFCMToken = true;
|
u.updateFCMToken = true;
|
||||||
return u;
|
return u;
|
||||||
});
|
});
|
||||||
|
await storage.write(key: SecureStorageKeys.googleFcm, value: fcmToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
FirebaseMessaging.instance.onTokenRefresh
|
FirebaseMessaging.instance.onTokenRefresh.listen((fcmToken) async {
|
||||||
.listen((fcmToken) async {
|
|
||||||
Log.info('Got new FCM TOKEN.');
|
|
||||||
await storage.write(
|
|
||||||
key: SecureStorageKeys.googleFcm,
|
|
||||||
value: fcmToken,
|
|
||||||
);
|
|
||||||
await updateUserdata((u) {
|
await updateUserdata((u) {
|
||||||
u.updateFCMToken = true;
|
u.updateFCMToken = true;
|
||||||
return u;
|
return u;
|
||||||
});
|
});
|
||||||
})
|
await storage.write(key: SecureStorageKeys.googleFcm, value: fcmToken);
|
||||||
.onError((err) {
|
}).onError((err) {
|
||||||
Log.error('could not listen on token refresh');
|
Log.error('could not listen on token refresh');
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -74,35 +64,21 @@ Future<void> checkForTokenUpdates() async {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initFCMAfterAuthenticated({bool force = false}) async {
|
Future<void> initFCMAfterAuthenticated() async {
|
||||||
if (gUser.updateFCMToken || force) {
|
if (gUser.updateFCMToken) {
|
||||||
const storage = FlutterSecureStorage();
|
const storage = FlutterSecureStorage();
|
||||||
final storedToken = await storage.read(key: SecureStorageKeys.googleFcm);
|
final storedToken = await storage.read(key: SecureStorageKeys.googleFcm);
|
||||||
if (storedToken != null) {
|
if (storedToken != null) {
|
||||||
final res = await apiService.updateFCMToken(storedToken);
|
final res = await apiService.updateFCMToken(storedToken);
|
||||||
if (res.isSuccess) {
|
if (res.isSuccess) {
|
||||||
Log.info('Uploaded new FCM token!');
|
Log.info('Uploaded new fmt token!');
|
||||||
await updateUserdata((u) {
|
await updateUserdata((u) {
|
||||||
u.updateFCMToken = false;
|
u.updateFCMToken = false;
|
||||||
return u;
|
return u;
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
Log.error('Could not update FCM token!');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.error('Could not send FCM update to server as token is empty.');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> resetFCMTokens() async {
|
|
||||||
await FirebaseInstallations.instance.delete();
|
|
||||||
Log.info('Firebase Installation successfully deleted.');
|
|
||||||
await FirebaseMessaging.instance.deleteToken();
|
|
||||||
Log.info('Old FCM deleted.');
|
|
||||||
await const FlutterSecureStorage().delete(key: SecureStorageKeys.googleFcm);
|
|
||||||
await checkForTokenUpdates();
|
|
||||||
await initFCMAfterAuthenticated(force: true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initFCMService() async {
|
Future<void> initFCMService() async {
|
||||||
|
|
@ -110,24 +86,36 @@ Future<void> initFCMService() async {
|
||||||
options: DefaultFirebaseOptions.currentPlatform,
|
options: DefaultFirebaseOptions.currentPlatform,
|
||||||
);
|
);
|
||||||
|
|
||||||
await checkForTokenUpdates();
|
unawaited(checkForTokenUpdates());
|
||||||
|
|
||||||
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
|
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
|
||||||
|
|
||||||
|
// You may set the permission requests to "provisional" which allows the user to choose what type
|
||||||
|
// of notifications they would like to receive once the user receives a notification.
|
||||||
|
// final notificationSettings =
|
||||||
|
// await FirebaseMessaging.instance.requestPermission(provisional: true);
|
||||||
await FirebaseMessaging.instance.requestPermission();
|
await FirebaseMessaging.instance.requestPermission();
|
||||||
|
|
||||||
|
// For apple platforms, ensure the APNS token is available before making any FCM plugin API calls
|
||||||
|
// if (Platform.isIOS) {
|
||||||
|
// final apnsToken = await FirebaseMessaging.instance.getAPNSToken();
|
||||||
|
// if (apnsToken == null) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
FirebaseMessaging.onMessage.listen(handleRemoteMessage);
|
FirebaseMessaging.onMessage.listen(handleRemoteMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@pragma('vm:entry-point')
|
@pragma('vm:entry-point')
|
||||||
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
||||||
final isInitialized = await initBackgroundExecution();
|
initLogger();
|
||||||
Log.info('Handling a background message: ${message.messageId}');
|
// Log.info('Handling a background message: ${message.messageId}');
|
||||||
await handleRemoteMessage(message);
|
await handleRemoteMessage(message);
|
||||||
|
|
||||||
if (Platform.isAndroid) {
|
if (Platform.isAndroid) {
|
||||||
if (isInitialized) {
|
if (await initBackgroundExecution()) {
|
||||||
await handlePeriodicTask(lastExecutionInSecondsLimit: 10);
|
await handlePeriodicTask();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// make sure every thing run...
|
// make sure every thing run...
|
||||||
|
|
@ -152,11 +140,7 @@ Future<void> handleRemoteMessage(RemoteMessage message) async {
|
||||||
final body =
|
final body =
|
||||||
message.notification?.body ?? message.data['body'] as String? ?? '';
|
message.notification?.body ?? message.data['body'] as String? ?? '';
|
||||||
await customLocalPushNotification(title, body);
|
await customLocalPushNotification(title, body);
|
||||||
|
} else if (message.data['push_data'] != null) {
|
||||||
|
await handlePushData(message.data['push_data'] as String);
|
||||||
}
|
}
|
||||||
|
|
||||||
// On Android the push notification is now shown in the server_message.dart. This ensures
|
|
||||||
// that the messages was successfully decrypted before showing the push notification
|
|
||||||
// else if (message.data['push_data'] != null) {
|
|
||||||
// await handlePushData(message.data['push_data'] as String);
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,15 +49,13 @@ Future<void> setupNotificationWithUsers({
|
||||||
|
|
||||||
final contacts = await twonlyDB.contactsDao.getAllContacts();
|
final contacts = await twonlyDB.contactsDao.getAllContacts();
|
||||||
for (final contact in contacts) {
|
for (final contact in contacts) {
|
||||||
final pushUser = pushUsers.firstWhereOrNull(
|
final pushUser =
|
||||||
(x) => x.userId == contact.userId,
|
pushUsers.firstWhereOrNull((x) => x.userId == contact.userId);
|
||||||
);
|
|
||||||
|
|
||||||
if (pushUser != null && pushUser.pushKeys.isNotEmpty) {
|
if (pushUser != null && pushUser.pushKeys.isNotEmpty) {
|
||||||
// make it harder to predict the change of the key
|
// make it harder to predict the change of the key
|
||||||
final timeBefore = clock.now().subtract(
|
final timeBefore =
|
||||||
Duration(days: 10 + random.nextInt(5)),
|
clock.now().subtract(Duration(days: 10 + 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(),
|
||||||
|
|
@ -199,7 +197,7 @@ Future<void> updateLastMessageId(int fromUserId, String messageId) async {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<PushNotification?> getPushNotificationFromEncryptedContent(
|
Future<PushNotification?> getPushNotificationFromEncryptedContent(
|
||||||
int? toUserId,
|
int toUserId,
|
||||||
String? messageId,
|
String? messageId,
|
||||||
EncryptedContent content,
|
EncryptedContent content,
|
||||||
) async {
|
) async {
|
||||||
|
|
@ -212,7 +210,7 @@ Future<PushNotification?> getPushNotificationFromEncryptedContent(
|
||||||
final msg = await twonlyDB.messagesDao
|
final msg = await twonlyDB.messagesDao
|
||||||
.getMessageById(content.reaction.targetMessageId)
|
.getMessageById(content.reaction.targetMessageId)
|
||||||
.getSingleOrNull();
|
.getSingleOrNull();
|
||||||
if (msg == null || msg.senderId != toUserId) {
|
if (msg == null || msg.senderId == null || msg.senderId != toUserId) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (msg.content != null) {
|
if (msg.content != null) {
|
||||||
|
|
@ -287,7 +285,7 @@ Future<PushNotification?> getPushNotificationFromEncryptedContent(
|
||||||
.getMessageById(content.reaction.targetMessageId)
|
.getMessageById(content.reaction.targetMessageId)
|
||||||
.getSingleOrNull();
|
.getSingleOrNull();
|
||||||
// These notifications should only be send to the original sender.
|
// These notifications should only be send to the original sender.
|
||||||
if (msg == null || msg.senderId != toUserId) {
|
if (msg == null || msg.senderId == null || msg.senderId != toUserId) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
switch (content.mediaUpdate.type) {
|
switch (content.mediaUpdate.type) {
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,9 @@ final StreamController<NotificationResponse> selectNotificationStream =
|
||||||
@pragma('vm:entry-point')
|
@pragma('vm:entry-point')
|
||||||
void notificationTapBackground(NotificationResponse notificationResponse) {
|
void notificationTapBackground(NotificationResponse notificationResponse) {
|
||||||
// ignore: avoid_print
|
// ignore: avoid_print
|
||||||
print(
|
print('notification(${notificationResponse.id}) action tapped: '
|
||||||
'notification(${notificationResponse.id}) action tapped: '
|
|
||||||
'${notificationResponse.actionId} with'
|
'${notificationResponse.actionId} with'
|
||||||
' payload: ${notificationResponse.payload}',
|
' payload: ${notificationResponse.payload}');
|
||||||
);
|
|
||||||
if (notificationResponse.input?.isNotEmpty ?? false) {
|
if (notificationResponse.input?.isNotEmpty ?? false) {
|
||||||
// ignore: avoid_print
|
// ignore: avoid_print
|
||||||
print(
|
print(
|
||||||
|
|
@ -28,9 +26,8 @@ final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
|
||||||
int id = 0;
|
int id = 0;
|
||||||
|
|
||||||
Future<void> setupPushNotification() async {
|
Future<void> setupPushNotification() async {
|
||||||
const initializationSettingsAndroid = AndroidInitializationSettings(
|
const initializationSettingsAndroid =
|
||||||
'ic_launcher_foreground',
|
AndroidInitializationSettings('ic_launcher_foreground');
|
||||||
);
|
|
||||||
|
|
||||||
final darwinNotificationCategories = <DarwinNotificationCategory>[];
|
final darwinNotificationCategories = <DarwinNotificationCategory>[];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,8 @@ Future<IdentityKeyPair?> getSignalIdentityKeyPair() async {
|
||||||
Future<void> signalHandleNewServerConnection() async {
|
Future<void> signalHandleNewServerConnection() async {
|
||||||
if (gUser.signalLastSignedPreKeyUpdated != null) {
|
if (gUser.signalLastSignedPreKeyUpdated != null) {
|
||||||
final fortyEightHoursAgo = clock.now().subtract(const Duration(hours: 48));
|
final fortyEightHoursAgo = clock.now().subtract(const Duration(hours: 48));
|
||||||
final isYoungerThan48Hours = (gUser.signalLastSignedPreKeyUpdated!).isAfter(
|
final isYoungerThan48Hours =
|
||||||
fortyEightHoursAgo,
|
(gUser.signalLastSignedPreKeyUpdated!).isAfter(fortyEightHoursAgo);
|
||||||
);
|
|
||||||
if (isYoungerThan48Hours) {
|
if (isYoungerThan48Hours) {
|
||||||
// The key does live for 48 hours then it expires and a new key is generated.
|
// The key does live for 48 hours then it expires and a new key is generated.
|
||||||
return;
|
return;
|
||||||
|
|
@ -77,9 +76,8 @@ Future<List<PreKeyRecord>> signalGetPreKeys() async {
|
||||||
Future<SignalIdentity?> getSignalIdentity() async {
|
Future<SignalIdentity?> getSignalIdentity() async {
|
||||||
try {
|
try {
|
||||||
const storage = FlutterSecureStorage();
|
const storage = FlutterSecureStorage();
|
||||||
var signalIdentityJson = await storage.read(
|
var signalIdentityJson =
|
||||||
key: SecureStorageKeys.signalIdentity,
|
await storage.read(key: SecureStorageKeys.signalIdentity);
|
||||||
);
|
|
||||||
if (signalIdentityJson == null) {
|
if (signalIdentityJson == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -106,17 +104,13 @@ Future<void> createIfNotExistsSignalIdentity() async {
|
||||||
final identityKeyPair = generateIdentityKeyPair();
|
final identityKeyPair = generateIdentityKeyPair();
|
||||||
final registrationId = generateRegistrationId(true);
|
final registrationId = generateRegistrationId(true);
|
||||||
|
|
||||||
final signalStore = ConnectSignalProtocolStore(
|
final signalStore =
|
||||||
identityKeyPair,
|
ConnectSignalProtocolStore(identityKeyPair, registrationId);
|
||||||
registrationId,
|
|
||||||
);
|
|
||||||
|
|
||||||
final signedPreKey = generateSignedPreKey(identityKeyPair, defaultDeviceId);
|
final signedPreKey = generateSignedPreKey(identityKeyPair, defaultDeviceId);
|
||||||
|
|
||||||
await signalStore.signedPreKeyStore.storeSignedPreKey(
|
await signalStore.signedPreKeyStore
|
||||||
signedPreKey.id,
|
.storeSignedPreKey(signedPreKey.id, signedPreKey);
|
||||||
signedPreKey,
|
|
||||||
);
|
|
||||||
|
|
||||||
final storedSignalIdentity = SignalIdentity(
|
final storedSignalIdentity = SignalIdentity(
|
||||||
identityKeyPairU8List: identityKeyPair.serialize(),
|
identityKeyPairU8List: identityKeyPair.serialize(),
|
||||||
|
|
|
||||||
|
|
@ -46,9 +46,8 @@ Future<bool> processSignalUserData(Response_UserData userData) async {
|
||||||
|
|
||||||
final tempIdentityKey = IdentityKey(
|
final tempIdentityKey = IdentityKey(
|
||||||
Curve.decodePoint(
|
Curve.decodePoint(
|
||||||
DjbECPublicKey(
|
DjbECPublicKey(Uint8List.fromList(userData.publicIdentityKey))
|
||||||
Uint8List.fromList(userData.publicIdentityKey),
|
.serialize(),
|
||||||
).serialize(),
|
|
||||||
1,
|
1,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,8 @@ Future<ConnectSignalProtocolStore?> getSignalStore() async {
|
||||||
Future<ConnectSignalProtocolStore> getSignalStoreFromIdentity(
|
Future<ConnectSignalProtocolStore> getSignalStoreFromIdentity(
|
||||||
SignalIdentity signalIdentity,
|
SignalIdentity signalIdentity,
|
||||||
) async {
|
) async {
|
||||||
final identityKeyPair = IdentityKeyPair.fromSerialized(
|
final identityKeyPair =
|
||||||
signalIdentity.identityKeyPairU8List,
|
IdentityKeyPair.fromSerialized(signalIdentity.identityKeyPairU8List);
|
||||||
);
|
|
||||||
|
|
||||||
return ConnectSignalProtocolStore(
|
return ConnectSignalProtocolStore(
|
||||||
identityKeyPair,
|
identityKeyPair,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
import 'dart:ui';
|
|
||||||
|
|
||||||
class DefaultColors {
|
|
||||||
static const messageSelf = Color.fromARGB(255, 58, 136, 102);
|
|
||||||
static const messageOther = Color.fromARGB(233, 68, 137, 255);
|
|
||||||
}
|
|
||||||
|
|
@ -6,21 +6,12 @@ import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
||||||
Future<void> createPushAvatars({int? forceForUserId}) async {
|
Future<void> createPushAvatars() async {
|
||||||
final contacts = await twonlyDB.contactsDao.getAllContacts();
|
final contacts = await twonlyDB.contactsDao.getAllContacts();
|
||||||
|
|
||||||
for (final contact in contacts) {
|
for (final contact in contacts) {
|
||||||
if (contact.avatarSvgCompressed == null) continue;
|
if (contact.avatarSvgCompressed == null) continue;
|
||||||
|
|
||||||
if (forceForUserId == null) {
|
|
||||||
if (avatarPNGFile(contact.userId).existsSync()) {
|
|
||||||
continue; // only create the avatar in case no avatar exists yet fot this user
|
|
||||||
}
|
|
||||||
} else if (contact.userId != forceForUserId) {
|
|
||||||
// only update the avatar for this specified contact
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
final avatarSvg = getAvatarSvg(contact.avatarSvgCompressed!);
|
final avatarSvg = getAvatarSvg(contact.avatarSvgCompressed!);
|
||||||
|
|
||||||
final pictureInfo = await vg.loadPicture(SvgStringLoader(avatarSvg), null);
|
final pictureInfo = await vg.loadPicture(SvgStringLoader(avatarSvg), null);
|
||||||
|
|
@ -36,9 +27,8 @@ Future<void> createPushAvatars({int? forceForUserId}) async {
|
||||||
}
|
}
|
||||||
|
|
||||||
File avatarPNGFile(int contactId) {
|
File avatarPNGFile(int contactId) {
|
||||||
final avatarsDirectory = Directory(
|
final avatarsDirectory =
|
||||||
'$globalApplicationCacheDirectory/avatars',
|
Directory('$globalApplicationCacheDirectory/avatars');
|
||||||
);
|
|
||||||
|
|
||||||
if (!avatarsDirectory.existsSync()) {
|
if (!avatarsDirectory.existsSync()) {
|
||||||
avatarsDirectory.createSync(recursive: true);
|
avatarsDirectory.createSync(recursive: true);
|
||||||
|
|
@ -52,10 +42,8 @@ Future<Uint8List> getUserAvatar() async {
|
||||||
return data.buffer.asUint8List();
|
return data.buffer.asUint8List();
|
||||||
}
|
}
|
||||||
|
|
||||||
final pictureInfo = await vg.loadPicture(
|
final pictureInfo =
|
||||||
SvgStringLoader(gUser.avatarSvg!),
|
await vg.loadPicture(SvgStringLoader(gUser.avatarSvg!), null);
|
||||||
null,
|
|
||||||
);
|
|
||||||
|
|
||||||
final image = await pictureInfo.picture.toImage(270, 300);
|
final image = await pictureInfo.picture.toImage(270, 300);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:io';
|
|
||||||
import 'package:mutex/mutex.dart';
|
|
||||||
import 'package:twonly/globals.dart';
|
|
||||||
|
|
||||||
Future<T> exclusiveAccess<T>({
|
|
||||||
required String lockName,
|
|
||||||
required Future<T> Function() action,
|
|
||||||
required Mutex mutex,
|
|
||||||
}) async {
|
|
||||||
final lockFile = File('$globalApplicationSupportDirectory/$lockName.lock');
|
|
||||||
return mutex.protect(() async {
|
|
||||||
var lockAcquired = false;
|
|
||||||
|
|
||||||
while (!lockAcquired) {
|
|
||||||
try {
|
|
||||||
lockFile.createSync(exclusive: true);
|
|
||||||
lockAcquired = true;
|
|
||||||
} on FileSystemException catch (e) {
|
|
||||||
final isExists = e is PathExistsException || e.osError?.errorCode == 17;
|
|
||||||
if (!isExists) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
final stat = lockFile.statSync();
|
|
||||||
if (stat.type != FileSystemEntityType.notFound) {
|
|
||||||
final age = DateTime.now().difference(stat.modified).inMilliseconds;
|
|
||||||
if (age > 1000) {
|
|
||||||
lockFile.deleteSync();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
await Future.delayed(const Duration(milliseconds: 50));
|
|
||||||
} catch (_) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return await action();
|
|
||||||
} finally {
|
|
||||||
if (lockAcquired) {
|
|
||||||
try {
|
|
||||||
if (lockFile.existsSync()) {
|
|
||||||
lockFile.deleteSync();
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
@ -6,13 +6,9 @@ import 'package:logging/logging.dart';
|
||||||
import 'package:mutex/mutex.dart';
|
import 'package:mutex/mutex.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/utils/exclusive_access.dart';
|
|
||||||
|
|
||||||
bool _isInitialized = false;
|
|
||||||
|
|
||||||
void initLogger() {
|
void initLogger() {
|
||||||
if (_isInitialized) return;
|
// Logger.root.level = kReleaseMode ? Level.INFO : Level.ALL;
|
||||||
_isInitialized = true;
|
|
||||||
Logger.root.level = Level.ALL;
|
Logger.root.level = Level.ALL;
|
||||||
Logger.root.onRecord.listen((record) async {
|
Logger.root.onRecord.listen((record) async {
|
||||||
unawaited(_writeLogToFile(record));
|
unawaited(_writeLogToFile(record));
|
||||||
|
|
@ -95,11 +91,46 @@ Future<String> readLast1000Lines() async {
|
||||||
final Mutex _logMutex = Mutex();
|
final Mutex _logMutex = Mutex();
|
||||||
|
|
||||||
Future<T> _protectFileAccess<T>(Future<T> Function() action) async {
|
Future<T> _protectFileAccess<T>(Future<T> Function() action) async {
|
||||||
return exclusiveAccess(
|
return _logMutex.protect(() async {
|
||||||
lockName: 'app.log',
|
final lockFile = File('$globalApplicationSupportDirectory/app.log.lock');
|
||||||
action: action,
|
var lockAcquired = false;
|
||||||
mutex: _logMutex,
|
|
||||||
);
|
while (!lockAcquired) {
|
||||||
|
try {
|
||||||
|
lockFile.createSync(exclusive: true);
|
||||||
|
lockAcquired = true;
|
||||||
|
} on FileSystemException catch (e) {
|
||||||
|
final isExists = e is PathExistsException || e.osError?.errorCode == 17;
|
||||||
|
if (!isExists) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
final stat = lockFile.statSync();
|
||||||
|
if (stat.type != FileSystemEntityType.notFound) {
|
||||||
|
final age = DateTime.now().difference(stat.modified).inMilliseconds;
|
||||||
|
if (age > 1000) {
|
||||||
|
lockFile.deleteSync();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
await Future.delayed(const Duration(milliseconds: 50));
|
||||||
|
} catch (_) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return await action();
|
||||||
|
} finally {
|
||||||
|
if (lockAcquired) {
|
||||||
|
try {
|
||||||
|
if (lockFile.existsSync()) {
|
||||||
|
lockFile.deleteSync();
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _writeLogToFile(LogRecord record) async {
|
Future<void> _writeLogToFile(LogRecord record) async {
|
||||||
|
|
@ -129,34 +160,17 @@ Future<void> cleanLogFile() async {
|
||||||
return _protectFileAccess(() async {
|
return _protectFileAccess(() async {
|
||||||
final logFile = File('$globalApplicationSupportDirectory/app.log');
|
final logFile = File('$globalApplicationSupportDirectory/app.log');
|
||||||
|
|
||||||
if (!logFile.existsSync()) {
|
if (logFile.existsSync()) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
final lines = await logFile.readAsLines();
|
final lines = await logFile.readAsLines();
|
||||||
|
|
||||||
final twoWeekAgo = clock.now().subtract(const Duration(days: 14));
|
if (lines.length <= 10000) return;
|
||||||
var keepStartIndex = -1;
|
|
||||||
|
|
||||||
for (var i = 0; i < lines.length; i += 100) {
|
final removeCount = lines.length - 10000;
|
||||||
if (lines[i].length >= 19) {
|
final remaining = lines.sublist(removeCount, lines.length);
|
||||||
final date = DateTime.tryParse(lines[i].substring(0, 19));
|
|
||||||
if (date != null && date.isAfter(twoWeekAgo)) {
|
|
||||||
keepStartIndex = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keepStartIndex == 0) return;
|
|
||||||
|
|
||||||
if (keepStartIndex == -1) {
|
|
||||||
await logFile.writeAsString('');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final remaining = lines.sublist(keepStartIndex);
|
|
||||||
final sink = logFile.openWrite()..writeAll(remaining, '\n');
|
final sink = logFile.openWrite()..writeAll(remaining, '\n');
|
||||||
await sink.close();
|
await sink.close();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -224,17 +224,13 @@ InputDecoration inputTextMessageDeco(BuildContext context) {
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
borderSide: BorderSide(
|
borderSide:
|
||||||
color: Theme.of(context).colorScheme.primary,
|
BorderSide(color: Theme.of(context).colorScheme.primary, width: 2),
|
||||||
width: 2,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
focusedBorder: OutlineInputBorder(
|
focusedBorder: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
borderSide: BorderSide(
|
borderSide:
|
||||||
color: Theme.of(context).colorScheme.primary,
|
BorderSide(color: Theme.of(context).colorScheme.primary, width: 2),
|
||||||
width: 2,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
enabledBorder: OutlineInputBorder(
|
enabledBorder: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
|
@ -257,13 +253,11 @@ String formatDateTime(BuildContext context, DateTime? dateTime) {
|
||||||
final now = clock.now();
|
final now = clock.now();
|
||||||
final difference = now.difference(dateTime);
|
final difference = now.difference(dateTime);
|
||||||
|
|
||||||
final date = DateFormat.yMd(
|
final date = DateFormat.yMd(Localizations.localeOf(context).toLanguageTag())
|
||||||
Localizations.localeOf(context).toLanguageTag(),
|
.format(dateTime);
|
||||||
).format(dateTime);
|
|
||||||
|
|
||||||
final time = DateFormat.Hm(
|
final time = DateFormat.Hm(Localizations.localeOf(context).toLanguageTag())
|
||||||
Localizations.localeOf(context).toLanguageTag(),
|
.format(dateTime);
|
||||||
).format(dateTime);
|
|
||||||
|
|
||||||
if (difference.inDays == 0) {
|
if (difference.inDays == 0) {
|
||||||
return time;
|
return time;
|
||||||
|
|
@ -365,21 +359,18 @@ String friendlyDateTime(
|
||||||
Locale? locale,
|
Locale? locale,
|
||||||
}) {
|
}) {
|
||||||
// Build date part
|
// Build date part
|
||||||
final datePart = DateFormat.yMd(
|
final datePart =
|
||||||
Localizations.localeOf(context).toString(),
|
DateFormat.yMd(Localizations.localeOf(context).toString()).format(dt);
|
||||||
).format(dt);
|
|
||||||
|
|
||||||
final use24Hour = MediaQuery.of(context).alwaysUse24HourFormat;
|
final use24Hour = MediaQuery.of(context).alwaysUse24HourFormat;
|
||||||
|
|
||||||
var timePart = '';
|
var timePart = '';
|
||||||
if (use24Hour) {
|
if (use24Hour) {
|
||||||
timePart = DateFormat.jm(
|
timePart =
|
||||||
Localizations.localeOf(context).toString(),
|
DateFormat.jm(Localizations.localeOf(context).toString()).format(dt);
|
||||||
).format(dt);
|
|
||||||
} else {
|
} else {
|
||||||
timePart = DateFormat.Hm(
|
timePart =
|
||||||
Localizations.localeOf(context).toString(),
|
DateFormat.Hm(Localizations.localeOf(context).toString()).format(dt);
|
||||||
).format(dt);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return '$timePart $datePart';
|
return '$timePart $datePart';
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,8 @@ Future<Uint8List> getProfileQrCodeData() async {
|
||||||
final publicProfile = PublicProfile(
|
final publicProfile = PublicProfile(
|
||||||
userId: Int64(gUser.userId),
|
userId: Int64(gUser.userId),
|
||||||
username: gUser.username,
|
username: gUser.username,
|
||||||
publicIdentityKey: (await signalStore.getIdentityKeyPair())
|
publicIdentityKey:
|
||||||
.getPublicKey()
|
(await signalStore.getIdentityKeyPair()).getPublicKey().serialize(),
|
||||||
.serialize(),
|
|
||||||
registrationId: Int64(signalIdentity.registrationId),
|
registrationId: Int64(signalIdentity.registrationId),
|
||||||
signedPrekey: signedPreKey.getKeyPair().publicKey.serialize(),
|
signedPrekey: signedPreKey.getKeyPair().publicKey.serialize(),
|
||||||
signedPrekeySignature: signedPreKey.signature,
|
signedPrekeySignature: signedPreKey.signature,
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,8 @@ Future<bool> isUserCreated() async {
|
||||||
|
|
||||||
Future<UserData?> getUser() async {
|
Future<UserData?> getUser() async {
|
||||||
try {
|
try {
|
||||||
final userJson = await const FlutterSecureStorage().read(
|
final userJson = await const FlutterSecureStorage()
|
||||||
key: SecureStorageKeys.userData,
|
.read(key: SecureStorageKeys.userData);
|
||||||
);
|
|
||||||
if (userJson == null) {
|
if (userJson == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -65,10 +64,8 @@ Future<UserData?> updateUserdata(
|
||||||
user.defaultShowTime = null;
|
user.defaultShowTime = null;
|
||||||
}
|
}
|
||||||
final updated = updateUser(user);
|
final updated = updateUser(user);
|
||||||
await const FlutterSecureStorage().write(
|
await const FlutterSecureStorage()
|
||||||
key: SecureStorageKeys.userData,
|
.write(key: SecureStorageKeys.userData, value: jsonEncode(updated));
|
||||||
value: jsonEncode(updated),
|
|
||||||
);
|
|
||||||
gUser = updated;
|
gUser = updated;
|
||||||
return updated;
|
return updated;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -34,15 +34,9 @@ class MainCameraPreview extends StatelessWidget {
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: mainCameraController
|
width: mainCameraController
|
||||||
.cameraController!
|
.cameraController!.value.previewSize!.height,
|
||||||
.value
|
|
||||||
.previewSize!
|
|
||||||
.height,
|
|
||||||
height: mainCameraController
|
height: mainCameraController
|
||||||
.cameraController!
|
.cameraController!.value.previewSize!.width,
|
||||||
.value
|
|
||||||
.previewSize!
|
|
||||||
.width,
|
|
||||||
child: CameraPreview(
|
child: CameraPreview(
|
||||||
key: mainCameraController.cameraPreviewKey,
|
key: mainCameraController.cameraPreviewKey,
|
||||||
mainCameraController.cameraController!,
|
mainCameraController.cameraController!,
|
||||||
|
|
@ -73,15 +67,9 @@ class MainCameraPreview extends StatelessWidget {
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: mainCameraController
|
width: mainCameraController
|
||||||
.cameraController!
|
.cameraController!.value.previewSize!.height,
|
||||||
.value
|
|
||||||
.previewSize!
|
|
||||||
.height,
|
|
||||||
height: mainCameraController
|
height: mainCameraController
|
||||||
.cameraController!
|
.cameraController!.value.previewSize!.width,
|
||||||
.value
|
|
||||||
.previewSize!
|
|
||||||
.width,
|
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
Positioned(
|
Positioned(
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ import 'package:twonly/src/views/camera/camera_preview_components/zoom_selector.
|
||||||
import 'package:twonly/src/views/camera/share_image_editor.view.dart';
|
import 'package:twonly/src/views/camera/share_image_editor.view.dart';
|
||||||
import 'package:twonly/src/views/camera/share_image_editor/action_button.dart';
|
import 'package:twonly/src/views/camera/share_image_editor/action_button.dart';
|
||||||
import 'package:twonly/src/views/components/avatar_icon.component.dart';
|
import 'package:twonly/src/views/components/avatar_icon.component.dart';
|
||||||
import 'package:twonly/src/views/components/loader/three_rotating_dots.loader.dart';
|
import 'package:twonly/src/views/components/loader.dart';
|
||||||
import 'package:twonly/src/views/components/media_view_sizing.dart';
|
import 'package:twonly/src/views/components/media_view_sizing.dart';
|
||||||
import 'package:twonly/src/views/home.view.dart';
|
import 'package:twonly/src/views/home.view.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
@ -181,9 +181,8 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
// Maybe this is the reason?
|
// Maybe this is the reason?
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
androidVolumeDownSub = FlutterAndroidVolumeKeydown.stream.listen((
|
androidVolumeDownSub =
|
||||||
event,
|
FlutterAndroidVolumeKeydown.stream.listen((event) {
|
||||||
) {
|
|
||||||
if (widget.isVisible) {
|
if (widget.isVisible) {
|
||||||
takePicture();
|
takePicture();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -298,9 +297,8 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final image = await mc.screenshotController.capture(
|
final image = await mc.screenshotController
|
||||||
pixelRatio: MediaQuery.of(context).devicePixelRatio,
|
.capture(pixelRatio: MediaQuery.of(context).devicePixelRatio);
|
||||||
);
|
|
||||||
|
|
||||||
if (await pushMediaEditor(image, null)) {
|
if (await pushMediaEditor(image, null)) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -316,8 +314,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
bool sharedFromGallery = false,
|
bool sharedFromGallery = false,
|
||||||
MediaType? mediaType,
|
MediaType? mediaType,
|
||||||
}) async {
|
}) async {
|
||||||
final type =
|
final type = mediaType ??
|
||||||
mediaType ??
|
|
||||||
((videoFilePath != null) ? MediaType.video : MediaType.image);
|
((videoFilePath != null) ? MediaType.video : MediaType.image);
|
||||||
final mediaFileService = await initializeMediaUpload(
|
final mediaFileService = await initializeMediaUpload(
|
||||||
type,
|
type,
|
||||||
|
|
@ -343,8 +340,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
await deInitVolumeControl();
|
await deInitVolumeControl();
|
||||||
if (!mounted) return true;
|
if (!mounted) return true;
|
||||||
|
|
||||||
final shouldReturn =
|
final shouldReturn = await Navigator.push(
|
||||||
await Navigator.push(
|
|
||||||
context,
|
context,
|
||||||
PageRouteBuilder(
|
PageRouteBuilder(
|
||||||
opaque: false,
|
opaque: false,
|
||||||
|
|
@ -356,15 +352,13 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
mainCameraController: mc,
|
mainCameraController: mc,
|
||||||
previewLink: mc.sharedLinkForPreview,
|
previewLink: mc.sharedLinkForPreview,
|
||||||
),
|
),
|
||||||
transitionsBuilder:
|
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||||
(context, animation, secondaryAnimation, child) {
|
|
||||||
return child;
|
return child;
|
||||||
},
|
},
|
||||||
transitionDuration: Duration.zero,
|
transitionDuration: Duration.zero,
|
||||||
reverseTransitionDuration: Duration.zero,
|
reverseTransitionDuration: Duration.zero,
|
||||||
),
|
),
|
||||||
)
|
) as bool?;
|
||||||
as bool?;
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
mc.isSharePreviewIsShown = false;
|
mc.isSharePreviewIsShown = false;
|
||||||
|
|
@ -402,15 +396,13 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mc.selectedCameraDetails.scaleFactor =
|
mc.selectedCameraDetails.scaleFactor = (_baseScaleFactor +
|
||||||
(_baseScaleFactor +
|
|
||||||
// ignore: avoid_dynamic_calls
|
// ignore: avoid_dynamic_calls
|
||||||
(_basePanY - (details.localPosition.dy as double)) / 30)
|
(_basePanY - (details.localPosition.dy as double)) / 30)
|
||||||
.clamp(1, mc.selectedCameraDetails.maxAvailableZoom);
|
.clamp(1, mc.selectedCameraDetails.maxAvailableZoom);
|
||||||
|
|
||||||
await mc.cameraController!.setZoomLevel(
|
await mc.cameraController!
|
||||||
mc.selectedCameraDetails.scaleFactor,
|
.setZoomLevel(mc.selectedCameraDetails.scaleFactor);
|
||||||
);
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
@ -442,9 +434,8 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
ScreenshotImage? image;
|
ScreenshotImage? image;
|
||||||
MediaType? mediaType;
|
MediaType? mediaType;
|
||||||
|
|
||||||
final isImage = imageExtensions.any(
|
final isImage =
|
||||||
(ext) => pickedFile.name.contains(ext),
|
imageExtensions.any((ext) => pickedFile.name.contains(ext));
|
||||||
);
|
|
||||||
if (isImage) {
|
if (isImage) {
|
||||||
if (pickedFile.name.contains('.gif')) {
|
if (pickedFile.name.contains('.gif')) {
|
||||||
mediaType = MediaType.gif;
|
mediaType = MediaType.gif;
|
||||||
|
|
@ -506,15 +497,10 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
mc.isVideoRecording = true;
|
mc.isVideoRecording = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (mc.selectedCameraDetails.isFlashOn) {
|
|
||||||
await mc.cameraController?.setFlashMode(FlashMode.torch);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await mc.cameraController?.startVideoRecording();
|
await mc.cameraController?.startVideoRecording();
|
||||||
_videoRecordingTimer = Timer.periodic(const Duration(milliseconds: 15), (
|
_videoRecordingTimer =
|
||||||
timer,
|
Timer.periodic(const Duration(milliseconds: 15), (timer) {
|
||||||
) {
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_currentTime = clock.now();
|
_currentTime = clock.now();
|
||||||
});
|
});
|
||||||
|
|
@ -535,7 +521,6 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
mc.isVideoRecording = false;
|
mc.isVideoRecording = false;
|
||||||
});
|
});
|
||||||
_showCameraException(e);
|
_showCameraException(e);
|
||||||
await mc.cameraController?.setFlashMode(FlashMode.off);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -546,8 +531,6 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
_videoRecordingTimer = null;
|
_videoRecordingTimer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
await mc.cameraController?.setFlashMode(FlashMode.off);
|
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_videoRecordingStarted = null;
|
_videoRecordingStarted = null;
|
||||||
mc.isVideoRecording = false;
|
mc.isVideoRecording = false;
|
||||||
|
|
@ -618,12 +601,8 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
keyTriggerButton.currentContext!.findRenderObject()! as RenderBox;
|
keyTriggerButton.currentContext!.findRenderObject()! as RenderBox;
|
||||||
final localPosition = renderBox.globalToLocal(details.globalPosition);
|
final localPosition = renderBox.globalToLocal(details.globalPosition);
|
||||||
|
|
||||||
final containerRect = Rect.fromLTWH(
|
final containerRect =
|
||||||
0,
|
Rect.fromLTWH(0, 0, renderBox.size.width, renderBox.size.height);
|
||||||
0,
|
|
||||||
renderBox.size.width,
|
|
||||||
renderBox.size.height,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (containerRect.contains(localPosition)) {
|
if (containerRect.contains(localPosition)) {
|
||||||
startVideoRecording();
|
startVideoRecording();
|
||||||
|
|
@ -697,14 +676,12 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
: Colors.white.withAlpha(160),
|
: Colors.white.withAlpha(160),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (mc.selectedCameraDetails.isFlashOn) {
|
if (mc.selectedCameraDetails.isFlashOn) {
|
||||||
await mc.cameraController?.setFlashMode(
|
await mc.cameraController
|
||||||
FlashMode.off,
|
?.setFlashMode(FlashMode.off);
|
||||||
);
|
|
||||||
mc.selectedCameraDetails.isFlashOn = false;
|
mc.selectedCameraDetails.isFlashOn = false;
|
||||||
} else {
|
} else {
|
||||||
await mc.cameraController?.setFlashMode(
|
await mc.cameraController
|
||||||
FlashMode.always,
|
?.setFlashMode(FlashMode.always);
|
||||||
);
|
|
||||||
mc.selectedCameraDetails.isFlashOn = true;
|
mc.selectedCameraDetails.isFlashOn = true;
|
||||||
}
|
}
|
||||||
setState(() {});
|
setState(() {});
|
||||||
|
|
@ -809,8 +786,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
mc.isSelectingFaceFilters
|
mc.isSelectingFaceFilters
|
||||||
? mc.currentFilterType.index ==
|
? mc.currentFilterType.index ==
|
||||||
FaceFilterType
|
FaceFilterType
|
||||||
.values
|
.values.length -
|
||||||
.length -
|
|
||||||
1
|
1
|
||||||
? FontAwesomeIcons.xmark
|
? FontAwesomeIcons.xmark
|
||||||
: FontAwesomeIcons.arrowRight
|
: FontAwesomeIcons.arrowRight
|
||||||
|
|
@ -960,13 +936,10 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
: 'assets/animations/failed.lottie',
|
: 'assets/animations/failed.lottie',
|
||||||
repeat: false,
|
repeat: false,
|
||||||
onLoaded: (p0) {
|
onLoaded: (p0) {
|
||||||
Future.delayed(
|
Future.delayed(const Duration(seconds: 4),
|
||||||
const Duration(seconds: 4),
|
|
||||||
() {
|
() {
|
||||||
widget.mainCameraController
|
widget.mainCameraController.setState();
|
||||||
.setState();
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,7 @@ extension FaceFilterTypeExtension on FaceFilterType {
|
||||||
}
|
}
|
||||||
|
|
||||||
FaceFilterType goLeft() {
|
FaceFilterType goLeft() {
|
||||||
final prevIndex =
|
final prevIndex = (index - 1 + FaceFilterType.values.length) %
|
||||||
(index - 1 + FaceFilterType.values.length) %
|
|
||||||
FaceFilterType.values.length;
|
FaceFilterType.values.length;
|
||||||
return FaceFilterType.values[prevIndex];
|
return FaceFilterType.values[prevIndex];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import 'package:camera/camera.dart';
|
||||||
import 'package:clock/clock.dart';
|
import 'package:clock/clock.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:drift/drift.dart' show Value;
|
import 'package:drift/drift.dart' show Value;
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:google_mlkit_barcode_scanning/google_mlkit_barcode_scanning.dart';
|
import 'package:google_mlkit_barcode_scanning/google_mlkit_barcode_scanning.dart';
|
||||||
|
|
@ -134,11 +133,6 @@ class MainCameraController {
|
||||||
await cameraController?.initialize();
|
await cameraController?.initialize();
|
||||||
await cameraController?.startImageStream(_processCameraImage);
|
await cameraController?.startImageStream(_processCameraImage);
|
||||||
await cameraController?.setZoomLevel(selectedCameraDetails.scaleFactor);
|
await cameraController?.setZoomLevel(selectedCameraDetails.scaleFactor);
|
||||||
if (gUser.videoStabilizationEnabled && !kDebugMode) {
|
|
||||||
await cameraController?.setVideoStabilizationMode(
|
|
||||||
VideoStabilizationMode.level1,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
if (!isVideoRecording) {
|
if (!isVideoRecording) {
|
||||||
|
|
|
||||||
|
|
@ -159,12 +159,8 @@ class BeardFilterPainter extends FaceFilterPainter {
|
||||||
..rotate(rotation)
|
..rotate(rotation)
|
||||||
..scale(scaleX, Platform.isAndroid ? -1 : 1);
|
..scale(scaleX, Platform.isAndroid ? -1 : 1);
|
||||||
|
|
||||||
final srcRect = Rect.fromLTWH(
|
final srcRect =
|
||||||
0,
|
Rect.fromLTWH(0, 0, image.width.toDouble(), image.height.toDouble());
|
||||||
0,
|
|
||||||
image.width.toDouble(),
|
|
||||||
image.height.toDouble(),
|
|
||||||
);
|
|
||||||
|
|
||||||
final aspectRatio = image.width / image.height;
|
final aspectRatio = image.width / image.height;
|
||||||
final dstWidth = width;
|
final dstWidth = width;
|
||||||
|
|
|
||||||
|
|
@ -56,9 +56,8 @@ class DogFilterPainter extends FaceFilterPainter {
|
||||||
final points = faceContour.points;
|
final points = faceContour.points;
|
||||||
if (points.isEmpty) continue;
|
if (points.isEmpty) continue;
|
||||||
|
|
||||||
final upperPoints = points
|
final upperPoints =
|
||||||
.where((p) => p.y < noseBase.position.y)
|
points.where((p) => p.y < noseBase.position.y).toList();
|
||||||
.toList();
|
|
||||||
|
|
||||||
if (upperPoints.isEmpty) continue;
|
if (upperPoints.isEmpty) continue;
|
||||||
|
|
||||||
|
|
@ -187,12 +186,8 @@ class DogFilterPainter extends FaceFilterPainter {
|
||||||
canvas.scale(scaleX, Platform.isAndroid ? -1 : 1);
|
canvas.scale(scaleX, Platform.isAndroid ? -1 : 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
final srcRect = Rect.fromLTWH(
|
final srcRect =
|
||||||
0,
|
Rect.fromLTWH(0, 0, image.width.toDouble(), image.height.toDouble());
|
||||||
0,
|
|
||||||
image.width.toDouble(),
|
|
||||||
image.height.toDouble(),
|
|
||||||
);
|
|
||||||
final aspectRatio = image.width / image.height;
|
final aspectRatio = image.width / image.height;
|
||||||
final dstWidth = size;
|
final dstWidth = size;
|
||||||
final dstHeight = size / aspectRatio;
|
final dstHeight = size / aspectRatio;
|
||||||
|
|
|
||||||
|
|
@ -52,8 +52,7 @@ class SaveToGalleryButtonState extends State<SaveToGalleryButton> {
|
||||||
await widget.storeImageAsOriginal!();
|
await widget.storeImageAsOriginal!();
|
||||||
}
|
}
|
||||||
|
|
||||||
final newMediaFile = await twonlyDB.mediaFilesDao
|
final newMediaFile = await twonlyDB.mediaFilesDao.insertMedia(
|
||||||
.insertOrUpdateMedia(
|
|
||||||
MediaFilesCompanion(
|
MediaFilesCompanion(
|
||||||
type: Value(widget.mediaService.mediaFile.type),
|
type: Value(widget.mediaService.mediaFile.type),
|
||||||
createdAt: Value(clock.now()),
|
createdAt: Value(clock.now()),
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,7 @@ class VideoRecordingTimer extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
Center(
|
Center(
|
||||||
child: CircularProgressIndicator(
|
child: CircularProgressIndicator(
|
||||||
value:
|
value: currentTime
|
||||||
currentTime
|
|
||||||
.difference(videoRecordingStarted!)
|
.difference(videoRecordingStarted!)
|
||||||
.inMilliseconds /
|
.inMilliseconds /
|
||||||
(maxVideoRecordingTime * 1000),
|
(maxVideoRecordingTime * 1000),
|
||||||
|
|
|
||||||
|
|
@ -51,9 +51,8 @@ class _CameraZoomButtonsState extends State<CameraZoomButtons> {
|
||||||
Future<void> initAsync() async {
|
Future<void> initAsync() async {
|
||||||
showWideAngleZoom = (await widget.controller.getMinZoomLevel()) < 1;
|
showWideAngleZoom = (await widget.controller.getMinZoomLevel()) < 1;
|
||||||
|
|
||||||
var index = gCameras.indexWhere(
|
var index =
|
||||||
(t) => t.lensType == CameraLensType.ultraWide,
|
gCameras.indexWhere((t) => t.lensType == CameraLensType.ultraWide);
|
||||||
);
|
|
||||||
if (index == -1) {
|
if (index == -1) {
|
||||||
index = gCameras.indexWhere(
|
index = gCameras.indexWhere(
|
||||||
(t) => t.lensType == CameraLensType.wide,
|
(t) => t.lensType == CameraLensType.wide,
|
||||||
|
|
@ -63,8 +62,7 @@ class _CameraZoomButtonsState extends State<CameraZoomButtons> {
|
||||||
_wideCameraIndex = index;
|
_wideCameraIndex = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
final isFront =
|
final isFront = widget.controller.description.lensDirection ==
|
||||||
widget.controller.description.lensDirection ==
|
|
||||||
CameraLensDirection.front;
|
CameraLensDirection.front;
|
||||||
|
|
||||||
if (!showWideAngleZoom &&
|
if (!showWideAngleZoom &&
|
||||||
|
|
@ -96,12 +94,10 @@ class _CameraZoomButtonsState extends State<CameraZoomButtons> {
|
||||||
);
|
);
|
||||||
|
|
||||||
const zoomTextStyle = TextStyle(fontSize: 13);
|
const zoomTextStyle = TextStyle(fontSize: 13);
|
||||||
final isSmallerFocused =
|
final isSmallerFocused = widget.scaleFactor < 1 ||
|
||||||
widget.scaleFactor < 1 ||
|
|
||||||
(showWideAngleZoomIOS &&
|
(showWideAngleZoomIOS &&
|
||||||
widget.selectedCameraDetails.cameraId == _wideCameraIndex);
|
widget.selectedCameraDetails.cameraId == _wideCameraIndex);
|
||||||
final isMiddleFocused =
|
final isMiddleFocused = widget.scaleFactor >= 1 &&
|
||||||
widget.scaleFactor >= 1 &&
|
|
||||||
widget.scaleFactor < 2 &&
|
widget.scaleFactor < 2 &&
|
||||||
!(showWideAngleZoomIOS &&
|
!(showWideAngleZoomIOS &&
|
||||||
widget.selectedCameraDetails.cameraId == _wideCameraIndex);
|
widget.selectedCameraDetails.cameraId == _wideCameraIndex);
|
||||||
|
|
@ -111,9 +107,8 @@ class _CameraZoomButtonsState extends State<CameraZoomButtons> {
|
||||||
widget.scaleFactor,
|
widget.scaleFactor,
|
||||||
);
|
);
|
||||||
|
|
||||||
final minLevel = beautifulZoomScale(
|
final minLevel =
|
||||||
widget.selectedCameraDetails.minAvailableZoom,
|
beautifulZoomScale(widget.selectedCameraDetails.minAvailableZoom);
|
||||||
);
|
|
||||||
final currentLevel = beautifulZoomScale(widget.scaleFactor);
|
final currentLevel = beautifulZoomScale(widget.scaleFactor);
|
||||||
return Center(
|
return Center(
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
|
|
@ -178,10 +173,9 @@ class _CameraZoomButtonsState extends State<CameraZoomButtons> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final level = min(
|
final level =
|
||||||
await widget.controller.getMaxZoomLevel(),
|
min(await widget.controller.getMaxZoomLevel(), 2)
|
||||||
2,
|
.toDouble();
|
||||||
).toDouble();
|
|
||||||
|
|
||||||
if (showWideAngleZoomIOS &&
|
if (showWideAngleZoomIOS &&
|
||||||
widget.selectedCameraDetails.cameraId ==
|
widget.selectedCameraDetails.cameraId ==
|
||||||
|
|
|
||||||
|
|
@ -55,9 +55,8 @@ class _ShareImageView extends State<ShareImageView> {
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
allGroupSub = twonlyDB.groupsDao.watchGroupsForShareImage().listen((
|
allGroupSub =
|
||||||
allGroups,
|
twonlyDB.groupsDao.watchGroupsForShareImage().listen((allGroups) async {
|
||||||
) async {
|
|
||||||
setState(() {
|
setState(() {
|
||||||
contacts = allGroups;
|
contacts = allGroups;
|
||||||
});
|
});
|
||||||
|
|
@ -87,9 +86,8 @@ class _ShareImageView extends State<ShareImageView> {
|
||||||
groups.sort((a, b) {
|
groups.sort((a, b) {
|
||||||
// First, compare by flameCounter
|
// First, compare by flameCounter
|
||||||
|
|
||||||
final flameComparison = getFlameCounterFromGroup(
|
final flameComparison =
|
||||||
b,
|
getFlameCounterFromGroup(b).compareTo(getFlameCounterFromGroup(a));
|
||||||
).compareTo(getFlameCounterFromGroup(a));
|
|
||||||
if (flameComparison != 0) {
|
if (flameComparison != 0) {
|
||||||
return flameComparison; // Sort by flameCounter in descending order
|
return flameComparison; // Sort by flameCounter in descending order
|
||||||
}
|
}
|
||||||
|
|
@ -158,12 +156,8 @@ class _ShareImageView extends State<ShareImageView> {
|
||||||
),
|
),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(
|
padding:
|
||||||
bottom: 40,
|
const EdgeInsets.only(bottom: 40, left: 10, top: 20, right: 10),
|
||||||
left: 10,
|
|
||||||
top: 20,
|
|
||||||
right: 10,
|
|
||||||
),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
|
|
@ -217,9 +211,8 @@ class _ShareImageView extends State<ShareImageView> {
|
||||||
return const BorderSide(width: 0);
|
return const BorderSide(width: 0);
|
||||||
}
|
}
|
||||||
return BorderSide(
|
return BorderSide(
|
||||||
color: Theme.of(
|
color:
|
||||||
context,
|
Theme.of(context).colorScheme.outline,
|
||||||
).colorScheme.outline,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
@ -261,10 +254,8 @@ class _ShareImageView extends State<ShareImageView> {
|
||||||
child: Container(
|
child: Container(
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border.all(
|
border:
|
||||||
color: context.color.primary,
|
Border.all(color: context.color.primary, width: 2),
|
||||||
width: 2,
|
|
||||||
),
|
|
||||||
color: context.color.primary,
|
color: context.color.primary,
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
|
|
@ -345,9 +336,8 @@ class UserList extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// Step 1: Sort the users alphabetically
|
// Step 1: Sort the users alphabetically
|
||||||
groups.sort(
|
groups
|
||||||
(a, b) => b.lastMessageExchange.compareTo(a.lastMessageExchange),
|
.sort((a, b) => b.lastMessageExchange.compareTo(a.lastMessageExchange));
|
||||||
);
|
|
||||||
|
|
||||||
return ListView.builder(
|
return ListView.builder(
|
||||||
restorationId: 'new_message_users_list',
|
restorationId: 'new_message_users_list',
|
||||||
|
|
|
||||||
|
|
@ -42,10 +42,8 @@ class BestFriendsSelector extends StatelessWidget {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding:
|
||||||
horizontal: 7,
|
const EdgeInsets.symmetric(horizontal: 7, vertical: 4),
|
||||||
vertical: 4,
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).colorScheme.outline.withAlpha(50),
|
color: Theme.of(context).colorScheme.outline.withAlpha(50),
|
||||||
boxShadow: const [
|
boxShadow: const [
|
||||||
|
|
@ -77,9 +75,8 @@ class BestFriendsSelector extends StatelessWidget {
|
||||||
Expanded(
|
Expanded(
|
||||||
child: UserCheckbox(
|
child: UserCheckbox(
|
||||||
key: ValueKey(groups[firstUserIndex]),
|
key: ValueKey(groups[firstUserIndex]),
|
||||||
isChecked: selectedGroupIds.contains(
|
isChecked: selectedGroupIds
|
||||||
groups[firstUserIndex].groupId,
|
.contains(groups[firstUserIndex].groupId),
|
||||||
),
|
|
||||||
group: groups[firstUserIndex],
|
group: groups[firstUserIndex],
|
||||||
onChanged: updateSelectedGroupIds,
|
onChanged: updateSelectedGroupIds,
|
||||||
),
|
),
|
||||||
|
|
@ -88,9 +85,8 @@ class BestFriendsSelector extends StatelessWidget {
|
||||||
Expanded(
|
Expanded(
|
||||||
child: UserCheckbox(
|
child: UserCheckbox(
|
||||||
key: ValueKey(groups[secondUserIndex]),
|
key: ValueKey(groups[secondUserIndex]),
|
||||||
isChecked: selectedGroupIds.contains(
|
isChecked: selectedGroupIds
|
||||||
groups[secondUserIndex].groupId,
|
.contains(groups[secondUserIndex].groupId),
|
||||||
),
|
|
||||||
group: groups[secondUserIndex],
|
group: groups[secondUserIndex],
|
||||||
onChanged: updateSelectedGroupIds,
|
onChanged: updateSelectedGroupIds,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -109,21 +109,13 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
sendingOrLoadingImage = false;
|
sendingOrLoadingImage = false;
|
||||||
loadingImage = false;
|
loadingImage = false;
|
||||||
});
|
});
|
||||||
videoController = VideoPlayerController.file(
|
videoController = VideoPlayerController.file(mediaService.originalPath);
|
||||||
mediaService.originalPath,
|
|
||||||
videoPlayerOptions: VideoPlayerOptions(
|
|
||||||
mixWithOthers: true,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
videoController?.setLooping(true);
|
videoController?.setLooping(true);
|
||||||
videoController
|
videoController?.initialize().then((_) async {
|
||||||
?.initialize()
|
|
||||||
.then((_) async {
|
|
||||||
await videoController!.play();
|
await videoController!.play();
|
||||||
setState(() {});
|
setState(() {});
|
||||||
})
|
// ignore: invalid_return_type_for_catch_error, argument_type_not_assignable_to_error_handler
|
||||||
// ignore: argument_type_not_assignable_to_error_handler, invalid_return_type_for_catch_error
|
}).catchError(Log.error);
|
||||||
.catchError(Log.error);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -213,8 +205,8 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
|
|
||||||
List<Widget> get actionsAtTheRight {
|
List<Widget> get actionsAtTheRight {
|
||||||
if (layers.isNotEmpty &&
|
if (layers.isNotEmpty &&
|
||||||
(layers.first.isEditing ||
|
layers.last.isEditing &&
|
||||||
(layers.last.isEditing && layers.last.hasCustomActionButtons))) {
|
layers.last.hasCustomActionButtons) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return <Widget>[
|
return <Widget>[
|
||||||
|
|
@ -254,15 +246,13 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
Icons.add_reaction_outlined,
|
Icons.add_reaction_outlined,
|
||||||
tooltipText: context.lang.addEmoji,
|
tooltipText: context.lang.addEmoji,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final layer =
|
final layer = await showModalBottomSheet(
|
||||||
await showModalBottomSheet(
|
|
||||||
context: context,
|
context: context,
|
||||||
backgroundColor: Colors.black,
|
backgroundColor: Colors.black,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return const EmojiPickerBottom();
|
return const EmojiPickerBottom();
|
||||||
},
|
},
|
||||||
)
|
) as Layer?;
|
||||||
as Layer?;
|
|
||||||
if (layer == null) return;
|
if (layer == null) return;
|
||||||
undoLayers.clear();
|
undoLayers.clear();
|
||||||
removedLayers.clear();
|
removedLayers.clear();
|
||||||
|
|
@ -287,8 +277,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
onPressed: _setImageDisplayTime,
|
onPressed: _setImageDisplayTime,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (media.type == MediaType.video) ...[
|
if (media.type == MediaType.video)
|
||||||
const SizedBox(height: 8),
|
|
||||||
ActionButton(
|
ActionButton(
|
||||||
(mediaService.removeAudio)
|
(mediaService.removeAudio)
|
||||||
? Icons.volume_off_rounded
|
? Icons.volume_off_rounded
|
||||||
|
|
@ -307,29 +296,6 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
if (mounted) setState(() {});
|
if (mounted) setState(() {});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
|
||||||
if (media.type == MediaType.image) ...[
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
ActionButton(
|
|
||||||
Icons.crop_rotate_outlined,
|
|
||||||
tooltipText: 'Crop or rotate image',
|
|
||||||
color: Colors.white,
|
|
||||||
onPressed: () async {
|
|
||||||
final first = layers.first;
|
|
||||||
if (first is BackgroundLayerData) {
|
|
||||||
first.isEditing = !first.isEditing;
|
|
||||||
}
|
|
||||||
setState(() {});
|
|
||||||
// await mediaService.toggleRemoveAudio();
|
|
||||||
// if (mediaService.removeAudio) {
|
|
||||||
// await videoController?.setVolume(0);
|
|
||||||
// } else {
|
|
||||||
// await videoController?.setVolume(100);
|
|
||||||
// }
|
|
||||||
// if (mounted) setState(() {});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
ActionButton(
|
ActionButton(
|
||||||
FontAwesomeIcons.shieldHeart,
|
FontAwesomeIcons.shieldHeart,
|
||||||
|
|
@ -382,8 +348,8 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
|
|
||||||
List<Widget> get actionsAtTheTop {
|
List<Widget> get actionsAtTheTop {
|
||||||
if (layers.isNotEmpty &&
|
if (layers.isNotEmpty &&
|
||||||
(layers.first.isEditing ||
|
layers.last.isEditing &&
|
||||||
(layers.last.isEditing && layers.last.hasCustomActionButtons))) {
|
layers.last.hasCustomActionButtons) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
|
|
@ -445,8 +411,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
|
|
||||||
await videoController?.pause();
|
await videoController?.pause();
|
||||||
if (isDisposed || !mounted) return;
|
if (isDisposed || !mounted) return;
|
||||||
final wasSend =
|
final wasSend = await Navigator.push(
|
||||||
await Navigator.push(
|
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => ShareImageView(
|
builder: (context) => ShareImageView(
|
||||||
|
|
@ -457,8 +422,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
additionalData: getAdditionalData(),
|
additionalData: getAdditionalData(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
) as bool?;
|
||||||
as bool?;
|
|
||||||
if (wasSend != null && wasSend && mounted) {
|
if (wasSend != null && wasSend && mounted) {
|
||||||
widget.mainCameraController?.onImageSend();
|
widget.mainCameraController?.onImageSend();
|
||||||
Navigator.pop(context, true);
|
Navigator.pop(context, true);
|
||||||
|
|
@ -507,7 +471,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
mediaService.tempPath.deleteSync();
|
mediaService.tempPath.deleteSync();
|
||||||
}
|
}
|
||||||
if (mediaService.originalPath.existsSync()) {
|
if (mediaService.originalPath.existsSync()) {
|
||||||
if (media.type == MediaType.image) {
|
if (media.type != MediaType.video) {
|
||||||
mediaService.originalPath.deleteSync();
|
mediaService.originalPath.deleteSync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -516,6 +480,8 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
if (media.type == MediaType.gif) {
|
if (media.type == MediaType.gif) {
|
||||||
if (bytes != null) {
|
if (bytes != null) {
|
||||||
mediaService.originalPath.writeAsBytesSync(bytes.toList());
|
mediaService.originalPath.writeAsBytesSync(bytes.toList());
|
||||||
|
} else {
|
||||||
|
Log.error('Could not load image bytes for gif!');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
image = await getEditedImageBytes();
|
image = await getEditedImageBytes();
|
||||||
|
|
@ -586,9 +552,8 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
});
|
});
|
||||||
// It is important that the user can sending the image only when the image is fully loaded otherwise if the user
|
// It is important that the user can sending the image only when the image is fully loaded otherwise if the user
|
||||||
// will click on send before the image is painted the screenshot will be transparent..
|
// will click on send before the image is painted the screenshot will be transparent..
|
||||||
_imageLoadingTimer = Timer.periodic(const Duration(milliseconds: 10), (
|
_imageLoadingTimer =
|
||||||
timer,
|
Timer.periodic(const Duration(milliseconds: 10), (timer) {
|
||||||
) {
|
|
||||||
final imageLayer = layers.first;
|
final imageLayer = layers.first;
|
||||||
if (imageLayer is BackgroundLayerData) {
|
if (imageLayer is BackgroundLayerData) {
|
||||||
if (imageLayer.imageLoaded) {
|
if (imageLayer.imageLoaded) {
|
||||||
|
|
@ -654,9 +619,8 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
await askToCloseThenClose();
|
await askToCloseThenClose();
|
||||||
},
|
},
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
backgroundColor: widget.sharedFromGallery
|
backgroundColor:
|
||||||
? null
|
widget.sharedFromGallery ? null : Colors.white.withAlpha(0),
|
||||||
: Colors.white.withAlpha(0),
|
|
||||||
resizeToAvoidBottomInset: false,
|
resizeToAvoidBottomInset: false,
|
||||||
body: Stack(
|
body: Stack(
|
||||||
fit: StackFit.expand,
|
fit: StackFit.expand,
|
||||||
|
|
@ -703,9 +667,8 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
iconColor: Theme.of(context).colorScheme.primary,
|
iconColor: Theme.of(context).colorScheme.primary,
|
||||||
foregroundColor: Theme.of(
|
foregroundColor:
|
||||||
context,
|
Theme.of(context).colorScheme.primary,
|
||||||
).colorScheme.primary,
|
|
||||||
),
|
),
|
||||||
onPressed: pushShareImageView,
|
onPressed: pushShareImageView,
|
||||||
child: const FaIcon(FontAwesomeIcons.userPlus),
|
child: const FaIcon(FontAwesomeIcons.userPlus),
|
||||||
|
|
@ -718,9 +681,9 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
width: 12,
|
width: 12,
|
||||||
child: CircularProgressIndicator(
|
child: CircularProgressIndicator(
|
||||||
strokeWidth: 2,
|
strokeWidth: 2,
|
||||||
color: Theme.of(
|
color: Theme.of(context)
|
||||||
context,
|
.colorScheme
|
||||||
).colorScheme.inversePrimary,
|
.inversePrimary,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: const FaIcon(FontAwesomeIcons.solidPaperPlane),
|
: const FaIcon(FontAwesomeIcons.solidPaperPlane),
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,6 @@
|
||||||
import 'dart:ui' as ui;
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
|
||||||
import 'package:photo_view/photo_view.dart';
|
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
|
||||||
import 'package:twonly/src/views/camera/share_image_editor/action_button.dart';
|
|
||||||
import 'package:twonly/src/views/camera/share_image_editor/layer_data.dart';
|
import 'package:twonly/src/views/camera/share_image_editor/layer_data.dart';
|
||||||
|
|
||||||
class BackgroundLayer extends StatefulWidget {
|
class BackgroundLayer extends StatefulWidget {
|
||||||
|
|
@ -33,17 +29,7 @@ class _BackgroundLayerState extends State<BackgroundLayer> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final scImage = widget.layerData.image.image;
|
final scImage = widget.layerData.image.image;
|
||||||
if (scImage == null || scImage.image == null) return Container();
|
if (scImage == null || scImage.image == null) return Container();
|
||||||
return Stack(
|
return Container(
|
||||||
children: [
|
|
||||||
Positioned.fill(
|
|
||||||
child: PhotoView.customChild(
|
|
||||||
enableRotation: true,
|
|
||||||
initialScale: PhotoViewComputedScale.contained,
|
|
||||||
minScale: PhotoViewComputedScale.contained,
|
|
||||||
backgroundDecoration: const BoxDecoration(
|
|
||||||
color: Colors.transparent,
|
|
||||||
),
|
|
||||||
child: Container(
|
|
||||||
width: widget.layerData.image.width.toDouble(),
|
width: widget.layerData.image.width.toDouble(),
|
||||||
height: widget.layerData.image.height.toDouble(),
|
height: widget.layerData.image.height.toDouble(),
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
|
|
@ -51,29 +37,6 @@ class _BackgroundLayerState extends State<BackgroundLayer> {
|
||||||
child: CustomPaint(
|
child: CustomPaint(
|
||||||
painter: UiImagePainter(scImage.image!),
|
painter: UiImagePainter(scImage.image!),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (widget.layerData.isEditing && widget.layerData.showCustomButtons)
|
|
||||||
Positioned(
|
|
||||||
top: 5,
|
|
||||||
left: 5,
|
|
||||||
right: 50,
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
ActionButton(
|
|
||||||
FontAwesomeIcons.check,
|
|
||||||
tooltipText: context.lang.imageEditorDrawOk,
|
|
||||||
onPressed: () async {
|
|
||||||
widget.layerData.isEditing = false;
|
|
||||||
widget.onUpdate!();
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -91,10 +91,8 @@ class _EmojiLayerState extends State<EmojiLayer> {
|
||||||
initialScale = widget.layerData.size;
|
initialScale = widget.layerData.size;
|
||||||
initialRotation = widget.layerData.rotation;
|
initialRotation = widget.layerData.rotation;
|
||||||
initialOffset = widget.layerData.offset;
|
initialOffset = widget.layerData.offset;
|
||||||
initialFocalPoint = Offset(
|
initialFocalPoint =
|
||||||
details.focalPoint.dx,
|
Offset(details.focalPoint.dx, details.focalPoint.dy);
|
||||||
details.focalPoint.dy,
|
|
||||||
);
|
|
||||||
|
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
|
|
@ -102,9 +100,8 @@ class _EmojiLayerState extends State<EmojiLayer> {
|
||||||
if (twoPointerWhereDown && details.pointerCount != 2) {
|
if (twoPointerWhereDown && details.pointerCount != 2) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final outlineBox =
|
final outlineBox = outlineKey.currentContext!
|
||||||
outlineKey.currentContext!.findRenderObject()!
|
.findRenderObject()! as RenderBox;
|
||||||
as RenderBox;
|
|
||||||
|
|
||||||
final emojiBox =
|
final emojiBox =
|
||||||
emojiKey.currentContext!.findRenderObject()! as RenderBox;
|
emojiKey.currentContext!.findRenderObject()! as RenderBox;
|
||||||
|
|
@ -136,11 +133,9 @@ class _EmojiLayerState extends State<EmojiLayer> {
|
||||||
initialRotation + details.rotation;
|
initialRotation + details.rotation;
|
||||||
|
|
||||||
// Update the position based on the translation
|
// Update the position based on the translation
|
||||||
final dx =
|
final dx = (initialOffset.dx) +
|
||||||
(initialOffset.dx) +
|
|
||||||
(details.focalPoint.dx - initialFocalPoint.dx);
|
(details.focalPoint.dx - initialFocalPoint.dx);
|
||||||
final dy =
|
final dy = (initialOffset.dy) +
|
||||||
(initialOffset.dy) +
|
|
||||||
(details.focalPoint.dy - initialFocalPoint.dy);
|
(details.focalPoint.dy - initialFocalPoint.dy);
|
||||||
widget.layerData.offset = Offset(dx, dy);
|
widget.layerData.offset = Offset(dx, dy);
|
||||||
});
|
});
|
||||||
|
|
@ -208,8 +203,7 @@ class _ScreenshotEmojiState extends State<ScreenshotEmoji> {
|
||||||
|
|
||||||
Future<void> _captureEmoji() async {
|
Future<void> _captureEmoji() async {
|
||||||
try {
|
try {
|
||||||
final boundary =
|
final boundary = _boundaryKey.currentContext?.findRenderObject()
|
||||||
_boundaryKey.currentContext?.findRenderObject()
|
|
||||||
as RenderRepaintBoundary?;
|
as RenderRepaintBoundary?;
|
||||||
if (boundary == null) return;
|
if (boundary == null) return;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -145,9 +145,8 @@ Future<List<Sticker>> getStickerIndex() async {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
final response = await http.get(
|
final response = await http
|
||||||
Uri.parse('https://twonly.eu/api/sticker/stickers.json'),
|
.get(Uri.parse('https://twonly.eu/api/sticker/stickers.json'));
|
||||||
);
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
await indexFile.writeAsString(response.body);
|
await indexFile.writeAsString(response.body);
|
||||||
final jsonList = json.decode(response.body) as List;
|
final jsonList = json.decode(response.body) as List;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/c
|
||||||
import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/cards/youtube.card.dart';
|
import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/cards/youtube.card.dart';
|
||||||
import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parse_link.dart';
|
import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parse_link.dart';
|
||||||
import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/base.dart';
|
import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/base.dart';
|
||||||
import 'package:twonly/src/views/components/loader/three_rotating_dots.loader.dart';
|
import 'package:twonly/src/views/components/loader.dart';
|
||||||
|
|
||||||
class LinkPreviewLayer extends StatefulWidget {
|
class LinkPreviewLayer extends StatefulWidget {
|
||||||
const LinkPreviewLayer({
|
const LinkPreviewLayer({
|
||||||
|
|
@ -32,9 +32,8 @@ class _LinkPreviewLayerState extends State<LinkPreviewLayer> {
|
||||||
|
|
||||||
Future<void> initAsync() async {
|
Future<void> initAsync() async {
|
||||||
if (widget.layerData.metadata == null) {
|
if (widget.layerData.metadata == null) {
|
||||||
widget.layerData.metadata = await getMetadata(
|
widget.layerData.metadata =
|
||||||
widget.layerData.link.toString(),
|
await getMetadata(widget.layerData.link.toString());
|
||||||
);
|
|
||||||
if (widget.layerData.metadata == null) {
|
if (widget.layerData.metadata == null) {
|
||||||
widget.layerData.error = true;
|
widget.layerData.error = true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ 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/database/daos/contacts.dao.dart';
|
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||||
import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/base.dart';
|
import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/base.dart';
|
||||||
import 'package:twonly/src/views/components/loader/three_rotating_dots.loader.dart';
|
import 'package:twonly/src/views/components/loader.dart';
|
||||||
|
|
||||||
class MastodonPostCard extends StatelessWidget {
|
class MastodonPostCard extends StatelessWidget {
|
||||||
const MastodonPostCard({required this.info, super.key});
|
const MastodonPostCard({required this.info, super.key});
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,8 @@ class MastodonParser with BaseMetaInfo {
|
||||||
final Document? _document;
|
final Document? _document;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Vendor? get vendor =>
|
Vendor? get vendor => ((_document?.head?.innerHtml
|
||||||
((_document?.head?.innerHtml.contains(
|
.contains('"repository":"mastodon/mastodon"') ??
|
||||||
'"repository":"mastodon/mastodon"',
|
|
||||||
) ??
|
|
||||||
false) &&
|
false) &&
|
||||||
(_document?.head?.innerHtml.contains('SocialMediaPosting') ?? false))
|
(_document?.head?.innerHtml.contains('SocialMediaPosting') ?? false))
|
||||||
? Vendor.mastodonSocialMediaPosting
|
? Vendor.mastodonSocialMediaPosting
|
||||||
|
|
|
||||||
|
|
@ -43,8 +43,7 @@ class _TextViewState extends State<TextLayer> {
|
||||||
if (parentBox != null) {
|
if (parentBox != null) {
|
||||||
final parentTopGlobal = parentBox.localToGlobal(Offset.zero).dy;
|
final parentTopGlobal = parentBox.localToGlobal(Offset.zero).dy;
|
||||||
final screenHeight = mq.size.height;
|
final screenHeight = mq.size.height;
|
||||||
localBottom =
|
localBottom = (screenHeight - globalDesiredBottom) -
|
||||||
(screenHeight - globalDesiredBottom) -
|
|
||||||
parentTopGlobal -
|
parentTopGlobal -
|
||||||
(parentBox.size.height);
|
(parentBox.size.height);
|
||||||
}
|
}
|
||||||
|
|
@ -88,8 +87,7 @@ class _TextViewState extends State<TextLayer> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (widget.layerData.isDeleted) return Container();
|
if (widget.layerData.isDeleted) return Container();
|
||||||
|
|
||||||
final bottom =
|
final bottom = MediaQuery.of(context).viewInsets.bottom +
|
||||||
MediaQuery.of(context).viewInsets.bottom +
|
|
||||||
MediaQuery.of(context).viewPadding.bottom;
|
MediaQuery.of(context).viewPadding.bottom;
|
||||||
|
|
||||||
// On Android it is possible to close the keyboard without `onEditingComplete` is triggered.
|
// On Android it is possible to close the keyboard without `onEditingComplete` is triggered.
|
||||||
|
|
@ -183,8 +181,7 @@ class _TextViewState extends State<TextLayer> {
|
||||||
}
|
}
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
onTap:
|
onTap: (context
|
||||||
(context
|
|
||||||
.watch<ImageEditorProvider>()
|
.watch<ImageEditorProvider>()
|
||||||
.someTextViewIsAlreadyEditing)
|
.someTextViewIsAlreadyEditing)
|
||||||
? null
|
? null
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue