mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-06-03 02:52:13 +00:00
Compare commits
No commits in common. "main" and "v0.1.8" have entirely different histories.
615 changed files with 19139 additions and 144652 deletions
4
.github/workflows/dev_github.yml
vendored
4
.github/workflows/dev_github.yml
vendored
|
|
@ -31,5 +31,5 @@ jobs:
|
||||||
- name: flutter analyze
|
- name: flutter analyze
|
||||||
run: flutter analyze
|
run: flutter analyze
|
||||||
|
|
||||||
# - name: flutter test
|
- name: flutter test
|
||||||
# run: flutter test
|
run: flutter test
|
||||||
|
|
|
||||||
62
.github/workflows/release_github.yml
vendored
Normal file
62
.github/workflows/release_github.yml
vendored
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
name: Publish on Github
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch: {}
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- pubspec.yaml
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build_and_publish:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
steps:
|
||||||
|
- name: Clone repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Flutter
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
channel: stable
|
||||||
|
|
||||||
|
- name: Cloning sub-repos
|
||||||
|
run: git submodule update --init --recursive
|
||||||
|
|
||||||
|
# - name: Check flutter code
|
||||||
|
# run: |
|
||||||
|
# flutter pub get
|
||||||
|
# flutter analyze
|
||||||
|
# flutter test
|
||||||
|
|
||||||
|
- name: Check flutter code
|
||||||
|
run: flutter pub get
|
||||||
|
|
||||||
|
- name: Create key.properties file
|
||||||
|
run: |
|
||||||
|
echo "storePassword=${{ secrets.STORE_PASSWORD }}" >> ./android/key.properties
|
||||||
|
echo "keyPassword=${{ secrets.KEY_PASSWORD }}" >> ./android/key.properties
|
||||||
|
echo "keyAlias=github-releases-signature" >> ./android/key.properties
|
||||||
|
echo "storeFile=./keystore.jks" >> ./android/key.properties
|
||||||
|
|
||||||
|
- name: Create keystore file
|
||||||
|
env:
|
||||||
|
KEYSTORE_FILE: ${{ secrets.KEYSTORE_FILE }}
|
||||||
|
run: echo $KEYSTORE_FILE | base64 --decode > ./android/app/keystore.jks
|
||||||
|
|
||||||
|
- name: Build Android APK
|
||||||
|
run: flutter build apk --release --split-per-abi
|
||||||
|
|
||||||
|
- name: Extract pubspec version
|
||||||
|
run: |
|
||||||
|
echo "PUBSPEC_VERSION=$(grep -oP 'version:\s*\K[^+]+(?=\+)' pubspec.yaml)" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Upload Release Binaries (stable)
|
||||||
|
uses: ncipollo/release-action@v1.18.0
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
tag: v${{ env.PUBSPEC_VERSION }}
|
||||||
|
allowUpdates: true
|
||||||
|
artifacts: build/app/outputs/flutter-apk/*.apk
|
||||||
9
.gitignore
vendored
9
.gitignore
vendored
|
|
@ -10,14 +10,8 @@
|
||||||
.history
|
.history
|
||||||
.svn/
|
.svn/
|
||||||
.swiftpm/
|
.swiftpm/
|
||||||
*.sqlite
|
|
||||||
*.sqlite-shm
|
|
||||||
*.sqlite-wal
|
|
||||||
migrate_working_dir/
|
migrate_working_dir/
|
||||||
|
|
||||||
fastlane/report.xml
|
|
||||||
fastlane/README.md
|
|
||||||
|
|
||||||
# IntelliJ related
|
# IntelliJ related
|
||||||
*.iml
|
*.iml
|
||||||
*.ipr
|
*.ipr
|
||||||
|
|
@ -53,6 +47,3 @@ app.*.map.json
|
||||||
/android/app/.cxx/
|
/android/app/.cxx/
|
||||||
android/.kotlin/
|
android/.kotlin/
|
||||||
devtools_options.yaml
|
devtools_options.yaml
|
||||||
rust/target
|
|
||||||
rust_dependencies/target
|
|
||||||
fastlane/repo/status/running.json
|
|
||||||
|
|
|
||||||
83
CHANGELOG.md
83
CHANGELOG.md
|
|
@ -1,88 +1,5 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## 0.2.26
|
|
||||||
|
|
||||||
- New: Import images from the gallery
|
|
||||||
- Improved: Media files are now stored in the dedicated "twonly" album
|
|
||||||
- Improved: UI components adapt to native styling (iOS/Android)
|
|
||||||
- Fix: Migration issue that resulted in a corrupted backup mechanism
|
|
||||||
- Fix: Database issues causing messages to be lost or the database to be corrupted
|
|
||||||
- Fix: Permission view did not disappear after they were granted
|
|
||||||
|
|
||||||
## 0.2.23
|
|
||||||
|
|
||||||
- Improved: Smaller UI changes
|
|
||||||
- Fix: Some messages were not marked as opened.
|
|
||||||
|
|
||||||
## 0.2.20
|
|
||||||
|
|
||||||
- New: Adds an "Ask a Friend" button to new contact suggestions.
|
|
||||||
- New: Adds security profiles.
|
|
||||||
- Improved: Onboarding flow for new users.
|
|
||||||
- Improved: Flame restore experience.
|
|
||||||
- Improved: The blue verification checkmark now displays the total number of verifications.
|
|
||||||
- Fix: Issue with receiving messages when user closed app while decrypting
|
|
||||||
- Fix: Background message fetching reliability.
|
|
||||||
- Fix: Issue with focus changing when taking a picture
|
|
||||||
- Fix: Issues with the camera initialization
|
|
||||||
|
|
||||||
## 0.2.16
|
|
||||||
|
|
||||||
- Fix: Images not shown after opening due to cleanup
|
|
||||||
|
|
||||||
## 0.2.15
|
|
||||||
|
|
||||||
- Fix: Issue with opening directly in chats
|
|
||||||
- Fix: Multiple smaller issues
|
|
||||||
|
|
||||||
## 0.2.13
|
|
||||||
|
|
||||||
- New: Tutorial on how to use zoom.
|
|
||||||
- New: Manage storage view.
|
|
||||||
- Improved: Media thumbnails for faster loading.
|
|
||||||
- Fix: Some messages were not marked as opened.
|
|
||||||
|
|
||||||
## 0.2.12
|
|
||||||
|
|
||||||
- New: Automatically mark identical media as opened across all chats (Settings > Chats).
|
|
||||||
- Improved: Memories viewer redesigned with smoother animations and new quick-action controls.
|
|
||||||
- Fix: Reliability of receiving media files.
|
|
||||||
|
|
||||||
## 0.2.11
|
|
||||||
|
|
||||||
- New: Create custom shortcuts to quickly share images with pre-selected groups
|
|
||||||
- New: Seamless recovery for iOS reinstallations
|
|
||||||
- Improved: Redesigned snackbar notifications
|
|
||||||
- Improved: New backup mechanism to allow larger backup files
|
|
||||||
- Improved: Move keys into a centralized Rust-owned structure stored in secure storage
|
|
||||||
- Fix: Messages occasionally not received until app restart
|
|
||||||
- Fix: Multiple smaller issues
|
|
||||||
|
|
||||||
## 0.2.10
|
|
||||||
|
|
||||||
- Fix: Issue with push notifications on Android
|
|
||||||
|
|
||||||
## 0.2.9
|
|
||||||
|
|
||||||
- Improved: Make contact avatars clickable
|
|
||||||
- Fix: Messages occasionally not received until app restart
|
|
||||||
- Fix: Complete setup would sometimes get stuck
|
|
||||||
|
|
||||||
## 0.2.8
|
|
||||||
|
|
||||||
- Fix: App did not launch sometimes on Android
|
|
||||||
|
|
||||||
## 0.2.0
|
|
||||||
|
|
||||||
- New: Feature to find friends without a phone number
|
|
||||||
- New: The verification state is now transferred to the scanned user
|
|
||||||
- New: Registration setup to configure the most important configurations
|
|
||||||
- Improved: Show ⌛ instead of the flame icon when it is about to expire
|
|
||||||
- Improved: FAQ is now in the app rather than opening in the browser
|
|
||||||
- Improved: Videos can now be paused
|
|
||||||
- Improved: Lock to record hands-free
|
|
||||||
- Fix: Many smaller issues
|
|
||||||
|
|
||||||
## 0.1.8
|
## 0.1.8
|
||||||
|
|
||||||
- Improved: Typos and grammar issues thanks to @AlbertUnruh
|
- Improved: Typos and grammar issues thanks to @AlbertUnruh
|
||||||
|
|
|
||||||
10
README.md
10
README.md
|
|
@ -1,16 +1,10 @@
|
||||||
# twonly
|
# twonly
|
||||||
|
|
||||||
<a href="https://twonly.eu" rel="some text"><img src="metadata/en-US/images/featureGraphic.png" alt="twonly, a privacy-friendly way to connect with friends through secure, spontaneous image sharing." /></a>
|
<a href="https://twonly.eu" rel="some text"><img src="docs/header.webp" alt="twonly, a privacy-friendly way to connect with friends through secure, spontaneous image sharing." /></a>
|
||||||
|
|
||||||
This repository contains the complete source code of the [twonly](https://twonly.eu) app. twonly is a replacement for Snapchat, but its purpose is not to replace instant messaging apps, as there are already [many fantastic alternatives](https://www.messenger-matrix.de/messenger-matrix-en.html) out there. It was started because I liked the basic features of Snapchat, such as opening with the camera, the easy-to-use image editor, and the focus on sending fun pictures to friends. But I was annoyed by Snapchat's forced AI chat, receiving random messages to follow strangers, and not knowing how my sent images/text messages were encrypted, if at all. I am also very critical of the direction in which the US is currently moving and therefore try to avoid US providers wherever possible.
|
This repository contains the complete source code of the [twonly](https://twonly.eu) app. twonly is a replacement for Snapchat, but its purpose is not to replace instant messaging apps, as there are already [many fantastic alternatives](https://www.messenger-matrix.de/messenger-matrix-en.html) out there. It was started because I liked the basic features of Snapchat, such as opening with the camera, the easy-to-use image editor, and the focus on sending fun pictures to friends. But I was annoyed by Snapchat's forced AI chat, receiving random messages to follow strangers, and not knowing how my sent images/text messages were encrypted, if at all. I am also very critical of the direction in which the US is currently moving and therefore try to avoid US providers wherever possible.
|
||||||
|
|
||||||
<p align="center">
|
<div style="margin: 10px 20px 10px 20px">
|
||||||
<img src="metadata/en-US/images/phoneScreenshots/01_share_moments.png" width="30%" alt="Share moments" />
|
|
||||||
<img src="metadata/en-US/images/phoneScreenshots/02_chat_list.png" width="30%" alt="Chat list" />
|
|
||||||
<img src="metadata/en-US/images/phoneScreenshots/03_groups.png" width="30%" alt="Groups" />
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div align="center" style="margin: 10px 20px 10px 20px">
|
|
||||||
<a href="https://apps.apple.com/de/app/twonly/id6743774441">
|
<a href="https://apps.apple.com/de/app/twonly/id6743774441">
|
||||||
<img alt="Get it on App Store button" src="https://twonly.eu/assets/buttons/download-on-the-app-store.svg"
|
<img alt="Get it on App Store button" src="https://twonly.eu/assets/buttons/download-on-the-app-store.svg"
|
||||||
width="100px" />
|
width="100px" />
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,9 @@ analyzer:
|
||||||
- "lib/src/model/protobuf/**"
|
- "lib/src/model/protobuf/**"
|
||||||
- "lib/src/model/protobuf/api/websocket/**"
|
- "lib/src/model/protobuf/api/websocket/**"
|
||||||
- "lib/generated/**"
|
- "lib/generated/**"
|
||||||
- "lib/core/**"
|
|
||||||
- "lib/src/localization/**"
|
|
||||||
- "rust_builder/"
|
|
||||||
- "dependencies/**"
|
- "dependencies/**"
|
||||||
- "pubspec.yaml"
|
- "pubspec.yaml"
|
||||||
- "**.arb"
|
- "*.arb"
|
||||||
- "test/drift/**"
|
- "test/drift/**"
|
||||||
- "**.g.dart"
|
- "**.g.dart"
|
||||||
|
|
||||||
|
|
|
||||||
2
android/.gitignore
vendored
2
android/.gitignore
vendored
|
|
@ -9,7 +9,5 @@ GeneratedPluginRegistrant.java
|
||||||
# Remember to never publicly share your keystore.
|
# Remember to never publicly share your keystore.
|
||||||
# See https://flutter.dev/to/reference-keystore
|
# See https://flutter.dev/to/reference-keystore
|
||||||
key.properties
|
key.properties
|
||||||
key.github.properties
|
|
||||||
key.properties.backup
|
|
||||||
**/*.keystore
|
**/*.keystore
|
||||||
**/*.jks
|
**/*.jks
|
||||||
|
|
|
||||||
|
|
@ -73,5 +73,4 @@ flutter {
|
||||||
dependencies {
|
dependencies {
|
||||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4'
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4'
|
||||||
implementation 'com.otaliastudios:transcoder:0.11.0'
|
implementation 'com.otaliastudios:transcoder:0.11.0'
|
||||||
implementation 'androidx.core:core-splashscreen:1.0.1'
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTop"
|
||||||
android:taskAffinity=""
|
android:taskAffinity=""
|
||||||
android:theme="@style/LaunchTheme"
|
android:theme="@style/LaunchTheme"
|
||||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
|
|
|
||||||
|
|
@ -6,38 +6,8 @@ import dev.darttools.flutter_android_volume_keydown.FlutterAndroidVolumeKeydownP
|
||||||
import android.view.KeyEvent.KEYCODE_VOLUME_DOWN
|
import android.view.KeyEvent.KEYCODE_VOLUME_DOWN
|
||||||
import android.view.KeyEvent.KEYCODE_VOLUME_UP
|
import android.view.KeyEvent.KEYCODE_VOLUME_UP
|
||||||
import io.flutter.embedding.engine.FlutterEngine
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
import android.content.Context
|
|
||||||
import io.crates.keyring.Keyring
|
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.net.Uri
|
|
||||||
import java.io.InputStream
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
|
||||||
import androidx.activity.result.PickVisualMediaRequest
|
|
||||||
import io.flutter.plugin.common.MethodChannel
|
|
||||||
|
|
||||||
class MainActivity : FlutterFragmentActivity() {
|
class MainActivity : FlutterFragmentActivity() {
|
||||||
private val CHANNEL = "eu.twonly/photo_picker"
|
|
||||||
private var pendingResult: MethodChannel.Result? = null
|
|
||||||
|
|
||||||
private lateinit var pickMultipleMedia: ActivityResultLauncher<PickVisualMediaRequest>
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
installSplashScreen()
|
|
||||||
|
|
||||||
pickMultipleMedia = registerForActivityResult(ActivityResultContracts.PickMultipleVisualMedia()) { uris ->
|
|
||||||
if (uris.isNotEmpty()) {
|
|
||||||
val uriStrings = uris.map { it.toString() }
|
|
||||||
pendingResult?.success(uriStrings)
|
|
||||||
} else {
|
|
||||||
pendingResult?.success(emptyList<String>())
|
|
||||||
}
|
|
||||||
pendingResult = null
|
|
||||||
}
|
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
|
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
|
||||||
if (keyCode == KEYCODE_VOLUME_DOWN && eventSink != null) {
|
if (keyCode == KEYCODE_VOLUME_DOWN && eventSink != null) {
|
||||||
|
|
@ -54,38 +24,7 @@ class MainActivity : FlutterFragmentActivity() {
|
||||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||||
super.configureFlutterEngine(flutterEngine)
|
super.configureFlutterEngine(flutterEngine)
|
||||||
|
|
||||||
Keyring.initializeNdkContext(applicationContext)
|
MediaStoreChannel.configure(flutterEngine, applicationContext)
|
||||||
|
|
||||||
VideoCompressionChannel.configure(flutterEngine, applicationContext)
|
VideoCompressionChannel.configure(flutterEngine, applicationContext)
|
||||||
|
|
||||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
|
|
||||||
when (call.method) {
|
|
||||||
"pickImages" -> {
|
|
||||||
pendingResult = result
|
|
||||||
pickMultipleMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
|
|
||||||
}
|
|
||||||
"getUriBytes" -> {
|
|
||||||
val uriString = call.argument<String>("uri")
|
|
||||||
if (uriString != null) {
|
|
||||||
try {
|
|
||||||
val uri = Uri.parse(uriString)
|
|
||||||
val inputStream: InputStream? = contentResolver.openInputStream(uri)
|
|
||||||
if (inputStream != null) {
|
|
||||||
val bytes = inputStream.readBytes()
|
|
||||||
inputStream.close()
|
|
||||||
result.success(bytes)
|
|
||||||
} else {
|
|
||||||
result.error("UNAVAILABLE", "Could not open InputStream", null)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
result.error("ERROR", e.message, null)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result.error("INVALID_ARGUMENT", "URI string is null", null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> result.notImplemented()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
92
android/app/src/main/kotlin/eu/twonly/MediaStoreChannel.kt
Normal file
92
android/app/src/main/kotlin/eu/twonly/MediaStoreChannel.kt
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
package eu.twonly
|
||||||
|
|
||||||
|
import android.content.ContentValues
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Environment
|
||||||
|
import android.provider.MediaStore
|
||||||
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileInputStream
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
|
||||||
|
object MediaStoreChannel {
|
||||||
|
private const val CHANNEL = "eu.twonly/mediaStore"
|
||||||
|
|
||||||
|
fun configure(flutterEngine: FlutterEngine, context: Context) {
|
||||||
|
val channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
|
||||||
|
|
||||||
|
channel.setMethodCallHandler { call, result ->
|
||||||
|
try {
|
||||||
|
if (call.method == "safeFileToDownload") {
|
||||||
|
val arguments = call.arguments<Map<String, String>>() as Map<String, String>
|
||||||
|
val sourceFile = arguments["sourceFile"]
|
||||||
|
if (sourceFile == null) {
|
||||||
|
result.success(false)
|
||||||
|
} else {
|
||||||
|
val inputStream = FileInputStream(File(sourceFile))
|
||||||
|
val outputName = File(sourceFile).name.takeIf { it.isNotEmpty() } ?: "memories.zip"
|
||||||
|
|
||||||
|
val savedUri = saveZipToDownloads(context, outputName, inputStream)
|
||||||
|
if (savedUri != null) {
|
||||||
|
result.success(savedUri.toString())
|
||||||
|
} else {
|
||||||
|
result.error("SAVE_FAILED", "Could not save ZIP", null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.notImplemented()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
result.error("EXCEPTION", e.message, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveZipToDownloads(
|
||||||
|
context: Context,
|
||||||
|
fileName: String = "archive.zip",
|
||||||
|
sourceStream: InputStream
|
||||||
|
): android.net.Uri? {
|
||||||
|
val resolver = context.contentResolver
|
||||||
|
|
||||||
|
val contentValues = ContentValues().apply {
|
||||||
|
put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
|
||||||
|
put(MediaStore.MediaColumns.MIME_TYPE, "application/zip")
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
|
||||||
|
put(MediaStore.MediaColumns.IS_PENDING, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val collection = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
|
||||||
|
} else {
|
||||||
|
MediaStore.Files.getContentUri("external")
|
||||||
|
}
|
||||||
|
|
||||||
|
val uri = resolver.insert(collection, contentValues) ?: return null
|
||||||
|
|
||||||
|
try {
|
||||||
|
resolver.openOutputStream(uri).use { out: OutputStream? ->
|
||||||
|
requireNotNull(out) { "Unable to open output stream" }
|
||||||
|
sourceStream.use { input ->
|
||||||
|
input.copyTo(out)
|
||||||
|
}
|
||||||
|
out.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
val done = ContentValues().apply { put(MediaStore.MediaColumns.IS_PENDING, 0) }
|
||||||
|
resolver.update(uri, done, null, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
return uri
|
||||||
|
} catch (e: Exception) {
|
||||||
|
try { resolver.delete(uri, null, null) } catch (_: Exception) {}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,12 +3,10 @@ package eu.twonly
|
||||||
import io.flutter.app.FlutterApplication
|
import io.flutter.app.FlutterApplication
|
||||||
import dev.fluttercommunity.workmanager.WorkmanagerDebug
|
import dev.fluttercommunity.workmanager.WorkmanagerDebug
|
||||||
import dev.fluttercommunity.workmanager.LoggingDebugHandler
|
import dev.fluttercommunity.workmanager.LoggingDebugHandler
|
||||||
import io.crates.keyring.Keyring
|
|
||||||
|
|
||||||
class MyApplication : FlutterApplication() {
|
class MyApplication : FlutterApplication() {
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
Keyring.initializeNdkContext(this)
|
|
||||||
// This enables the internal plugin logging to Logcat
|
// This enables the internal plugin logging to Logcat
|
||||||
WorkmanagerDebug.setCurrent(LoggingDebugHandler())
|
WorkmanagerDebug.setCurrent(LoggingDebugHandler())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
package io.crates.keyring
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
|
|
||||||
class Keyring {
|
|
||||||
companion object {
|
|
||||||
init {
|
|
||||||
// Replace with the name of your compiled Rust library
|
|
||||||
System.loadLibrary("rust_lib_twonly")
|
|
||||||
}
|
|
||||||
// The underlying Rust crate provides the implementation for this
|
|
||||||
external fun initializeNdkContext(context: Context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:aapt="http://schemas.android.com/aapt">
|
|
||||||
<aapt:attr name="android:drawable">
|
|
||||||
<vector
|
|
||||||
android:width="100dp"
|
|
||||||
android:height="100dp"
|
|
||||||
android:viewportWidth="640"
|
|
||||||
android:viewportHeight="640">
|
|
||||||
|
|
||||||
<!-- Wrap everything in a scaling group to add padding and prevent splash screen circular cropping -->
|
|
||||||
<group
|
|
||||||
android:pivotX="320"
|
|
||||||
android:pivotY="320"
|
|
||||||
android:scaleX="0.6"
|
|
||||||
android:scaleY="0.6">
|
|
||||||
|
|
||||||
<!-- Link One pivots around its visual center (approx X=416, Y=288) -->
|
|
||||||
<group
|
|
||||||
android:name="link_one_group"
|
|
||||||
android:pivotX="416"
|
|
||||||
android:pivotY="288">
|
|
||||||
<path
|
|
||||||
android:name="link_one_path"
|
|
||||||
android:fillColor="#fff"
|
|
||||||
android:pathData="M451.5 160C434.9 160 418.8 164.5 404.7 172.7C388.9 156.7 370.5 143.3 350.2 133.2C378.4 109.2 414.3 96 451.5 96C537.9 96 608 166 608 252.5C608 294 591.5 333.8 562.2 363.1L491.1 434.2C461.8 463.5 422 480 380.5 480C294.1 480 224 410 224 323.5C224 322 224 320.5 224.1 319C224.6 301.3 239.3 287.4 257 287.9C274.7 288.4 288.6 303.1 288.1 320.8C288.1 321.7 288.1 322.6 288.1 323.4C288.1 374.5 329.5 415.9 380.6 415.9C405.1 415.9 428.6 406.2 446 388.8L517.1 317.7C534.4 300.4 544.2 276.8 544.2 252.3C544.2 201.2 502.8 159.8 451.7 159.8z" />
|
|
||||||
</group>
|
|
||||||
|
|
||||||
<!-- Link Two pivots around its visual center (approx X=224, Y=352) -->
|
|
||||||
<group
|
|
||||||
android:name="link_two_group"
|
|
||||||
android:pivotX="224"
|
|
||||||
android:pivotY="352">
|
|
||||||
<path
|
|
||||||
android:name="link_two_path"
|
|
||||||
android:fillColor="#ffffff"
|
|
||||||
android:pathData="M307.2 237.3C305.3 236.5 303.4 235.4 301.7 234.2C289.1 227.7 274.7 224 259.6 224C235.1 224 211.6 233.7 194.2 251.1L123.1 322.2C105.8 339.5 96 363.1 96 387.6C96 438.7 137.4 480.1 188.5 480.1C205 480.1 221.1 475.7 235.2 467.5C251 483.5 269.4 496.9 289.8 507C261.6 530.9 225.8 544.2 188.5 544.2C102.1 544.2 32 474.2 32 387.7C32 346.2 48.5 306.4 77.8 277.1L148.9 206C178.2 176.7 218 160.2 259.5 160.2C346.1 160.2 416 230.8 416 317.1C416 318.4 416 319.7 416 321C415.6 338.7 400.9 352.6 383.2 352.2C365.5 351.8 351.6 337.1 352 319.4C352 318.6 352 317.9 352 317.1C352 283.4 334 253.8 307.2 237.5z" />
|
|
||||||
</group>
|
|
||||||
</group>
|
|
||||||
</vector>
|
|
||||||
</aapt:attr>
|
|
||||||
|
|
||||||
<!-- Rotate Link One smoothly back and forth -->
|
|
||||||
<target android:name="link_one_group">
|
|
||||||
<aapt:attr name="android:animation">
|
|
||||||
<objectAnimator
|
|
||||||
android:duration="800"
|
|
||||||
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
|
|
||||||
android:propertyName="rotation"
|
|
||||||
android:repeatCount="-1"
|
|
||||||
android:repeatMode="reverse"
|
|
||||||
android:valueFrom="-3"
|
|
||||||
android:valueTo="3" />
|
|
||||||
</aapt:attr>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<!-- Rotate Link Two smoothly in the opposite direction to create the opening/closing effect -->
|
|
||||||
<target android:name="link_two_group">
|
|
||||||
<aapt:attr name="android:animation">
|
|
||||||
<objectAnimator
|
|
||||||
android:duration="800"
|
|
||||||
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
|
|
||||||
android:propertyName="rotation"
|
|
||||||
android:repeatCount="-1"
|
|
||||||
android:repeatMode="reverse"
|
|
||||||
android:valueFrom="3"
|
|
||||||
android:valueTo="-3" />
|
|
||||||
</aapt:attr>
|
|
||||||
</target>
|
|
||||||
</animated-vector>
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||||
<style name="LaunchTheme" parent="Theme.SplashScreen">
|
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
<!-- Configure the Androidx Splash Screen API parameters -->
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
<item name="windowSplashScreenBackground">#FF57CC99</item>
|
the Flutter engine draws its first frame -->
|
||||||
<item name="windowSplashScreenAnimatedIcon">@drawable/link_animated</item>
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
<item name="windowSplashScreenAnimationDuration">800</item>
|
|
||||||
<item name="postSplashScreenTheme">@style/NormalTheme</item>
|
|
||||||
</style>
|
</style>
|
||||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
This theme determines the color of the Android Window while your
|
This theme determines the color of the Android Window while your
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||||
<style name="LaunchTheme" parent="Theme.SplashScreen">
|
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
<!-- Configure the Androidx Splash Screen API parameters -->
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
<item name="windowSplashScreenBackground">#FF57CC99</item>
|
the Flutter engine draws its first frame -->
|
||||||
<item name="windowSplashScreenAnimatedIcon">@drawable/link_animated</item>
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
<item name="windowSplashScreenAnimationDuration">800</item>
|
|
||||||
<item name="postSplashScreenTheme">@style/NormalTheme</item>
|
|
||||||
</style>
|
</style>
|
||||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
This theme determines the color of the Android Window while your
|
This theme determines the color of the Android Window while your
|
||||||
|
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
# Example signing credentials configuration for GitHub Releases.
|
|
||||||
# Copy this file to 'key.github.properties' and fill in your actual credentials.
|
|
||||||
# Do not commit the actual 'key.github.properties' file to version control.
|
|
||||||
|
|
||||||
storePassword=YOUR_GITHUB_RELEASE_STORE_PASSWORD
|
|
||||||
keyPassword=YOUR_GITHUB_RELEASE_KEY_PASSWORD
|
|
||||||
keyAlias=github-releases-signature
|
|
||||||
storeFile=/absolute/path/to/your/github-release-keystore.jks
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
|
|
@ -1,3 +0,0 @@
|
||||||
<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">
|
|
||||||
<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.107 4.000L9.339 4.000L9.339 12.000L7.832 12.000L7.832 6.246L7.817 6.232L7.339 6.638L6.948 6.899L6.455 7.159L5.861 7.391L5.861 6.014L6.165 5.899L6.499 5.725L6.861 5.493L7.296 5.159L7.643 4.812L7.832 4.565L8.049 4.174L8.107 4.000Z"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 732 B |
|
|
@ -1,3 +0,0 @@
|
||||||
<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">
|
|
||||||
<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.01zM7.899 4.000L8.391 4.000L8.768 4.043L9.029 4.101L9.362 4.217L9.623 4.348L9.942 4.580L10.145 4.783L10.304 4.986L10.406 5.145L10.551 5.464L10.652 5.899L10.667 6.406L10.594 6.884L10.478 7.246L10.290 7.638L10.043 8.029L9.812 8.333L9.507 8.667L8.406 9.696L7.928 10.174L7.754 10.391L7.638 10.580L10.667 10.594L10.667 12.000L5.333 12.000L5.391 11.609L5.522 11.159L5.710 10.725L6.000 10.246L6.232 9.942L6.638 9.478L7.464 8.652L8.072 8.087L8.594 7.551L8.870 7.203L9.029 6.899L9.087 6.739L9.145 6.449L9.145 6.174L9.101 5.928L8.986 5.667L8.768 5.435L8.652 5.362L8.522 5.304L8.246 5.246L7.971 5.246L7.783 5.275L7.652 5.319L7.406 5.464L7.232 5.652L7.101 5.913L7.029 6.203L7.000 6.493L5.493 6.348L5.565 5.870L5.725 5.362L5.957 4.942L6.275 4.594L6.638 4.348L6.942 4.203L7.377 4.072L7.899 4.000Z"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.3 KiB |
|
|
@ -1,3 +0,0 @@
|
||||||
<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">
|
|
||||||
<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.01zM7.730 4.000L8.142 4.000L8.555 4.057L8.854 4.142L9.224 4.313L9.523 4.527L9.794 4.797L10.064 5.196L10.206 5.580L10.249 5.851L10.235 6.263L10.149 6.591L10.007 6.875L9.836 7.103L9.566 7.359L9.125 7.644L9.409 7.730L9.708 7.872L9.950 8.043L10.164 8.256L10.335 8.498L10.477 8.797L10.548 9.039L10.591 9.338L10.577 9.836L10.448 10.377L10.249 10.776L10.093 11.004L9.893 11.231L9.523 11.544L9.125 11.772L8.911 11.858L8.598 11.943L8.171 12.000L7.587 11.986L7.146 11.900L6.733 11.744L6.320 11.488L5.950 11.132L5.680 10.733L5.509 10.320L5.409 9.865L5.409 9.808L6.847 9.637L6.861 9.765L6.947 10.064L7.004 10.192L7.160 10.420L7.416 10.633L7.630 10.733L7.843 10.776L8.171 10.762L8.327 10.719L8.498 10.633L8.797 10.363L8.968 10.064L9.039 9.822L9.068 9.609L9.053 9.167L8.954 8.826L8.769 8.541L8.512 8.327L8.214 8.214L7.872 8.199L7.331 8.299L7.488 7.132L7.929 7.089L8.256 6.975L8.384 6.890L8.569 6.705L8.683 6.505L8.754 6.221L8.754 5.979L8.698 5.737L8.598 5.552L8.427 5.381L8.242 5.281L8.000 5.224L7.772 5.224L7.445 5.324L7.317 5.409L7.132 5.594L7.032 5.751L6.947 5.964L6.890 6.278L5.523 6.036L5.623 5.623L5.794 5.181L5.950 4.911L6.235 4.584L6.591 4.327L6.961 4.157L7.302 4.057L7.730 4.000Z"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.6 KiB |
|
|
@ -1 +1 @@
|
||||||
Subproject commit 72d9bd6320bca1f1d29c6e61c3821fed326c0abe
|
Subproject commit 24d048b4abbe5c266b09965cc6f3ebdf83f97855
|
||||||
BIN
docs/header.webp
Normal file
BIN
docs/header.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 81 KiB |
|
|
@ -1,2 +0,0 @@
|
||||||
json_key_file(ENV["GOOGLE_PLAY_JSON_KEY_PATH"] || "../../local_data/accesskeys/upload_track_releases_google_play.json")
|
|
||||||
package_name("eu.twonly") # Your application ID
|
|
||||||
|
|
@ -1,144 +0,0 @@
|
||||||
default_platform(:android)
|
|
||||||
|
|
||||||
platform :android do
|
|
||||||
desc "Submit a new App Bundle to the Google Play Internal Track"
|
|
||||||
lane :internal do
|
|
||||||
# This lane assumes that `flutter build appbundle` has already been run from the flutter root.
|
|
||||||
upload_to_play_store(
|
|
||||||
track: 'internal',
|
|
||||||
aab: 'build/app/outputs/bundle/release/app-release.aab',
|
|
||||||
skip_upload_metadata: true,
|
|
||||||
skip_upload_images: true,
|
|
||||||
skip_upload_screenshots: true
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
desc "Build the application locally and upload it as a new GitHub release"
|
|
||||||
lane :release_github do
|
|
||||||
# Read pubspec.yaml to get the version
|
|
||||||
pubspec_path = File.expand_path("../pubspec.yaml", __dir__)
|
|
||||||
unless File.exist?(pubspec_path)
|
|
||||||
UI.user_error!("Could not find pubspec.yaml at #{pubspec_path}")
|
|
||||||
end
|
|
||||||
|
|
||||||
pubspec_content = File.read(pubspec_path)
|
|
||||||
version_match = pubspec_content.match(/^version:\s*([^+]+)/)
|
|
||||||
unless version_match
|
|
||||||
UI.user_error!("Could not extract version from pubspec.yaml")
|
|
||||||
end
|
|
||||||
|
|
||||||
version = version_match[1].strip
|
|
||||||
tag_name = "v#{version}"
|
|
||||||
UI.message("Extracted version: #{version} (tag: #{tag_name})")
|
|
||||||
|
|
||||||
# Load release notes from CHANGELOG.md
|
|
||||||
changelog_path = File.expand_path("../CHANGELOG.md", __dir__)
|
|
||||||
release_notes = "Automated local release via Fastlane"
|
|
||||||
if File.exist?(changelog_path)
|
|
||||||
changelog_content = File.read(changelog_path)
|
|
||||||
escaped_version = Regexp.escape(version)
|
|
||||||
pattern = /##\s*\[?#{escaped_version}\]?(.*?)(?=##\s*|\z)/m
|
|
||||||
match = changelog_content.match(pattern)
|
|
||||||
if match
|
|
||||||
release_notes = match[1].strip
|
|
||||||
UI.message("Loaded release notes from CHANGELOG.md:\n#{release_notes}")
|
|
||||||
else
|
|
||||||
UI.important("Could not find release notes for version #{version} in CHANGELOG.md. Using default description.")
|
|
||||||
end
|
|
||||||
else
|
|
||||||
UI.important("CHANGELOG.md not found at #{changelog_path}. Using default description.")
|
|
||||||
end
|
|
||||||
|
|
||||||
# Handle key.properties swapping if key.github.properties exists
|
|
||||||
key_properties_path = File.expand_path("../android/key.properties", __dir__)
|
|
||||||
github_properties_path = File.expand_path("../android/key.github.properties", __dir__)
|
|
||||||
backup_properties_path = File.expand_path("../android/key.properties.backup", __dir__)
|
|
||||||
|
|
||||||
swapped_properties = false
|
|
||||||
if File.exist?(github_properties_path)
|
|
||||||
UI.message("Found key.github.properties. Swapping in for the build...")
|
|
||||||
if File.exist?(key_properties_path)
|
|
||||||
FileUtils.cp(key_properties_path, backup_properties_path)
|
|
||||||
end
|
|
||||||
FileUtils.cp(github_properties_path, key_properties_path)
|
|
||||||
swapped_properties = true
|
|
||||||
else
|
|
||||||
UI.message("No key.github.properties found. Building with default key.properties...")
|
|
||||||
end
|
|
||||||
|
|
||||||
begin
|
|
||||||
# Build the Android application
|
|
||||||
UI.message("Building Android APK...")
|
|
||||||
Dir.chdir(File.expand_path("..", __dir__)) do
|
|
||||||
sh("flutter build apk --release --split-per-abi")
|
|
||||||
end
|
|
||||||
ensure
|
|
||||||
# Restore original key.properties if swapped
|
|
||||||
if swapped_properties
|
|
||||||
UI.message("Restoring original key.properties...")
|
|
||||||
if File.exist?(backup_properties_path)
|
|
||||||
FileUtils.cp(backup_properties_path, key_properties_path)
|
|
||||||
FileUtils.rm(backup_properties_path)
|
|
||||||
else
|
|
||||||
FileUtils.rm_f(key_properties_path)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Find built APKs
|
|
||||||
apk_glob = File.expand_path("../build/app/outputs/flutter-apk/*-release.apk", __dir__)
|
|
||||||
apks = Dir.glob(apk_glob)
|
|
||||||
|
|
||||||
if apks.empty?
|
|
||||||
UI.user_error!("No release APKs found matching #{apk_glob}")
|
|
||||||
end
|
|
||||||
|
|
||||||
UI.message("Found APKs to upload: #{apks.join(', ')}")
|
|
||||||
|
|
||||||
# Retrieve GitHub Token (fall back to gh auth token)
|
|
||||||
github_token = ENV["GITHUB_TOKEN"]
|
|
||||||
if github_token.nil? || github_token.empty?
|
|
||||||
UI.message("GITHUB_TOKEN env variable not set. Retrieving token via GitHub CLI (gh auth token)...")
|
|
||||||
begin
|
|
||||||
github_token = sh("gh auth token").strip
|
|
||||||
rescue => e
|
|
||||||
UI.user_error!("Failed to retrieve token from gh CLI. Make sure gh is installed and authenticated, or GITHUB_TOKEN environment variable is set. Error: #{e}")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
UI.message("Creating GitHub Release #{tag_name}...")
|
|
||||||
set_github_release(
|
|
||||||
repository_name: "twonlyapp/twonly-app",
|
|
||||||
api_token: github_token,
|
|
||||||
tag_name: tag_name,
|
|
||||||
name: "Release #{tag_name}",
|
|
||||||
description: release_notes,
|
|
||||||
upload_assets: apks
|
|
||||||
)
|
|
||||||
UI.success("Successfully uploaded release #{tag_name} to GitHub!")
|
|
||||||
|
|
||||||
# F-Droid deployment
|
|
||||||
fdroid_repo_dir = "/Users/tobi/Documents/drive/twonly/F-Droid/repo"
|
|
||||||
UI.message("Starting F-Droid deployment...")
|
|
||||||
FileUtils.mkdir_p(fdroid_repo_dir)
|
|
||||||
|
|
||||||
apks.each do |apk_path|
|
|
||||||
basename = File.basename(apk_path)
|
|
||||||
new_name = "eu.twonly_v#{version}-#{basename}"
|
|
||||||
dest_path = File.join(fdroid_repo_dir, new_name)
|
|
||||||
UI.message("Copying APK to F-Droid repo: #{dest_path}")
|
|
||||||
FileUtils.cp(apk_path, dest_path)
|
|
||||||
end
|
|
||||||
|
|
||||||
fdroid_dir = "/Users/tobi/Documents/drive/twonly/F-Droid"
|
|
||||||
update_script = File.join(fdroid_dir, "update.sh")
|
|
||||||
if File.exist?(update_script)
|
|
||||||
UI.message("Executing F-Droid update script...")
|
|
||||||
Dir.chdir(fdroid_dir) do
|
|
||||||
sh("chmod +x ./update.sh && ./update.sh")
|
|
||||||
end
|
|
||||||
else
|
|
||||||
UI.important("F-Droid update script not found at #{update_script}")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
rust_input: crate::bridge
|
|
||||||
rust_root: rust
|
|
||||||
dart_output: lib/core
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:integration_test/integration_test.dart';
|
|
||||||
import 'package:twonly/core/frb_generated.dart';
|
|
||||||
import 'package:twonly/locator.dart';
|
|
||||||
import 'package:twonly/src/services/background/callback_dispatcher.background.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
|
||||||
setUpAll(() async => RustLib.init());
|
|
||||||
|
|
||||||
test('Can initialize twonlyDB and connect to api server', () async {
|
|
||||||
// Initialize global variables
|
|
||||||
await initBackgroundExecution();
|
|
||||||
|
|
||||||
// Try to connect to the API server
|
|
||||||
final connected = await apiService.connect();
|
|
||||||
|
|
||||||
// Print out the result or test it
|
|
||||||
expect(connected, isA<bool>());
|
|
||||||
|
|
||||||
// We can also check if it's connected
|
|
||||||
// Depending on your test environment, this might be true or false
|
|
||||||
// if the server is unreachable without further setup
|
|
||||||
// expect(apiService.isConnected, isA<bool>());
|
|
||||||
|
|
||||||
// Close the connection after the test
|
|
||||||
if (apiService.isConnected) {
|
|
||||||
await apiService.close(() {});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
@ -230,12 +230,12 @@ struct PushKey: Sendable {
|
||||||
// MARK: - Code below here is support for the SwiftProtobuf runtime.
|
// MARK: - Code below here is support for the SwiftProtobuf runtime.
|
||||||
|
|
||||||
extension PushKind: SwiftProtobuf._ProtoNameProviding {
|
extension PushKind: SwiftProtobuf._ProtoNameProviding {
|
||||||
static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0REACTION\0\u{1}RESPONSE\0\u{1}TEXT\0\u{1}VIDEO\0\u{1}TWONLY\0\u{1}IMAGE\0\u{1}CONTACT_REQUEST\0\u{1}ACCEPT_REQUEST\0\u{1}STORED_MEDIA_FILE\0\u{1}TEST_NOTIFICATION\0\u{1}REOPENED_MEDIA\0\u{1}REACTION_TO_VIDEO\0\u{1}REACTION_TO_TEXT\0\u{1}REACTION_TO_IMAGE\0\u{1}REACTION_TO_AUDIO\0\u{1}ADDED_TO_GROUP\0\u{1}AUDIO\0")
|
static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0reaction\0\u{1}response\0\u{1}text\0\u{1}video\0\u{1}twonly\0\u{1}image\0\u{1}contactRequest\0\u{1}acceptRequest\0\u{1}storedMediaFile\0\u{1}testNotification\0\u{1}reopenedMedia\0\u{1}reactionToVideo\0\u{1}reactionToText\0\u{1}reactionToImage\0\u{1}reactionToAudio\0\u{1}addedToGroup\0\u{1}audio\0")
|
||||||
}
|
}
|
||||||
|
|
||||||
extension EncryptedPushNotification: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
extension EncryptedPushNotification: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||||
static let protoMessageName: String = "EncryptedPushNotification"
|
static let protoMessageName: String = "EncryptedPushNotification"
|
||||||
static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}key_id\0\u{1}nonce\0\u{1}ciphertext\0\u{1}mac\0")
|
static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}keyId\0\u{1}nonce\0\u{1}ciphertext\0\u{1}mac\0")
|
||||||
|
|
||||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||||
|
|
@ -280,7 +280,7 @@ extension EncryptedPushNotification: SwiftProtobuf.Message, SwiftProtobuf._Messa
|
||||||
|
|
||||||
extension PushNotification: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
extension PushNotification: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||||
static let protoMessageName: String = "PushNotification"
|
static let protoMessageName: String = "PushNotification"
|
||||||
static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}kind\0\u{3}message_id\0\u{3}additional_content\0")
|
static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}kind\0\u{1}messageId\0\u{1}additionalContent\0")
|
||||||
|
|
||||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||||
|
|
@ -354,7 +354,7 @@ extension PushUsers: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation
|
||||||
|
|
||||||
extension PushUser: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
extension PushUser: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||||
static let protoMessageName: String = "PushUser"
|
static let protoMessageName: String = "PushUser"
|
||||||
static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}user_id\0\u{3}display_name\0\u{1}blocked\0\u{3}last_message_id\0\u{3}push_keys\0")
|
static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}userId\0\u{1}displayName\0\u{1}blocked\0\u{1}lastMessageId\0\u{1}pushKeys\0")
|
||||||
|
|
||||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||||
|
|
@ -408,7 +408,7 @@ extension PushUser: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB
|
||||||
|
|
||||||
extension PushKey: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
extension PushKey: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||||
static let protoMessageName: String = "PushKey"
|
static let protoMessageName: String = "PushKey"
|
||||||
static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}id\0\u{1}key\0\u{3}created_at_unix_timestamp\0")
|
static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}id\0\u{1}key\0\u{1}createdAtUnixTimestamp\0")
|
||||||
|
|
||||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||||
|
|
|
||||||
|
|
@ -231,8 +231,6 @@ PODS:
|
||||||
- in_app_purchase_storekit (0.0.1):
|
- in_app_purchase_storekit (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- integration_test (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
- libwebp (1.5.0):
|
- libwebp (1.5.0):
|
||||||
- libwebp/demux (= 1.5.0)
|
- libwebp/demux (= 1.5.0)
|
||||||
- libwebp/mux (= 1.5.0)
|
- libwebp/mux (= 1.5.0)
|
||||||
|
|
@ -276,24 +274,17 @@ PODS:
|
||||||
- nanopb/encode (= 3.30910.0)
|
- nanopb/encode (= 3.30910.0)
|
||||||
- nanopb/decode (3.30910.0)
|
- nanopb/decode (3.30910.0)
|
||||||
- nanopb/encode (3.30910.0)
|
- nanopb/encode (3.30910.0)
|
||||||
|
- no_screenshot (0.10.0):
|
||||||
|
- Flutter
|
||||||
- package_info_plus (0.4.5):
|
- package_info_plus (0.4.5):
|
||||||
- Flutter
|
- Flutter
|
||||||
- permission_handler_apple (9.3.0):
|
- permission_handler_apple (9.3.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- photo_manager (3.9.0):
|
|
||||||
- Flutter
|
|
||||||
- FlutterMacOS
|
|
||||||
- pro_video_editor (0.0.1):
|
- pro_video_editor (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- PromisesObjC (2.4.0)
|
- PromisesObjC (2.4.0)
|
||||||
- restart_app (1.7.3):
|
- restart_app (1.7.3):
|
||||||
- Flutter
|
- Flutter
|
||||||
- rust_lib_twonly (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
- screen_protector (1.5.1):
|
|
||||||
- Flutter
|
|
||||||
- ScreenProtectorKit (= 1.5.1)
|
|
||||||
- ScreenProtectorKit (1.5.1)
|
|
||||||
- SDWebImage (5.21.7):
|
- SDWebImage (5.21.7):
|
||||||
- SDWebImage/Core (= 5.21.7)
|
- SDWebImage/Core (= 5.21.7)
|
||||||
- SDWebImage/Core (5.21.7)
|
- SDWebImage/Core (5.21.7)
|
||||||
|
|
@ -313,6 +304,31 @@ PODS:
|
||||||
- sqflite_darwin (0.0.4):
|
- sqflite_darwin (0.0.4):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
|
- sqlite3 (3.52.0):
|
||||||
|
- sqlite3/common (= 3.52.0)
|
||||||
|
- sqlite3/common (3.52.0)
|
||||||
|
- sqlite3/dbstatvtab (3.52.0):
|
||||||
|
- sqlite3/common
|
||||||
|
- sqlite3/fts5 (3.52.0):
|
||||||
|
- sqlite3/common
|
||||||
|
- sqlite3/math (3.52.0):
|
||||||
|
- sqlite3/common
|
||||||
|
- sqlite3/perf-threadsafe (3.52.0):
|
||||||
|
- sqlite3/common
|
||||||
|
- sqlite3/rtree (3.52.0):
|
||||||
|
- sqlite3/common
|
||||||
|
- sqlite3/session (3.52.0):
|
||||||
|
- sqlite3/common
|
||||||
|
- sqlite3_flutter_libs (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
|
- sqlite3 (~> 3.52.0)
|
||||||
|
- sqlite3/dbstatvtab
|
||||||
|
- sqlite3/fts5
|
||||||
|
- sqlite3/math
|
||||||
|
- sqlite3/perf-threadsafe
|
||||||
|
- sqlite3/rtree
|
||||||
|
- sqlite3/session
|
||||||
- SwiftProtobuf (1.36.1)
|
- SwiftProtobuf (1.36.1)
|
||||||
- SwiftyGif (5.4.5)
|
- SwiftyGif (5.4.5)
|
||||||
- url_launcher_ios (0.0.1):
|
- url_launcher_ios (0.0.1):
|
||||||
|
|
@ -354,19 +370,17 @@ DEPENDENCIES:
|
||||||
- GoogleUtilities
|
- GoogleUtilities
|
||||||
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||||
- in_app_purchase_storekit (from `.symlinks/plugins/in_app_purchase_storekit/darwin`)
|
- in_app_purchase_storekit (from `.symlinks/plugins/in_app_purchase_storekit/darwin`)
|
||||||
- integration_test (from `.symlinks/plugins/integration_test/ios`)
|
|
||||||
- local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`)
|
- local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`)
|
||||||
|
- no_screenshot (from `.symlinks/plugins/no_screenshot/ios`)
|
||||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||||
- photo_manager (from `.symlinks/plugins/photo_manager/darwin`)
|
|
||||||
- pro_video_editor (from `.symlinks/plugins/pro_video_editor/ios`)
|
- pro_video_editor (from `.symlinks/plugins/pro_video_editor/ios`)
|
||||||
- restart_app (from `.symlinks/plugins/restart_app/ios`)
|
- restart_app (from `.symlinks/plugins/restart_app/ios`)
|
||||||
- rust_lib_twonly (from `.symlinks/plugins/rust_lib_twonly/ios`)
|
|
||||||
- screen_protector (from `.symlinks/plugins/screen_protector/ios`)
|
|
||||||
- sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`)
|
- sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`)
|
||||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||||
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
||||||
|
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`)
|
||||||
- SwiftProtobuf
|
- SwiftProtobuf
|
||||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||||
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
|
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
|
||||||
|
|
@ -398,10 +412,10 @@ SPEC REPOS:
|
||||||
- MLKitVision
|
- MLKitVision
|
||||||
- nanopb
|
- nanopb
|
||||||
- PromisesObjC
|
- PromisesObjC
|
||||||
- ScreenProtectorKit
|
|
||||||
- SDWebImage
|
- SDWebImage
|
||||||
- SDWebImageWebPCoder
|
- SDWebImageWebPCoder
|
||||||
- Sentry
|
- Sentry
|
||||||
|
- sqlite3
|
||||||
- SwiftProtobuf
|
- SwiftProtobuf
|
||||||
- SwiftyGif
|
- SwiftyGif
|
||||||
|
|
||||||
|
|
@ -456,24 +470,18 @@ EXTERNAL SOURCES:
|
||||||
:path: ".symlinks/plugins/image_picker_ios/ios"
|
:path: ".symlinks/plugins/image_picker_ios/ios"
|
||||||
in_app_purchase_storekit:
|
in_app_purchase_storekit:
|
||||||
:path: ".symlinks/plugins/in_app_purchase_storekit/darwin"
|
:path: ".symlinks/plugins/in_app_purchase_storekit/darwin"
|
||||||
integration_test:
|
|
||||||
:path: ".symlinks/plugins/integration_test/ios"
|
|
||||||
local_auth_darwin:
|
local_auth_darwin:
|
||||||
:path: ".symlinks/plugins/local_auth_darwin/darwin"
|
:path: ".symlinks/plugins/local_auth_darwin/darwin"
|
||||||
|
no_screenshot:
|
||||||
|
:path: ".symlinks/plugins/no_screenshot/ios"
|
||||||
package_info_plus:
|
package_info_plus:
|
||||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||||
permission_handler_apple:
|
permission_handler_apple:
|
||||||
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||||
photo_manager:
|
|
||||||
:path: ".symlinks/plugins/photo_manager/darwin"
|
|
||||||
pro_video_editor:
|
pro_video_editor:
|
||||||
:path: ".symlinks/plugins/pro_video_editor/ios"
|
:path: ".symlinks/plugins/pro_video_editor/ios"
|
||||||
restart_app:
|
restart_app:
|
||||||
:path: ".symlinks/plugins/restart_app/ios"
|
:path: ".symlinks/plugins/restart_app/ios"
|
||||||
rust_lib_twonly:
|
|
||||||
:path: ".symlinks/plugins/rust_lib_twonly/ios"
|
|
||||||
screen_protector:
|
|
||||||
:path: ".symlinks/plugins/screen_protector/ios"
|
|
||||||
sentry_flutter:
|
sentry_flutter:
|
||||||
:path: ".symlinks/plugins/sentry_flutter/ios"
|
:path: ".symlinks/plugins/sentry_flutter/ios"
|
||||||
share_plus:
|
share_plus:
|
||||||
|
|
@ -482,6 +490,8 @@ EXTERNAL SOURCES:
|
||||||
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||||
sqflite_darwin:
|
sqflite_darwin:
|
||||||
:path: ".symlinks/plugins/sqflite_darwin/darwin"
|
:path: ".symlinks/plugins/sqflite_darwin/darwin"
|
||||||
|
sqlite3_flutter_libs:
|
||||||
|
:path: ".symlinks/plugins/sqlite3_flutter_libs/darwin"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||||
video_player_avfoundation:
|
video_player_avfoundation:
|
||||||
|
|
@ -530,7 +540,6 @@ SPEC CHECKSUMS:
|
||||||
GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6
|
GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6
|
||||||
image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
|
image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
|
||||||
in_app_purchase_storekit: 22cca7d08eebca9babdf4d07d0baccb73325d3c8
|
in_app_purchase_storekit: 22cca7d08eebca9babdf4d07d0baccb73325d3c8
|
||||||
integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
|
|
||||||
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
|
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
|
||||||
local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb
|
local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb
|
||||||
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
|
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
|
||||||
|
|
@ -540,15 +549,12 @@ SPEC CHECKSUMS:
|
||||||
MLKitFaceDetection: 32549f1e70e6e7731261bf9cea2b74095e2531cb
|
MLKitFaceDetection: 32549f1e70e6e7731261bf9cea2b74095e2531cb
|
||||||
MLKitVision: 39a5a812db83c4a0794445088e567f3631c11961
|
MLKitVision: 39a5a812db83c4a0794445088e567f3631c11961
|
||||||
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
||||||
|
no_screenshot: 03c8ac6586f9652cd45e3d12d74e5992256403ac
|
||||||
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
||||||
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
|
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
|
||||||
photo_manager: 25fd77df14f4f0ba5ef99e2c61814dde77e2bceb
|
|
||||||
pro_video_editor: 44ef9a6d48dbd757ed428cf35396dd05f35c7830
|
pro_video_editor: 44ef9a6d48dbd757ed428cf35396dd05f35c7830
|
||||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||||
restart_app: 0714144901e260eae68f7afc2fc4aacc1a323ad2
|
restart_app: 0714144901e260eae68f7afc2fc4aacc1a323ad2
|
||||||
rust_lib_twonly: 73165b05d0cda50db45852db63f49caa7f319520
|
|
||||||
screen_protector: 18c6aca2dc5d2a832f6787a5318f97f03e9d3150
|
|
||||||
ScreenProtectorKit: 6ceb3e0808341a9bc15d175bff40dfdd4b32da71
|
|
||||||
SDWebImage: e9fc87c1aab89a8ab1bbd74eba378c6f53be8abf
|
SDWebImage: e9fc87c1aab89a8ab1bbd74eba378c6f53be8abf
|
||||||
SDWebImageWebPCoder: 0e06e365080397465cc73a7a9b472d8a3bd0f377
|
SDWebImageWebPCoder: 0e06e365080397465cc73a7a9b472d8a3bd0f377
|
||||||
Sentry: d587a8fe91ca13503ecd69a1905f3e8a0fcf61be
|
Sentry: d587a8fe91ca13503ecd69a1905f3e8a0fcf61be
|
||||||
|
|
@ -556,6 +562,8 @@ SPEC CHECKSUMS:
|
||||||
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
|
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
|
||||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||||
|
sqlite3: a51c07cf16e023d6c48abd5e5791a61a47354921
|
||||||
|
sqlite3_flutter_libs: b3e120efe9a82017e5552a620f696589ed4f62ab
|
||||||
SwiftProtobuf: 9e106a71456f4d3f6a3b0c8fd87ef0be085efc38
|
SwiftProtobuf: 9e106a71456f4d3f6a3b0c8fd87ef0be085efc38
|
||||||
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
||||||
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
|
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,6 @@ import workmanager_apple
|
||||||
|
|
||||||
WorkmanagerDebug.setCurrent(LoggingDebugHandler())
|
WorkmanagerDebug.setCurrent(LoggingDebugHandler())
|
||||||
|
|
||||||
WorkmanagerPlugin.setPluginRegistrantCallback { registry in
|
|
||||||
GeneratedPluginRegistrant.register(with: registry)
|
|
||||||
}
|
|
||||||
|
|
||||||
WorkmanagerPlugin.registerPeriodicTask(
|
WorkmanagerPlugin.registerPeriodicTask(
|
||||||
withIdentifier: "eu.twonly.periodic_task",
|
withIdentifier: "eu.twonly.periodic_task",
|
||||||
frequency: NSNumber(value: 20 * 60)
|
frequency: NSNumber(value: 20 * 60)
|
||||||
|
|
@ -36,9 +32,7 @@ import workmanager_apple
|
||||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func application(
|
override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
|
||||||
_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]
|
|
||||||
) -> Bool {
|
|
||||||
|
|
||||||
let sharingIntent = SwiftFlutterSharingIntentPlugin.instance
|
let sharingIntent = SwiftFlutterSharingIntentPlugin.instance
|
||||||
if sharingIntent.hasSameSchemePrefix(url: url) {
|
if sharingIntent.hasSameSchemePrefix(url: url) {
|
||||||
|
|
@ -60,8 +54,7 @@ import workmanager_apple
|
||||||
NSLog(
|
NSLog(
|
||||||
"Application delegate method userNotificationCenter:didReceive:withCompletionHandler: is called with user info: %@",
|
"Application delegate method userNotificationCenter:didReceive:withCompletionHandler: is called with user info: %@",
|
||||||
response.notification.request.content.userInfo)
|
response.notification.request.content.userInfo)
|
||||||
super.userNotificationCenter(
|
//...
|
||||||
center, didReceive: response, withCompletionHandler: completionHandler)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func userNotificationCenter(
|
override func userNotificationCenter(
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
||||||
</imageView>
|
</imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
<color key="backgroundColor" red="0.341176" green="0.8" blue="0.6" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
||||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
||||||
|
|
|
||||||
186
lib/app.dart
186
lib/app.dart
|
|
@ -1,121 +1,118 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/locator.dart';
|
|
||||||
import 'package:twonly/src/localization/generated/app_localizations.dart';
|
import 'package:twonly/src/localization/generated/app_localizations.dart';
|
||||||
|
import 'package:twonly/src/providers/connection.provider.dart';
|
||||||
|
import 'package:twonly/src/providers/purchases.provider.dart';
|
||||||
import 'package:twonly/src/providers/routing.provider.dart';
|
import 'package:twonly/src/providers/routing.provider.dart';
|
||||||
import 'package:twonly/src/providers/settings.provider.dart';
|
import 'package:twonly/src/providers/settings.provider.dart';
|
||||||
|
import 'package:twonly/src/services/subscription.service.dart';
|
||||||
|
import 'package:twonly/src/themes/dark.dart';
|
||||||
|
import 'package:twonly/src/themes/light.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/pow.dart';
|
import 'package:twonly/src/utils/pow.dart';
|
||||||
import 'package:twonly/src/visual/components/app_outdated.comp.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
import 'package:twonly/src/visual/themes/dark.dart';
|
import 'package:twonly/src/views/components/app_outdated.dart';
|
||||||
import 'package:twonly/src/visual/themes/light.dart';
|
import 'package:twonly/src/views/home.view.dart';
|
||||||
import 'package:twonly/src/visual/views/critical_error.view.dart';
|
import 'package:twonly/src/views/onboarding/onboarding.view.dart';
|
||||||
import 'package:twonly/src/visual/views/home.view.dart';
|
import 'package:twonly/src/views/onboarding/register.view.dart';
|
||||||
import 'package:twonly/src/visual/views/onboarding/onboarding.view.dart';
|
import 'package:twonly/src/views/settings/backup/setup_backup.view.dart';
|
||||||
import 'package:twonly/src/visual/views/onboarding/register.view.dart';
|
import 'package:twonly/src/views/unlock_twonly.view.dart';
|
||||||
import 'package:twonly/src/visual/views/onboarding/setup.view.dart';
|
|
||||||
import 'package:twonly/src/visual/views/recovery.view.dart';
|
|
||||||
import 'package:twonly/src/visual/views/unlock_twonly.view.dart';
|
|
||||||
|
|
||||||
class App extends StatefulWidget {
|
class App extends StatefulWidget {
|
||||||
const App({
|
const App({super.key});
|
||||||
required this.storageError,
|
|
||||||
required this.recoveryPossible,
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
final bool storageError;
|
|
||||||
final bool recoveryPossible;
|
|
||||||
@override
|
@override
|
||||||
State<App> createState() => _AppState();
|
State<App> createState() => _AppState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppState extends State<App> with WidgetsBindingObserver {
|
class _AppState extends State<App> with WidgetsBindingObserver {
|
||||||
bool _wasPaused = false;
|
bool wasPaused = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
AppState.isAppInBackground = false;
|
globalIsAppInBackground = false;
|
||||||
WidgetsBinding.instance.addObserver(this);
|
WidgetsBinding.instance.addObserver(this);
|
||||||
|
|
||||||
|
globalCallbackConnectionState = ({required isConnected}) async {
|
||||||
|
await context.read<CustomChangeProvider>().updateConnectionState(
|
||||||
|
isConnected,
|
||||||
|
);
|
||||||
|
await setUserPlan();
|
||||||
|
};
|
||||||
|
|
||||||
|
globalCallbackUpdatePlan = (plan) {
|
||||||
|
context.read<PurchasesProvider>().updatePlan(plan);
|
||||||
|
};
|
||||||
|
|
||||||
|
unawaited(initAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setUserPlan() async {
|
||||||
|
final user = await getUser();
|
||||||
|
if (user != null && mounted) {
|
||||||
|
if (mounted) {
|
||||||
|
context.read<PurchasesProvider>().updatePlan(
|
||||||
|
planFromString(user.subscriptionPlan),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> initAsync() async {
|
||||||
|
await setUserPlan();
|
||||||
|
await apiService.connect();
|
||||||
|
await apiService.listenToNetworkChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||||
super.didChangeAppLifecycleState(state);
|
super.didChangeAppLifecycleState(state);
|
||||||
if (state == AppLifecycleState.resumed) {
|
if (state == AppLifecycleState.resumed) {
|
||||||
if (_wasPaused) {
|
if (wasPaused) {
|
||||||
AppState.isAppInBackground = false;
|
globalIsAppInBackground = false;
|
||||||
twonlyDB.markUpdated();
|
twonlyDB.markUpdated();
|
||||||
unawaited(apiService.connect());
|
unawaited(apiService.connect());
|
||||||
}
|
}
|
||||||
} else if (state == AppLifecycleState.paused) {
|
} else if (state == AppLifecycleState.paused) {
|
||||||
_wasPaused = true;
|
wasPaused = true;
|
||||||
AppState.isAppInBackground = true;
|
globalIsAppInBackground = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
WidgetsBinding.instance.removeObserver(this);
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
|
globalCallbackConnectionState = ({required isConnected}) {};
|
||||||
|
globalCallbackUpdatePlan = (planId) {};
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ListenableBuilder(
|
return ListenableBuilder(
|
||||||
listenable: context.read<SettingsChangeProvider>(),
|
listenable: context.watch<SettingsChangeProvider>(),
|
||||||
builder: (context, child) {
|
builder: (context, child) {
|
||||||
const localizationsDelegates = [
|
return MaterialApp.router(
|
||||||
|
routerConfig: routerProvider,
|
||||||
|
scaffoldMessengerKey: globalRootScaffoldMessengerKey,
|
||||||
|
localizationsDelegates: const [
|
||||||
AppLocalizations.delegate,
|
AppLocalizations.delegate,
|
||||||
GlobalMaterialLocalizations.delegate,
|
GlobalMaterialLocalizations.delegate,
|
||||||
GlobalWidgetsLocalizations.delegate,
|
GlobalWidgetsLocalizations.delegate,
|
||||||
GlobalCupertinoLocalizations.delegate,
|
GlobalCupertinoLocalizations.delegate,
|
||||||
];
|
],
|
||||||
|
debugShowCheckedModeBanner: false,
|
||||||
const supportedLocales = [
|
supportedLocales: const [
|
||||||
Locale('en', ''),
|
Locale('en', ''),
|
||||||
Locale('de', ''),
|
Locale('de', ''),
|
||||||
];
|
],
|
||||||
|
|
||||||
if (widget.storageError) {
|
|
||||||
return MaterialApp(
|
|
||||||
localizationsDelegates: localizationsDelegates,
|
|
||||||
debugShowCheckedModeBanner: false,
|
|
||||||
supportedLocales: supportedLocales,
|
|
||||||
title: 'twonly',
|
title: 'twonly',
|
||||||
theme: lightTheme,
|
theme: lightTheme,
|
||||||
darkTheme: darkTheme,
|
darkTheme: darkTheme,
|
||||||
themeMode: context.read<SettingsChangeProvider>().themeMode,
|
themeMode: context.watch<SettingsChangeProvider>().themeMode,
|
||||||
home: const CriticalErrorView(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (widget.recoveryPossible) {
|
|
||||||
return MaterialApp(
|
|
||||||
localizationsDelegates: localizationsDelegates,
|
|
||||||
debugShowCheckedModeBanner: false,
|
|
||||||
supportedLocales: supportedLocales,
|
|
||||||
title: 'twonly',
|
|
||||||
theme: lightTheme,
|
|
||||||
darkTheme: darkTheme,
|
|
||||||
themeMode: context.read<SettingsChangeProvider>().themeMode,
|
|
||||||
home: const RecoveryView(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return MaterialApp.router(
|
|
||||||
routerConfig: routerProvider,
|
|
||||||
localizationsDelegates: localizationsDelegates,
|
|
||||||
debugShowCheckedModeBanner: false,
|
|
||||||
supportedLocales: supportedLocales,
|
|
||||||
title: 'twonly',
|
|
||||||
theme: lightTheme,
|
|
||||||
darkTheme: darkTheme,
|
|
||||||
themeMode: context.read<SettingsChangeProvider>().themeMode,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -133,46 +130,41 @@ class AppMainWidget extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppMainWidgetState extends State<AppMainWidget> {
|
class _AppMainWidgetState extends State<AppMainWidget> {
|
||||||
|
bool _isUserCreated = false;
|
||||||
|
bool _showDatabaseMigration = false;
|
||||||
bool _showOnboarding = true;
|
bool _showOnboarding = true;
|
||||||
bool _isLoaded = false;
|
bool _isLoaded = false;
|
||||||
|
bool _skipBackup = false;
|
||||||
bool _isTwonlyLocked = true;
|
bool _isTwonlyLocked = true;
|
||||||
bool _wasLogged = true;
|
|
||||||
late int _initialPage;
|
|
||||||
|
|
||||||
(Future<int>?, bool) _proofOfWork = (null, false);
|
(Future<int>?, bool) _proofOfWork = (null, false);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
|
||||||
_initialPage = widget.initialPage;
|
|
||||||
Log.info('AppWidgetState: initState started');
|
|
||||||
initAsync();
|
initAsync();
|
||||||
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initAsync() async {
|
Future<void> initAsync() async {
|
||||||
Log.info('AppWidgetState: initAsync started');
|
_isUserCreated = await isUserCreated();
|
||||||
if (userService.isUserCreated) {
|
|
||||||
if (_initialPage != 0) {
|
if (_isUserCreated) {
|
||||||
final count = await twonlyDB.contactsDao.getContactsCount();
|
|
||||||
if (count == 0) {
|
|
||||||
_initialPage = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
unawaited(FirebaseMessaging.instance.requestPermission());
|
|
||||||
} catch (e) {
|
|
||||||
Log.error(e);
|
|
||||||
}
|
|
||||||
if (_isTwonlyLocked) {
|
if (_isTwonlyLocked) {
|
||||||
// do not change in case twonly was already unlocked at some point
|
// do not change in case twonly was already unlocked at some point
|
||||||
_isTwonlyLocked = userService.currentUser.screenLockEnabled;
|
_isTwonlyLocked = gUser.screenLockEnabled;
|
||||||
}
|
}
|
||||||
} else {
|
if (gUser.appVersion < 62) {
|
||||||
|
_showDatabaseMigration = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_isUserCreated && !_showDatabaseMigration) {
|
||||||
// This means the user is in the onboarding screen, so start with the Proof of Work.
|
// This means the user is in the onboarding screen, so start with the Proof of Work.
|
||||||
|
|
||||||
final (proof, disabled) = await apiService.getProofOfWork();
|
final (proof, disabled) = await apiService.getProofOfWork();
|
||||||
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.
|
||||||
_proofOfWork = (
|
_proofOfWork = (
|
||||||
calculatePoW(proof.prefix, proof.difficulty.toInt()),
|
calculatePoW(proof.prefix, proof.difficulty.toInt()),
|
||||||
false,
|
false,
|
||||||
|
|
@ -189,35 +181,31 @@ class _AppMainWidgetState extends State<AppMainWidget> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (!_wasLogged) {
|
|
||||||
Log.info('AppWidgetState: build started (_isLoaded: $_isLoaded)');
|
|
||||||
if (_isLoaded) {
|
|
||||||
_wasLogged = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!_isLoaded) {
|
if (!_isLoaded) {
|
||||||
return Center(child: Container());
|
return Center(child: Container());
|
||||||
}
|
}
|
||||||
|
|
||||||
late Widget child;
|
late Widget child;
|
||||||
|
|
||||||
if (userService.isUserCreated) {
|
if (_showDatabaseMigration) {
|
||||||
|
child = const Center(child: Text('Please reinstall twonly.'));
|
||||||
|
} else if (_isUserCreated) {
|
||||||
if (_isTwonlyLocked) {
|
if (_isTwonlyLocked) {
|
||||||
child = UnlockTwonlyView(
|
child = UnlockTwonlyView(
|
||||||
callbackOnSuccess: () => setState(() {
|
callbackOnSuccess: () => setState(() {
|
||||||
_isTwonlyLocked = false;
|
_isTwonlyLocked = false;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
} else if (!userService.currentUser.skipSetupPages && userService.currentUser.currentSetupPage != null) {
|
} else if (gUser.twonlySafeBackup == null && !_skipBackup) {
|
||||||
// This will only be shown in case the user have not skipped
|
child = SetupBackupView(
|
||||||
child = SetupView(
|
callBack: () {
|
||||||
onUpdate: () => setState(() {
|
_skipBackup = true;
|
||||||
// userService.currentUser has updated...
|
setState(() {});
|
||||||
}),
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
child = HomeView(
|
child = HomeView(
|
||||||
initialPage: _initialPage,
|
initialPage: widget.initialPage,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (_showOnboarding) {
|
} else if (_showOnboarding) {
|
||||||
|
|
@ -236,7 +224,7 @@ class _AppMainWidgetState extends State<AppMainWidget> {
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
child,
|
child,
|
||||||
const AppOutdatedComp(),
|
const AppOutdated(),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
// This file is automatically generated, so please do not edit it.
|
|
||||||
// @generated by `flutter_rust_bridge`@ 2.12.0.
|
|
||||||
|
|
||||||
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
|
|
||||||
|
|
||||||
import '../frb_generated.dart';
|
|
||||||
import '../lib.dart';
|
|
||||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
|
||||||
|
|
||||||
class BackupPasswordKeys {
|
|
||||||
final U8Array32 backupId;
|
|
||||||
final U8Array32 encryptionKey;
|
|
||||||
|
|
||||||
const BackupPasswordKeys({
|
|
||||||
required this.backupId,
|
|
||||||
required this.encryptionKey,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => backupId.hashCode ^ encryptionKey.hashCode;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) =>
|
|
||||||
identical(this, other) ||
|
|
||||||
other is BackupPasswordKeys &&
|
|
||||||
runtimeType == other.runtimeType &&
|
|
||||||
backupId == other.backupId &&
|
|
||||||
encryptionKey == other.encryptionKey;
|
|
||||||
}
|
|
||||||
|
|
@ -1,97 +0,0 @@
|
||||||
// This file is automatically generated, so please do not edit it.
|
|
||||||
// @generated by `flutter_rust_bridge`@ 2.12.0.
|
|
||||||
|
|
||||||
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
|
|
||||||
|
|
||||||
import 'frb_generated.dart';
|
|
||||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
|
||||||
|
|
||||||
// These functions are ignored because they are not marked as `pub`: `get_twonly_flutter`
|
|
||||||
// These types are ignored because they are neither used by any `pub` functions nor (for structs and enums) marked `#[frb(unignore)]`: `TwonlyFlutter`
|
|
||||||
|
|
||||||
Future<void> initializeTwonlyFlutter({required InitConfig config}) =>
|
|
||||||
RustLib.instance.api.crateBridgeInitializeTwonlyFlutter(config: config);
|
|
||||||
|
|
||||||
class AnnouncedUser {
|
|
||||||
final PlatformInt64 userId;
|
|
||||||
final Uint8List publicKey;
|
|
||||||
final PlatformInt64 publicId;
|
|
||||||
|
|
||||||
const AnnouncedUser({
|
|
||||||
required this.userId,
|
|
||||||
required this.publicKey,
|
|
||||||
required this.publicId,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => userId.hashCode ^ publicKey.hashCode ^ publicId.hashCode;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) =>
|
|
||||||
identical(this, other) ||
|
|
||||||
other is AnnouncedUser &&
|
|
||||||
runtimeType == other.runtimeType &&
|
|
||||||
userId == other.userId &&
|
|
||||||
publicKey == other.publicKey &&
|
|
||||||
publicId == other.publicId;
|
|
||||||
}
|
|
||||||
|
|
||||||
class InitConfig {
|
|
||||||
final String databaseDir;
|
|
||||||
final String dataDir;
|
|
||||||
|
|
||||||
const InitConfig({
|
|
||||||
required this.databaseDir,
|
|
||||||
required this.dataDir,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => databaseDir.hashCode ^ dataDir.hashCode;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) =>
|
|
||||||
identical(this, other) ||
|
|
||||||
other is InitConfig &&
|
|
||||||
runtimeType == other.runtimeType &&
|
|
||||||
databaseDir == other.databaseDir &&
|
|
||||||
dataDir == other.dataDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
class OtherPromotion {
|
|
||||||
final int promotionId;
|
|
||||||
final PlatformInt64 publicId;
|
|
||||||
final PlatformInt64 fromContactId;
|
|
||||||
final int threshold;
|
|
||||||
final Uint8List announcementShare;
|
|
||||||
final PlatformInt64? publicKeyVerifiedTimestamp;
|
|
||||||
|
|
||||||
const OtherPromotion({
|
|
||||||
required this.promotionId,
|
|
||||||
required this.publicId,
|
|
||||||
required this.fromContactId,
|
|
||||||
required this.threshold,
|
|
||||||
required this.announcementShare,
|
|
||||||
this.publicKeyVerifiedTimestamp,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode =>
|
|
||||||
promotionId.hashCode ^
|
|
||||||
publicId.hashCode ^
|
|
||||||
fromContactId.hashCode ^
|
|
||||||
threshold.hashCode ^
|
|
||||||
announcementShare.hashCode ^
|
|
||||||
publicKeyVerifiedTimestamp.hashCode;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) =>
|
|
||||||
identical(this, other) ||
|
|
||||||
other is OtherPromotion &&
|
|
||||||
runtimeType == other.runtimeType &&
|
|
||||||
promotionId == other.promotionId &&
|
|
||||||
publicId == other.publicId &&
|
|
||||||
fromContactId == other.fromContactId &&
|
|
||||||
threshold == other.threshold &&
|
|
||||||
announcementShare == other.announcementShare &&
|
|
||||||
publicKeyVerifiedTimestamp == other.publicKeyVerifiedTimestamp;
|
|
||||||
}
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
// This file is automatically generated, so please do not edit it.
|
|
||||||
// @generated by `flutter_rust_bridge`@ 2.12.0.
|
|
||||||
|
|
||||||
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
|
|
||||||
|
|
||||||
import '../bridge.dart';
|
|
||||||
import '../frb_generated.dart';
|
|
||||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
|
||||||
|
|
||||||
// These functions are ignored because they are not marked as `pub`: `get_callbacks`
|
|
||||||
// These types are ignored because they are neither used by any `pub` functions nor (for structs and enums) marked `#[frb(unignore)]`: `FlutterCallbacks`, `Logging`, `UserDiscoveryCallbacks`
|
|
||||||
|
|
||||||
Future<void> initFlutterCallbacks({
|
|
||||||
required FutureOr<RustStreamSink<String>> Function() loggingGetStreamSink,
|
|
||||||
required FutureOr<Uint8List?> Function(Uint8List) userDiscoverySignData,
|
|
||||||
required FutureOr<bool> Function(Uint8List, Uint8List, Uint8List)
|
|
||||||
userDiscoveryVerifySignature,
|
|
||||||
required FutureOr<bool> Function(PlatformInt64, Uint8List)
|
|
||||||
userDiscoveryVerifyStoredPubkey,
|
|
||||||
required FutureOr<bool> Function(List<Uint8List>) userDiscoverySetShares,
|
|
||||||
required FutureOr<Uint8List?> Function(PlatformInt64)
|
|
||||||
userDiscoveryGetShareForContact,
|
|
||||||
required FutureOr<bool> Function(PlatformInt64, PlatformInt64, Uint8List)
|
|
||||||
userDiscoveryPushOwnPromotionAndClearOldVersion,
|
|
||||||
required FutureOr<List<Uint8List>?> Function(PlatformInt64)
|
|
||||||
userDiscoveryGetOwnPromotionsAfterVersion,
|
|
||||||
required FutureOr<bool> Function(OtherPromotion)
|
|
||||||
userDiscoveryStoreOtherPromotion,
|
|
||||||
required FutureOr<List<OtherPromotion>?> Function(PlatformInt64)
|
|
||||||
userDiscoveryGetOtherPromotionsByPublicId,
|
|
||||||
required FutureOr<AnnouncedUser?> Function(PlatformInt64)
|
|
||||||
userDiscoveryGetAnnouncedUserByPublicId,
|
|
||||||
required FutureOr<Uint8List?> Function(PlatformInt64)
|
|
||||||
userDiscoveryGetContactVersion,
|
|
||||||
required FutureOr<bool> Function(PlatformInt64, Uint8List)
|
|
||||||
userDiscoverySetContactVersion,
|
|
||||||
required FutureOr<bool> Function(PlatformInt64, AnnouncedUser, PlatformInt64?)
|
|
||||||
userDiscoveryPushNewUserRelation,
|
|
||||||
required FutureOr<Uint8List?> Function(PlatformInt64)
|
|
||||||
userDiscoveryGetContactPromotion,
|
|
||||||
}) => RustLib.instance.api.crateBridgeCallbacksInitFlutterCallbacks(
|
|
||||||
loggingGetStreamSink: loggingGetStreamSink,
|
|
||||||
userDiscoverySignData: userDiscoverySignData,
|
|
||||||
userDiscoveryVerifySignature: userDiscoveryVerifySignature,
|
|
||||||
userDiscoveryVerifyStoredPubkey: userDiscoveryVerifyStoredPubkey,
|
|
||||||
userDiscoverySetShares: userDiscoverySetShares,
|
|
||||||
userDiscoveryGetShareForContact: userDiscoveryGetShareForContact,
|
|
||||||
userDiscoveryPushOwnPromotionAndClearOldVersion:
|
|
||||||
userDiscoveryPushOwnPromotionAndClearOldVersion,
|
|
||||||
userDiscoveryGetOwnPromotionsAfterVersion:
|
|
||||||
userDiscoveryGetOwnPromotionsAfterVersion,
|
|
||||||
userDiscoveryStoreOtherPromotion: userDiscoveryStoreOtherPromotion,
|
|
||||||
userDiscoveryGetOtherPromotionsByPublicId:
|
|
||||||
userDiscoveryGetOtherPromotionsByPublicId,
|
|
||||||
userDiscoveryGetAnnouncedUserByPublicId:
|
|
||||||
userDiscoveryGetAnnouncedUserByPublicId,
|
|
||||||
userDiscoveryGetContactVersion: userDiscoveryGetContactVersion,
|
|
||||||
userDiscoverySetContactVersion: userDiscoverySetContactVersion,
|
|
||||||
userDiscoveryPushNewUserRelation: userDiscoveryPushNewUserRelation,
|
|
||||||
userDiscoveryGetContactPromotion: userDiscoveryGetContactPromotion,
|
|
||||||
);
|
|
||||||
|
|
@ -1,87 +0,0 @@
|
||||||
// This file is automatically generated, so please do not edit it.
|
|
||||||
// @generated by `flutter_rust_bridge`@ 2.12.0.
|
|
||||||
|
|
||||||
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
|
|
||||||
|
|
||||||
import '../../frb_generated.dart';
|
|
||||||
import '../../keys/backup_password_keys.dart';
|
|
||||||
import '../../lib.dart';
|
|
||||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
|
||||||
|
|
||||||
class RustBackupArchive {
|
|
||||||
const RustBackupArchive();
|
|
||||||
|
|
||||||
static Future<(String, String)> createBackupArchive() => RustLib.instance.api
|
|
||||||
.crateBridgeWrapperBackupRustBackupArchiveCreateBackupArchive();
|
|
||||||
|
|
||||||
static Future<String?> getBackupDownloadToken() => RustLib.instance.api
|
|
||||||
.crateBridgeWrapperBackupRustBackupArchiveGetBackupDownloadToken();
|
|
||||||
|
|
||||||
static Future<void> restoreBackupArchive({required String filePath}) =>
|
|
||||||
RustLib.instance.api
|
|
||||||
.crateBridgeWrapperBackupRustBackupArchiveRestoreBackupArchive(
|
|
||||||
filePath: filePath,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => 0;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) =>
|
|
||||||
identical(this, other) ||
|
|
||||||
other is RustBackupArchive && runtimeType == other.runtimeType;
|
|
||||||
}
|
|
||||||
|
|
||||||
class RustBackupIdentity {
|
|
||||||
const RustBackupIdentity();
|
|
||||||
|
|
||||||
static Future<String?> getBackupId() => RustLib.instance.api
|
|
||||||
.crateBridgeWrapperBackupRustBackupIdentityGetBackupId();
|
|
||||||
|
|
||||||
static Future<BackupPasswordKeys> getBackupPasswordKeys({
|
|
||||||
required PlatformInt64 userId,
|
|
||||||
required String password,
|
|
||||||
}) => RustLib.instance.api
|
|
||||||
.crateBridgeWrapperBackupRustBackupIdentityGetBackupPasswordKeys(
|
|
||||||
userId: userId,
|
|
||||||
password: password,
|
|
||||||
);
|
|
||||||
|
|
||||||
static Future<Uint8List> getIdentityBackupBytes() => RustLib.instance.api
|
|
||||||
.crateBridgeWrapperBackupRustBackupIdentityGetIdentityBackupBytes();
|
|
||||||
|
|
||||||
static Future<void> importBackupPasswordKeys({
|
|
||||||
required List<int> backupId,
|
|
||||||
required List<int> encryptionKey,
|
|
||||||
}) => RustLib.instance.api
|
|
||||||
.crateBridgeWrapperBackupRustBackupIdentityImportBackupPasswordKeys(
|
|
||||||
backupId: backupId,
|
|
||||||
encryptionKey: encryptionKey,
|
|
||||||
);
|
|
||||||
|
|
||||||
static Future<void> restoreIdentityBackup({
|
|
||||||
required BackupPasswordKeys keys,
|
|
||||||
required List<int> encryptedBytes,
|
|
||||||
}) => RustLib.instance.api
|
|
||||||
.crateBridgeWrapperBackupRustBackupIdentityRestoreIdentityBackup(
|
|
||||||
keys: keys,
|
|
||||||
encryptedBytes: encryptedBytes,
|
|
||||||
);
|
|
||||||
|
|
||||||
static Future<void> setBackupPasswordKeys({
|
|
||||||
required PlatformInt64 userId,
|
|
||||||
required String password,
|
|
||||||
}) => RustLib.instance.api
|
|
||||||
.crateBridgeWrapperBackupRustBackupIdentitySetBackupPasswordKeys(
|
|
||||||
userId: userId,
|
|
||||||
password: password,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => 0;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) =>
|
|
||||||
identical(this, other) ||
|
|
||||||
other is RustBackupIdentity && runtimeType == other.runtimeType;
|
|
||||||
}
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
// This file is automatically generated, so please do not edit it.
|
|
||||||
// @generated by `flutter_rust_bridge`@ 2.12.0.
|
|
||||||
|
|
||||||
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
|
|
||||||
|
|
||||||
import '../../frb_generated.dart';
|
|
||||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
|
||||||
|
|
||||||
class RustKeyManager {
|
|
||||||
const RustKeyManager();
|
|
||||||
|
|
||||||
static Future<Uint8List> getLoginToken() => RustLib.instance.api
|
|
||||||
.crateBridgeWrapperKeyManagerRustKeyManagerGetLoginToken();
|
|
||||||
|
|
||||||
static Future<(Uint8List, PlatformInt64)> getSignalIdentity() => RustLib
|
|
||||||
.instance
|
|
||||||
.api
|
|
||||||
.crateBridgeWrapperKeyManagerRustKeyManagerGetSignalIdentity();
|
|
||||||
|
|
||||||
static Future<PlatformInt64?> getUserId() => RustLib.instance.api
|
|
||||||
.crateBridgeWrapperKeyManagerRustKeyManagerGetUserId();
|
|
||||||
|
|
||||||
static Future<void> importSignalIdentity({
|
|
||||||
required List<int> identityKeyPairStructure,
|
|
||||||
required PlatformInt64 registrationId,
|
|
||||||
required Map<PlatformInt64, Uint8List> signedPreKeyStore,
|
|
||||||
}) => RustLib.instance.api
|
|
||||||
.crateBridgeWrapperKeyManagerRustKeyManagerImportSignalIdentity(
|
|
||||||
identityKeyPairStructure: identityKeyPairStructure,
|
|
||||||
registrationId: registrationId,
|
|
||||||
signedPreKeyStore: signedPreKeyStore,
|
|
||||||
);
|
|
||||||
|
|
||||||
static Future<Uint8List?> loadSignedPrekey({
|
|
||||||
required PlatformInt64 signedPreKeyId,
|
|
||||||
}) => RustLib.instance.api
|
|
||||||
.crateBridgeWrapperKeyManagerRustKeyManagerLoadSignedPrekey(
|
|
||||||
signedPreKeyId: signedPreKeyId,
|
|
||||||
);
|
|
||||||
|
|
||||||
static Future<Map<PlatformInt64, Uint8List>> loadSignedPrekeys() => RustLib
|
|
||||||
.instance
|
|
||||||
.api
|
|
||||||
.crateBridgeWrapperKeyManagerRustKeyManagerLoadSignedPrekeys();
|
|
||||||
|
|
||||||
static Future<void> removeKeyManager() => RustLib.instance.api
|
|
||||||
.crateBridgeWrapperKeyManagerRustKeyManagerRemoveKeyManager();
|
|
||||||
|
|
||||||
static Future<void> removeSignedPrekey({
|
|
||||||
required PlatformInt64 signedPreKeyId,
|
|
||||||
}) => RustLib.instance.api
|
|
||||||
.crateBridgeWrapperKeyManagerRustKeyManagerRemoveSignedPrekey(
|
|
||||||
signedPreKeyId: signedPreKeyId,
|
|
||||||
);
|
|
||||||
|
|
||||||
static Future<void> setUserId({required PlatformInt64 userId}) => RustLib
|
|
||||||
.instance
|
|
||||||
.api
|
|
||||||
.crateBridgeWrapperKeyManagerRustKeyManagerSetUserId(userId: userId);
|
|
||||||
|
|
||||||
static Future<void> storeSignedPrekey({
|
|
||||||
required PlatformInt64 signedPreKeyId,
|
|
||||||
required List<int> record,
|
|
||||||
}) => RustLib.instance.api
|
|
||||||
.crateBridgeWrapperKeyManagerRustKeyManagerStoreSignedPrekey(
|
|
||||||
signedPreKeyId: signedPreKeyId,
|
|
||||||
record: record,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => 0;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) =>
|
|
||||||
identical(this, other) ||
|
|
||||||
other is RustKeyManager && runtimeType == other.runtimeType;
|
|
||||||
}
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
// This file is automatically generated, so please do not edit it.
|
|
||||||
// @generated by `flutter_rust_bridge`@ 2.12.0.
|
|
||||||
|
|
||||||
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
|
|
||||||
|
|
||||||
import '../../frb_generated.dart';
|
|
||||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
|
||||||
|
|
||||||
class FlutterUserDiscovery {
|
|
||||||
const FlutterUserDiscovery();
|
|
||||||
|
|
||||||
static Future<Uint8List> getCurrentVersion() => RustLib.instance.api
|
|
||||||
.crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryGetCurrentVersion();
|
|
||||||
|
|
||||||
static Future<List<Uint8List>> getNewMessages({
|
|
||||||
required PlatformInt64 contactId,
|
|
||||||
required List<int> receivedVersion,
|
|
||||||
}) => RustLib.instance.api
|
|
||||||
.crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryGetNewMessages(
|
|
||||||
contactId: contactId,
|
|
||||||
receivedVersion: receivedVersion,
|
|
||||||
);
|
|
||||||
|
|
||||||
static Future<void> handleNewMessages({
|
|
||||||
required PlatformInt64 contactId,
|
|
||||||
PlatformInt64? publicKeyVerifiedTimestamp,
|
|
||||||
required List<Uint8List> messages,
|
|
||||||
}) => RustLib.instance.api
|
|
||||||
.crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryHandleNewMessages(
|
|
||||||
contactId: contactId,
|
|
||||||
publicKeyVerifiedTimestamp: publicKeyVerifiedTimestamp,
|
|
||||||
messages: messages,
|
|
||||||
);
|
|
||||||
|
|
||||||
static Future<void> initializeOrUpdate({
|
|
||||||
required int threshold,
|
|
||||||
required PlatformInt64 userId,
|
|
||||||
required List<int> publicKey,
|
|
||||||
required bool sharePromotion,
|
|
||||||
}) => RustLib.instance.api
|
|
||||||
.crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryInitializeOrUpdate(
|
|
||||||
threshold: threshold,
|
|
||||||
userId: userId,
|
|
||||||
publicKey: publicKey,
|
|
||||||
sharePromotion: sharePromotion,
|
|
||||||
);
|
|
||||||
|
|
||||||
static Future<Uint8List?> shouldRequestNewMessages({
|
|
||||||
required PlatformInt64 contactId,
|
|
||||||
required List<int> version,
|
|
||||||
}) => RustLib.instance.api
|
|
||||||
.crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryShouldRequestNewMessages(
|
|
||||||
contactId: contactId,
|
|
||||||
version: version,
|
|
||||||
);
|
|
||||||
|
|
||||||
static Future<void> updateVerificationStateForUser({
|
|
||||||
required PlatformInt64 contactId,
|
|
||||||
PlatformInt64? publicKeyVerifiedTimestamp,
|
|
||||||
}) => RustLib.instance.api
|
|
||||||
.crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryUpdateVerificationStateForUser(
|
|
||||||
contactId: contactId,
|
|
||||||
publicKeyVerifiedTimestamp: publicKeyVerifiedTimestamp,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => 0;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) =>
|
|
||||||
identical(this, other) ||
|
|
||||||
other is FlutterUserDiscovery && runtimeType == other.runtimeType;
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
// This file is automatically generated, so please do not edit it.
|
|
||||||
// @generated by `flutter_rust_bridge`@ 2.12.0.
|
|
||||||
|
|
||||||
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
|
|
||||||
|
|
||||||
import 'frb_generated.dart';
|
|
||||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
|
||||||
|
|
||||||
class InitConfig {
|
|
||||||
final String databasePath;
|
|
||||||
final String dataDirectory;
|
|
||||||
|
|
||||||
const InitConfig({
|
|
||||||
required this.databasePath,
|
|
||||||
required this.dataDirectory,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => databasePath.hashCode ^ dataDirectory.hashCode;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) =>
|
|
||||||
identical(this, other) ||
|
|
||||||
other is InitConfig &&
|
|
||||||
runtimeType == other.runtimeType &&
|
|
||||||
databasePath == other.databasePath &&
|
|
||||||
dataDirectory == other.dataDirectory;
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,669 +0,0 @@
|
||||||
// This file is automatically generated, so please do not edit it.
|
|
||||||
// @generated by `flutter_rust_bridge`@ 2.12.0.
|
|
||||||
|
|
||||||
// ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field
|
|
||||||
|
|
||||||
import 'bridge.dart';
|
|
||||||
import 'bridge/callbacks.dart';
|
|
||||||
import 'bridge/wrapper/backup.dart';
|
|
||||||
import 'bridge/wrapper/key_manager.dart';
|
|
||||||
import 'bridge/wrapper/user_discovery.dart';
|
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:ffi' as ffi;
|
|
||||||
import 'frb_generated.dart';
|
|
||||||
import 'keys/backup_password_keys.dart';
|
|
||||||
import 'lib.dart';
|
|
||||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated_io.dart';
|
|
||||||
|
|
||||||
abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|
||||||
RustLibApiImplPlatform({
|
|
||||||
required super.handler,
|
|
||||||
required super.wire,
|
|
||||||
required super.generalizedFrbRustBinding,
|
|
||||||
required super.portManager,
|
|
||||||
});
|
|
||||||
|
|
||||||
@protected
|
|
||||||
AnyhowException dco_decode_AnyhowException(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
FutureOr<RustStreamSink<String>> Function()
|
|
||||||
dco_decode_DartFn_Inputs__Output_StreamSink_String_Sse_AnyhowException(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
FutureOr<AnnouncedUser?> Function(PlatformInt64)
|
|
||||||
dco_decode_DartFn_Inputs_i_64_Output_opt_box_autoadd_announced_user_AnyhowException(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
FutureOr<List<Uint8List>?> Function(PlatformInt64)
|
|
||||||
dco_decode_DartFn_Inputs_i_64_Output_opt_list_list_prim_u_8_strict_AnyhowException(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
FutureOr<List<OtherPromotion>?> Function(PlatformInt64)
|
|
||||||
dco_decode_DartFn_Inputs_i_64_Output_opt_list_other_promotion_AnyhowException(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
FutureOr<Uint8List?> Function(PlatformInt64)
|
|
||||||
dco_decode_DartFn_Inputs_i_64_Output_opt_list_prim_u_8_strict_AnyhowException(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
FutureOr<bool> Function(PlatformInt64, AnnouncedUser, PlatformInt64?)
|
|
||||||
dco_decode_DartFn_Inputs_i_64_announced_user_opt_box_autoadd_i_64_Output_bool_AnyhowException(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
FutureOr<bool> Function(PlatformInt64, PlatformInt64, Uint8List)
|
|
||||||
dco_decode_DartFn_Inputs_i_64_i_64_list_prim_u_8_strict_Output_bool_AnyhowException(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
FutureOr<bool> Function(PlatformInt64, Uint8List)
|
|
||||||
dco_decode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_bool_AnyhowException(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
FutureOr<bool> Function(List<Uint8List>)
|
|
||||||
dco_decode_DartFn_Inputs_list_list_prim_u_8_strict_Output_bool_AnyhowException(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
FutureOr<Uint8List?> Function(Uint8List)
|
|
||||||
dco_decode_DartFn_Inputs_list_prim_u_8_strict_Output_opt_list_prim_u_8_strict_AnyhowException(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
FutureOr<bool> Function(Uint8List, Uint8List, Uint8List)
|
|
||||||
dco_decode_DartFn_Inputs_list_prim_u_8_strict_list_prim_u_8_strict_list_prim_u_8_strict_Output_bool_AnyhowException(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
FutureOr<bool> Function(OtherPromotion)
|
|
||||||
dco_decode_DartFn_Inputs_other_promotion_Output_bool_AnyhowException(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
Object dco_decode_DartOpaque(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
Map<PlatformInt64, Uint8List> dco_decode_Map_i_64_list_prim_u_8_strict_None(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
RustStreamSink<String> dco_decode_StreamSink_String_Sse(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
String dco_decode_String(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
AnnouncedUser dco_decode_announced_user(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
BackupPasswordKeys dco_decode_backup_password_keys(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
bool dco_decode_bool(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
AnnouncedUser dco_decode_box_autoadd_announced_user(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
BackupPasswordKeys dco_decode_box_autoadd_backup_password_keys(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
PlatformInt64 dco_decode_box_autoadd_i_64(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
InitConfig dco_decode_box_autoadd_init_config(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
FlutterUserDiscovery dco_decode_flutter_user_discovery(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
PlatformInt64 dco_decode_i_64(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
InitConfig dco_decode_init_config(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
PlatformInt64 dco_decode_isize(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
List<Uint8List> dco_decode_list_list_prim_u_8_strict(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
List<OtherPromotion> dco_decode_list_other_promotion(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
List<int> dco_decode_list_prim_u_8_loose(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
Uint8List dco_decode_list_prim_u_8_strict(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
List<(PlatformInt64, Uint8List)>
|
|
||||||
dco_decode_list_record_i_64_list_prim_u_8_strict(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
String? dco_decode_opt_String(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
AnnouncedUser? dco_decode_opt_box_autoadd_announced_user(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
PlatformInt64? dco_decode_opt_box_autoadd_i_64(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
List<Uint8List>? dco_decode_opt_list_list_prim_u_8_strict(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
List<OtherPromotion>? dco_decode_opt_list_other_promotion(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
Uint8List? dco_decode_opt_list_prim_u_8_strict(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
OtherPromotion dco_decode_other_promotion(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
(PlatformInt64, Uint8List) dco_decode_record_i_64_list_prim_u_8_strict(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
(Uint8List, PlatformInt64) dco_decode_record_list_prim_u_8_strict_i_64(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
(String, String) dco_decode_record_string_string(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
RustBackupArchive dco_decode_rust_backup_archive(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
RustBackupIdentity dco_decode_rust_backup_identity(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
RustKeyManager dco_decode_rust_key_manager(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
int dco_decode_u_32(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
int dco_decode_u_8(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
U8Array32 dco_decode_u_8_array_32(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void dco_decode_unit(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
BigInt dco_decode_usize(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
AnyhowException sse_decode_AnyhowException(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
Object sse_decode_DartOpaque(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
Map<PlatformInt64, Uint8List> sse_decode_Map_i_64_list_prim_u_8_strict_None(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
RustStreamSink<String> sse_decode_StreamSink_String_Sse(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
String sse_decode_String(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
AnnouncedUser sse_decode_announced_user(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
BackupPasswordKeys sse_decode_backup_password_keys(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
bool sse_decode_bool(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
AnnouncedUser sse_decode_box_autoadd_announced_user(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
BackupPasswordKeys sse_decode_box_autoadd_backup_password_keys(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
PlatformInt64 sse_decode_box_autoadd_i_64(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
InitConfig sse_decode_box_autoadd_init_config(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
FlutterUserDiscovery sse_decode_flutter_user_discovery(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
PlatformInt64 sse_decode_i_64(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
InitConfig sse_decode_init_config(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
PlatformInt64 sse_decode_isize(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
List<Uint8List> sse_decode_list_list_prim_u_8_strict(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
List<OtherPromotion> sse_decode_list_other_promotion(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
List<int> sse_decode_list_prim_u_8_loose(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
List<(PlatformInt64, Uint8List)>
|
|
||||||
sse_decode_list_record_i_64_list_prim_u_8_strict(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
String? sse_decode_opt_String(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
AnnouncedUser? sse_decode_opt_box_autoadd_announced_user(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
PlatformInt64? sse_decode_opt_box_autoadd_i_64(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
List<Uint8List>? sse_decode_opt_list_list_prim_u_8_strict(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
List<OtherPromotion>? sse_decode_opt_list_other_promotion(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
Uint8List? sse_decode_opt_list_prim_u_8_strict(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
OtherPromotion sse_decode_other_promotion(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
(PlatformInt64, Uint8List) sse_decode_record_i_64_list_prim_u_8_strict(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
(Uint8List, PlatformInt64) sse_decode_record_list_prim_u_8_strict_i_64(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
(String, String) sse_decode_record_string_string(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
RustBackupArchive sse_decode_rust_backup_archive(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
RustBackupIdentity sse_decode_rust_backup_identity(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
RustKeyManager sse_decode_rust_key_manager(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
int sse_decode_u_32(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
int sse_decode_u_8(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
U8Array32 sse_decode_u_8_array_32(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_decode_unit(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
BigInt sse_decode_usize(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
int sse_decode_i_32(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_AnyhowException(
|
|
||||||
AnyhowException self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_DartFn_Inputs__Output_StreamSink_String_Sse_AnyhowException(
|
|
||||||
FutureOr<RustStreamSink<String>> Function() self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void
|
|
||||||
sse_encode_DartFn_Inputs_i_64_Output_opt_box_autoadd_announced_user_AnyhowException(
|
|
||||||
FutureOr<AnnouncedUser?> Function(PlatformInt64) self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void
|
|
||||||
sse_encode_DartFn_Inputs_i_64_Output_opt_list_list_prim_u_8_strict_AnyhowException(
|
|
||||||
FutureOr<List<Uint8List>?> Function(PlatformInt64) self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void
|
|
||||||
sse_encode_DartFn_Inputs_i_64_Output_opt_list_other_promotion_AnyhowException(
|
|
||||||
FutureOr<List<OtherPromotion>?> Function(PlatformInt64) self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void
|
|
||||||
sse_encode_DartFn_Inputs_i_64_Output_opt_list_prim_u_8_strict_AnyhowException(
|
|
||||||
FutureOr<Uint8List?> Function(PlatformInt64) self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void
|
|
||||||
sse_encode_DartFn_Inputs_i_64_announced_user_opt_box_autoadd_i_64_Output_bool_AnyhowException(
|
|
||||||
FutureOr<bool> Function(PlatformInt64, AnnouncedUser, PlatformInt64?) self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void
|
|
||||||
sse_encode_DartFn_Inputs_i_64_i_64_list_prim_u_8_strict_Output_bool_AnyhowException(
|
|
||||||
FutureOr<bool> Function(PlatformInt64, PlatformInt64, Uint8List) self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void
|
|
||||||
sse_encode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_bool_AnyhowException(
|
|
||||||
FutureOr<bool> Function(PlatformInt64, Uint8List) self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void
|
|
||||||
sse_encode_DartFn_Inputs_list_list_prim_u_8_strict_Output_bool_AnyhowException(
|
|
||||||
FutureOr<bool> Function(List<Uint8List>) self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void
|
|
||||||
sse_encode_DartFn_Inputs_list_prim_u_8_strict_Output_opt_list_prim_u_8_strict_AnyhowException(
|
|
||||||
FutureOr<Uint8List?> Function(Uint8List) self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void
|
|
||||||
sse_encode_DartFn_Inputs_list_prim_u_8_strict_list_prim_u_8_strict_list_prim_u_8_strict_Output_bool_AnyhowException(
|
|
||||||
FutureOr<bool> Function(Uint8List, Uint8List, Uint8List) self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_DartFn_Inputs_other_promotion_Output_bool_AnyhowException(
|
|
||||||
FutureOr<bool> Function(OtherPromotion) self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_DartOpaque(Object self, SseSerializer serializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_Map_i_64_list_prim_u_8_strict_None(
|
|
||||||
Map<PlatformInt64, Uint8List> self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_StreamSink_String_Sse(
|
|
||||||
RustStreamSink<String> self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_String(String self, SseSerializer serializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_announced_user(AnnouncedUser self, SseSerializer serializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_backup_password_keys(
|
|
||||||
BackupPasswordKeys self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_bool(bool self, SseSerializer serializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_box_autoadd_announced_user(
|
|
||||||
AnnouncedUser self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_box_autoadd_backup_password_keys(
|
|
||||||
BackupPasswordKeys self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_box_autoadd_i_64(
|
|
||||||
PlatformInt64 self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_box_autoadd_init_config(
|
|
||||||
InitConfig self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_flutter_user_discovery(
|
|
||||||
FlutterUserDiscovery self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_init_config(InitConfig self, SseSerializer serializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_isize(PlatformInt64 self, SseSerializer serializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_list_list_prim_u_8_strict(
|
|
||||||
List<Uint8List> self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_list_other_promotion(
|
|
||||||
List<OtherPromotion> self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_list_prim_u_8_loose(List<int> self, SseSerializer serializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_list_prim_u_8_strict(
|
|
||||||
Uint8List self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_list_record_i_64_list_prim_u_8_strict(
|
|
||||||
List<(PlatformInt64, Uint8List)> self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_opt_String(String? self, SseSerializer serializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_opt_box_autoadd_announced_user(
|
|
||||||
AnnouncedUser? self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_opt_box_autoadd_i_64(
|
|
||||||
PlatformInt64? self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_opt_list_list_prim_u_8_strict(
|
|
||||||
List<Uint8List>? self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_opt_list_other_promotion(
|
|
||||||
List<OtherPromotion>? self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_opt_list_prim_u_8_strict(
|
|
||||||
Uint8List? self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_other_promotion(
|
|
||||||
OtherPromotion self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_record_i_64_list_prim_u_8_strict(
|
|
||||||
(PlatformInt64, Uint8List) self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_record_list_prim_u_8_strict_i_64(
|
|
||||||
(Uint8List, PlatformInt64) self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_record_string_string(
|
|
||||||
(String, String) self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_rust_backup_archive(
|
|
||||||
RustBackupArchive self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_rust_backup_identity(
|
|
||||||
RustBackupIdentity self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_rust_key_manager(
|
|
||||||
RustKeyManager self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_u_32(int self, SseSerializer serializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_u_8(int self, SseSerializer serializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_u_8_array_32(U8Array32 self, SseSerializer serializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_unit(void self, SseSerializer serializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_usize(BigInt self, SseSerializer serializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_i_32(int self, SseSerializer serializer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Section: wire_class
|
|
||||||
|
|
||||||
class RustLibWire implements BaseWire {
|
|
||||||
factory RustLibWire.fromExternalLibrary(ExternalLibrary lib) =>
|
|
||||||
RustLibWire(lib.ffiDynamicLibrary);
|
|
||||||
|
|
||||||
/// Holds the symbol lookup function.
|
|
||||||
final ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
|
|
||||||
_lookup;
|
|
||||||
|
|
||||||
/// The symbols are looked up in [dynamicLibrary].
|
|
||||||
RustLibWire(ffi.DynamicLibrary dynamicLibrary)
|
|
||||||
: _lookup = dynamicLibrary.lookup;
|
|
||||||
}
|
|
||||||
|
|
@ -1,669 +0,0 @@
|
||||||
// This file is automatically generated, so please do not edit it.
|
|
||||||
// @generated by `flutter_rust_bridge`@ 2.12.0.
|
|
||||||
|
|
||||||
// ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field
|
|
||||||
|
|
||||||
// Static analysis wrongly picks the IO variant, thus ignore this
|
|
||||||
// ignore_for_file: argument_type_not_assignable
|
|
||||||
|
|
||||||
import 'bridge.dart';
|
|
||||||
import 'bridge/callbacks.dart';
|
|
||||||
import 'bridge/wrapper/backup.dart';
|
|
||||||
import 'bridge/wrapper/key_manager.dart';
|
|
||||||
import 'bridge/wrapper/user_discovery.dart';
|
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:convert';
|
|
||||||
import 'frb_generated.dart';
|
|
||||||
import 'keys/backup_password_keys.dart';
|
|
||||||
import 'lib.dart';
|
|
||||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated_web.dart';
|
|
||||||
|
|
||||||
abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|
||||||
RustLibApiImplPlatform({
|
|
||||||
required super.handler,
|
|
||||||
required super.wire,
|
|
||||||
required super.generalizedFrbRustBinding,
|
|
||||||
required super.portManager,
|
|
||||||
});
|
|
||||||
|
|
||||||
@protected
|
|
||||||
AnyhowException dco_decode_AnyhowException(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
FutureOr<RustStreamSink<String>> Function()
|
|
||||||
dco_decode_DartFn_Inputs__Output_StreamSink_String_Sse_AnyhowException(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
FutureOr<AnnouncedUser?> Function(PlatformInt64)
|
|
||||||
dco_decode_DartFn_Inputs_i_64_Output_opt_box_autoadd_announced_user_AnyhowException(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
FutureOr<List<Uint8List>?> Function(PlatformInt64)
|
|
||||||
dco_decode_DartFn_Inputs_i_64_Output_opt_list_list_prim_u_8_strict_AnyhowException(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
FutureOr<List<OtherPromotion>?> Function(PlatformInt64)
|
|
||||||
dco_decode_DartFn_Inputs_i_64_Output_opt_list_other_promotion_AnyhowException(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
FutureOr<Uint8List?> Function(PlatformInt64)
|
|
||||||
dco_decode_DartFn_Inputs_i_64_Output_opt_list_prim_u_8_strict_AnyhowException(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
FutureOr<bool> Function(PlatformInt64, AnnouncedUser, PlatformInt64?)
|
|
||||||
dco_decode_DartFn_Inputs_i_64_announced_user_opt_box_autoadd_i_64_Output_bool_AnyhowException(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
FutureOr<bool> Function(PlatformInt64, PlatformInt64, Uint8List)
|
|
||||||
dco_decode_DartFn_Inputs_i_64_i_64_list_prim_u_8_strict_Output_bool_AnyhowException(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
FutureOr<bool> Function(PlatformInt64, Uint8List)
|
|
||||||
dco_decode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_bool_AnyhowException(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
FutureOr<bool> Function(List<Uint8List>)
|
|
||||||
dco_decode_DartFn_Inputs_list_list_prim_u_8_strict_Output_bool_AnyhowException(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
FutureOr<Uint8List?> Function(Uint8List)
|
|
||||||
dco_decode_DartFn_Inputs_list_prim_u_8_strict_Output_opt_list_prim_u_8_strict_AnyhowException(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
FutureOr<bool> Function(Uint8List, Uint8List, Uint8List)
|
|
||||||
dco_decode_DartFn_Inputs_list_prim_u_8_strict_list_prim_u_8_strict_list_prim_u_8_strict_Output_bool_AnyhowException(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
FutureOr<bool> Function(OtherPromotion)
|
|
||||||
dco_decode_DartFn_Inputs_other_promotion_Output_bool_AnyhowException(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
Object dco_decode_DartOpaque(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
Map<PlatformInt64, Uint8List> dco_decode_Map_i_64_list_prim_u_8_strict_None(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
RustStreamSink<String> dco_decode_StreamSink_String_Sse(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
String dco_decode_String(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
AnnouncedUser dco_decode_announced_user(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
BackupPasswordKeys dco_decode_backup_password_keys(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
bool dco_decode_bool(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
AnnouncedUser dco_decode_box_autoadd_announced_user(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
BackupPasswordKeys dco_decode_box_autoadd_backup_password_keys(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
PlatformInt64 dco_decode_box_autoadd_i_64(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
InitConfig dco_decode_box_autoadd_init_config(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
FlutterUserDiscovery dco_decode_flutter_user_discovery(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
PlatformInt64 dco_decode_i_64(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
InitConfig dco_decode_init_config(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
PlatformInt64 dco_decode_isize(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
List<Uint8List> dco_decode_list_list_prim_u_8_strict(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
List<OtherPromotion> dco_decode_list_other_promotion(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
List<int> dco_decode_list_prim_u_8_loose(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
Uint8List dco_decode_list_prim_u_8_strict(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
List<(PlatformInt64, Uint8List)>
|
|
||||||
dco_decode_list_record_i_64_list_prim_u_8_strict(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
String? dco_decode_opt_String(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
AnnouncedUser? dco_decode_opt_box_autoadd_announced_user(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
PlatformInt64? dco_decode_opt_box_autoadd_i_64(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
List<Uint8List>? dco_decode_opt_list_list_prim_u_8_strict(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
List<OtherPromotion>? dco_decode_opt_list_other_promotion(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
Uint8List? dco_decode_opt_list_prim_u_8_strict(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
OtherPromotion dco_decode_other_promotion(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
(PlatformInt64, Uint8List) dco_decode_record_i_64_list_prim_u_8_strict(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
(Uint8List, PlatformInt64) dco_decode_record_list_prim_u_8_strict_i_64(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
(String, String) dco_decode_record_string_string(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
RustBackupArchive dco_decode_rust_backup_archive(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
RustBackupIdentity dco_decode_rust_backup_identity(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
RustKeyManager dco_decode_rust_key_manager(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
int dco_decode_u_32(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
int dco_decode_u_8(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
U8Array32 dco_decode_u_8_array_32(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void dco_decode_unit(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
BigInt dco_decode_usize(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
AnyhowException sse_decode_AnyhowException(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
Object sse_decode_DartOpaque(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
Map<PlatformInt64, Uint8List> sse_decode_Map_i_64_list_prim_u_8_strict_None(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
RustStreamSink<String> sse_decode_StreamSink_String_Sse(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
String sse_decode_String(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
AnnouncedUser sse_decode_announced_user(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
BackupPasswordKeys sse_decode_backup_password_keys(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
bool sse_decode_bool(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
AnnouncedUser sse_decode_box_autoadd_announced_user(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
BackupPasswordKeys sse_decode_box_autoadd_backup_password_keys(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
PlatformInt64 sse_decode_box_autoadd_i_64(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
InitConfig sse_decode_box_autoadd_init_config(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
FlutterUserDiscovery sse_decode_flutter_user_discovery(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
PlatformInt64 sse_decode_i_64(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
InitConfig sse_decode_init_config(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
PlatformInt64 sse_decode_isize(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
List<Uint8List> sse_decode_list_list_prim_u_8_strict(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
List<OtherPromotion> sse_decode_list_other_promotion(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
List<int> sse_decode_list_prim_u_8_loose(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
List<(PlatformInt64, Uint8List)>
|
|
||||||
sse_decode_list_record_i_64_list_prim_u_8_strict(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
String? sse_decode_opt_String(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
AnnouncedUser? sse_decode_opt_box_autoadd_announced_user(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
PlatformInt64? sse_decode_opt_box_autoadd_i_64(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
List<Uint8List>? sse_decode_opt_list_list_prim_u_8_strict(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
List<OtherPromotion>? sse_decode_opt_list_other_promotion(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
Uint8List? sse_decode_opt_list_prim_u_8_strict(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
OtherPromotion sse_decode_other_promotion(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
(PlatformInt64, Uint8List) sse_decode_record_i_64_list_prim_u_8_strict(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
(Uint8List, PlatformInt64) sse_decode_record_list_prim_u_8_strict_i_64(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
(String, String) sse_decode_record_string_string(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
RustBackupArchive sse_decode_rust_backup_archive(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
RustBackupIdentity sse_decode_rust_backup_identity(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
RustKeyManager sse_decode_rust_key_manager(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
int sse_decode_u_32(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
int sse_decode_u_8(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
U8Array32 sse_decode_u_8_array_32(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_decode_unit(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
BigInt sse_decode_usize(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
int sse_decode_i_32(SseDeserializer deserializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_AnyhowException(
|
|
||||||
AnyhowException self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_DartFn_Inputs__Output_StreamSink_String_Sse_AnyhowException(
|
|
||||||
FutureOr<RustStreamSink<String>> Function() self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void
|
|
||||||
sse_encode_DartFn_Inputs_i_64_Output_opt_box_autoadd_announced_user_AnyhowException(
|
|
||||||
FutureOr<AnnouncedUser?> Function(PlatformInt64) self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void
|
|
||||||
sse_encode_DartFn_Inputs_i_64_Output_opt_list_list_prim_u_8_strict_AnyhowException(
|
|
||||||
FutureOr<List<Uint8List>?> Function(PlatformInt64) self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void
|
|
||||||
sse_encode_DartFn_Inputs_i_64_Output_opt_list_other_promotion_AnyhowException(
|
|
||||||
FutureOr<List<OtherPromotion>?> Function(PlatformInt64) self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void
|
|
||||||
sse_encode_DartFn_Inputs_i_64_Output_opt_list_prim_u_8_strict_AnyhowException(
|
|
||||||
FutureOr<Uint8List?> Function(PlatformInt64) self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void
|
|
||||||
sse_encode_DartFn_Inputs_i_64_announced_user_opt_box_autoadd_i_64_Output_bool_AnyhowException(
|
|
||||||
FutureOr<bool> Function(PlatformInt64, AnnouncedUser, PlatformInt64?) self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void
|
|
||||||
sse_encode_DartFn_Inputs_i_64_i_64_list_prim_u_8_strict_Output_bool_AnyhowException(
|
|
||||||
FutureOr<bool> Function(PlatformInt64, PlatformInt64, Uint8List) self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void
|
|
||||||
sse_encode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_bool_AnyhowException(
|
|
||||||
FutureOr<bool> Function(PlatformInt64, Uint8List) self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void
|
|
||||||
sse_encode_DartFn_Inputs_list_list_prim_u_8_strict_Output_bool_AnyhowException(
|
|
||||||
FutureOr<bool> Function(List<Uint8List>) self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void
|
|
||||||
sse_encode_DartFn_Inputs_list_prim_u_8_strict_Output_opt_list_prim_u_8_strict_AnyhowException(
|
|
||||||
FutureOr<Uint8List?> Function(Uint8List) self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void
|
|
||||||
sse_encode_DartFn_Inputs_list_prim_u_8_strict_list_prim_u_8_strict_list_prim_u_8_strict_Output_bool_AnyhowException(
|
|
||||||
FutureOr<bool> Function(Uint8List, Uint8List, Uint8List) self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_DartFn_Inputs_other_promotion_Output_bool_AnyhowException(
|
|
||||||
FutureOr<bool> Function(OtherPromotion) self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_DartOpaque(Object self, SseSerializer serializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_Map_i_64_list_prim_u_8_strict_None(
|
|
||||||
Map<PlatformInt64, Uint8List> self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_StreamSink_String_Sse(
|
|
||||||
RustStreamSink<String> self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_String(String self, SseSerializer serializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_announced_user(AnnouncedUser self, SseSerializer serializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_backup_password_keys(
|
|
||||||
BackupPasswordKeys self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_bool(bool self, SseSerializer serializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_box_autoadd_announced_user(
|
|
||||||
AnnouncedUser self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_box_autoadd_backup_password_keys(
|
|
||||||
BackupPasswordKeys self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_box_autoadd_i_64(
|
|
||||||
PlatformInt64 self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_box_autoadd_init_config(
|
|
||||||
InitConfig self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_flutter_user_discovery(
|
|
||||||
FlutterUserDiscovery self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_init_config(InitConfig self, SseSerializer serializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_isize(PlatformInt64 self, SseSerializer serializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_list_list_prim_u_8_strict(
|
|
||||||
List<Uint8List> self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_list_other_promotion(
|
|
||||||
List<OtherPromotion> self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_list_prim_u_8_loose(List<int> self, SseSerializer serializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_list_prim_u_8_strict(
|
|
||||||
Uint8List self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_list_record_i_64_list_prim_u_8_strict(
|
|
||||||
List<(PlatformInt64, Uint8List)> self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_opt_String(String? self, SseSerializer serializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_opt_box_autoadd_announced_user(
|
|
||||||
AnnouncedUser? self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_opt_box_autoadd_i_64(
|
|
||||||
PlatformInt64? self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_opt_list_list_prim_u_8_strict(
|
|
||||||
List<Uint8List>? self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_opt_list_other_promotion(
|
|
||||||
List<OtherPromotion>? self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_opt_list_prim_u_8_strict(
|
|
||||||
Uint8List? self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_other_promotion(
|
|
||||||
OtherPromotion self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_record_i_64_list_prim_u_8_strict(
|
|
||||||
(PlatformInt64, Uint8List) self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_record_list_prim_u_8_strict_i_64(
|
|
||||||
(Uint8List, PlatformInt64) self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_record_string_string(
|
|
||||||
(String, String) self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_rust_backup_archive(
|
|
||||||
RustBackupArchive self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_rust_backup_identity(
|
|
||||||
RustBackupIdentity self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_rust_key_manager(
|
|
||||||
RustKeyManager self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_u_32(int self, SseSerializer serializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_u_8(int self, SseSerializer serializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_u_8_array_32(U8Array32 self, SseSerializer serializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_unit(void self, SseSerializer serializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_usize(BigInt self, SseSerializer serializer);
|
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_i_32(int self, SseSerializer serializer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Section: wire_class
|
|
||||||
|
|
||||||
class RustLibWire implements BaseWire {
|
|
||||||
RustLibWire.fromExternalLibrary(ExternalLibrary lib);
|
|
||||||
}
|
|
||||||
|
|
||||||
@JS('wasm_bindgen')
|
|
||||||
external RustLibWasmModule get wasmModule;
|
|
||||||
|
|
||||||
@JS()
|
|
||||||
@anonymous
|
|
||||||
extension type RustLibWasmModule._(JSObject _) implements JSObject {}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
// This file is automatically generated, so please do not edit it.
|
|
||||||
// @generated by `flutter_rust_bridge`@ 2.12.0.
|
|
||||||
|
|
||||||
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
|
|
||||||
|
|
||||||
import '../frb_generated.dart';
|
|
||||||
import '../lib.dart';
|
|
||||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
|
||||||
|
|
||||||
class BackupPasswordKeys {
|
|
||||||
final U8Array32 backupId;
|
|
||||||
final U8Array32 encryptionKey;
|
|
||||||
|
|
||||||
const BackupPasswordKeys({
|
|
||||||
required this.backupId,
|
|
||||||
required this.encryptionKey,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => backupId.hashCode ^ encryptionKey.hashCode;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) =>
|
|
||||||
identical(this, other) ||
|
|
||||||
other is BackupPasswordKeys &&
|
|
||||||
runtimeType == other.runtimeType &&
|
|
||||||
backupId == other.backupId &&
|
|
||||||
encryptionKey == other.encryptionKey;
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
// This file is automatically generated, so please do not edit it.
|
|
||||||
// @generated by `flutter_rust_bridge`@ 2.12.0.
|
|
||||||
|
|
||||||
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
|
|
||||||
|
|
||||||
import 'frb_generated.dart';
|
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
|
||||||
|
|
||||||
class U8Array32 extends NonGrowableListView<int> {
|
|
||||||
static const arraySize = 32;
|
|
||||||
|
|
||||||
@internal
|
|
||||||
Uint8List get inner => _inner;
|
|
||||||
final Uint8List _inner;
|
|
||||||
|
|
||||||
U8Array32(this._inner) : assert(_inner.length == arraySize), super(_inner);
|
|
||||||
|
|
||||||
U8Array32.init() : this(Uint8List(arraySize));
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +1,43 @@
|
||||||
import 'dart:async';
|
|
||||||
import 'package:camera/camera.dart';
|
import 'package:camera/camera.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
|
import 'package:twonly/src/model/json/userdata.dart';
|
||||||
|
import 'package:twonly/src/services/api.service.dart';
|
||||||
|
import 'package:twonly/src/services/subscription.service.dart';
|
||||||
|
|
||||||
class AppEnvironment {
|
late ApiService apiService;
|
||||||
static late String cacheDir;
|
|
||||||
static late String supportDir;
|
|
||||||
|
|
||||||
static bool _isInitialized = false;
|
// uses for background notification
|
||||||
|
late TwonlyDB twonlyDB;
|
||||||
|
|
||||||
// will be loaded in the main_camera_controller.dart
|
List<CameraDescription> gCameras = <CameraDescription>[];
|
||||||
static List<CameraDescription> cameras = [];
|
|
||||||
|
|
||||||
static Future<void> init() async {
|
// Cached UserData in the memory. Every time the user data is changed the `updateUserdata` function is called,
|
||||||
if (_isInitialized) return;
|
// which will update this global variable. The variable is set in the main.dart and after the user has registered in the register.view.dart
|
||||||
cacheDir = (await getApplicationCacheDirectory()).path;
|
late UserData gUser;
|
||||||
supportDir = (await getApplicationSupportDirectory()).path;
|
|
||||||
Log.init();
|
|
||||||
_isInitialized = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void initTesting({String? customCacheDir, String? customSupportDir}) {
|
// The following global function can be called from anywhere to update
|
||||||
cacheDir = customCacheDir ?? '/tmp/twonly_cache';
|
// the UI when something changed. The callbacks will be set by
|
||||||
supportDir = customSupportDir ?? '/tmp/twonly_support';
|
// App widget.
|
||||||
_isInitialized = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class AppState {
|
// This callback called by the apiProvider
|
||||||
static bool isAppInBackground = true;
|
void Function({required bool isConnected}) globalCallbackConnectionState =
|
||||||
static bool isInBackgroundTask = false;
|
({
|
||||||
static bool allowErrorTrackingViaSentry = false;
|
required isConnected,
|
||||||
static bool gotMessageFromServer = false;
|
}) {};
|
||||||
static int latestAppVersionId = 116;
|
void Function() globalCallbackAppIsOutdated = () {};
|
||||||
static bool hasCameraPermissions = false;
|
void Function() globalCallbackNewDeviceRegistered = () {};
|
||||||
}
|
void Function(SubscriptionPlan plan) globalCallbackUpdatePlan = (plan) {};
|
||||||
|
|
||||||
|
Map<String, VoidCallback> globalUserDataChangedCallBack = {};
|
||||||
|
|
||||||
|
bool globalIsAppInBackground = true;
|
||||||
|
bool globalIsInBackgroundTask = false;
|
||||||
|
bool globalAllowErrorTrackingViaSentry = false;
|
||||||
|
bool globalGotMessageFromServer = false;
|
||||||
|
|
||||||
|
late String globalApplicationCacheDirectory;
|
||||||
|
late String globalApplicationSupportDirectory;
|
||||||
|
|
||||||
|
final GlobalKey<ScaffoldMessengerState> globalRootScaffoldMessengerKey =
|
||||||
|
GlobalKey<ScaffoldMessengerState>();
|
||||||
|
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
import 'package:get_it/get_it.dart';
|
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
|
||||||
import 'package:twonly/src/services/api.service.dart';
|
|
||||||
import 'package:twonly/src/services/user.service.dart';
|
|
||||||
|
|
||||||
final GetIt locator = GetIt.instance;
|
|
||||||
|
|
||||||
void setupLocator() {
|
|
||||||
locator
|
|
||||||
..registerLazySingleton<UserService>(UserService.new)
|
|
||||||
..registerLazySingleton<ApiService>(ApiService.new)
|
|
||||||
..registerLazySingleton<TwonlyDB>(TwonlyDB.new);
|
|
||||||
}
|
|
||||||
|
|
||||||
UserService get userService => locator<UserService>();
|
|
||||||
ApiService get apiService => locator<ApiService>();
|
|
||||||
TwonlyDB get twonlyDB => locator<TwonlyDB>();
|
|
||||||
197
lib/main.dart
197
lib/main.dart
|
|
@ -1,116 +1,59 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:camera/camera.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:mutex/mutex.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||||
import 'package:twonly/app.dart';
|
import 'package:twonly/app.dart';
|
||||||
import 'package:twonly/core/bridge.dart' as bridge;
|
|
||||||
import 'package:twonly/core/bridge/wrapper/key_manager.dart';
|
|
||||||
import 'package:twonly/core/frb_generated.dart';
|
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/callbacks/callbacks.dart';
|
|
||||||
import 'package:twonly/src/providers/connection.provider.dart';
|
import 'package:twonly/src/providers/connection.provider.dart';
|
||||||
import 'package:twonly/src/providers/image_editor.provider.dart';
|
import 'package:twonly/src/providers/image_editor.provider.dart';
|
||||||
import 'package:twonly/src/providers/purchases.provider.dart';
|
import 'package:twonly/src/providers/purchases.provider.dart';
|
||||||
import 'package:twonly/src/providers/settings.provider.dart';
|
import 'package:twonly/src/providers/settings.provider.dart';
|
||||||
import 'package:twonly/src/services/api/mediafiles/media_background.api.dart';
|
import 'package:twonly/src/services/api.service.dart';
|
||||||
import 'package:twonly/src/services/api/mediafiles/upload.api.dart';
|
import 'package:twonly/src/services/api/mediafiles/download.service.dart';
|
||||||
|
import 'package:twonly/src/services/api/mediafiles/media_background.service.dart';
|
||||||
|
import 'package:twonly/src/services/api/mediafiles/upload.service.dart';
|
||||||
import 'package:twonly/src/services/background/callback_dispatcher.background.dart';
|
import 'package:twonly/src/services/background/callback_dispatcher.background.dart';
|
||||||
import 'package:twonly/src/services/backup.service.dart';
|
import 'package:twonly/src/services/backup/create.backup.dart';
|
||||||
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||||
import 'package:twonly/src/services/memories/memories.service.dart';
|
|
||||||
import 'package:twonly/src/services/migrations.service.dart';
|
|
||||||
import 'package:twonly/src/services/notifications/fcm.notifications.dart';
|
import 'package:twonly/src/services/notifications/fcm.notifications.dart';
|
||||||
import 'package:twonly/src/services/notifications/setup.notifications.dart';
|
import 'package:twonly/src/services/notifications/setup.notifications.dart';
|
||||||
import 'package:twonly/src/services/user_discovery.service.dart';
|
|
||||||
import 'package:twonly/src/utils/avatars.dart';
|
import 'package:twonly/src/utils/avatars.dart';
|
||||||
import 'package:twonly/src/utils/exclusive_access.utils.dart';
|
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/startup_guard.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
|
|
||||||
final _initMutex = Mutex();
|
|
||||||
|
|
||||||
/// This function is used to initialized the absolute minimum so it
|
|
||||||
/// can also be used by the backend without the UI was loaded.
|
|
||||||
Future<bool> twonlyMinimumInitialization() async {
|
|
||||||
Log.info('twonlyMinimumInitialization: called');
|
|
||||||
final hasStorageError = await exclusiveAccess(
|
|
||||||
lockName: 'init',
|
|
||||||
mutex: _initMutex,
|
|
||||||
action: () async {
|
|
||||||
Log.info('twonlyMinimumInitialization: started');
|
|
||||||
setupLocator();
|
|
||||||
|
|
||||||
Log.info('twonlyMinimumInitialization: RustLib.init()');
|
|
||||||
await RustLib.init();
|
|
||||||
|
|
||||||
Log.info('twonlyMinimumInitialization: initFlutterCallbacksForRust()');
|
|
||||||
await initFlutterCallbacksForRust();
|
|
||||||
|
|
||||||
Log.info('twonlyMinimumInitialization: bridge.initializeTwonlyFlutter()');
|
|
||||||
try {
|
|
||||||
await bridge.initializeTwonlyFlutter(
|
|
||||||
config: bridge.InitConfig(
|
|
||||||
databaseDir: AppEnvironment.supportDir,
|
|
||||||
dataDir: AppEnvironment.supportDir,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
Log.error(e);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
Log.info('twonlyMinimumInitialization: finished');
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return hasStorageError;
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
final binding = SentryWidgetsFlutterBinding.ensureInitialized();
|
SentryWidgetsFlutterBinding.ensureInitialized();
|
||||||
await AppEnvironment.init();
|
|
||||||
final stopwatch = Stopwatch()..start();
|
|
||||||
|
|
||||||
unawaited(StartupGuard.markAppStartup());
|
globalApplicationCacheDirectory = (await getApplicationCacheDirectory()).path;
|
||||||
|
globalApplicationSupportDirectory =
|
||||||
|
(await getApplicationSupportDirectory()).path;
|
||||||
|
|
||||||
var storageError = await twonlyMinimumInitialization();
|
initLogger();
|
||||||
await initFCMService();
|
await initFCMService();
|
||||||
|
|
||||||
var userExists = false;
|
var user = await getUser();
|
||||||
|
|
||||||
var recoveryPossible = false;
|
if (Platform.isIOS && user != null) {
|
||||||
|
final db = File('$globalApplicationSupportDirectory/twonly.sqlite');
|
||||||
if (!storageError) {
|
if (!db.existsSync()) {
|
||||||
try {
|
Log.error('[twonly] IOS: App was removed and then reinstalled again...');
|
||||||
userExists = await userService.tryInit();
|
await const FlutterSecureStorage().deleteAll();
|
||||||
} catch (e) {
|
user = await getUser();
|
||||||
Log.error('Failed to initialize user session due to storage error: $e');
|
|
||||||
storageError = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!userExists && !storageError) {
|
if (user != null) {
|
||||||
try {
|
gUser = user;
|
||||||
final userId = await RustKeyManager.getUserId();
|
|
||||||
if (userId != null) {
|
|
||||||
recoveryPossible = true;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
Log.error('Could not check KeyManager userId for iOS recovery: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.info('User loaded.');
|
if (user.allowErrorTrackingViaSentry) {
|
||||||
|
globalAllowErrorTrackingViaSentry = true;
|
||||||
final settingsController = SettingsChangeProvider()..loadSettings();
|
|
||||||
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
|
||||||
unawaited(initFileDownloader());
|
|
||||||
|
|
||||||
if (userExists) {
|
|
||||||
if (userService.currentUser.allowErrorTrackingViaSentry) {
|
|
||||||
AppState.allowErrorTrackingViaSentry = true;
|
|
||||||
await SentryFlutter.init(
|
await SentryFlutter.init(
|
||||||
(options) => options
|
(options) => options
|
||||||
..dsn =
|
..dsn =
|
||||||
|
|
@ -120,23 +63,52 @@ void main() async {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await runMigrations();
|
unawaited(performTwonlySafeBackup());
|
||||||
// We wait for the first frame to be rendered before starting heavy tasks.
|
unawaited(initializeBackgroundTaskManager());
|
||||||
// This ensures the splash screen is dismissed on Android immediately.
|
} else {
|
||||||
binding.addPostFrameCallback((_) async {
|
Log.info('User is not yet register. Ensure all local data is removed.');
|
||||||
await Future.delayed(const Duration(seconds: 1));
|
await deleteLocalUserData();
|
||||||
unawaited(postStartupTasks());
|
|
||||||
unawaited(apiService.connect());
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await apiService.listenToNetworkChanges();
|
final settingsController = SettingsChangeProvider();
|
||||||
|
|
||||||
stopwatch.stop();
|
await settingsController.loadSettings();
|
||||||
|
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
||||||
|
|
||||||
Log.info(
|
unawaited(setupPushNotification());
|
||||||
'Initialization finished after ${stopwatch.elapsed}. Calling runApp...',
|
|
||||||
);
|
gCameras = await availableCameras();
|
||||||
|
|
||||||
|
apiService = ApiService();
|
||||||
|
twonlyDB = TwonlyDB();
|
||||||
|
|
||||||
|
if (user != null) {
|
||||||
|
if (gUser.appVersion < 90) {
|
||||||
|
// BUG: Requested media files for reupload where not reuploaded because the wrong state...
|
||||||
|
await twonlyDB.mediaFilesDao.updateAllRetransmissionUploadingState();
|
||||||
|
await updateUserdata((u) {
|
||||||
|
u.appVersion = 90;
|
||||||
|
return u;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (gUser.appVersion < 91) {
|
||||||
|
// BUG: Requested media files for reupload where not reuploaded because the wrong state...
|
||||||
|
await makeMigrationToVersion91();
|
||||||
|
await updateUserdata((u) {
|
||||||
|
u.appVersion = 91;
|
||||||
|
return u;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await twonlyDB.messagesDao.purgeMessageTable();
|
||||||
|
await twonlyDB.receiptsDao.purgeReceivedReceipts();
|
||||||
|
unawaited(MediaFileService.purgeTempFolder());
|
||||||
|
|
||||||
|
await initFileDownloader();
|
||||||
|
unawaited(finishStartedPreprocessing());
|
||||||
|
|
||||||
|
unawaited(createPushAvatars());
|
||||||
|
|
||||||
runApp(
|
runApp(
|
||||||
MultiProvider(
|
MultiProvider(
|
||||||
|
|
@ -146,34 +118,7 @@ void main() async {
|
||||||
ChangeNotifierProvider(create: (_) => ImageEditorProvider()),
|
ChangeNotifierProvider(create: (_) => ImageEditorProvider()),
|
||||||
ChangeNotifierProvider(create: (_) => PurchasesProvider()),
|
ChangeNotifierProvider(create: (_) => PurchasesProvider()),
|
||||||
],
|
],
|
||||||
child: App(
|
child: const App(),
|
||||||
storageError: storageError,
|
|
||||||
recoveryPossible: recoveryPossible,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> postStartupTasks() async {
|
|
||||||
Log.info('Post startup started.');
|
|
||||||
unawaited(MemoriesService.prewarmCache());
|
|
||||||
|
|
||||||
// 1. Immediate background cleanup (Non-blocking for UI)
|
|
||||||
await twonlyDB.messagesDao.purgeMessageTable();
|
|
||||||
unawaited(twonlyDB.receiptsDao.purgeReceivedReceipts());
|
|
||||||
unawaited(MediaFileService.purgeTempFolder());
|
|
||||||
|
|
||||||
// 2. Service initializations
|
|
||||||
unawaited(setupPushNotification());
|
|
||||||
unawaited(finishStartedPreprocessing());
|
|
||||||
unawaited(createPushAvatars());
|
|
||||||
|
|
||||||
unawaited(UserDiscoveryService.verifyInitializationOnStartup());
|
|
||||||
|
|
||||||
await Future.delayed(const Duration(seconds: 10));
|
|
||||||
unawaited(initializeBackgroundTaskManager());
|
|
||||||
// 3. Delayed tasks (Wait for app to settle)
|
|
||||||
await Future.delayed(const Duration(minutes: 2));
|
|
||||||
unawaited(BackupService.makeBackup());
|
|
||||||
unawaited(cleanLogFile());
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
import 'package:twonly/core/bridge/callbacks.dart';
|
|
||||||
import 'package:twonly/src/callbacks/logging.callbacks.dart';
|
|
||||||
import 'package:twonly/src/callbacks/user_discovery.callbacks.dart';
|
|
||||||
|
|
||||||
Future<void> initFlutterCallbacksForRust() async {
|
|
||||||
await initFlutterCallbacks(
|
|
||||||
loggingGetStreamSink: LoggingCallbacks.getStreamSink,
|
|
||||||
userDiscoverySetShares: UserDiscoveryCallbacks.setShares,
|
|
||||||
userDiscoveryGetShareForContact:
|
|
||||||
UserDiscoveryCallbacks.userDiscoveryGetShareForContact,
|
|
||||||
userDiscoveryPushOwnPromotionAndClearOldVersion:
|
|
||||||
UserDiscoveryCallbacks.userDiscoveryPushOwnPromotionAndClearOldVersion,
|
|
||||||
userDiscoveryPushNewUserRelation:
|
|
||||||
UserDiscoveryCallbacks.pushNewUserRelation,
|
|
||||||
userDiscoveryGetOwnPromotionsAfterVersion:
|
|
||||||
UserDiscoveryCallbacks.getOwnPromotionsAfterVersion,
|
|
||||||
userDiscoveryStoreOtherPromotion:
|
|
||||||
UserDiscoveryCallbacks.storeOtherPromotion,
|
|
||||||
userDiscoveryGetOtherPromotionsByPublicId:
|
|
||||||
UserDiscoveryCallbacks.getOtherPromotionsByPublicId,
|
|
||||||
userDiscoveryGetAnnouncedUserByPublicId:
|
|
||||||
UserDiscoveryCallbacks.getAnnouncedUserByPublicId,
|
|
||||||
userDiscoveryGetContactVersion: UserDiscoveryCallbacks.getContactVersion,
|
|
||||||
userDiscoverySetContactVersion: UserDiscoveryCallbacks.setContactVersion,
|
|
||||||
userDiscoverySignData: UserDiscoveryCallbacks.signData,
|
|
||||||
userDiscoveryVerifySignature: UserDiscoveryCallbacks.verifySignature,
|
|
||||||
userDiscoveryVerifyStoredPubkey: UserDiscoveryCallbacks.verifyStoredPubKey,
|
|
||||||
userDiscoveryGetContactPromotion:
|
|
||||||
UserDiscoveryCallbacks.getContactPromotion,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:io';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
|
||||||
import 'package:twonly/src/utils/log.dart';
|
|
||||||
|
|
||||||
class LoggingCallbacks {
|
|
||||||
static Future<RustStreamSink<String>> getStreamSink() async {
|
|
||||||
final dartLogSink = RustStreamSink<String>();
|
|
||||||
|
|
||||||
Timer.periodic(const Duration(milliseconds: 100), (timer) {
|
|
||||||
try {
|
|
||||||
dartLogSink.stream.listen(
|
|
||||||
(log) {
|
|
||||||
if (log.contains('INFO ')) {
|
|
||||||
Log.info(log.split('INFO ')[1]);
|
|
||||||
} else if (log.contains('DEBUG ')) {
|
|
||||||
Log.info(log.split('DEBUG ')[1]);
|
|
||||||
} else if (kDebugMode && !Platform.environment.containsKey('FLUTTER_TEST')) {
|
|
||||||
// ignore: avoid_print
|
|
||||||
print(log);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
timer.cancel();
|
|
||||||
} catch (e) {
|
|
||||||
// stream not yet initialized
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return dartLogSink;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,326 +0,0 @@
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:drift/drift.dart';
|
|
||||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'
|
|
||||||
show Curve, IdentityKey;
|
|
||||||
// ignore: implementation_imports
|
|
||||||
import 'package:libsignal_protocol_dart/src/ecc/ed25519.dart';
|
|
||||||
import 'package:twonly/core/bridge.dart';
|
|
||||||
import 'package:twonly/locator.dart';
|
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
|
||||||
import 'package:twonly/src/services/signal/identity.signal.dart';
|
|
||||||
import 'package:twonly/src/services/signal/session.signal.dart';
|
|
||||||
import 'package:twonly/src/utils/log.dart';
|
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
|
||||||
|
|
||||||
class UserDiscoveryCallbacks {
|
|
||||||
static Future<Uint8List?> signData(
|
|
||||||
Uint8List inputData,
|
|
||||||
) async {
|
|
||||||
Log.info('UserDiscoveryCallbacks: signData started');
|
|
||||||
var privKey = (await getSignalIdentityKeyPair())?.getPrivateKey();
|
|
||||||
if (privKey == null) {
|
|
||||||
Log.error('UserDiscoveryCallbacks: signData failed, privKey is null');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
final random = getRandomUint8List(32);
|
|
||||||
final signature = sign(
|
|
||||||
privKey.serialize(),
|
|
||||||
inputData,
|
|
||||||
random,
|
|
||||||
);
|
|
||||||
privKey = null;
|
|
||||||
Log.info('UserDiscoveryCallbacks: signData finished');
|
|
||||||
return signature;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<bool> verifySignature(
|
|
||||||
Uint8List inputData,
|
|
||||||
Uint8List pubKey,
|
|
||||||
Uint8List signature,
|
|
||||||
) async {
|
|
||||||
try {
|
|
||||||
return Curve.verifySignature(
|
|
||||||
IdentityKey.fromBytes(pubKey, 0).publicKey,
|
|
||||||
inputData,
|
|
||||||
signature,
|
|
||||||
);
|
|
||||||
} catch (_) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<bool> verifyStoredPubKey(
|
|
||||||
int contactId,
|
|
||||||
Uint8List pubKey,
|
|
||||||
) async {
|
|
||||||
try {
|
|
||||||
final storedPublicKey = await getPublicKeyFromContact(contactId);
|
|
||||||
if (storedPublicKey != null) {
|
|
||||||
return storedPublicKey.equals(pubKey);
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} catch (_) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<bool> setShares(List<Uint8List> shares) async {
|
|
||||||
try {
|
|
||||||
// First remove all old shares then insert all the new shares
|
|
||||||
await twonlyDB.delete(twonlyDB.userDiscoveryShares).go();
|
|
||||||
await twonlyDB.batch((b) {
|
|
||||||
b.insertAll(
|
|
||||||
twonlyDB.userDiscoveryShares,
|
|
||||||
shares
|
|
||||||
.map((s) => UserDiscoverySharesCompanion(share: Value(s)))
|
|
||||||
.toList(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
} catch (e) {
|
|
||||||
Log.error(e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<Uint8List?> userDiscoveryGetShareForContact(
|
|
||||||
int contactId,
|
|
||||||
) async {
|
|
||||||
return twonlyDB.transaction(() async {
|
|
||||||
// 1. Check if this contact already has a share assigned
|
|
||||||
final existing =
|
|
||||||
await (twonlyDB.select(twonlyDB.userDiscoveryShares)
|
|
||||||
..where((tbl) => tbl.contactId.equals(contactId))
|
|
||||||
..limit(1))
|
|
||||||
.getSingleOrNull();
|
|
||||||
|
|
||||||
if (existing != null) {
|
|
||||||
return existing.share;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. No share found. Find an available one (where contactId is null)
|
|
||||||
final available =
|
|
||||||
await (twonlyDB.select(twonlyDB.userDiscoveryShares)
|
|
||||||
..where((tbl) => tbl.contactId.isNull())
|
|
||||||
..limit(1))
|
|
||||||
.getSingleOrNull();
|
|
||||||
|
|
||||||
if (available != null) {
|
|
||||||
// 3. Assign the contactId to this available share
|
|
||||||
await (twonlyDB.update(
|
|
||||||
twonlyDB.userDiscoveryShares,
|
|
||||||
)..where((tbl) => tbl.shareId.equals(available.shareId))).write(
|
|
||||||
UserDiscoverySharesCompanion(
|
|
||||||
contactId: Value(contactId),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return available.share;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null; // 4. No existing or available shares found
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<bool> userDiscoveryPushOwnPromotionAndClearOldVersion(
|
|
||||||
int contactId,
|
|
||||||
int version,
|
|
||||||
Uint8List promotion,
|
|
||||||
) async {
|
|
||||||
try {
|
|
||||||
// Old promotions from this users should be removed...
|
|
||||||
await (twonlyDB.update(
|
|
||||||
twonlyDB.userDiscoveryOwnPromotions,
|
|
||||||
)..where((t) => t.contactId.equals(contactId))).write(
|
|
||||||
UserDiscoveryOwnPromotionsCompanion(promotion: Value(Uint8List(0))),
|
|
||||||
);
|
|
||||||
await twonlyDB
|
|
||||||
.into(twonlyDB.userDiscoveryOwnPromotions)
|
|
||||||
.insert(
|
|
||||||
UserDiscoveryOwnPromotionsCompanion.insert(
|
|
||||||
contactId: contactId,
|
|
||||||
promotion: promotion,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
} catch (e) {
|
|
||||||
Log.error(e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<List<Uint8List>> getOwnPromotionsAfterVersion(
|
|
||||||
int version,
|
|
||||||
) async {
|
|
||||||
final query = twonlyDB.select(twonlyDB.userDiscoveryOwnPromotions)
|
|
||||||
..where((tbl) => tbl.versionId.isBiggerThanValue(version));
|
|
||||||
|
|
||||||
final rows = await query.get();
|
|
||||||
return rows.map((r) => r.promotion).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<bool> storeOtherPromotion(
|
|
||||||
OtherPromotion promotion,
|
|
||||||
) async {
|
|
||||||
try {
|
|
||||||
await twonlyDB
|
|
||||||
.into(twonlyDB.userDiscoveryOtherPromotions)
|
|
||||||
.insertOnConflictUpdate(
|
|
||||||
UserDiscoveryOtherPromotionsCompanion(
|
|
||||||
promotionId: Value(promotion.promotionId),
|
|
||||||
publicId: Value(promotion.publicId),
|
|
||||||
fromContactId: Value(promotion.fromContactId),
|
|
||||||
threshold: Value(promotion.threshold),
|
|
||||||
announcementShare: Value(promotion.announcementShare),
|
|
||||||
publicKeyVerifiedTimestamp: Value(
|
|
||||||
promotion.publicKeyVerifiedTimestamp == null
|
|
||||||
? null
|
|
||||||
: DateTime.fromMillisecondsSinceEpoch(
|
|
||||||
promotion.publicKeyVerifiedTimestamp!,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
} catch (e) {
|
|
||||||
Log.error(e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<List<OtherPromotion>> getOtherPromotionsByPublicId(
|
|
||||||
int publicId,
|
|
||||||
) async {
|
|
||||||
final rows = await (twonlyDB.select(
|
|
||||||
twonlyDB.userDiscoveryOtherPromotions,
|
|
||||||
)..where((tbl) => tbl.publicId.equals(publicId))).get();
|
|
||||||
|
|
||||||
return rows
|
|
||||||
.map(
|
|
||||||
(row) => OtherPromotion(
|
|
||||||
promotionId: row.promotionId,
|
|
||||||
publicId: row.publicId,
|
|
||||||
fromContactId: row.fromContactId,
|
|
||||||
threshold: row.threshold,
|
|
||||||
announcementShare: row.announcementShare,
|
|
||||||
publicKeyVerifiedTimestamp:
|
|
||||||
row.publicKeyVerifiedTimestamp?.millisecondsSinceEpoch,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<AnnouncedUser?> getAnnouncedUserByPublicId(
|
|
||||||
int publicId,
|
|
||||||
) async {
|
|
||||||
final row = await (twonlyDB.select(
|
|
||||||
twonlyDB.userDiscoveryAnnouncedUsers,
|
|
||||||
)..where((tbl) => tbl.publicId.equals(publicId))).getSingleOrNull();
|
|
||||||
if (row == null) return null;
|
|
||||||
return AnnouncedUser(
|
|
||||||
userId: row.announcedUserId,
|
|
||||||
publicKey: row.announcedPublicKey,
|
|
||||||
publicId: row.publicId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<bool> pushNewUserRelation(
|
|
||||||
int fromContactId,
|
|
||||||
AnnouncedUser announcedUser,
|
|
||||||
int? publicKeyVerifiedTimestamp,
|
|
||||||
) async {
|
|
||||||
try {
|
|
||||||
await twonlyDB.transaction(() async {
|
|
||||||
// 1. Ensure the user exists in the AnnouncedUsers table
|
|
||||||
await twonlyDB
|
|
||||||
.into(twonlyDB.userDiscoveryAnnouncedUsers)
|
|
||||||
.insertOnConflictUpdate(
|
|
||||||
UserDiscoveryAnnouncedUsersCompanion(
|
|
||||||
announcedUserId: Value(announcedUser.userId),
|
|
||||||
announcedPublicKey: Value(announcedUser.publicKey),
|
|
||||||
publicId: Value(announcedUser.publicId),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// 2. Insert or update the relation
|
|
||||||
await twonlyDB
|
|
||||||
.into(twonlyDB.userDiscoveryUserRelations)
|
|
||||||
.insertOnConflictUpdate(
|
|
||||||
UserDiscoveryUserRelationsCompanion.insert(
|
|
||||||
announcedUserId: announcedUser.userId,
|
|
||||||
fromContactId: fromContactId,
|
|
||||||
publicKeyVerifiedTimestamp: Value(
|
|
||||||
publicKeyVerifiedTimestamp != null
|
|
||||||
? DateTime.fromMillisecondsSinceEpoch(
|
|
||||||
publicKeyVerifiedTimestamp,
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
} catch (e) {
|
|
||||||
Log.error(e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// static Future<Map<AnnouncedUser, List<(int, DateTime?)>>>
|
|
||||||
// getAllAnnouncedUsers() async {
|
|
||||||
// final query = twonlyDB.select(twonlyDB.userDiscoveryAnnouncedUsers).join([
|
|
||||||
// innerJoin(
|
|
||||||
// twonlyDB.userDiscoveryUserRelations,
|
|
||||||
// twonlyDB.userDiscoveryUserRelations.announcedUserId.equalsExp(
|
|
||||||
// twonlyDB.userDiscoveryAnnouncedUsers.announcedUserId,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ]);
|
|
||||||
|
|
||||||
// final results = await query.get();
|
|
||||||
// final map = <UserDiscoveryAnnouncedUser, List<(int, DateTime?)>>{};
|
|
||||||
|
|
||||||
// for (final row in results) {
|
|
||||||
// final user = row.readTable(twonlyDB.userDiscoveryAnnouncedUsers);
|
|
||||||
// final relation = row.readTable(twonlyDB.userDiscoveryUserRelations);
|
|
||||||
|
|
||||||
// map.putIfAbsent(user, () => []).add(
|
|
||||||
// (relation.fromContactId, relation.publicKeyVerifiedTimestamp),
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return map;
|
|
||||||
// }
|
|
||||||
|
|
||||||
static Future<Uint8List?> getContactVersion(int contactId) async {
|
|
||||||
final row = await (twonlyDB.select(
|
|
||||||
twonlyDB.contacts,
|
|
||||||
)..where((tbl) => tbl.userId.equals(contactId))).getSingleOrNull();
|
|
||||||
return row?.userDiscoveryVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<bool> setContactVersion(int contactId, Uint8List update) async {
|
|
||||||
try {
|
|
||||||
await (twonlyDB.update(twonlyDB.contacts)
|
|
||||||
..where((tbl) => tbl.userId.equals(contactId)))
|
|
||||||
.write(ContactsCompanion(userDiscoveryVersion: Value(update)));
|
|
||||||
return true;
|
|
||||||
} catch (e) {
|
|
||||||
Log.error(e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<Uint8List?> getContactPromotion(int contactId) async {
|
|
||||||
try {
|
|
||||||
final row = await (twonlyDB.select(
|
|
||||||
twonlyDB.userDiscoveryOwnPromotions,
|
|
||||||
)..where((tbl) => tbl.contactId.equals(contactId))).getSingleOrNull();
|
|
||||||
return row?.promotion;
|
|
||||||
} catch (e) {
|
|
||||||
Log.error(e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
class KeyValueKeys {
|
class KeyValueKeys {
|
||||||
static const String lastPeriodicTaskExecution =
|
static const String lastPeriodicTaskExecution =
|
||||||
'last_periodic_task_execution';
|
'last_periodic_task_execution';
|
||||||
static const String currentBackupState = 'current_backup_state';
|
|
||||||
static const String backupRecoveryState = 'backup_recovery_state';
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ class Routes {
|
||||||
static const String settingsAccount = '/settings/account';
|
static const String settingsAccount = '/settings/account';
|
||||||
static const String settingsSubscription = '/settings/subscription';
|
static const String settingsSubscription = '/settings/subscription';
|
||||||
static const String settingsBackup = '/settings/backup';
|
static const String settingsBackup = '/settings/backup';
|
||||||
|
static const String settingsBackupServer = '/settings/backup/server';
|
||||||
static const String settingsBackupRecovery = '/settings/backup/recovery';
|
static const String settingsBackupRecovery = '/settings/backup/recovery';
|
||||||
static const String settingsBackupSetup = '/settings/backup/setup';
|
static const String settingsBackupSetup = '/settings/backup/setup';
|
||||||
static const String settingsAppearance = '/settings/appearance';
|
static const String settingsAppearance = '/settings/appearance';
|
||||||
|
|
@ -33,16 +34,9 @@ class Routes {
|
||||||
static const String settingsPrivacy = '/settings/privacy';
|
static const String settingsPrivacy = '/settings/privacy';
|
||||||
static const String settingsPrivacyBlockUsers =
|
static const String settingsPrivacyBlockUsers =
|
||||||
'/settings/privacy/block_users';
|
'/settings/privacy/block_users';
|
||||||
static const String settingsPrivacyUserDiscovery =
|
|
||||||
'/settings/privacy/user_discovery';
|
|
||||||
static const String settingsPrivacyProfileSelection =
|
|
||||||
'/settings/privacy/profile_selection';
|
|
||||||
static const String settingsNotification = '/settings/notification';
|
static const String settingsNotification = '/settings/notification';
|
||||||
static const String settingsStorage = '/settings/storage_data';
|
static const String settingsStorage = '/settings/storage_data';
|
||||||
static const String settingsStorageManage = '/settings/storage_data/manage';
|
|
||||||
static const String settingsStorageImport = '/settings/storage_data/import';
|
static const String settingsStorageImport = '/settings/storage_data/import';
|
||||||
static const String settingsStorageImportGallery =
|
|
||||||
'/settings/storage_data/import_gallery';
|
|
||||||
static const String settingsStorageExport = '/settings/storage_data/export';
|
static const String settingsStorageExport = '/settings/storage_data/export';
|
||||||
static const String settingsHelp = '/settings/help';
|
static const String settingsHelp = '/settings/help';
|
||||||
static const String settingsHelpFaq = '/settings/help/faq';
|
static const String settingsHelpFaq = '/settings/help/faq';
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,11 @@
|
||||||
class SecureStorageKeys {
|
class SecureStorageKeys {
|
||||||
@Deprecated('Use the secure storage in rust')
|
|
||||||
static const String signalIdentity = 'signal_identity';
|
static const String signalIdentity = 'signal_identity';
|
||||||
@Deprecated('Use the secure storage in rust')
|
|
||||||
static const String signalSignedPreKey = 'signed_pre_key_store';
|
static const String signalSignedPreKey = 'signed_pre_key_store';
|
||||||
@Deprecated('Use the login token')
|
|
||||||
static const String apiAuthToken = 'api_auth_token';
|
static const String apiAuthToken = 'api_auth_token';
|
||||||
|
static const String googleFcm = 'google_fcm';
|
||||||
@Deprecated('Use user.json file')
|
|
||||||
static const String userData = 'userData';
|
static const String userData = 'userData';
|
||||||
|
static const String twonlySafeLastBackupHash = 'twonly_safe_last_backup_hash';
|
||||||
|
|
||||||
// Not required for backup...
|
|
||||||
static const String receivingPushKeys = 'push_keys_receiving';
|
static const String receivingPushKeys = 'push_keys_receiving';
|
||||||
static const String sendingPushKeys = 'push_keys_sending';
|
static const String sendingPushKeys = 'push_keys_sending';
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/tables/contacts.table.dart';
|
import 'package:twonly/src/database/tables/contacts.table.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
|
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
|
||||||
|
|
@ -7,7 +7,7 @@ import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
part 'contacts.dao.g.dart';
|
part 'contacts.dao.g.dart';
|
||||||
|
|
||||||
@DriftAccessor(tables: [Contacts, KeyVerifications])
|
@DriftAccessor(tables: [Contacts])
|
||||||
class ContactsDao extends DatabaseAccessor<TwonlyDB> with _$ContactsDaoMixin {
|
class ContactsDao extends DatabaseAccessor<TwonlyDB> with _$ContactsDaoMixin {
|
||||||
// this constructor is required so that the main database can create an instance
|
// this constructor is required so that the main database can create an instance
|
||||||
// of this object.
|
// of this object.
|
||||||
|
|
@ -103,13 +103,6 @@ class ContactsDao extends DatabaseAccessor<TwonlyDB> with _$ContactsDaoMixin {
|
||||||
return select(contacts).get();
|
return select(contacts).get();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> getContactsCount() async {
|
|
||||||
final count = contacts.userId.count();
|
|
||||||
final query = selectOnly(contacts)..addColumns([count]);
|
|
||||||
final result = await query.map((row) => row.read(count)).getSingle();
|
|
||||||
return result ?? 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream<int?> watchContactsBlocked() {
|
Stream<int?> watchContactsBlocked() {
|
||||||
final count = contacts.userId.count();
|
final count = contacts.userId.count();
|
||||||
final query = selectOnly(contacts)
|
final query = selectOnly(contacts)
|
||||||
|
|
@ -141,44 +134,6 @@ class ContactsDao extends DatabaseAccessor<TwonlyDB> with _$ContactsDaoMixin {
|
||||||
.watch();
|
.watch();
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<List<Contact>> watchContactsAnnouncedViaUserDiscovery() {
|
|
||||||
return (select(contacts)..where((t) {
|
|
||||||
var expr =
|
|
||||||
t.userDiscoveryVersion.isNotNull() &
|
|
||||||
t.userDiscoveryExcluded.equals(false) &
|
|
||||||
t.accountDeleted.equals(false) &
|
|
||||||
t.mediaSendCounter.isBiggerOrEqualValue(
|
|
||||||
userService.currentUser.requiredSendImages,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (userService.currentUser.userDiscoveryRequiresManualApproval) {
|
|
||||||
expr = expr & t.userDiscoveryManualApproved.equals(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return expr;
|
|
||||||
}))
|
|
||||||
.watch();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<Contact>> getContactsAnnouncedViaUserDiscovery() async {
|
|
||||||
return (select(contacts)..where((t) {
|
|
||||||
var expr =
|
|
||||||
t.userDiscoveryVersion.isNotNull() &
|
|
||||||
t.userDiscoveryExcluded.equals(false) &
|
|
||||||
t.accountDeleted.equals(false) &
|
|
||||||
t.mediaSendCounter.isBiggerOrEqualValue(
|
|
||||||
userService.currentUser.requiredSendImages,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (userService.currentUser.userDiscoveryRequiresManualApproval) {
|
|
||||||
expr = expr & t.userDiscoveryManualApproved.equals(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return expr;
|
|
||||||
}))
|
|
||||||
.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream<List<Contact>> watchAllContacts() {
|
Stream<List<Contact>> watchAllContacts() {
|
||||||
return select(contacts).watch();
|
return select(contacts).watch();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,6 @@ part of 'contacts.dao.dart';
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
mixin _$ContactsDaoMixin on DatabaseAccessor<TwonlyDB> {
|
mixin _$ContactsDaoMixin on DatabaseAccessor<TwonlyDB> {
|
||||||
$ContactsTable get contacts => attachedDatabase.contacts;
|
$ContactsTable get contacts => attachedDatabase.contacts;
|
||||||
$KeyVerificationsTable get keyVerifications =>
|
|
||||||
attachedDatabase.keyVerifications;
|
|
||||||
ContactsDaoManager get managers => ContactsDaoManager(this);
|
ContactsDaoManager get managers => ContactsDaoManager(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -15,9 +13,4 @@ class ContactsDaoManager {
|
||||||
ContactsDaoManager(this._db);
|
ContactsDaoManager(this._db);
|
||||||
$$ContactsTableTableManager get contacts =>
|
$$ContactsTableTableManager get contacts =>
|
||||||
$$ContactsTableTableManager(_db.attachedDatabase, _db.contacts);
|
$$ContactsTableTableManager(_db.attachedDatabase, _db.contacts);
|
||||||
$$KeyVerificationsTableTableManager get keyVerifications =>
|
|
||||||
$$KeyVerificationsTableTableManager(
|
|
||||||
_db.attachedDatabase,
|
|
||||||
_db.keyVerifications,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:hashlib/random.dart';
|
import 'package:hashlib/random.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
|
||||||
import 'package:twonly/src/database/tables/groups.table.dart';
|
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/services/flame.service.dart';
|
import 'package:twonly/src/services/flame.service.dart';
|
||||||
|
|
@ -114,10 +113,7 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
|
||||||
int contactId,
|
int contactId,
|
||||||
GroupsCompanion group,
|
GroupsCompanion group,
|
||||||
) async {
|
) async {
|
||||||
final groupIdDirectChat = getUUIDforDirectChat(
|
final groupIdDirectChat = getUUIDforDirectChat(contactId, gUser.userId);
|
||||||
contactId,
|
|
||||||
userService.currentUser.userId,
|
|
||||||
);
|
|
||||||
final insertGroup = group.copyWith(
|
final insertGroup = group.copyWith(
|
||||||
groupId: Value(groupIdDirectChat),
|
groupId: Value(groupIdDirectChat),
|
||||||
isDirectChat: const Value(true),
|
isDirectChat: const Value(true),
|
||||||
|
|
@ -140,10 +136,15 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Group?> _insertGroup(GroupsCompanion group) async {
|
Future<Group?> _insertGroup(GroupsCompanion group) async {
|
||||||
await into(groups).insertOnConflictUpdate(group);
|
try {
|
||||||
return (select(
|
await into(groups).insert(group);
|
||||||
|
return await (select(
|
||||||
groups,
|
groups,
|
||||||
)..where((t) => t.groupId.equals(group.groupId.value))).getSingleOrNull();
|
)..where((t) => t.groupId.equals(group.groupId.value))).getSingle();
|
||||||
|
} catch (e) {
|
||||||
|
Log.error('Could not insert group: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Contact>> getGroupContact(String groupId) async {
|
Future<List<Contact>> getGroupContact(String groupId) async {
|
||||||
|
|
@ -208,10 +209,7 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<Group?> watchDirectChat(int contactId) {
|
Stream<Group?> watchDirectChat(int contactId) {
|
||||||
final groupId = getUUIDforDirectChat(
|
final groupId = getUUIDforDirectChat(contactId, gUser.userId);
|
||||||
contactId,
|
|
||||||
userService.currentUser.userId,
|
|
||||||
);
|
|
||||||
return (select(
|
return (select(
|
||||||
groups,
|
groups,
|
||||||
)..where((t) => t.groupId.equals(groupId))).watchSingleOrNull();
|
)..where((t) => t.groupId.equals(groupId))).watchSingleOrNull();
|
||||||
|
|
@ -237,7 +235,7 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
|
||||||
)..where((t) => t.groupId.equals(groupId))).getSingleOrNull();
|
)..where((t) => t.groupId.equals(groupId))).getSingleOrNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<({int counter, bool isExpiring})> watchFlameCounter(String groupId) {
|
Stream<int> watchFlameCounter(String groupId) {
|
||||||
return (select(groups)..where(
|
return (select(groups)..where(
|
||||||
(u) =>
|
(u) =>
|
||||||
u.groupId.equals(groupId) &
|
u.groupId.equals(groupId) &
|
||||||
|
|
@ -245,7 +243,7 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
|
||||||
u.lastMessageSend.isNotNull(),
|
u.lastMessageSend.isNotNull(),
|
||||||
))
|
))
|
||||||
.watchSingleOrNull()
|
.watchSingleOrNull()
|
||||||
.map(getFlameCounterFromGroup);
|
.asyncMap(getFlameCounterFromGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Group>> getAllDirectChats() {
|
Future<List<Group>> getAllDirectChats() {
|
||||||
|
|
@ -273,7 +271,7 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
|
||||||
groups.groupId.equalsExp(groupMembers.groupId),
|
groups.groupId.equalsExp(groupMembers.groupId),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)..where(groups.isDirectChat.equals(false)));
|
)..where(groups.isDirectChat.isNull()));
|
||||||
return query.map((row) => row.readTable(groupMembers)).get();
|
return query.map((row) => row.readTable(groupMembers)).get();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Log.error(e);
|
Log.error(e);
|
||||||
|
|
@ -293,27 +291,6 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
|
||||||
return query.map((row) => row.readTable(groups)).getSingleOrNull();
|
return query.map((row) => row.readTable(groups)).getSingleOrNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Group?> createOrGetDirectChat(int contactId) async {
|
|
||||||
var directChat = await getDirectChat(contactId);
|
|
||||||
if (directChat == null) {
|
|
||||||
final contact = await attachedDatabase.contactsDao.getContactById(
|
|
||||||
contactId,
|
|
||||||
);
|
|
||||||
if (contact == null) {
|
|
||||||
Log.error('Contact $contactId not found, cannot create direct chat');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
await createNewDirectChat(
|
|
||||||
contactId,
|
|
||||||
GroupsCompanion(
|
|
||||||
groupName: Value(getContactDisplayName(contact)),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
directChat = await getDirectChat(contactId);
|
|
||||||
}
|
|
||||||
return directChat;
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream<int> watchSumTotalMediaCounter() {
|
Stream<int> watchSumTotalMediaCounter() {
|
||||||
final query = selectOnly(groups)
|
final query = selectOnly(groups)
|
||||||
..addColumns([groups.totalMediaCounter.sum()]);
|
..addColumns([groups.totalMediaCounter.sum()]);
|
||||||
|
|
@ -334,33 +311,4 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
|
||||||
))
|
))
|
||||||
.write(GroupsCompanion(lastMessageExchange: Value(newLastMessage)));
|
.write(GroupsCompanion(lastMessageExchange: Value(newLastMessage)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<List<Group>> watchNonDirectGroupsForMember(int contactId) {
|
|
||||||
final query =
|
|
||||||
select(groups).join([
|
|
||||||
innerJoin(
|
|
||||||
groupMembers,
|
|
||||||
groupMembers.groupId.equalsExp(groups.groupId),
|
|
||||||
),
|
|
||||||
])..where(
|
|
||||||
groups.isDirectChat.equals(false) &
|
|
||||||
groupMembers.contactId.equals(contactId),
|
|
||||||
);
|
|
||||||
|
|
||||||
return query.map((row) => row.readTable(groups)).watch();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<Group>> getGroupsForMember(int contactId) {
|
|
||||||
final query =
|
|
||||||
select(groups).join([
|
|
||||||
innerJoin(
|
|
||||||
groupMembers,
|
|
||||||
groupMembers.groupId.equalsExp(groups.groupId),
|
|
||||||
),
|
|
||||||
])..where(
|
|
||||||
groupMembers.contactId.equals(contactId),
|
|
||||||
);
|
|
||||||
|
|
||||||
return query.map((row) => row.readTable(groups)).get();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,261 +0,0 @@
|
||||||
import 'package:clock/clock.dart';
|
|
||||||
import 'package:drift/drift.dart';
|
|
||||||
import 'package:twonly/core/bridge/wrapper/user_discovery.dart';
|
|
||||||
import 'package:twonly/locator.dart';
|
|
||||||
import 'package:twonly/src/database/tables/contacts.table.dart';
|
|
||||||
import 'package:twonly/src/database/tables/groups.table.dart';
|
|
||||||
import 'package:twonly/src/database/tables/user_discovery.table.dart';
|
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
|
||||||
import 'package:twonly/src/utils/log.dart';
|
|
||||||
|
|
||||||
part 'key_verification.dao.g.dart';
|
|
||||||
|
|
||||||
enum VerificationStatus { trusted, partialTrusted, notTrusted }
|
|
||||||
|
|
||||||
@DriftAccessor(
|
|
||||||
tables: [
|
|
||||||
Contacts,
|
|
||||||
VerificationTokens,
|
|
||||||
KeyVerifications,
|
|
||||||
GroupMembers,
|
|
||||||
UserDiscoveryUserRelations,
|
|
||||||
],
|
|
||||||
)
|
|
||||||
class KeyVerificationDao extends DatabaseAccessor<TwonlyDB>
|
|
||||||
with _$KeyVerificationDaoMixin {
|
|
||||||
// ignore: matching_super_parameters
|
|
||||||
KeyVerificationDao(super.db);
|
|
||||||
|
|
||||||
Future<List<VerificationToken>> getRecentVerificationTokens() {
|
|
||||||
// Tokens are only valid for one hour, so if the users are currently offline, the verification notification will still work later.
|
|
||||||
final cutoff = DateTime.now().subtract(const Duration(hours: 1));
|
|
||||||
return (select(
|
|
||||||
verificationTokens,
|
|
||||||
)..where((t) => t.createdAt.isBiggerOrEqualValue(cutoff))).get();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<int> insertVerificationToken(Uint8List token) {
|
|
||||||
return into(verificationTokens).insert(
|
|
||||||
VerificationTokensCompanion.insert(token: token),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a map of contactId → the verification type of the earliest
|
|
||||||
/// [KeyVerification] row for that contact.
|
|
||||||
Future<Map<int, VerificationType>>
|
|
||||||
getFirstVerificationTypeByContacts() async {
|
|
||||||
final rows = await (select(
|
|
||||||
keyVerifications,
|
|
||||||
)..orderBy([(kv) => OrderingTerm.asc(kv.createdAt)])).get();
|
|
||||||
|
|
||||||
final result = <int, VerificationType>{};
|
|
||||||
for (final row in rows) {
|
|
||||||
result.putIfAbsent(row.contactId, () => row.type);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> isContactVerified(int contactId) async {
|
|
||||||
final row =
|
|
||||||
await (select(keyVerifications)
|
|
||||||
..where((kv) => kv.contactId.equals(contactId))
|
|
||||||
..limit(1))
|
|
||||||
.getSingleOrNull();
|
|
||||||
return row != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream<List<KeyVerification>> watchContactVerification(int contactId) {
|
|
||||||
return (select(
|
|
||||||
keyVerifications,
|
|
||||||
)..where((kv) => kv.contactId.equals(contactId))).watch();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<KeyVerification>> getContactVerification(int contactId) async {
|
|
||||||
return (select(
|
|
||||||
keyVerifications,
|
|
||||||
)..where((kv) => kv.contactId.equals(contactId))).get();
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream<List<(Contact, DateTime)>> watchTransferredTrustVerifications(
|
|
||||||
int contactId,
|
|
||||||
) {
|
|
||||||
final kv = keyVerifications;
|
|
||||||
final ur = userDiscoveryUserRelations;
|
|
||||||
|
|
||||||
final query =
|
|
||||||
(select(contacts)..where((u) => u.userId.equals(contactId).not())).join(
|
|
||||||
[
|
|
||||||
innerJoin(
|
|
||||||
ur,
|
|
||||||
ur.fromContactId.equalsExp(contacts.userId),
|
|
||||||
),
|
|
||||||
innerJoin(kv, kv.contactId.equalsExp(ur.fromContactId)),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
..where(
|
|
||||||
ur.announcedUserId.equals(contactId) &
|
|
||||||
ur.publicKeyVerifiedTimestamp.isNotNull(),
|
|
||||||
)
|
|
||||||
..groupBy([contacts.userId]);
|
|
||||||
|
|
||||||
return query.watch().map((rows) {
|
|
||||||
return rows.map((row) {
|
|
||||||
final contact = row.readTable(contacts);
|
|
||||||
final timestamp = row.readTable(ur).publicKeyVerifiedTimestamp!;
|
|
||||||
return (contact, timestamp);
|
|
||||||
}).toList();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<int> getTransferredTrustVerificationsCount() async {
|
|
||||||
final kv = keyVerifications;
|
|
||||||
final ur = userDiscoveryUserRelations;
|
|
||||||
|
|
||||||
final query = selectOnly(ur, distinct: true)
|
|
||||||
..addColumns([ur.announcedUserId])
|
|
||||||
..join([
|
|
||||||
innerJoin(contacts, contacts.userId.equalsExp(ur.fromContactId)),
|
|
||||||
innerJoin(kv, kv.contactId.equalsExp(ur.fromContactId)),
|
|
||||||
])
|
|
||||||
..where(
|
|
||||||
ur.publicKeyVerifiedTimestamp.isNotNull() &
|
|
||||||
ur.announcedUserId.equalsExp(ur.fromContactId).not(),
|
|
||||||
)
|
|
||||||
..groupBy([ur.announcedUserId]);
|
|
||||||
|
|
||||||
final rows = await query.get();
|
|
||||||
return rows.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<int> getCountOfContactsWithVerificationBadge() async {
|
|
||||||
final kv = keyVerifications;
|
|
||||||
final ur = userDiscoveryUserRelations;
|
|
||||||
|
|
||||||
final query = selectOnly(ur, distinct: true)
|
|
||||||
..addColumns([ur.announcedUserId])
|
|
||||||
..join([
|
|
||||||
innerJoin(contacts, contacts.userId.equalsExp(ur.fromContactId)),
|
|
||||||
innerJoin(kv, kv.contactId.equalsExp(ur.fromContactId)),
|
|
||||||
])
|
|
||||||
..where(
|
|
||||||
ur.publicKeyVerifiedTimestamp.isNotNull() &
|
|
||||||
ur.announcedUserId.equalsExp(ur.fromContactId).not(),
|
|
||||||
)
|
|
||||||
..groupBy([ur.announcedUserId]);
|
|
||||||
|
|
||||||
final rows = await query.get();
|
|
||||||
final transferredIds = rows.map((r) => r.read(ur.announcedUserId)!).toSet();
|
|
||||||
|
|
||||||
final directVerifications = await select(kv).get();
|
|
||||||
final directIds = directVerifications.map((v) => v.contactId).toSet();
|
|
||||||
|
|
||||||
// Reduce transferred contacts where announcedUserId is already in KeyVerifications
|
|
||||||
transferredIds.removeWhere(directIds.contains);
|
|
||||||
|
|
||||||
// Add count of all users who are in the KeyVerification table
|
|
||||||
return transferredIds.length + directIds.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream<VerificationStatus> watchAllGroupMembersVerified(String groupId) {
|
|
||||||
final gm = groupMembers;
|
|
||||||
final directKv = alias(keyVerifications, 'directKv');
|
|
||||||
final ur = userDiscoveryUserRelations;
|
|
||||||
final verifierKv = alias(keyVerifications, 'verifierKv');
|
|
||||||
|
|
||||||
final query = select(gm).join([
|
|
||||||
leftOuterJoin(directKv, directKv.contactId.equalsExp(gm.contactId)),
|
|
||||||
leftOuterJoin(
|
|
||||||
ur,
|
|
||||||
ur.announcedUserId.equalsExp(gm.contactId) &
|
|
||||||
ur.publicKeyVerifiedTimestamp.isNotNull() &
|
|
||||||
ur.fromContactId.equalsExp(gm.contactId).not(),
|
|
||||||
),
|
|
||||||
leftOuterJoin(
|
|
||||||
verifierKv,
|
|
||||||
verifierKv.contactId.equalsExp(ur.fromContactId),
|
|
||||||
),
|
|
||||||
])..where(gm.groupId.equals(groupId));
|
|
||||||
|
|
||||||
return query.watch().map((rows) {
|
|
||||||
if (rows.isEmpty) return VerificationStatus.notTrusted;
|
|
||||||
|
|
||||||
final memberTrustMap = <int, ({bool direct, bool partial})>{};
|
|
||||||
|
|
||||||
for (final row in rows) {
|
|
||||||
final contactId = row.readTable(gm).contactId;
|
|
||||||
final isDirect = row.readTableOrNull(directKv) != null;
|
|
||||||
final isPartial = row.readTableOrNull(verifierKv) != null;
|
|
||||||
|
|
||||||
final current =
|
|
||||||
memberTrustMap[contactId] ?? (direct: false, partial: false);
|
|
||||||
memberTrustMap[contactId] = (
|
|
||||||
direct: current.direct || isDirect,
|
|
||||||
partial: current.partial || isPartial,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final allDirect = memberTrustMap.values.every((m) => m.direct);
|
|
||||||
if (allDirect) return VerificationStatus.trusted;
|
|
||||||
|
|
||||||
final allAtLeastPartial = memberTrustMap.values.every(
|
|
||||||
(m) => m.direct || m.partial,
|
|
||||||
);
|
|
||||||
if (allAtLeastPartial) return VerificationStatus.partialTrusted;
|
|
||||||
|
|
||||||
return VerificationStatus.notTrusted;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> addKeyVerification(int contactId, VerificationType type) async {
|
|
||||||
try {
|
|
||||||
await into(keyVerifications).insertOnConflictUpdate(
|
|
||||||
KeyVerificationsCompanion(
|
|
||||||
contactId: Value(contactId),
|
|
||||||
type: Value(type),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (userService.currentUser.isUserDiscoveryEnabled) {
|
|
||||||
await FlutterUserDiscovery.updateVerificationStateForUser(
|
|
||||||
contactId: contactId,
|
|
||||||
publicKeyVerifiedTimestamp: clock.now().millisecondsSinceEpoch,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
Log.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> deleteKeyVerification(int contactId) async {
|
|
||||||
try {
|
|
||||||
await (delete(
|
|
||||||
keyVerifications,
|
|
||||||
)..where((kv) => kv.contactId.equals(contactId))).go();
|
|
||||||
if (userService.currentUser.isUserDiscoveryEnabled) {
|
|
||||||
await FlutterUserDiscovery.updateVerificationStateForUser(
|
|
||||||
contactId: contactId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
Log.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> deleteKeyVerificationById(
|
|
||||||
int verificationId,
|
|
||||||
int contactId,
|
|
||||||
) async {
|
|
||||||
try {
|
|
||||||
await (delete(
|
|
||||||
keyVerifications,
|
|
||||||
)..where((kv) => kv.verificationId.equals(verificationId))).go();
|
|
||||||
final remaining = await getContactVerification(contactId);
|
|
||||||
if (remaining.isEmpty && userService.currentUser.isUserDiscoveryEnabled) {
|
|
||||||
await FlutterUserDiscovery.updateVerificationStateForUser(
|
|
||||||
contactId: contactId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
Log.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'key_verification.dao.dart';
|
|
||||||
|
|
||||||
// ignore_for_file: type=lint
|
|
||||||
mixin _$KeyVerificationDaoMixin on DatabaseAccessor<TwonlyDB> {
|
|
||||||
$ContactsTable get contacts => attachedDatabase.contacts;
|
|
||||||
$VerificationTokensTable get verificationTokens =>
|
|
||||||
attachedDatabase.verificationTokens;
|
|
||||||
$KeyVerificationsTable get keyVerifications =>
|
|
||||||
attachedDatabase.keyVerifications;
|
|
||||||
$GroupsTable get groups => attachedDatabase.groups;
|
|
||||||
$GroupMembersTable get groupMembers => attachedDatabase.groupMembers;
|
|
||||||
$UserDiscoveryAnnouncedUsersTable get userDiscoveryAnnouncedUsers =>
|
|
||||||
attachedDatabase.userDiscoveryAnnouncedUsers;
|
|
||||||
$UserDiscoveryUserRelationsTable get userDiscoveryUserRelations =>
|
|
||||||
attachedDatabase.userDiscoveryUserRelations;
|
|
||||||
KeyVerificationDaoManager get managers => KeyVerificationDaoManager(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
class KeyVerificationDaoManager {
|
|
||||||
final _$KeyVerificationDaoMixin _db;
|
|
||||||
KeyVerificationDaoManager(this._db);
|
|
||||||
$$ContactsTableTableManager get contacts =>
|
|
||||||
$$ContactsTableTableManager(_db.attachedDatabase, _db.contacts);
|
|
||||||
$$VerificationTokensTableTableManager get verificationTokens =>
|
|
||||||
$$VerificationTokensTableTableManager(
|
|
||||||
_db.attachedDatabase,
|
|
||||||
_db.verificationTokens,
|
|
||||||
);
|
|
||||||
$$KeyVerificationsTableTableManager get keyVerifications =>
|
|
||||||
$$KeyVerificationsTableTableManager(
|
|
||||||
_db.attachedDatabase,
|
|
||||||
_db.keyVerifications,
|
|
||||||
);
|
|
||||||
$$GroupsTableTableManager get groups =>
|
|
||||||
$$GroupsTableTableManager(_db.attachedDatabase, _db.groups);
|
|
||||||
$$GroupMembersTableTableManager get groupMembers =>
|
|
||||||
$$GroupMembersTableTableManager(_db.attachedDatabase, _db.groupMembers);
|
|
||||||
$$UserDiscoveryAnnouncedUsersTableTableManager
|
|
||||||
get userDiscoveryAnnouncedUsers =>
|
|
||||||
$$UserDiscoveryAnnouncedUsersTableTableManager(
|
|
||||||
_db.attachedDatabase,
|
|
||||||
_db.userDiscoveryAnnouncedUsers,
|
|
||||||
);
|
|
||||||
$$UserDiscoveryUserRelationsTableTableManager
|
|
||||||
get userDiscoveryUserRelations =>
|
|
||||||
$$UserDiscoveryUserRelationsTableTableManager(
|
|
||||||
_db.attachedDatabase,
|
|
||||||
_db.userDiscoveryUserRelations,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -65,10 +65,6 @@ class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
|
||||||
)..where((t) => t.mediaId.equals(mediaId))).getSingleOrNull();
|
)..where((t) => t.mediaId.equals(mediaId))).getSingleOrNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<MediaFile>> getMediaFilesByIds(List<String> mediaIds) async {
|
|
||||||
return (select(mediaFiles)..where((t) => t.mediaId.isIn(mediaIds))).get();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<MediaFile?> getDraftMediaFile() async {
|
Future<MediaFile?> getDraftMediaFile() async {
|
||||||
final medias = await (select(
|
final medias = await (select(
|
||||||
mediaFiles,
|
mediaFiles,
|
||||||
|
|
@ -114,15 +110,9 @@ class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
|
||||||
.get();
|
.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<MediaFile>> getAllMediaFilesPendingMigration() async {
|
Future<List<MediaFile>> getAllNonHashedStoredMediaFiles() async {
|
||||||
return (select(mediaFiles)..where(
|
return (select(mediaFiles)..where(
|
||||||
(t) =>
|
(t) => t.stored.equals(true) & t.storedFileHash.isNull(),
|
||||||
t.stored.equals(true) &
|
|
||||||
(t.storedFileHash.isNull() |
|
|
||||||
t.hasCropAnalyzed.equals(false) |
|
|
||||||
(t.hasThumbnail.equals(false) &
|
|
||||||
t.type.equals(MediaType.audio.name).not()) |
|
|
||||||
t.sizeInBytes.isNull()),
|
|
||||||
))
|
))
|
||||||
.get();
|
.get();
|
||||||
}
|
}
|
||||||
|
|
@ -164,43 +154,4 @@ class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<String>> getMessageIdsByMediaHash(
|
|
||||||
Uint8List hash,
|
|
||||||
int senderId,
|
|
||||||
) async {
|
|
||||||
final query =
|
|
||||||
select(db.messages).join([
|
|
||||||
innerJoin(
|
|
||||||
mediaFiles,
|
|
||||||
mediaFiles.mediaId.equalsExp(db.messages.mediaId),
|
|
||||||
),
|
|
||||||
])..where(
|
|
||||||
mediaFiles.storedFileHash.equals(hash) &
|
|
||||||
db.messages.senderId.equals(senderId) &
|
|
||||||
db.messages.openedAt.isNull(),
|
|
||||||
);
|
|
||||||
|
|
||||||
final rows = await query.get();
|
|
||||||
return rows.map((row) => row.readTable(db.messages).messageId).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<MediaFile>> getMediaByHash(Uint8List hash) async {
|
|
||||||
final query = select(db.mediaFiles)
|
|
||||||
..where((t) => t.storedFileHash.equals(hash));
|
|
||||||
return query.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Map<MediaType, int>> getStorageStats() async {
|
|
||||||
final rows = await select(mediaFiles).get();
|
|
||||||
final stats = <MediaType, int>{};
|
|
||||||
|
|
||||||
for (final row in rows) {
|
|
||||||
final type = row.type;
|
|
||||||
final size = row.sizeInBytes ?? 0;
|
|
||||||
stats[type] = (stats[type] ?? 0) + size;
|
|
||||||
}
|
|
||||||
|
|
||||||
return stats;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
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';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/tables/contacts.table.dart';
|
import 'package:twonly/src/database/tables/contacts.table.dart';
|
||||||
import 'package:twonly/src/database/tables/groups.table.dart';
|
import 'package:twonly/src/database/tables/groups.table.dart';
|
||||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||||
|
|
@ -102,7 +102,7 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
||||||
t.openedAt.isNull() |
|
t.openedAt.isNull() |
|
||||||
t.mediaStored.equals(true)) &
|
t.mediaStored.equals(true)) &
|
||||||
(t.isDeletedFromSender.equals(true) |
|
(t.isDeletedFromSender.equals(true) |
|
||||||
(t.type.equals(MessageType.text.name).not() &
|
(t.type.equals(MessageType.text.name).not() |
|
||||||
t.type.equals(MessageType.media.name).not()) |
|
t.type.equals(MessageType.media.name).not()) |
|
||||||
(t.type.equals(MessageType.text.name) &
|
(t.type.equals(MessageType.text.name) &
|
||||||
t.content.isNotNull()) |
|
t.content.isNotNull()) |
|
||||||
|
|
@ -140,25 +140,17 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
||||||
Future<void> purgeMessageTable() async {
|
Future<void> purgeMessageTable() async {
|
||||||
final allGroups = await select(groups).get();
|
final allGroups = await select(groups).get();
|
||||||
|
|
||||||
final groupedByTime = <int, List<String>>{};
|
for (final group in allGroups) {
|
||||||
for (final g in allGroups) {
|
|
||||||
groupedByTime
|
|
||||||
.putIfAbsent(g.deleteMessagesAfterMilliseconds, () => [])
|
|
||||||
.add(g.groupId);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (final entry in groupedByTime.entries) {
|
|
||||||
final deletionTime = clock.now().subtract(
|
final deletionTime = clock.now().subtract(
|
||||||
Duration(milliseconds: entry.key),
|
Duration(
|
||||||
|
milliseconds: group.deleteMessagesAfterMilliseconds,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
final groupIds = entry.value;
|
|
||||||
|
|
||||||
final deletedCount =
|
|
||||||
await (delete(messages)..where(
|
await (delete(messages)..where(
|
||||||
(m) =>
|
(m) =>
|
||||||
m.groupId.isIn(groupIds) &
|
m.groupId.equals(group.groupId) &
|
||||||
((m.mediaStored.equals(true) &
|
(m.mediaStored.equals(true) &
|
||||||
m.isDeletedFromSender.equals(true)) |
|
m.isDeletedFromSender.equals(true) |
|
||||||
m.mediaStored.equals(false)) &
|
m.mediaStored.equals(false)) &
|
||||||
// Only remove the message when ALL members have seen it. Otherwise the receipt will also be deleted which could cause issues in case a member opens the image later..
|
// Only remove the message when ALL members have seen it. Otherwise the receipt will also be deleted which could cause issues in case a member opens the image later..
|
||||||
(m.openedByAll.isSmallerThanValue(deletionTime) |
|
(m.openedByAll.isSmallerThanValue(deletionTime) |
|
||||||
|
|
@ -166,13 +158,19 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
||||||
m.createdAt.isSmallerThanValue(deletionTime))),
|
m.createdAt.isSmallerThanValue(deletionTime))),
|
||||||
))
|
))
|
||||||
.go();
|
.go();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (deletedCount > 0) {
|
Future<void> openedAllTextMessages(String groupId) {
|
||||||
Log.info(
|
final updates = MessagesCompanion(openedAt: Value(clock.now()));
|
||||||
'Deleted $deletedCount messages for groups $groupIds due to retention policy.',
|
return (update(messages)..where(
|
||||||
);
|
(t) =>
|
||||||
}
|
t.groupId.equals(groupId) &
|
||||||
}
|
t.senderId.isNotNull() &
|
||||||
|
t.openedAt.isNull() &
|
||||||
|
t.type.equals(MessageType.text.name),
|
||||||
|
))
|
||||||
|
.write(updates);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> handleMessageDeletion(
|
Future<void> handleMessageDeletion(
|
||||||
|
|
@ -186,15 +184,7 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (msg.mediaId != null && contactId != null) {
|
if (msg.mediaId != null && contactId != null) {
|
||||||
final otherMessagesWithSameMedia =
|
// contactId -> When a image is send to multiple and one message is delete the image should be still available...
|
||||||
await (select(messages)..where(
|
|
||||||
(t) =>
|
|
||||||
t.mediaId.equals(msg.mediaId!) &
|
|
||||||
t.messageId.equals(messageId).not(),
|
|
||||||
))
|
|
||||||
.get();
|
|
||||||
|
|
||||||
if (otherMessagesWithSameMedia.isEmpty) {
|
|
||||||
await (delete(
|
await (delete(
|
||||||
mediaFiles,
|
mediaFiles,
|
||||||
)..where((t) => t.mediaId.equals(msg.mediaId!))).go();
|
)..where((t) => t.mediaId.equals(msg.mediaId!))).go();
|
||||||
|
|
@ -203,18 +193,11 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
||||||
if (mediaService != null) {
|
if (mediaService != null) {
|
||||||
mediaService.fullMediaRemoval();
|
mediaService.fullMediaRemoval();
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
Log.info(
|
|
||||||
'Media ${msg.mediaId} is still used by ${otherMessagesWithSameMedia.length} other messages. Skipping physical deletion.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
await (delete(
|
await (delete(
|
||||||
messageHistories,
|
messageHistories,
|
||||||
)..where((t) => t.messageId.equals(messageId))).go();
|
)..where((t) => t.messageId.equals(messageId))).go();
|
||||||
|
|
||||||
await twonlyDB.receiptsDao.deleteReceiptsByMessageId(messageId);
|
|
||||||
|
|
||||||
await (update(messages)..where(
|
await (update(messages)..where(
|
||||||
(t) => t.messageId.equals(messageId),
|
(t) => t.messageId.equals(messageId),
|
||||||
))
|
))
|
||||||
|
|
@ -256,70 +239,41 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> handleMessagesOpened(
|
Future<void> handleMessagesOpened(
|
||||||
Value<int> contactId,
|
int contactId,
|
||||||
List<String> messageIds,
|
List<String> messageIds,
|
||||||
DateTime timestamp,
|
DateTime timestamp,
|
||||||
) async {
|
) async {
|
||||||
|
await batch((batch) async {
|
||||||
for (final messageId in messageIds) {
|
for (final messageId in messageIds) {
|
||||||
try {
|
batch.insert(
|
||||||
var actionTimestamp = timestamp;
|
messageActions,
|
||||||
final msg = await getMessageById(messageId).getSingleOrNull();
|
|
||||||
if (msg != null && actionTimestamp.isBefore(msg.createdAt)) {
|
|
||||||
Log.warn(
|
|
||||||
'Receiver clock skew detected for message $messageId. '
|
|
||||||
'Action timestamp $actionTimestamp is before message creation ${msg.createdAt}. '
|
|
||||||
'Clamping to creation time.',
|
|
||||||
);
|
|
||||||
actionTimestamp = msg.createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
final ts = actionTimestamp;
|
|
||||||
await transaction(() async {
|
|
||||||
await into(messageActions).insertOnConflictUpdate(
|
|
||||||
MessageActionsCompanion(
|
MessageActionsCompanion(
|
||||||
messageId: Value(messageId),
|
messageId: Value(messageId),
|
||||||
contactId: contactId,
|
contactId: Value(contactId),
|
||||||
type: const Value(MessageActionType.openedAt),
|
type: const Value(MessageActionType.openedAt),
|
||||||
actionAt: Value(ts),
|
actionAt: Value(timestamp),
|
||||||
),
|
),
|
||||||
|
mode: InsertMode.insertOrReplace,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final messageId in messageIds) {
|
||||||
final isOpenedByAll = await haveAllMembers(
|
final isOpenedByAll = await haveAllMembers(
|
||||||
messageId,
|
messageId,
|
||||||
MessageActionType.openedAt,
|
MessageActionType.openedAt,
|
||||||
);
|
);
|
||||||
await (update(
|
final now = clock.now();
|
||||||
messages,
|
|
||||||
)..where((tbl) => tbl.messageId.equals(messageId))).write(
|
batch.update(
|
||||||
|
twonlyDB.messages,
|
||||||
MessagesCompanion(
|
MessagesCompanion(
|
||||||
openedAt: Value(ts),
|
openedAt: Value(now),
|
||||||
openedByAll: Value(isOpenedByAll ? ts : null),
|
openedByAll: Value(isOpenedByAll ? now : null),
|
||||||
),
|
),
|
||||||
|
where: (tbl) => tbl.messageId.equals(messageId),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Read-back verification: confirm the write was persisted.
|
|
||||||
final verified = await getMessageById(messageId).getSingleOrNull();
|
|
||||||
if (verified != null && verified.openedAt == null) {
|
|
||||||
Log.warn(
|
|
||||||
'handleMessagesOpened read-back failed for $messageId, retrying',
|
|
||||||
);
|
|
||||||
await (update(
|
|
||||||
messages,
|
|
||||||
)..where((tbl) => tbl.messageId.equals(messageId))).write(
|
|
||||||
MessagesCompanion(
|
|
||||||
openedAt: Value(actionTimestamp),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.info(
|
|
||||||
'handleMessagesOpened completed for message $messageId',
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
Log.error('handleMessagesOpened failed for $messageId: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> handleMessageAckByServer(
|
Future<void> handleMessageAckByServer(
|
||||||
|
|
@ -327,7 +281,6 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
||||||
String messageId,
|
String messageId,
|
||||||
DateTime timestamp,
|
DateTime timestamp,
|
||||||
) async {
|
) async {
|
||||||
await transaction(() async {
|
|
||||||
await into(messageActions).insertOnConflictUpdate(
|
await into(messageActions).insertOnConflictUpdate(
|
||||||
MessageActionsCompanion(
|
MessageActionsCompanion(
|
||||||
messageId: Value(messageId),
|
messageId: Value(messageId),
|
||||||
|
|
@ -338,16 +291,14 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
||||||
);
|
);
|
||||||
await twonlyDB.messagesDao.updateMessageId(
|
await twonlyDB.messagesDao.updateMessageId(
|
||||||
messageId,
|
messageId,
|
||||||
MessagesCompanion(ackByServer: Value(timestamp)),
|
MessagesCompanion(ackByServer: Value(clock.now())),
|
||||||
);
|
);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> haveAllMembers(
|
Future<bool> haveAllMembers(
|
||||||
String messageId,
|
String messageId,
|
||||||
MessageActionType action,
|
MessageActionType action,
|
||||||
) async {
|
) async {
|
||||||
try {
|
|
||||||
final message = await twonlyDB.messagesDao
|
final message = await twonlyDB.messagesDao
|
||||||
.getMessageById(messageId)
|
.getMessageById(messageId)
|
||||||
.getSingleOrNull();
|
.getSingleOrNull();
|
||||||
|
|
@ -358,36 +309,29 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
||||||
|
|
||||||
final actions =
|
final actions =
|
||||||
await (select(messageActions)..where(
|
await (select(messageActions)..where(
|
||||||
(t) =>
|
(t) => t.type.equals(action.name) & t.messageId.equals(messageId),
|
||||||
t.type.equals(action.name) & t.messageId.equals(messageId),
|
|
||||||
))
|
))
|
||||||
.get();
|
.get();
|
||||||
|
|
||||||
return members.length == actions.length;
|
return members.length == actions.length;
|
||||||
} catch (e) {
|
|
||||||
Log.error(e);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateMessageId(
|
Future<void> updateMessageId(
|
||||||
String messageId,
|
String messageId,
|
||||||
MessagesCompanion updatedValues,
|
MessagesCompanion updatedValues,
|
||||||
) async {
|
) async {
|
||||||
final count = await (update(
|
await (update(
|
||||||
messages,
|
messages,
|
||||||
)..where((c) => c.messageId.equals(messageId))).write(updatedValues);
|
)..where((c) => c.messageId.equals(messageId))).write(updatedValues);
|
||||||
Log.info('Updated $count message(s) with messageId $messageId');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateMessagesByMediaId(
|
Future<void> updateMessagesByMediaId(
|
||||||
String mediaId,
|
String mediaId,
|
||||||
MessagesCompanion updatedValues,
|
MessagesCompanion updatedValues,
|
||||||
) async {
|
) {
|
||||||
final count = await (update(
|
return (update(
|
||||||
messages,
|
messages,
|
||||||
)..where((c) => c.mediaId.equals(mediaId))).write(updatedValues);
|
)..where((c) => c.mediaId.equals(mediaId))).write(updatedValues);
|
||||||
Log.info('Updated $count message(s) with mediaId $mediaId');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Message?> insertMessage(MessagesCompanion message) async {
|
Future<Message?> insertMessage(MessagesCompanion message) async {
|
||||||
|
|
@ -400,7 +344,7 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await into(messages).insertOnConflictUpdate(insertMessage);
|
final rowId = await into(messages).insertOnConflictUpdate(insertMessage);
|
||||||
|
|
||||||
await twonlyDB.groupsDao.updateGroup(
|
await twonlyDB.groupsDao.updateGroup(
|
||||||
message.groupId.value,
|
message.groupId.value,
|
||||||
|
|
@ -421,11 +365,9 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final messageId = insertMessage.messageId.value;
|
|
||||||
|
|
||||||
return await (select(
|
return await (select(
|
||||||
messages,
|
messages,
|
||||||
)..where((t) => t.messageId.equals(messageId))).getSingle();
|
)..where((t) => t.rowId.equals(rowId))).getSingle();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Log.error('Could not insert message: $e');
|
Log.error('Could not insert message: $e');
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -457,10 +399,6 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
||||||
return (select(messages)..where((t) => t.mediaId.equals(mediaId))).get();
|
return (select(messages)..where((t) => t.mediaId.equals(mediaId))).get();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Message>> getMessagesByMediaIds(List<String> mediaIds) async {
|
|
||||||
return (select(messages)..where((t) => t.mediaId.isIn(mediaIds))).get();
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream<List<(MessageAction, Contact)>> watchMessageActions(String messageId) {
|
Stream<List<(MessageAction, Contact)>> watchMessageActions(String messageId) {
|
||||||
final query = (select(messageActions).join([
|
final query = (select(messageActions).join([
|
||||||
leftOuterJoin(
|
leftOuterJoin(
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/tables/contacts.table.dart';
|
import 'package:twonly/src/database/tables/contacts.table.dart';
|
||||||
import 'package:twonly/src/database/tables/reactions.table.dart';
|
import 'package:twonly/src/database/tables/reactions.table.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/visual/components/animate_icon.comp.dart';
|
import 'package:twonly/src/views/components/animate_icon.dart';
|
||||||
|
|
||||||
part 'reactions.dao.g.dart';
|
part 'reactions.dao.g.dart';
|
||||||
|
|
||||||
|
|
@ -29,14 +29,7 @@ class ReactionsDao extends DatabaseAccessor<TwonlyDB> with _$ReactionsDaoMixin {
|
||||||
final msg = await twonlyDB.messagesDao
|
final msg = await twonlyDB.messagesDao
|
||||||
.getMessageById(messageId)
|
.getMessageById(messageId)
|
||||||
.getSingleOrNull();
|
.getSingleOrNull();
|
||||||
if (msg == null) {
|
if (msg == null || msg.groupId != groupId) return;
|
||||||
Log.error('updateReaction: Message $messageId not found!');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (msg.groupId != groupId) {
|
|
||||||
Log.error('updateReaction: Message groupId ${msg.groupId} != $groupId');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (remove) {
|
if (remove) {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import 'package:twonly/src/database/tables/contacts.table.dart';
|
||||||
import 'package:twonly/src/database/tables/messages.table.dart';
|
import 'package:twonly/src/database/tables/messages.table.dart';
|
||||||
import 'package:twonly/src/database/tables/receipts.table.dart';
|
import 'package:twonly/src/database/tables/receipts.table.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/services/api/mediafiles/upload.api.dart';
|
import 'package:twonly/src/services/api/mediafiles/upload.service.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
part 'receipts.dao.g.dart';
|
part 'receipts.dao.g.dart';
|
||||||
|
|
@ -54,13 +54,6 @@ class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
|
||||||
.go();
|
.go();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteReceiptsByMessageId(String messageId) async {
|
|
||||||
await (delete(receipts)..where(
|
|
||||||
(t) => t.messageId.equals(messageId),
|
|
||||||
))
|
|
||||||
.go();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> deleteReceiptForUser(int contactId) async {
|
Future<void> deleteReceiptForUser(int contactId) async {
|
||||||
await (delete(receipts)..where(
|
await (delete(receipts)..where(
|
||||||
(t) => t.contactId.equals(contactId),
|
(t) => t.contactId.equals(contactId),
|
||||||
|
|
@ -98,11 +91,10 @@ class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
|
||||||
receiptId: Value(uuid.v4()),
|
receiptId: Value(uuid.v4()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
await into(receipts).insert(insertEntry);
|
final id = await into(receipts).insert(insertEntry);
|
||||||
final receiptId = insertEntry.receiptId.value;
|
|
||||||
return await (select(
|
return await (select(
|
||||||
receipts,
|
receipts,
|
||||||
)..where((t) => t.receiptId.equals(receiptId))).getSingle();
|
)..where((t) => t.rowId.equals(id))).getSingle();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// ignore error, receipts is already in the database...
|
// ignore error, receipts is already in the database...
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -191,23 +183,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<Receipt?> rotateReceiptId(String oldReceiptId) async {
|
|
||||||
final newReceiptId = uuid.v4();
|
|
||||||
await updateReceipt(
|
|
||||||
oldReceiptId,
|
|
||||||
ReceiptsCompanion(
|
|
||||||
receiptId: Value(newReceiptId),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
final updatedReceipt = await getReceiptById(newReceiptId);
|
|
||||||
if (updatedReceipt == null) {
|
|
||||||
Log.error(
|
|
||||||
'Tried to change the receipt ID, but could not get the updated receipt...',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return updatedReceipt;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> updateReceiptByContactAndMessageId(
|
Future<void> updateReceiptByContactAndMessageId(
|
||||||
int contactId,
|
int contactId,
|
||||||
String messageId,
|
String messageId,
|
||||||
|
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
import 'package:drift/drift.dart';
|
|
||||||
import 'package:twonly/src/database/tables/shortcuts.table.dart';
|
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
|
||||||
|
|
||||||
part 'shortcuts.dao.g.dart';
|
|
||||||
|
|
||||||
@DriftAccessor(
|
|
||||||
tables: [
|
|
||||||
Shortcuts,
|
|
||||||
ShortcutMembers,
|
|
||||||
],
|
|
||||||
)
|
|
||||||
class ShortcutsDao extends DatabaseAccessor<TwonlyDB> with _$ShortcutsDaoMixin {
|
|
||||||
ShortcutsDao(super.db);
|
|
||||||
|
|
||||||
Stream<List<Shortcut>> watchAllShortcuts() {
|
|
||||||
return select(shortcuts).watch();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Shortcut?> getShortcutByEmoji(String emoji) {
|
|
||||||
return (select(
|
|
||||||
shortcuts,
|
|
||||||
)..where((t) => t.emoji.equals(emoji))).getSingleOrNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> createShortcut(String emoji) async {
|
|
||||||
try {
|
|
||||||
await into(shortcuts).insert(
|
|
||||||
ShortcutsCompanion.insert(emoji: emoji),
|
|
||||||
);
|
|
||||||
// ignore: empty_catches
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> addShortcutMembers(int shortcutId, List<String> groupIds) async {
|
|
||||||
await batch((b) {
|
|
||||||
b.insertAll(
|
|
||||||
shortcutMembers,
|
|
||||||
groupIds.map(
|
|
||||||
(gId) => ShortcutMembersCompanion.insert(
|
|
||||||
shortcutId: shortcutId,
|
|
||||||
groupId: gId,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<ShortcutMember>> getShortcutMembers(int shortcutId) {
|
|
||||||
return (select(
|
|
||||||
shortcutMembers,
|
|
||||||
)..where((t) => t.shortcutId.equals(shortcutId))).get();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> incrementUsage(int shortcutId) async {
|
|
||||||
await customStatement(
|
|
||||||
'UPDATE shortcuts SET usage_counter = usage_counter + 1 WHERE id = ?',
|
|
||||||
[shortcutId],
|
|
||||||
);
|
|
||||||
// Notify updates to trigger streams
|
|
||||||
notifyUpdates({TableUpdate.onTable(shortcuts, kind: UpdateKind.update)});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> updateShortcut(int shortcutId, String emoji) async {
|
|
||||||
await (update(shortcuts)..where((t) => t.id.equals(shortcutId))).write(
|
|
||||||
ShortcutsCompanion(emoji: Value(emoji)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> deleteShortcutMembers(int shortcutId) async {
|
|
||||||
await (delete(
|
|
||||||
shortcutMembers,
|
|
||||||
)..where((t) => t.shortcutId.equals(shortcutId))).go();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> deleteShortcut(int shortcutId) async {
|
|
||||||
await (delete(shortcuts)..where((t) => t.id.equals(shortcutId))).go();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'shortcuts.dao.dart';
|
|
||||||
|
|
||||||
// ignore_for_file: type=lint
|
|
||||||
mixin _$ShortcutsDaoMixin on DatabaseAccessor<TwonlyDB> {
|
|
||||||
$ShortcutsTable get shortcuts => attachedDatabase.shortcuts;
|
|
||||||
$GroupsTable get groups => attachedDatabase.groups;
|
|
||||||
$ShortcutMembersTable get shortcutMembers => attachedDatabase.shortcutMembers;
|
|
||||||
ShortcutsDaoManager get managers => ShortcutsDaoManager(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
class ShortcutsDaoManager {
|
|
||||||
final _$ShortcutsDaoMixin _db;
|
|
||||||
ShortcutsDaoManager(this._db);
|
|
||||||
$$ShortcutsTableTableManager get shortcuts =>
|
|
||||||
$$ShortcutsTableTableManager(_db.attachedDatabase, _db.shortcuts);
|
|
||||||
$$GroupsTableTableManager get groups =>
|
|
||||||
$$GroupsTableTableManager(_db.attachedDatabase, _db.groups);
|
|
||||||
$$ShortcutMembersTableTableManager get shortcutMembers =>
|
|
||||||
$$ShortcutMembersTableTableManager(
|
|
||||||
_db.attachedDatabase,
|
|
||||||
_db.shortcutMembers,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,251 +0,0 @@
|
||||||
import 'package:drift/drift.dart';
|
|
||||||
import 'package:twonly/src/database/tables/contacts.table.dart';
|
|
||||||
import 'package:twonly/src/database/tables/user_discovery.table.dart';
|
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
|
||||||
import 'package:twonly/src/utils/log.dart';
|
|
||||||
|
|
||||||
part 'user_discovery.dao.g.dart';
|
|
||||||
|
|
||||||
typedef AnnouncedUsersWithRelations =
|
|
||||||
Map<UserDiscoveryAnnouncedUser, List<(Contact, DateTime?)>>;
|
|
||||||
|
|
||||||
@DriftAccessor(
|
|
||||||
tables: [
|
|
||||||
UserDiscoveryAnnouncedUsers,
|
|
||||||
UserDiscoveryUserRelations,
|
|
||||||
UserDiscoveryOwnPromotions,
|
|
||||||
UserDiscoveryOtherPromotions,
|
|
||||||
UserDiscoveryShares,
|
|
||||||
Contacts,
|
|
||||||
],
|
|
||||||
)
|
|
||||||
class UserDiscoveryDao extends DatabaseAccessor<TwonlyDB>
|
|
||||||
with _$UserDiscoveryDaoMixin {
|
|
||||||
// this constructor is required so that the main database can create an instance
|
|
||||||
// of this object.
|
|
||||||
// ignore: matching_super_parameters
|
|
||||||
UserDiscoveryDao(super.db);
|
|
||||||
|
|
||||||
/// 1. Get count for contacts which are in announced but not in the contacts table
|
|
||||||
|
|
||||||
/// Returns all users which are not yet in the contacts table but have no data loaded (e.g. Avatar, username and display name)
|
|
||||||
Future<List<UserDiscoveryAnnouncedUser>>
|
|
||||||
getNewAnnouncementsWithoutData() async {
|
|
||||||
final query =
|
|
||||||
select(userDiscoveryAnnouncedUsers).join([
|
|
||||||
leftOuterJoin(
|
|
||||||
contacts,
|
|
||||||
contacts.userId.equalsExp(
|
|
||||||
userDiscoveryAnnouncedUsers.announcedUserId,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
])
|
|
||||||
// Apply filters:
|
|
||||||
// 1. The user must NOT exist in the contacts table
|
|
||||||
// 2. The username must be null
|
|
||||||
..where(
|
|
||||||
contacts.userId.isNull() &
|
|
||||||
userDiscoveryAnnouncedUsers.username.isNull(),
|
|
||||||
);
|
|
||||||
|
|
||||||
return (await query.get())
|
|
||||||
.map((row) => row.readTable(userDiscoveryAnnouncedUsers))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<AnnouncedUsersWithRelations>
|
|
||||||
getAllAnnouncedUsersWithRelations() async {
|
|
||||||
final query = select(userDiscoveryAnnouncedUsers).join([
|
|
||||||
innerJoin(
|
|
||||||
userDiscoveryUserRelations,
|
|
||||||
userDiscoveryUserRelations.announcedUserId.equalsExp(
|
|
||||||
userDiscoveryAnnouncedUsers.announcedUserId,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
innerJoin(
|
|
||||||
contacts,
|
|
||||||
contacts.userId.equalsExp(
|
|
||||||
userDiscoveryUserRelations.fromContactId,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
])..where(userDiscoveryAnnouncedUsers.username.isNotNull());
|
|
||||||
|
|
||||||
final rows = await query.get();
|
|
||||||
// ignore: omit_local_variable_types
|
|
||||||
final AnnouncedUsersWithRelations results = {};
|
|
||||||
|
|
||||||
for (final row in rows) {
|
|
||||||
final user = row.readTable(userDiscoveryAnnouncedUsers);
|
|
||||||
final relation = row.readTable(userDiscoveryUserRelations);
|
|
||||||
final contact = row.readTable(contacts);
|
|
||||||
|
|
||||||
final relationData = (
|
|
||||||
contact,
|
|
||||||
relation.publicKeyVerifiedTimestamp,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!results.containsKey(user)) {
|
|
||||||
results[user] = [];
|
|
||||||
}
|
|
||||||
results[user]!.add(relationData);
|
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream<AnnouncedUsersWithRelations> watchAllAnnouncedUsersWithRelations() {
|
|
||||||
final query = select(userDiscoveryAnnouncedUsers).join([
|
|
||||||
innerJoin(
|
|
||||||
userDiscoveryUserRelations,
|
|
||||||
userDiscoveryUserRelations.announcedUserId.equalsExp(
|
|
||||||
userDiscoveryAnnouncedUsers.announcedUserId,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
innerJoin(
|
|
||||||
contacts,
|
|
||||||
contacts.userId.equalsExp(
|
|
||||||
userDiscoveryUserRelations.fromContactId,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
])..where(userDiscoveryAnnouncedUsers.username.isNotNull());
|
|
||||||
|
|
||||||
return query.watch().map((rows) {
|
|
||||||
// ignore: omit_local_variable_types
|
|
||||||
final AnnouncedUsersWithRelations results = {};
|
|
||||||
|
|
||||||
for (final row in rows) {
|
|
||||||
final user = row.readTable(userDiscoveryAnnouncedUsers);
|
|
||||||
final relation = row.readTable(userDiscoveryUserRelations);
|
|
||||||
final contact = row.readTable(contacts);
|
|
||||||
|
|
||||||
final relationData = (
|
|
||||||
contact,
|
|
||||||
relation.publicKeyVerifiedTimestamp,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!results.containsKey(user)) {
|
|
||||||
results[user] = [];
|
|
||||||
}
|
|
||||||
results[user]!.add(relationData);
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.info('results = ${results.length}');
|
|
||||||
|
|
||||||
return results;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream<AnnouncedUsersWithRelations> watchNewAnnouncedUsersWithRelations() {
|
|
||||||
final announcedContact = alias(contacts, 'announcedContact');
|
|
||||||
final query =
|
|
||||||
select(userDiscoveryAnnouncedUsers).join([
|
|
||||||
innerJoin(
|
|
||||||
userDiscoveryUserRelations,
|
|
||||||
userDiscoveryUserRelations.announcedUserId.equalsExp(
|
|
||||||
userDiscoveryAnnouncedUsers.announcedUserId,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
innerJoin(
|
|
||||||
contacts,
|
|
||||||
contacts.userId.equalsExp(
|
|
||||||
userDiscoveryUserRelations.fromContactId,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
leftOuterJoin(
|
|
||||||
announcedContact,
|
|
||||||
announcedContact.userId.equalsExp(
|
|
||||||
userDiscoveryAnnouncedUsers.announcedUserId,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
])..where(
|
|
||||||
userDiscoveryAnnouncedUsers.username.isNotNull() &
|
|
||||||
userDiscoveryAnnouncedUsers.isHidden.equals(false) &
|
|
||||||
(announcedContact.userId.isNull() |
|
|
||||||
announcedContact.deletedByUser.equals(true)),
|
|
||||||
);
|
|
||||||
|
|
||||||
return query.watch().map((rows) {
|
|
||||||
// ignore: omit_local_variable_types
|
|
||||||
final AnnouncedUsersWithRelations results = {};
|
|
||||||
|
|
||||||
for (final row in rows) {
|
|
||||||
final user = row.readTable(userDiscoveryAnnouncedUsers);
|
|
||||||
final relation = row.readTable(userDiscoveryUserRelations);
|
|
||||||
final contact = row.readTable(contacts);
|
|
||||||
|
|
||||||
final relationData = (
|
|
||||||
contact,
|
|
||||||
relation.publicKeyVerifiedTimestamp,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!results.containsKey(user)) {
|
|
||||||
results[user] = [];
|
|
||||||
}
|
|
||||||
results[user]!.add(relationData);
|
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream<int> watchNewAnnouncementsWithDataCount() {
|
|
||||||
final countExp = userDiscoveryAnnouncedUsers.announcedUserId.count();
|
|
||||||
|
|
||||||
final query = selectOnly(userDiscoveryAnnouncedUsers)
|
|
||||||
..addColumns([countExp])
|
|
||||||
..where(
|
|
||||||
// Filters: Has a username AND has not been shown to the user yet
|
|
||||||
userDiscoveryAnnouncedUsers.username.isNotNull() &
|
|
||||||
userDiscoveryAnnouncedUsers.wasShownToTheUser.equals(false) &
|
|
||||||
userDiscoveryAnnouncedUsers.isHidden.equals(false),
|
|
||||||
);
|
|
||||||
|
|
||||||
return query.watchSingle().map((row) => row.read(countExp) ?? 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> markAllValidAnnouncedUsersAsShown() async {
|
|
||||||
await (update(userDiscoveryAnnouncedUsers)..where(
|
|
||||||
(t) =>
|
|
||||||
t.username.isNotNull() &
|
|
||||||
t.wasShownToTheUser.equals(false) &
|
|
||||||
t.isHidden.equals(false),
|
|
||||||
))
|
|
||||||
.write(
|
|
||||||
const UserDiscoveryAnnouncedUsersCompanion(
|
|
||||||
wasShownToTheUser: Value(true),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> updateAnnouncedUser(
|
|
||||||
int announcedUserId,
|
|
||||||
UserDiscoveryAnnouncedUsersCompanion updatedValues,
|
|
||||||
) async {
|
|
||||||
await (update(
|
|
||||||
userDiscoveryAnnouncedUsers,
|
|
||||||
)..where((c) => c.announcedUserId.equals(announcedUserId))).write(
|
|
||||||
updatedValues,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<UserDiscoveryAnnouncedUser?> getAnnouncedUserById(int id) async {
|
|
||||||
return (select(
|
|
||||||
userDiscoveryAnnouncedUsers,
|
|
||||||
)..where((tbl) => tbl.announcedUserId.equals(id))).getSingleOrNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream<List<UserDiscoveryAnnouncedUser>> watchAllAnnouncedUsers() =>
|
|
||||||
select(userDiscoveryAnnouncedUsers).watch();
|
|
||||||
|
|
||||||
Stream<List<UserDiscoveryUserRelation>> watchAllUserRelations() =>
|
|
||||||
select(userDiscoveryUserRelations).watch();
|
|
||||||
|
|
||||||
Stream<List<UserDiscoveryOwnPromotion>> watchAllOwnPromotions() =>
|
|
||||||
select(userDiscoveryOwnPromotions).watch();
|
|
||||||
|
|
||||||
Stream<List<UserDiscoveryOtherPromotion>> watchAllOtherPromotions() =>
|
|
||||||
select(userDiscoveryOtherPromotions).watch();
|
|
||||||
|
|
||||||
Stream<List<UserDiscoveryShare>> watchAllShares() =>
|
|
||||||
select(userDiscoveryShares).watch();
|
|
||||||
}
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'user_discovery.dao.dart';
|
|
||||||
|
|
||||||
// ignore_for_file: type=lint
|
|
||||||
mixin _$UserDiscoveryDaoMixin on DatabaseAccessor<TwonlyDB> {
|
|
||||||
$UserDiscoveryAnnouncedUsersTable get userDiscoveryAnnouncedUsers =>
|
|
||||||
attachedDatabase.userDiscoveryAnnouncedUsers;
|
|
||||||
$ContactsTable get contacts => attachedDatabase.contacts;
|
|
||||||
$UserDiscoveryUserRelationsTable get userDiscoveryUserRelations =>
|
|
||||||
attachedDatabase.userDiscoveryUserRelations;
|
|
||||||
$UserDiscoveryOwnPromotionsTable get userDiscoveryOwnPromotions =>
|
|
||||||
attachedDatabase.userDiscoveryOwnPromotions;
|
|
||||||
$UserDiscoveryOtherPromotionsTable get userDiscoveryOtherPromotions =>
|
|
||||||
attachedDatabase.userDiscoveryOtherPromotions;
|
|
||||||
$UserDiscoverySharesTable get userDiscoveryShares =>
|
|
||||||
attachedDatabase.userDiscoveryShares;
|
|
||||||
UserDiscoveryDaoManager get managers => UserDiscoveryDaoManager(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
class UserDiscoveryDaoManager {
|
|
||||||
final _$UserDiscoveryDaoMixin _db;
|
|
||||||
UserDiscoveryDaoManager(this._db);
|
|
||||||
$$UserDiscoveryAnnouncedUsersTableTableManager
|
|
||||||
get userDiscoveryAnnouncedUsers =>
|
|
||||||
$$UserDiscoveryAnnouncedUsersTableTableManager(
|
|
||||||
_db.attachedDatabase,
|
|
||||||
_db.userDiscoveryAnnouncedUsers,
|
|
||||||
);
|
|
||||||
$$ContactsTableTableManager get contacts =>
|
|
||||||
$$ContactsTableTableManager(_db.attachedDatabase, _db.contacts);
|
|
||||||
$$UserDiscoveryUserRelationsTableTableManager
|
|
||||||
get userDiscoveryUserRelations =>
|
|
||||||
$$UserDiscoveryUserRelationsTableTableManager(
|
|
||||||
_db.attachedDatabase,
|
|
||||||
_db.userDiscoveryUserRelations,
|
|
||||||
);
|
|
||||||
$$UserDiscoveryOwnPromotionsTableTableManager
|
|
||||||
get userDiscoveryOwnPromotions =>
|
|
||||||
$$UserDiscoveryOwnPromotionsTableTableManager(
|
|
||||||
_db.attachedDatabase,
|
|
||||||
_db.userDiscoveryOwnPromotions,
|
|
||||||
);
|
|
||||||
$$UserDiscoveryOtherPromotionsTableTableManager
|
|
||||||
get userDiscoveryOtherPromotions =>
|
|
||||||
$$UserDiscoveryOtherPromotionsTableTableManager(
|
|
||||||
_db.attachedDatabase,
|
|
||||||
_db.userDiscoveryOtherPromotions,
|
|
||||||
);
|
|
||||||
$$UserDiscoverySharesTableTableManager get userDiscoveryShares =>
|
|
||||||
$$UserDiscoverySharesTableTableManager(
|
|
||||||
_db.attachedDatabase,
|
|
||||||
_db.userDiscoveryShares,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,164 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
import 'package:drift/drift.dart';
|
|
||||||
import 'package:twonly/locator.dart';
|
|
||||||
import 'package:twonly/src/utils/log.dart';
|
|
||||||
|
|
||||||
class DriftLoggingInterceptor extends QueryInterceptor {
|
|
||||||
bool get _isEnabled {
|
|
||||||
try {
|
|
||||||
if (!userService.isUserCreated) return false;
|
|
||||||
return userService.currentUser.enableDatabaseLogging;
|
|
||||||
} catch (_) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> _findUuids(dynamic value) {
|
|
||||||
if (value == null) return const [];
|
|
||||||
final uuids = <String>[];
|
|
||||||
final uuidRegex = RegExp(
|
|
||||||
'[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}',
|
|
||||||
);
|
|
||||||
if (value is String) {
|
|
||||||
for (final match in uuidRegex.allMatches(value)) {
|
|
||||||
uuids.add(match.group(0)!);
|
|
||||||
}
|
|
||||||
} else if (value is Iterable) {
|
|
||||||
for (final element in value) {
|
|
||||||
uuids.addAll(_findUuids(element));
|
|
||||||
}
|
|
||||||
} else if (value is Map) {
|
|
||||||
for (final element in value.values) {
|
|
||||||
uuids.addAll(_findUuids(element));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
final str = value.toString();
|
|
||||||
for (final match in uuidRegex.allMatches(str)) {
|
|
||||||
uuids.add(match.group(0)!);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return uuids.toSet().toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<T> _run<T>(
|
|
||||||
String operation,
|
|
||||||
String statement,
|
|
||||||
List<Object?> args,
|
|
||||||
Future<T> Function() query,
|
|
||||||
) async {
|
|
||||||
if (!_isEnabled) {
|
|
||||||
return query();
|
|
||||||
}
|
|
||||||
final stopwatch = Stopwatch()..start();
|
|
||||||
try {
|
|
||||||
final result = await query();
|
|
||||||
final elapsed = stopwatch.elapsedMilliseconds;
|
|
||||||
final uuids = _findUuids(args);
|
|
||||||
if (uuids.isNotEmpty) {
|
|
||||||
Log.info(
|
|
||||||
'[DriftDB] $operation succeeded in ${elapsed}ms: "$statement" | UUIDs: $uuids',
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
Log.info(
|
|
||||||
'[DriftDB] $operation succeeded in ${elapsed}ms: "$statement"',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
} catch (e) {
|
|
||||||
final elapsed = stopwatch.elapsedMilliseconds;
|
|
||||||
final uuids = _findUuids(args);
|
|
||||||
if (uuids.isNotEmpty) {
|
|
||||||
Log.info(
|
|
||||||
'[DriftDB] $operation failed after ${elapsed}ms ($e): "$statement" | UUIDs: $uuids',
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
Log.info(
|
|
||||||
'[DriftDB] $operation failed after ${elapsed}ms ($e): "$statement"',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<int> runInsert(
|
|
||||||
QueryExecutor executor,
|
|
||||||
String statement,
|
|
||||||
List<Object?> args,
|
|
||||||
) {
|
|
||||||
return _run('INSERT', statement, args, () => executor.runInsert(statement, args));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<int> runUpdate(
|
|
||||||
QueryExecutor executor,
|
|
||||||
String statement,
|
|
||||||
List<Object?> args,
|
|
||||||
) {
|
|
||||||
return _run('UPDATE', statement, args, () => executor.runUpdate(statement, args));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<int> runDelete(
|
|
||||||
QueryExecutor executor,
|
|
||||||
String statement,
|
|
||||||
List<Object?> args,
|
|
||||||
) {
|
|
||||||
return _run('DELETE', statement, args, () => executor.runDelete(statement, args));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> runCustom(
|
|
||||||
QueryExecutor executor,
|
|
||||||
String statement,
|
|
||||||
List<Object?> args,
|
|
||||||
) {
|
|
||||||
return _run('CUSTOM', statement, args, () => executor.runCustom(statement, args));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> runBatched(
|
|
||||||
QueryExecutor executor,
|
|
||||||
BatchedStatements statements,
|
|
||||||
) async {
|
|
||||||
if (!_isEnabled) {
|
|
||||||
return executor.runBatched(statements);
|
|
||||||
}
|
|
||||||
final stopwatch = Stopwatch()..start();
|
|
||||||
try {
|
|
||||||
await executor.runBatched(statements);
|
|
||||||
final elapsed = stopwatch.elapsedMilliseconds;
|
|
||||||
final uuids = <String>[];
|
|
||||||
for (final batchArg in statements.arguments) {
|
|
||||||
uuids.addAll(_findUuids(batchArg.arguments));
|
|
||||||
}
|
|
||||||
final statementsStr = statements.statements.join('; ');
|
|
||||||
if (uuids.isNotEmpty) {
|
|
||||||
Log.info(
|
|
||||||
'[DriftDB] BATCH succeeded in ${elapsed}ms: "$statementsStr" | UUIDs: $uuids',
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
Log.info(
|
|
||||||
'[DriftDB] BATCH succeeded in ${elapsed}ms: "$statementsStr"',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
final elapsed = stopwatch.elapsedMilliseconds;
|
|
||||||
final uuids = <String>[];
|
|
||||||
for (final batchArg in statements.arguments) {
|
|
||||||
uuids.addAll(_findUuids(batchArg.arguments));
|
|
||||||
}
|
|
||||||
final statementsStr = statements.statements.join('; ');
|
|
||||||
if (uuids.isNotEmpty) {
|
|
||||||
Log.info(
|
|
||||||
'[DriftDB] BATCH failed after ${elapsed}ms ($e): "$statementsStr" | UUIDs: $uuids',
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
Log.info(
|
|
||||||
'[DriftDB] BATCH failed after ${elapsed}ms ($e): "$statementsStr"',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,11 +1,11 @@
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
|
|
||||||
class SignalIdentityKeyStore extends IdentityKeyStore {
|
class ConnectIdentityKeyStore extends IdentityKeyStore {
|
||||||
SignalIdentityKeyStore(this.identityKeyPair, this.localRegistrationId);
|
ConnectIdentityKeyStore(this.identityKeyPair, this.localRegistrationId);
|
||||||
|
|
||||||
final IdentityKeyPair identityKeyPair;
|
final IdentityKeyPair identityKeyPair;
|
||||||
final int localRegistrationId;
|
final int localRegistrationId;
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
class SignalPreKeyStore 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(
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
|
|
||||||
class SignalSenderKeyStore extends SenderKeyStore {
|
class ConnectSenderKeyStore extends SenderKeyStore {
|
||||||
@override
|
@override
|
||||||
Future<SenderKeyRecord> loadSenderKey(SenderKeyName senderKeyName) async {
|
Future<SenderKeyRecord> loadSenderKey(SenderKeyName senderKeyName) async {
|
||||||
final identity =
|
final identity =
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
|
|
||||||
class SignalSessionStore extends SessionStore {
|
class ConnectSessionStore extends SessionStore {
|
||||||
@override
|
@override
|
||||||
Future<bool> containsSession(SignalProtocolAddress address) async {
|
Future<bool> containsSession(SignalProtocolAddress address) async {
|
||||||
final sessions =
|
final sessions =
|
||||||
|
|
@ -1,23 +1,23 @@
|
||||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||||
import 'package:twonly/src/database/signal/signal_identity_key_store.dart';
|
import 'package:twonly/src/database/signal/connect_identity_key_store.dart';
|
||||||
import 'package:twonly/src/database/signal/signal_pre_key_store.dart';
|
import 'package:twonly/src/database/signal/connect_pre_key_store.dart';
|
||||||
import 'package:twonly/src/database/signal/signal_session_store.dart';
|
import 'package:twonly/src/database/signal/connect_session_store.dart';
|
||||||
import 'package:twonly/src/database/signal/signal_signed_pre_key_store.dart';
|
import 'package:twonly/src/database/signal/connect_signed_pre_key_store.dart';
|
||||||
|
|
||||||
class SignalSignalProtocolStore implements SignalProtocolStore {
|
class ConnectSignalProtocolStore implements SignalProtocolStore {
|
||||||
SignalSignalProtocolStore(
|
ConnectSignalProtocolStore(
|
||||||
IdentityKeyPair identityKeyPair,
|
IdentityKeyPair identityKeyPair,
|
||||||
int registrationId,
|
int registrationId,
|
||||||
) {
|
) {
|
||||||
_identityKeyStore = SignalIdentityKeyStore(
|
_identityKeyStore = ConnectIdentityKeyStore(
|
||||||
identityKeyPair,
|
identityKeyPair,
|
||||||
registrationId,
|
registrationId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final preKeyStore = SignalPreKeyStore();
|
final preKeyStore = ConnectPreKeyStore();
|
||||||
final sessionStore = SignalSessionStore();
|
final sessionStore = ConnectSessionStore();
|
||||||
final signedPreKeyStore = SignalSignedPreKeyStore();
|
final signedPreKeyStore = ConnectSignedPreKeyStore();
|
||||||
|
|
||||||
late IdentityKeyStore _identityKeyStore;
|
late IdentityKeyStore _identityKeyStore;
|
||||||
|
|
||||||
80
lib/src/database/signal/connect_signed_pre_key_store.dart
Normal file
80
lib/src/database/signal/connect_signed_pre_key_store.dart
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
import 'dart:collection';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
|
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||||
|
import 'package:twonly/src/constants/secure_storage_keys.dart';
|
||||||
|
|
||||||
|
class ConnectSignedPreKeyStore extends SignedPreKeyStore {
|
||||||
|
Future<HashMap<int, Uint8List>> getStore() async {
|
||||||
|
const storage = FlutterSecureStorage();
|
||||||
|
final storeSerialized = await storage.read(
|
||||||
|
key: SecureStorageKeys.signalSignedPreKey,
|
||||||
|
);
|
||||||
|
final store = HashMap<int, Uint8List>();
|
||||||
|
if (storeSerialized == null) {
|
||||||
|
return store;
|
||||||
|
}
|
||||||
|
final storeHashMap = json.decode(storeSerialized) as List<dynamic>;
|
||||||
|
for (final item in storeHashMap) {
|
||||||
|
// ignore: avoid_dynamic_calls
|
||||||
|
store[item[0] as int] = base64Decode(item[1] as String);
|
||||||
|
}
|
||||||
|
return store;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> safeStore(HashMap<int, Uint8List> store) async {
|
||||||
|
const storage = FlutterSecureStorage();
|
||||||
|
final storeHashMap = <List<dynamic>>[];
|
||||||
|
for (final item in store.entries) {
|
||||||
|
storeHashMap.add([item.key, base64Encode(item.value)]);
|
||||||
|
}
|
||||||
|
final storeSerialized = json.encode(storeHashMap);
|
||||||
|
await storage.write(
|
||||||
|
key: SecureStorageKeys.signalSignedPreKey,
|
||||||
|
value: storeSerialized,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<SignedPreKeyRecord> loadSignedPreKey(int signedPreKeyId) async {
|
||||||
|
final store = await getStore();
|
||||||
|
if (!store.containsKey(signedPreKeyId)) {
|
||||||
|
throw InvalidKeyIdException(
|
||||||
|
'No such signed prekey record! $signedPreKeyId',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return SignedPreKeyRecord.fromSerialized(store[signedPreKeyId]!);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<SignedPreKeyRecord>> loadSignedPreKeys() async {
|
||||||
|
final store = await getStore();
|
||||||
|
final results = <SignedPreKeyRecord>[];
|
||||||
|
for (final serialized in store.values) {
|
||||||
|
results.add(SignedPreKeyRecord.fromSerialized(serialized));
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> storeSignedPreKey(
|
||||||
|
int signedPreKeyId,
|
||||||
|
SignedPreKeyRecord record,
|
||||||
|
) async {
|
||||||
|
final store = await getStore();
|
||||||
|
store[signedPreKeyId] = record.serialize();
|
||||||
|
await safeStore(store);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> containsSignedPreKey(int signedPreKeyId) async =>
|
||||||
|
(await getStore()).containsKey(signedPreKeyId);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> removeSignedPreKey(int signedPreKeyId) async {
|
||||||
|
final store = await getStore();
|
||||||
|
store.remove(signedPreKeyId);
|
||||||
|
await safeStore(store);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
import 'dart:collection';
|
|
||||||
import 'dart:convert';
|
|
||||||
import 'package:drift/drift.dart';
|
|
||||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
|
||||||
import 'package:twonly/locator.dart';
|
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
|
||||||
import 'package:twonly/src/utils/log.dart';
|
|
||||||
import 'package:twonly/src/utils/secure_storage.dart';
|
|
||||||
|
|
||||||
Future<HashMap<int, Uint8List>> getSignalSignedPreKeyStoreOld() async {
|
|
||||||
final storeSerialized = await SecureStorage.instance.read(
|
|
||||||
key: 'signed_pre_key_store',
|
|
||||||
);
|
|
||||||
final store = HashMap<int, Uint8List>();
|
|
||||||
if (storeSerialized == null) {
|
|
||||||
return store;
|
|
||||||
}
|
|
||||||
final storeHashMap = json.decode(storeSerialized) as List<dynamic>;
|
|
||||||
for (final item in storeHashMap) {
|
|
||||||
// ignore: avoid_dynamic_calls
|
|
||||||
store[item[0] as int] = base64Decode(item[1] as String);
|
|
||||||
}
|
|
||||||
return store;
|
|
||||||
}
|
|
||||||
|
|
||||||
class SignalSignedPreKeyStore extends SignedPreKeyStore {
|
|
||||||
@override
|
|
||||||
Future<SignedPreKeyRecord> loadSignedPreKey(int signedPreKeyId) async {
|
|
||||||
final record = await (twonlyDB.select(
|
|
||||||
twonlyDB.signalSignedPreKeyStores,
|
|
||||||
)..where((tbl) => tbl.signedPreKeyId.equals(signedPreKeyId))).get();
|
|
||||||
if (record.isEmpty) {
|
|
||||||
throw InvalidKeyIdException(
|
|
||||||
'No such signed prekey record! $signedPreKeyId',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return SignedPreKeyRecord.fromSerialized(record.first.signedPreKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<SignedPreKeyRecord>> loadSignedPreKeys() async {
|
|
||||||
final records = await twonlyDB
|
|
||||||
.select(twonlyDB.signalSignedPreKeyStores)
|
|
||||||
.get();
|
|
||||||
return records
|
|
||||||
.map((r) => SignedPreKeyRecord.fromSerialized(r.signedPreKey))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> storeSignedPreKey(
|
|
||||||
int signedPreKeyId,
|
|
||||||
SignedPreKeyRecord record,
|
|
||||||
) async {
|
|
||||||
final companion = SignalSignedPreKeyStoresCompanion(
|
|
||||||
signedPreKeyId: Value(signedPreKeyId),
|
|
||||||
signedPreKey: Value(record.serialize()),
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await twonlyDB
|
|
||||||
.into(twonlyDB.signalSignedPreKeyStores)
|
|
||||||
.insert(companion, mode: InsertMode.insertOrReplace);
|
|
||||||
} catch (e) {
|
|
||||||
Log.error('$e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<bool> containsSignedPreKey(int signedPreKeyId) async {
|
|
||||||
final record = await (twonlyDB.select(
|
|
||||||
twonlyDB.signalSignedPreKeyStores,
|
|
||||||
)..where((tbl) => tbl.signedPreKeyId.equals(signedPreKeyId))).get();
|
|
||||||
return record.isNotEmpty;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> removeSignedPreKey(int signedPreKeyId) async {
|
|
||||||
await (twonlyDB.delete(
|
|
||||||
twonlyDB.signalSignedPreKeyStores,
|
|
||||||
)..where((tbl) => tbl.signedPreKeyId.equals(signedPreKeyId))).go();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
@DataClassName('Contact')
|
|
||||||
class Contacts extends Table {
|
class Contacts extends Table {
|
||||||
IntColumn get userId => integer()();
|
IntColumn get userId => integer()();
|
||||||
|
|
||||||
|
|
@ -23,46 +22,6 @@ class Contacts extends Table {
|
||||||
|
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
||||||
|
|
||||||
// contact_versions: HashMap<UserID, Vec<u8>>,
|
|
||||||
BlobColumn get userDiscoveryVersion => blob().nullable()();
|
|
||||||
|
|
||||||
BoolColumn get userDiscoveryExcluded =>
|
|
||||||
boolean().withDefault(const Constant(false))();
|
|
||||||
|
|
||||||
BoolColumn get userDiscoveryManualApproved =>
|
|
||||||
boolean().nullable().withDefault(const Constant(false))();
|
|
||||||
|
|
||||||
IntColumn get mediaSendCounter => integer().withDefault(const Constant(0))();
|
|
||||||
IntColumn get mediaReceivedCounter =>
|
|
||||||
integer().withDefault(const Constant(0))();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {userId};
|
Set<Column> get primaryKey => {userId};
|
||||||
}
|
}
|
||||||
|
|
||||||
enum VerificationType {
|
|
||||||
migratedFromOldVersion,
|
|
||||||
qrScanned,
|
|
||||||
link,
|
|
||||||
secretQrToken,
|
|
||||||
contactSharedByVerified,
|
|
||||||
}
|
|
||||||
|
|
||||||
@DataClassName('KeyVerification')
|
|
||||||
class KeyVerifications extends Table {
|
|
||||||
IntColumn get verificationId => integer().autoIncrement()();
|
|
||||||
IntColumn get contactId => integer().references(
|
|
||||||
Contacts,
|
|
||||||
#userId,
|
|
||||||
onDelete: KeyAction.cascade,
|
|
||||||
)();
|
|
||||||
TextColumn get type => textEnum<VerificationType>()();
|
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
|
||||||
}
|
|
||||||
|
|
||||||
@DataClassName('VerificationToken')
|
|
||||||
class VerificationTokens extends Table {
|
|
||||||
IntColumn get tokenId => integer().autoIncrement()();
|
|
||||||
BlobColumn get token => blob()();
|
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -83,8 +83,6 @@ enum GroupActionType {
|
||||||
demoteToMember,
|
demoteToMember,
|
||||||
updatedGroupName,
|
updatedGroupName,
|
||||||
changeDisplayMaxTime,
|
changeDisplayMaxTime,
|
||||||
updatedContactUsername,
|
|
||||||
updatedContactDisplayName,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@DataClassName('GroupHistory')
|
@DataClassName('GroupHistory')
|
||||||
|
|
|
||||||
|
|
@ -50,9 +50,6 @@ class MediaFiles extends Table {
|
||||||
|
|
||||||
BoolColumn get stored => boolean().withDefault(const Constant(false))();
|
BoolColumn get stored => boolean().withDefault(const Constant(false))();
|
||||||
BoolColumn get isDraftMedia => boolean().withDefault(const Constant(false))();
|
BoolColumn get isDraftMedia => boolean().withDefault(const Constant(false))();
|
||||||
BoolColumn get isFavorite => boolean().withDefault(const Constant(false))();
|
|
||||||
BoolColumn get hasCropAnalyzed =>
|
|
||||||
boolean().withDefault(const Constant(false))();
|
|
||||||
|
|
||||||
IntColumn get preProgressingProcess => integer().nullable()();
|
IntColumn get preProgressingProcess => integer().nullable()();
|
||||||
|
|
||||||
|
|
@ -69,14 +66,7 @@ class MediaFiles extends Table {
|
||||||
|
|
||||||
BlobColumn get storedFileHash => blob().nullable()();
|
BlobColumn get storedFileHash => blob().nullable()();
|
||||||
|
|
||||||
BoolColumn get hasThumbnail =>
|
|
||||||
boolean().withDefault(const Constant(false))();
|
|
||||||
|
|
||||||
IntColumn get sizeInBytes => integer().nullable()();
|
|
||||||
|
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
||||||
TextColumn get createdAtMonth => text().nullable()();
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {mediaId};
|
Set<Column> get primaryKey => {mediaId};
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import 'package:twonly/src/database/tables/contacts.table.dart';
|
||||||
import 'package:twonly/src/database/tables/groups.table.dart';
|
import 'package:twonly/src/database/tables/groups.table.dart';
|
||||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||||
|
|
||||||
enum MessageType { media, text, contacts, restoreFlameCounter, askAboutUser }
|
enum MessageType { media, text, contacts, restoreFlameCounter }
|
||||||
|
|
||||||
@DataClassName('Message')
|
@DataClassName('Message')
|
||||||
class Messages extends Table {
|
class Messages extends Table {
|
||||||
|
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
import 'package:drift/drift.dart';
|
|
||||||
import 'package:twonly/src/database/tables/groups.table.dart';
|
|
||||||
|
|
||||||
@DataClassName('Shortcut')
|
|
||||||
class Shortcuts extends Table {
|
|
||||||
IntColumn get id => integer().autoIncrement()();
|
|
||||||
TextColumn get emoji => text().unique()();
|
|
||||||
IntColumn get usageCounter => integer().withDefault(const Constant(0))();
|
|
||||||
}
|
|
||||||
|
|
||||||
@DataClassName('ShortcutMember')
|
|
||||||
class ShortcutMembers extends Table {
|
|
||||||
IntColumn get shortcutId => integer().references(
|
|
||||||
Shortcuts,
|
|
||||||
#id,
|
|
||||||
onDelete: KeyAction.cascade,
|
|
||||||
)();
|
|
||||||
TextColumn get groupId => text().references(
|
|
||||||
Groups,
|
|
||||||
#groupId,
|
|
||||||
onDelete: KeyAction.cascade,
|
|
||||||
)();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Set<Column> get primaryKey => {shortcutId, groupId};
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
import 'package:drift/drift.dart';
|
|
||||||
|
|
||||||
@DataClassName('SignalSignedPreKeyStore')
|
|
||||||
class SignalSignedPreKeyStores extends Table {
|
|
||||||
IntColumn get signedPreKeyId => integer()();
|
|
||||||
BlobColumn get signedPreKey => blob()();
|
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Set<Column> get primaryKey => {signedPreKeyId};
|
|
||||||
}
|
|
||||||
|
|
@ -1,89 +0,0 @@
|
||||||
import 'package:drift/drift.dart';
|
|
||||||
import 'package:twonly/src/database/tables/contacts.table.dart';
|
|
||||||
|
|
||||||
// config: Option<Vec<u8>>,
|
|
||||||
|
|
||||||
// announced_users: HashMap<AnnouncedUser, Vec<(UserID, Option<i64>)>>,
|
|
||||||
@DataClassName('UserDiscoveryAnnouncedUser')
|
|
||||||
class UserDiscoveryAnnouncedUsers extends Table {
|
|
||||||
IntColumn get announcedUserId => integer()();
|
|
||||||
BlobColumn get announcedPublicKey => blob()();
|
|
||||||
IntColumn get publicId => integer().unique()();
|
|
||||||
|
|
||||||
// When a new user got announced this data will be requested without adding the users to the contacts...
|
|
||||||
TextColumn get username => text().nullable()();
|
|
||||||
|
|
||||||
BoolColumn get wasShownToTheUser =>
|
|
||||||
boolean().withDefault(const Constant(false))();
|
|
||||||
BoolColumn get isHidden => boolean().withDefault(const Constant(false))();
|
|
||||||
BoolColumn get wasAskedFriends =>
|
|
||||||
boolean().withDefault(const Constant(false))();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Set<Column> get primaryKey => {announcedUserId};
|
|
||||||
}
|
|
||||||
|
|
||||||
// announced_users: HashMap<AnnouncedUser, Vec<(UserID, Option<i64>)>>,
|
|
||||||
@DataClassName('UserDiscoveryUserRelation')
|
|
||||||
class UserDiscoveryUserRelations extends Table {
|
|
||||||
IntColumn get announcedUserId => integer().references(
|
|
||||||
UserDiscoveryAnnouncedUsers,
|
|
||||||
#announcedUserId,
|
|
||||||
onDelete: KeyAction.cascade,
|
|
||||||
)();
|
|
||||||
|
|
||||||
IntColumn get fromContactId => integer().references(
|
|
||||||
Contacts,
|
|
||||||
#userId,
|
|
||||||
onDelete: KeyAction.cascade,
|
|
||||||
)();
|
|
||||||
|
|
||||||
DateTimeColumn get publicKeyVerifiedTimestamp => dateTime().nullable()();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Set<Column> get primaryKey => {announcedUserId, fromContactId};
|
|
||||||
}
|
|
||||||
|
|
||||||
// own_promotions: Vec<(UserID, Vec<u8>)>,
|
|
||||||
@DataClassName('UserDiscoveryOwnPromotion')
|
|
||||||
class UserDiscoveryOwnPromotions extends Table {
|
|
||||||
IntColumn get versionId => integer().autoIncrement()();
|
|
||||||
IntColumn get contactId => integer().references(
|
|
||||||
Contacts,
|
|
||||||
#userId,
|
|
||||||
onDelete: KeyAction.cascade,
|
|
||||||
)();
|
|
||||||
BlobColumn get promotion => blob()();
|
|
||||||
}
|
|
||||||
|
|
||||||
// other_promotions: Vec<OtherPromotion>,
|
|
||||||
@DataClassName('UserDiscoveryOtherPromotion')
|
|
||||||
class UserDiscoveryOtherPromotions extends Table {
|
|
||||||
IntColumn get fromContactId => integer().references(
|
|
||||||
Contacts,
|
|
||||||
#userId,
|
|
||||||
onDelete: KeyAction.cascade,
|
|
||||||
)();
|
|
||||||
|
|
||||||
IntColumn get promotionId => integer()();
|
|
||||||
IntColumn get publicId => integer()();
|
|
||||||
IntColumn get threshold => integer()();
|
|
||||||
BlobColumn get announcementShare => blob()();
|
|
||||||
DateTimeColumn get publicKeyVerifiedTimestamp => dateTime().nullable()();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Set<Column> get primaryKey => {fromContactId, publicId};
|
|
||||||
}
|
|
||||||
|
|
||||||
// unused_shares: Vec<Vec<u8>>,
|
|
||||||
// used_shares: HashMap<UserID, Vec<u8>>,
|
|
||||||
@DataClassName('UserDiscoveryShare')
|
|
||||||
class UserDiscoveryShares extends Table {
|
|
||||||
IntColumn get shareId => integer().autoIncrement()();
|
|
||||||
BlobColumn get share => blob()();
|
|
||||||
IntColumn get contactId => integer().nullable().references(
|
|
||||||
Contacts,
|
|
||||||
#userId,
|
|
||||||
onDelete: KeyAction.cascade,
|
|
||||||
)();
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +1,24 @@
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:drift_flutter/drift_flutter.dart'
|
import 'package:drift_flutter/drift_flutter.dart'
|
||||||
show DriftNativeOptions, driftDatabase;
|
show DriftNativeOptions, driftDatabase;
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:twonly/locator.dart';
|
|
||||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||||
import 'package:twonly/src/database/daos/groups.dao.dart';
|
import 'package:twonly/src/database/daos/groups.dao.dart';
|
||||||
import 'package:twonly/src/database/daos/key_verification.dao.dart';
|
|
||||||
import 'package:twonly/src/database/daos/mediafiles.dao.dart';
|
import 'package:twonly/src/database/daos/mediafiles.dao.dart';
|
||||||
import 'package:twonly/src/database/daos/messages.dao.dart';
|
import 'package:twonly/src/database/daos/messages.dao.dart';
|
||||||
import 'package:twonly/src/database/daos/reactions.dao.dart';
|
import 'package:twonly/src/database/daos/reactions.dao.dart';
|
||||||
import 'package:twonly/src/database/daos/receipts.dao.dart';
|
import 'package:twonly/src/database/daos/receipts.dao.dart';
|
||||||
import 'package:twonly/src/database/daos/shortcuts.dao.dart';
|
|
||||||
import 'package:twonly/src/database/daos/user_discovery.dao.dart';
|
|
||||||
import 'package:twonly/src/database/drift_logging_interceptor.dart';
|
|
||||||
import 'package:twonly/src/database/tables/contacts.table.dart';
|
import 'package:twonly/src/database/tables/contacts.table.dart';
|
||||||
import 'package:twonly/src/database/tables/groups.table.dart';
|
import 'package:twonly/src/database/tables/groups.table.dart';
|
||||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||||
import 'package:twonly/src/database/tables/messages.table.dart';
|
import 'package:twonly/src/database/tables/messages.table.dart';
|
||||||
import 'package:twonly/src/database/tables/reactions.table.dart';
|
import 'package:twonly/src/database/tables/reactions.table.dart';
|
||||||
import 'package:twonly/src/database/tables/receipts.table.dart';
|
import 'package:twonly/src/database/tables/receipts.table.dart';
|
||||||
import 'package:twonly/src/database/tables/shortcuts.table.dart';
|
|
||||||
import 'package:twonly/src/database/tables/signal_identity_key_store.table.dart';
|
import 'package:twonly/src/database/tables/signal_identity_key_store.table.dart';
|
||||||
import 'package:twonly/src/database/tables/signal_pre_key_store.table.dart';
|
import 'package:twonly/src/database/tables/signal_pre_key_store.table.dart';
|
||||||
import 'package:twonly/src/database/tables/signal_sender_key_store.table.dart';
|
import 'package:twonly/src/database/tables/signal_sender_key_store.table.dart';
|
||||||
import 'package:twonly/src/database/tables/signal_session_store.table.dart';
|
import 'package:twonly/src/database/tables/signal_session_store.table.dart';
|
||||||
import 'package:twonly/src/database/tables/signal_signed_pre_key_store.table.dart';
|
|
||||||
import 'package:twonly/src/database/tables/user_discovery.table.dart';
|
|
||||||
import 'package:twonly/src/database/twonly.db.steps.dart';
|
import 'package:twonly/src/database/twonly.db.steps.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
|
|
@ -47,18 +40,8 @@ part 'twonly.db.g.dart';
|
||||||
SignalPreKeyStores,
|
SignalPreKeyStores,
|
||||||
SignalSenderKeyStores,
|
SignalSenderKeyStores,
|
||||||
SignalSessionStores,
|
SignalSessionStores,
|
||||||
SignalSignedPreKeyStores,
|
|
||||||
MessageActions,
|
MessageActions,
|
||||||
GroupHistories,
|
GroupHistories,
|
||||||
KeyVerifications,
|
|
||||||
VerificationTokens,
|
|
||||||
UserDiscoveryAnnouncedUsers,
|
|
||||||
UserDiscoveryUserRelations,
|
|
||||||
UserDiscoveryOtherPromotions,
|
|
||||||
UserDiscoveryOwnPromotions,
|
|
||||||
UserDiscoveryShares,
|
|
||||||
Shortcuts,
|
|
||||||
ShortcutMembers,
|
|
||||||
],
|
],
|
||||||
daos: [
|
daos: [
|
||||||
MessagesDao,
|
MessagesDao,
|
||||||
|
|
@ -67,9 +50,6 @@ part 'twonly.db.g.dart';
|
||||||
GroupsDao,
|
GroupsDao,
|
||||||
ReactionsDao,
|
ReactionsDao,
|
||||||
MediaFilesDao,
|
MediaFilesDao,
|
||||||
UserDiscoveryDao,
|
|
||||||
KeyVerificationDao,
|
|
||||||
ShortcutsDao,
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
class TwonlyDB extends _$TwonlyDB {
|
class TwonlyDB extends _$TwonlyDB {
|
||||||
|
|
@ -82,29 +62,16 @@ class TwonlyDB extends _$TwonlyDB {
|
||||||
TwonlyDB.forTesting(DatabaseConnection super.connection);
|
TwonlyDB.forTesting(DatabaseConnection super.connection);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get schemaVersion => 17;
|
int get schemaVersion => 11;
|
||||||
|
|
||||||
static QueryExecutor _openConnection() {
|
static QueryExecutor _openConnection() {
|
||||||
final connection = driftDatabase(
|
return driftDatabase(
|
||||||
name: 'twonly',
|
name: 'twonly',
|
||||||
native: DriftNativeOptions(
|
native: const DriftNativeOptions(
|
||||||
databaseDirectory: getApplicationSupportDirectory,
|
databaseDirectory: getApplicationSupportDirectory,
|
||||||
shareAcrossIsolates: true,
|
shareAcrossIsolates: true,
|
||||||
setup: (rawDb) {
|
|
||||||
rawDb
|
|
||||||
..execute('PRAGMA journal_mode=DELETE;')
|
|
||||||
..execute('PRAGMA synchronous=FULL;')
|
|
||||||
..execute('PRAGMA busy_timeout=5000;');
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
try {
|
|
||||||
if (userService.isUserCreated &&
|
|
||||||
userService.currentUser.enableDatabaseLogging) {
|
|
||||||
return connection.interceptWith(DriftLoggingInterceptor());
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
return connection;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -126,6 +93,7 @@ class TwonlyDB extends _$TwonlyDB {
|
||||||
},
|
},
|
||||||
from3To4: (m, schema) async {
|
from3To4: (m, schema) async {
|
||||||
await m.alterTable(
|
await m.alterTable(
|
||||||
|
// ignore: experimental_member_use
|
||||||
TableMigration(
|
TableMigration(
|
||||||
schema.groupHistories,
|
schema.groupHistories,
|
||||||
columnTransformer: {
|
columnTransformer: {
|
||||||
|
|
@ -158,7 +126,9 @@ class TwonlyDB extends _$TwonlyDB {
|
||||||
await m.deleteTable('signal_contact_pre_keys');
|
await m.deleteTable('signal_contact_pre_keys');
|
||||||
await m.deleteTable('signal_contact_signed_pre_keys');
|
await m.deleteTable('signal_contact_signed_pre_keys');
|
||||||
// For message_actions
|
// For message_actions
|
||||||
|
// ignore: experimental_member_use
|
||||||
await m.alterTable(TableMigration(schema.messageHistories));
|
await m.alterTable(TableMigration(schema.messageHistories));
|
||||||
|
// ignore: experimental_member_use
|
||||||
await m.alterTable(TableMigration(schema.messageActions));
|
await m.alterTable(TableMigration(schema.messageActions));
|
||||||
},
|
},
|
||||||
from8To9: (m, schema) async {
|
from8To9: (m, schema) async {
|
||||||
|
|
@ -183,56 +153,6 @@ class TwonlyDB extends _$TwonlyDB {
|
||||||
schema.groupMembers.lastTypeIndicator,
|
schema.groupMembers.lastTypeIndicator,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
from11To12: (m, schema) async {
|
|
||||||
await m.createTable(schema.verificationTokens);
|
|
||||||
await m.createTable(schema.keyVerifications);
|
|
||||||
await m.createTable(schema.userDiscoveryAnnouncedUsers);
|
|
||||||
await m.createTable(schema.userDiscoveryOwnPromotions);
|
|
||||||
await m.createTable(schema.userDiscoveryOtherPromotions);
|
|
||||||
await m.createTable(schema.userDiscoveryShares);
|
|
||||||
await m.createTable(schema.userDiscoveryUserRelations);
|
|
||||||
final columns = [
|
|
||||||
schema.contacts.userDiscoveryVersion,
|
|
||||||
schema.contacts.mediaReceivedCounter,
|
|
||||||
schema.contacts.mediaSendCounter,
|
|
||||||
schema.contacts.userDiscoveryExcluded,
|
|
||||||
schema.contacts.userDiscoveryManualApproved,
|
|
||||||
];
|
|
||||||
for (final column in columns) {
|
|
||||||
await m.addColumn(schema.contacts, column);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
from12To13: (m, schema) async {
|
|
||||||
await m.createTable(schema.shortcuts);
|
|
||||||
await m.createTable(schema.shortcutMembers);
|
|
||||||
},
|
|
||||||
from13To14: (m, schema) async {
|
|
||||||
await m.addColumn(
|
|
||||||
schema.mediaFiles,
|
|
||||||
schema.mediaFiles.createdAtMonth,
|
|
||||||
);
|
|
||||||
await m.addColumn(schema.mediaFiles, schema.mediaFiles.isFavorite);
|
|
||||||
await m.addColumn(
|
|
||||||
schema.mediaFiles,
|
|
||||||
schema.mediaFiles.hasCropAnalyzed,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
from14To15: (m, schema) async {
|
|
||||||
await m.createTable(schema.signalSignedPreKeyStores);
|
|
||||||
},
|
|
||||||
from15To16: (m, schema) async {
|
|
||||||
await m.addColumn(
|
|
||||||
schema.mediaFiles,
|
|
||||||
schema.mediaFiles.hasThumbnail,
|
|
||||||
);
|
|
||||||
await m.addColumn(schema.mediaFiles, schema.mediaFiles.sizeInBytes);
|
|
||||||
},
|
|
||||||
from16To17: (m, schema) async {
|
|
||||||
await m.addColumn(
|
|
||||||
schema.userDiscoveryAnnouncedUsers,
|
|
||||||
schema.userDiscoveryAnnouncedUsers.wasAskedFriends,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
)(m, from, to);
|
)(m, from, to);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -254,4 +174,38 @@ class TwonlyDB extends _$TwonlyDB {
|
||||||
Log.info('Table: $tableName, Size: $tableSize bytes');
|
Log.info('Table: $tableName, Size: $tableSize bytes');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> deleteDataForTwonlySafe() async {
|
||||||
|
await (delete(messages)..where(
|
||||||
|
(t) =>
|
||||||
|
(t.mediaStored.equals(false) &
|
||||||
|
t.isDeletedFromSender.equals(false)),
|
||||||
|
))
|
||||||
|
.go();
|
||||||
|
await update(messages).write(
|
||||||
|
const MessagesCompanion(
|
||||||
|
downloadToken: Value(null),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await (delete(mediaFiles)..where(
|
||||||
|
(t) => (t.stored.equals(false)),
|
||||||
|
))
|
||||||
|
.go();
|
||||||
|
await delete(receipts).go();
|
||||||
|
await delete(receivedReceipts).go();
|
||||||
|
await update(contacts).write(
|
||||||
|
const ContactsCompanion(
|
||||||
|
avatarSvgCompressed: Value(null),
|
||||||
|
senderProfileCounter: Value(0),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await (delete(signalPreKeyStores)..where(
|
||||||
|
(t) => (t.createdAt.isSmallerThanValue(
|
||||||
|
clock.now().subtract(
|
||||||
|
const Duration(days: 25),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
))
|
||||||
|
.go();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
1860
lib/src/localization/generated/app_localizations_sv.dart
Normal file
1860
lib/src/localization/generated/app_localizations_sv.dart
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1 +1 @@
|
||||||
Subproject commit 189bf8f4dbe2bee4f19a15b9640b8826e4f2e235
|
Subproject commit 57ec512977e514fca6413622bb4a7e03701f09a0
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
|
||||||
part 'backup.model.g.dart';
|
|
||||||
|
|
||||||
enum LastBackupUploadState { none, pending, failed, success }
|
|
||||||
|
|
||||||
@JsonSerializable()
|
|
||||||
class CurrentBackupStatus {
|
|
||||||
CurrentBackupStatus();
|
|
||||||
factory CurrentBackupStatus.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$CurrentBackupStatusFromJson(json);
|
|
||||||
|
|
||||||
LastBackupUploadState identityState = LastBackupUploadState.none;
|
|
||||||
DateTime? identityLastSuccessFull;
|
|
||||||
int? identitySize;
|
|
||||||
|
|
||||||
LastBackupUploadState archiveState = LastBackupUploadState.none;
|
|
||||||
|
|
||||||
DateTime? archiveLastSuccessFull;
|
|
||||||
int? archiveSize;
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$CurrentBackupStatusToJson(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
enum BackupRecoveryState {
|
|
||||||
// The userId was loaded from the server and the user is asked to enter his password.
|
|
||||||
identityBackupStarted,
|
|
||||||
// -> Download identity, replace keymanager
|
|
||||||
|
|
||||||
// Identity was downloaded and Keymanager was updated
|
|
||||||
archiveBackupStarted,
|
|
||||||
// -> Download archive, replace files, restart app
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonSerializable()
|
|
||||||
class BackupRecovery {
|
|
||||||
BackupRecovery({
|
|
||||||
required this.username,
|
|
||||||
required this.password,
|
|
||||||
required this.userId,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory BackupRecovery.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$BackupRecoveryFromJson(json);
|
|
||||||
|
|
||||||
String username;
|
|
||||||
String password;
|
|
||||||
int userId;
|
|
||||||
BackupRecoveryState state = BackupRecoveryState.identityBackupStarted;
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$BackupRecoveryToJson(this);
|
|
||||||
}
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'backup.model.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// JsonSerializableGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
CurrentBackupStatus _$CurrentBackupStatusFromJson(Map<String, dynamic> json) =>
|
|
||||||
CurrentBackupStatus()
|
|
||||||
..identityState = $enumDecode(
|
|
||||||
_$LastBackupUploadStateEnumMap,
|
|
||||||
json['identityState'],
|
|
||||||
)
|
|
||||||
..identityLastSuccessFull = json['identityLastSuccessFull'] == null
|
|
||||||
? null
|
|
||||||
: DateTime.parse(json['identityLastSuccessFull'] as String)
|
|
||||||
..identitySize = (json['identitySize'] as num?)?.toInt()
|
|
||||||
..archiveState = $enumDecode(
|
|
||||||
_$LastBackupUploadStateEnumMap,
|
|
||||||
json['archiveState'],
|
|
||||||
)
|
|
||||||
..archiveLastSuccessFull = json['archiveLastSuccessFull'] == null
|
|
||||||
? null
|
|
||||||
: DateTime.parse(json['archiveLastSuccessFull'] as String)
|
|
||||||
..archiveSize = (json['archiveSize'] as num?)?.toInt();
|
|
||||||
|
|
||||||
Map<String, dynamic> _$CurrentBackupStatusToJson(
|
|
||||||
CurrentBackupStatus instance,
|
|
||||||
) => <String, dynamic>{
|
|
||||||
'identityState': _$LastBackupUploadStateEnumMap[instance.identityState]!,
|
|
||||||
'identityLastSuccessFull': instance.identityLastSuccessFull
|
|
||||||
?.toIso8601String(),
|
|
||||||
'identitySize': instance.identitySize,
|
|
||||||
'archiveState': _$LastBackupUploadStateEnumMap[instance.archiveState]!,
|
|
||||||
'archiveLastSuccessFull': instance.archiveLastSuccessFull?.toIso8601String(),
|
|
||||||
'archiveSize': instance.archiveSize,
|
|
||||||
};
|
|
||||||
|
|
||||||
const _$LastBackupUploadStateEnumMap = {
|
|
||||||
LastBackupUploadState.none: 'none',
|
|
||||||
LastBackupUploadState.pending: 'pending',
|
|
||||||
LastBackupUploadState.failed: 'failed',
|
|
||||||
LastBackupUploadState.success: 'success',
|
|
||||||
};
|
|
||||||
|
|
||||||
BackupRecovery _$BackupRecoveryFromJson(Map<String, dynamic> json) =>
|
|
||||||
BackupRecovery(
|
|
||||||
username: json['username'] as String,
|
|
||||||
password: json['password'] as String,
|
|
||||||
userId: (json['userId'] as num).toInt(),
|
|
||||||
)..state = $enumDecode(_$BackupRecoveryStateEnumMap, json['state']);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$BackupRecoveryToJson(BackupRecovery instance) =>
|
|
||||||
<String, dynamic>{
|
|
||||||
'username': instance.username,
|
|
||||||
'password': instance.password,
|
|
||||||
'userId': instance.userId,
|
|
||||||
'state': _$BackupRecoveryStateEnumMap[instance.state]!,
|
|
||||||
};
|
|
||||||
|
|
||||||
const _$BackupRecoveryStateEnumMap = {
|
|
||||||
BackupRecoveryState.identityBackupStarted: 'identityBackupStarted',
|
|
||||||
BackupRecoveryState.archiveBackupStarted: 'archiveBackupStarted',
|
|
||||||
};
|
|
||||||
|
|
@ -1,89 +0,0 @@
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
|
||||||
|
|
||||||
part 'faq.model.g.dart';
|
|
||||||
|
|
||||||
@JsonSerializable()
|
|
||||||
class FaqData {
|
|
||||||
const FaqData({required this.languages});
|
|
||||||
|
|
||||||
factory FaqData.fromJson(Map<String, dynamic> json) {
|
|
||||||
return FaqData(
|
|
||||||
languages: json.map(
|
|
||||||
(key, value) => MapEntry(
|
|
||||||
key,
|
|
||||||
(value as Map<String, dynamic>).map(
|
|
||||||
(catKey, catValue) => MapEntry(
|
|
||||||
catKey,
|
|
||||||
FaqCategory.fromJson(catValue as Map<String, dynamic>),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final Map<String, Map<String, FaqCategory>> languages;
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => languages.map(
|
|
||||||
(key, value) => MapEntry(
|
|
||||||
key,
|
|
||||||
value.map((catKey, catValue) => MapEntry(catKey, catValue.toJson())),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonSerializable()
|
|
||||||
class FaqCategory {
|
|
||||||
const FaqCategory({
|
|
||||||
required this.meta,
|
|
||||||
required this.questions,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory FaqCategory.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$FaqCategoryFromJson(json);
|
|
||||||
|
|
||||||
final FaqMeta meta;
|
|
||||||
final List<FaqQuestion> questions;
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$FaqCategoryToJson(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonSerializable()
|
|
||||||
class FaqMeta {
|
|
||||||
const FaqMeta({
|
|
||||||
required this.title,
|
|
||||||
required this.desc,
|
|
||||||
this.priority = 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory FaqMeta.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$FaqMetaFromJson(json);
|
|
||||||
|
|
||||||
final String title;
|
|
||||||
final String desc;
|
|
||||||
|
|
||||||
@JsonKey(defaultValue: 0)
|
|
||||||
final int priority;
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$FaqMetaToJson(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonSerializable()
|
|
||||||
class FaqQuestion {
|
|
||||||
const FaqQuestion({
|
|
||||||
required this.id,
|
|
||||||
required this.title,
|
|
||||||
required this.body,
|
|
||||||
required this.path,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory FaqQuestion.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$FaqQuestionFromJson(json);
|
|
||||||
|
|
||||||
final String id;
|
|
||||||
final String title;
|
|
||||||
final String body;
|
|
||||||
final String path;
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$FaqQuestionToJson(this);
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue