mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-06-02 22:52:12 +00:00
Compare commits
124 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6501590dd7 | |||
| d03c42659c | |||
| 849f748968 | |||
| 9e28bb82a2 | |||
| 9beb8ef9d7 | |||
| a688954d76 | |||
| 358f93979e | |||
| dc0ef25d73 | |||
| 7559434f86 | |||
| 62457f1f48 | |||
| 0602f043d2 | |||
| 0c32b41dd0 | |||
| c10dc19342 | |||
| 872592af21 | |||
| c7826ad6dd | |||
| 25c826bff3 | |||
| 789bcda34f | |||
| 874cf5fecc | |||
| 34607e05d1 | |||
| 3499a08155 | |||
| a50c2ba7d7 | |||
| fae5ca3d25 | |||
| d6432677df | |||
| 00cb615e56 | |||
| 1ad304ec2e | |||
| cd5409d021 | |||
| b7c4832ee2 | |||
| f42a49cadf | |||
| 2d6a2e436f | |||
| c0e45cfe1f | |||
| d9da953f77 | |||
| b788146beb | |||
| 6f8f1efe81 | |||
| 304190387d | |||
| 65d188c4f2 | |||
| d32e319c49 | |||
| 2cb51d668a | |||
| 927589a505 | |||
| fc5c74eaed | |||
| d7e4da0e55 | |||
| bfde01cbc5 | |||
| 7614da00b1 | |||
| dec79f3463 | |||
| 236d94622c | |||
| 5bcb3b3efe | |||
| c77c369212 | |||
| 5fb51b20d7 | |||
| df974cd9f7 | |||
| 0204a41d43 | |||
| 11c0ad908e | |||
| 7283852ba5 | |||
| 805d7a66b3 | |||
| ea41158872 | |||
| 32231d11c2 | |||
| 68c99c271f | |||
| 91eedc76b0 | |||
| d0eee1893e | |||
| fe2dd06213 | |||
| 102d2579ce | |||
| 190be5b694 | |||
| c2ac706239 | |||
| e9b550023f | |||
| 5556532879 | |||
| f3b64646f5 | |||
| 0a9c74f515 | |||
| 07bc47062c | |||
| 8c84d802fd | |||
| ebc643cbe4 | |||
| d52f1eefea | |||
| 78cdb9244c | |||
| 4b9180c3c7 | |||
| ed0b7160b9 | |||
| 697e9a99f8 | |||
| a5fc97c648 | |||
| 5ec8afc01f | |||
| 7be9acb379 | |||
| fe0bb01f49 | |||
| 23004acbed | |||
| f7211fed08 | |||
| 9bb2ea2825 | |||
| d5642896a8 | |||
| 93ee6e60dd | |||
| dda3677907 | |||
| 0818fd0a75 | |||
| a1ca45c2b9 | |||
| 3d1b38192e | |||
| f45638c58d | |||
| 09129639e1 | |||
| f2b27e19f2 | |||
| ba06126a3c | |||
| 9941c6e870 | |||
| 1e6ce639cf | |||
| d7dffa82ff | |||
| 0a91e34348 | |||
| e6b549e897 | |||
| 4d39eb0bf4 | |||
| 7634177191 | |||
| 61979aedcb | |||
| 4dbc369003 | |||
| f735070a7c | |||
| 3e49e293f4 | |||
| 4d9c356400 | |||
| 105129023a | |||
| 64b304d99e | |||
| 5fa253ec32 | |||
| f323bc03eb | |||
| e6a468c065 | |||
| 149478df11 | |||
| d976737942 | |||
| d86252d800 | |||
| 8c15a95165 | |||
| dc044ee0d2 | |||
| 8898395d72 | |||
| d8a3c4a4d7 | |||
| 52bc628752 | |||
| 28fffbfce5 | |||
| 3acd207de6 | |||
| 0a972d023f | |||
| cdababa3c8 | |||
| c9eb270324 | |||
| 6a611767fc | |||
| 7f7aba8e08 | |||
| f553713ff8 | |||
| 281014133a |
348 changed files with 66939 additions and 8127 deletions
4
.github/workflows/dev_github.yml
vendored
4
.github/workflows/dev_github.yml
vendored
|
|
@ -31,5 +31,5 @@ jobs:
|
|||
- name: flutter analyze
|
||||
run: flutter analyze
|
||||
|
||||
- name: flutter test
|
||||
run: flutter test
|
||||
# - name: flutter test
|
||||
# run: flutter test
|
||||
|
|
|
|||
68
.github/workflows/release_github.yml
vendored
68
.github/workflows/release_github.yml
vendored
|
|
@ -1,68 +0,0 @@
|
|||
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: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Install requirements
|
||||
run: sudo apt-get install protobuf-compiler
|
||||
|
||||
- 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,8 +10,14 @@
|
|||
.history
|
||||
.svn/
|
||||
.swiftpm/
|
||||
*.sqlite
|
||||
*.sqlite-shm
|
||||
*.sqlite-wal
|
||||
migrate_working_dir/
|
||||
|
||||
fastlane/report.xml
|
||||
fastlane/README.md
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
|
|
@ -48,4 +54,5 @@ app.*.map.json
|
|||
android/.kotlin/
|
||||
devtools_options.yaml
|
||||
rust/target
|
||||
rust_dependencies/target
|
||||
rust_dependencies/target
|
||||
fastlane/repo/status/running.json
|
||||
|
|
|
|||
72
CHANGELOG.md
72
CHANGELOG.md
|
|
@ -1,5 +1,77 @@
|
|||
# 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
|
||||
|
|
|
|||
10
README.md
10
README.md
|
|
@ -1,10 +1,16 @@
|
|||
# twonly
|
||||
|
||||
<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>
|
||||
<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>
|
||||
|
||||
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.
|
||||
|
||||
<div style="margin: 10px 20px 10px 20px">
|
||||
<p align="center">
|
||||
<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">
|
||||
<img alt="Get it on App Store button" src="https://twonly.eu/assets/buttons/download-on-the-app-store.svg"
|
||||
width="100px" />
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ analyzer:
|
|||
- "lib/generated/**"
|
||||
- "lib/core/**"
|
||||
- "lib/src/localization/**"
|
||||
- "rust_builder/"
|
||||
- "dependencies/**"
|
||||
- "pubspec.yaml"
|
||||
- "**.arb"
|
||||
|
|
|
|||
2
android/.gitignore
vendored
2
android/.gitignore
vendored
|
|
@ -9,5 +9,7 @@ GeneratedPluginRegistrant.java
|
|||
# Remember to never publicly share your keystore.
|
||||
# See https://flutter.dev/to/reference-keystore
|
||||
key.properties
|
||||
key.github.properties
|
||||
key.properties.backup
|
||||
**/*.keystore
|
||||
**/*.jks
|
||||
|
|
|
|||
|
|
@ -73,4 +73,5 @@ flutter {
|
|||
dependencies {
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4'
|
||||
implementation 'com.otaliastudios:transcoder:0.11.0'
|
||||
implementation 'androidx.core:core-splashscreen:1.0.1'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:launchMode="singleTask"
|
||||
android:taskAffinity=""
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
|
|
|
|||
|
|
@ -6,8 +6,38 @@ import dev.darttools.flutter_android_volume_keydown.FlutterAndroidVolumeKeydownP
|
|||
import android.view.KeyEvent.KEYCODE_VOLUME_DOWN
|
||||
import android.view.KeyEvent.KEYCODE_VOLUME_UP
|
||||
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() {
|
||||
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 {
|
||||
if (keyCode == KEYCODE_VOLUME_DOWN && eventSink != null) {
|
||||
|
|
@ -24,7 +54,38 @@ class MainActivity : FlutterFragmentActivity() {
|
|||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine)
|
||||
|
||||
MediaStoreChannel.configure(flutterEngine, applicationContext)
|
||||
Keyring.initializeNdkContext(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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,92 +0,0 @@
|
|||
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,10 +3,12 @@ package eu.twonly
|
|||
import io.flutter.app.FlutterApplication
|
||||
import dev.fluttercommunity.workmanager.WorkmanagerDebug
|
||||
import dev.fluttercommunity.workmanager.LoggingDebugHandler
|
||||
import io.crates.keyring.Keyring
|
||||
|
||||
class MyApplication : FlutterApplication() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
Keyring.initializeNdkContext(this)
|
||||
// This enables the internal plugin logging to Logcat
|
||||
WorkmanagerDebug.setCurrent(LoggingDebugHandler())
|
||||
}
|
||||
|
|
|
|||
14
android/app/src/main/kotlin/io/crates/keyring/Keyring.kt
Normal file
14
android/app/src/main/kotlin/io/crates/keyring/Keyring.kt
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
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)
|
||||
}
|
||||
}
|
||||
69
android/app/src/main/res/drawable/link_animated.xml
Normal file
69
android/app/src/main/res/drawable/link_animated.xml
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
<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,10 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
<style name="LaunchTheme" parent="Theme.SplashScreen">
|
||||
<!-- Configure the Androidx Splash Screen API parameters -->
|
||||
<item name="windowSplashScreenBackground">#FF57CC99</item>
|
||||
<item name="windowSplashScreenAnimatedIcon">@drawable/link_animated</item>
|
||||
<item name="windowSplashScreenAnimationDuration">800</item>
|
||||
<item name="postSplashScreenTheme">@style/NormalTheme</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
<!-- Set the background color to the primary color so the white logo is visible -->
|
||||
<item name="android:colorBackground">#FF57CC99</item>
|
||||
<style name="LaunchTheme" parent="Theme.SplashScreen">
|
||||
<!-- Configure the Androidx Splash Screen API parameters -->
|
||||
<item name="windowSplashScreenBackground">#FF57CC99</item>
|
||||
<item name="windowSplashScreenAnimatedIcon">@drawable/link_animated</item>
|
||||
<item name="windowSplashScreenAnimationDuration">800</item>
|
||||
<item name="postSplashScreenTheme">@style/NormalTheme</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
|
|
|
|||
8
android/key.github.properties.example
Normal file
8
android/key.github.properties.example
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# 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
|
||||
1
assets/animated_icons/distorted_face.json
Normal file
1
assets/animated_icons/distorted_face.json
Normal file
File diff suppressed because one or more lines are too long
BIN
assets/fonts/NotoColorEmoji.ttf
Normal file
BIN
assets/fonts/NotoColorEmoji.ttf
Normal file
Binary file not shown.
|
|
@ -0,0 +1,3 @@
|
|||
<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>
|
||||
|
After Width: | Height: | Size: 732 B |
|
|
@ -0,0 +1,3 @@
|
|||
<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>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
|
|
@ -0,0 +1,3 @@
|
|||
<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>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
|
|
@ -1 +1 @@
|
|||
Subproject commit e0c6a9617a20a8d6bc1ad4c6b9c2e229feb5f37a
|
||||
Subproject commit 72d9bd6320bca1f1d29c6e61c3821fed326c0abe
|
||||
BIN
docs/header.webp
BIN
docs/header.webp
Binary file not shown.
|
Before Width: | Height: | Size: 81 KiB |
2
fastlane/Appfile
Normal file
2
fastlane/Appfile
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
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
|
||||
144
fastlane/Fastfile
Normal file
144
fastlane/Fastfile
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
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
|
||||
|
|
@ -280,6 +280,9 @@ PODS:
|
|||
- Flutter
|
||||
- permission_handler_apple (9.3.0):
|
||||
- Flutter
|
||||
- photo_manager (3.9.0):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- pro_video_editor (0.0.1):
|
||||
- Flutter
|
||||
- PromisesObjC (2.4.0)
|
||||
|
|
@ -355,6 +358,7 @@ DEPENDENCIES:
|
|||
- local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/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`)
|
||||
- restart_app (from `.symlinks/plugins/restart_app/ios`)
|
||||
- rust_lib_twonly (from `.symlinks/plugins/rust_lib_twonly/ios`)
|
||||
|
|
@ -460,6 +464,8 @@ EXTERNAL SOURCES:
|
|||
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||
permission_handler_apple:
|
||||
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||
photo_manager:
|
||||
:path: ".symlinks/plugins/photo_manager/darwin"
|
||||
pro_video_editor:
|
||||
:path: ".symlinks/plugins/pro_video_editor/ios"
|
||||
restart_app:
|
||||
|
|
@ -536,6 +542,7 @@ SPEC CHECKSUMS:
|
|||
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
||||
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
||||
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
|
||||
photo_manager: 25fd77df14f4f0ba5ef99e2c61814dde77e2bceb
|
||||
pro_video_editor: 44ef9a6d48dbd757ed428cf35396dd05f35c7830
|
||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||
restart_app: 0714144901e260eae68f7afc2fc4aacc1a323ad2
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import workmanager_apple
|
|||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
) -> Bool {
|
||||
UNUserNotificationCenter.current().delegate = self
|
||||
|
||||
|
||||
if let registrar = self.registrar(forPlugin: "VideoCompressionChannel") {
|
||||
VideoCompressionChannel.register(with: registrar.messenger())
|
||||
}
|
||||
|
|
@ -32,20 +32,22 @@ import workmanager_apple
|
|||
WorkmanagerPlugin.registerBGProcessingTask(
|
||||
withIdentifier: "eu.twonly.processing_task"
|
||||
)
|
||||
|
||||
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
|
||||
override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
|
||||
override func application(
|
||||
_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]
|
||||
) -> Bool {
|
||||
|
||||
let sharingIntent = SwiftFlutterSharingIntentPlugin.instance
|
||||
if sharingIntent.hasSameSchemePrefix(url: url) {
|
||||
return sharingIntent.application(app, open: url, options: options)
|
||||
}
|
||||
let sharingIntent = SwiftFlutterSharingIntentPlugin.instance
|
||||
if sharingIntent.hasSameSchemePrefix(url: url) {
|
||||
return sharingIntent.application(app, open: url, options: options)
|
||||
}
|
||||
|
||||
// Proceed url handling for other Flutter libraries like app_links
|
||||
return super.application(app, open: url, options:options)
|
||||
}
|
||||
// Proceed url handling for other Flutter libraries like app_links
|
||||
return super.application(app, open: url, options: options)
|
||||
}
|
||||
|
||||
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
|
||||
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
|
||||
|
|
@ -58,7 +60,8 @@ import workmanager_apple
|
|||
NSLog(
|
||||
"Application delegate method userNotificationCenter:didReceive:withCompletionHandler: is called with user info: %@",
|
||||
response.notification.request.content.userInfo)
|
||||
//...
|
||||
super.userNotificationCenter(
|
||||
center, didReceive: response, withCompletionHandler: completionHandler)
|
||||
}
|
||||
|
||||
override func userNotificationCenter(
|
||||
|
|
@ -86,4 +89,4 @@ import workmanager_apple
|
|||
completionHandler([.alert, .sound])
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
54
lib/app.dart
54
lib/app.dart
|
|
@ -1,5 +1,4 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
|
|
@ -19,11 +18,17 @@ import 'package:twonly/src/visual/views/home.view.dart';
|
|||
import 'package:twonly/src/visual/views/onboarding/onboarding.view.dart';
|
||||
import 'package:twonly/src/visual/views/onboarding/register.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 {
|
||||
const App({required this.storageError, super.key});
|
||||
const App({
|
||||
required this.storageError,
|
||||
required this.recoveryPossible,
|
||||
super.key,
|
||||
});
|
||||
final bool storageError;
|
||||
final bool recoveryPossible;
|
||||
@override
|
||||
State<App> createState() => _AppState();
|
||||
}
|
||||
|
|
@ -78,7 +83,6 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
|||
|
||||
if (widget.storageError) {
|
||||
return MaterialApp(
|
||||
scaffoldMessengerKey: AppGlobalKeys.scaffoldMessengerKey,
|
||||
localizationsDelegates: localizationsDelegates,
|
||||
debugShowCheckedModeBanner: false,
|
||||
supportedLocales: supportedLocales,
|
||||
|
|
@ -90,9 +94,21 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
|||
);
|
||||
}
|
||||
|
||||
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,
|
||||
scaffoldMessengerKey: AppGlobalKeys.scaffoldMessengerKey,
|
||||
localizationsDelegates: localizationsDelegates,
|
||||
debugShowCheckedModeBanner: false,
|
||||
supportedLocales: supportedLocales,
|
||||
|
|
@ -120,18 +136,33 @@ class _AppMainWidgetState extends State<AppMainWidget> {
|
|||
bool _showOnboarding = true;
|
||||
bool _isLoaded = false;
|
||||
bool _isTwonlyLocked = true;
|
||||
bool _wasLogged = true;
|
||||
late int _initialPage;
|
||||
|
||||
(Future<int>?, bool) _proofOfWork = (null, false);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
initAsync();
|
||||
super.initState();
|
||||
_initialPage = widget.initialPage;
|
||||
Log.info('AppWidgetState: initState started');
|
||||
initAsync();
|
||||
}
|
||||
|
||||
Future<void> initAsync() async {
|
||||
Log.info('AppWidgetState: initAsync started');
|
||||
if (userService.isUserCreated) {
|
||||
await FirebaseMessaging.instance.requestPermission();
|
||||
if (_initialPage != 0) {
|
||||
final count = await twonlyDB.contactsDao.getContactsCount();
|
||||
if (count == 0) {
|
||||
_initialPage = 0;
|
||||
}
|
||||
}
|
||||
try {
|
||||
unawaited(FirebaseMessaging.instance.requestPermission());
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
}
|
||||
if (_isTwonlyLocked) {
|
||||
// do not change in case twonly was already unlocked at some point
|
||||
_isTwonlyLocked = userService.currentUser.screenLockEnabled;
|
||||
|
|
@ -158,6 +189,12 @@ class _AppMainWidgetState extends State<AppMainWidget> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!_wasLogged) {
|
||||
Log.info('AppWidgetState: build started (_isLoaded: $_isLoaded)');
|
||||
if (_isLoaded) {
|
||||
_wasLogged = true;
|
||||
}
|
||||
}
|
||||
if (!_isLoaded) {
|
||||
return Center(child: Container());
|
||||
}
|
||||
|
|
@ -171,8 +208,7 @@ class _AppMainWidgetState extends State<AppMainWidget> {
|
|||
_isTwonlyLocked = false;
|
||||
}),
|
||||
);
|
||||
} else if (!userService.currentUser.skipSetupPages &&
|
||||
userService.currentUser.currentSetupPage != null) {
|
||||
} else if (!userService.currentUser.skipSetupPages && userService.currentUser.currentSetupPage != null) {
|
||||
// This will only be shown in case the user have not skipped
|
||||
child = SetupView(
|
||||
onUpdate: () => setState(() {
|
||||
|
|
@ -181,7 +217,7 @@ class _AppMainWidgetState extends State<AppMainWidget> {
|
|||
);
|
||||
} else {
|
||||
child = HomeView(
|
||||
initialPage: widget.initialPage,
|
||||
initialPage: _initialPage,
|
||||
);
|
||||
}
|
||||
} else if (_showOnboarding) {
|
||||
|
|
|
|||
29
lib/core/backup/backup_password.dart
Normal file
29
lib/core/backup/backup_password.dart
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
// 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;
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ 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 TwonlyConfig config}) =>
|
||||
Future<void> initializeTwonlyFlutter({required InitConfig config}) =>
|
||||
RustLib.instance.api.crateBridgeInitializeTwonlyFlutter(config: config);
|
||||
|
||||
class AnnouncedUser {
|
||||
|
|
@ -36,6 +36,27 @@ class AnnouncedUser {
|
|||
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;
|
||||
|
|
@ -74,24 +95,3 @@ class OtherPromotion {
|
|||
announcementShare == other.announcementShare &&
|
||||
publicKeyVerifiedTimestamp == other.publicKeyVerifiedTimestamp;
|
||||
}
|
||||
|
||||
class TwonlyConfig {
|
||||
final String databasePath;
|
||||
final String dataDirectory;
|
||||
|
||||
const TwonlyConfig({
|
||||
required this.databasePath,
|
||||
required this.dataDirectory,
|
||||
});
|
||||
|
||||
@override
|
||||
int get hashCode => databasePath.hashCode ^ dataDirectory.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is TwonlyConfig &&
|
||||
runtimeType == other.runtimeType &&
|
||||
databasePath == other.databasePath &&
|
||||
dataDirectory == other.dataDirectory;
|
||||
}
|
||||
|
|
|
|||
87
lib/core/bridge/wrapper/backup.dart
Normal file
87
lib/core/bridge/wrapper/backup.dart
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
// 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;
|
||||
}
|
||||
77
lib/core/bridge/wrapper/key_manager.dart
Normal file
77
lib/core/bridge/wrapper/key_manager.dart
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
// 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;
|
||||
}
|
||||
28
lib/core/context.dart
Normal file
28
lib/core/context.dart
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
// 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
|
|
@ -5,11 +5,15 @@
|
|||
|
||||
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> {
|
||||
|
|
@ -98,6 +102,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@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);
|
||||
|
||||
|
|
@ -107,17 +116,23 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@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
|
||||
TwonlyConfig dco_decode_box_autoadd_twonly_config(dynamic raw);
|
||||
InitConfig dco_decode_box_autoadd_init_config(dynamic raw);
|
||||
|
||||
@protected
|
||||
FlutterUserDiscovery dco_decode_flutter_user_discovery(dynamic raw);
|
||||
|
|
@ -125,6 +140,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
PlatformInt64 dco_decode_i_64(dynamic raw);
|
||||
|
||||
@protected
|
||||
InitConfig dco_decode_init_config(dynamic raw);
|
||||
|
||||
@protected
|
||||
PlatformInt64 dco_decode_isize(dynamic raw);
|
||||
|
||||
|
|
@ -140,6 +158,13 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@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);
|
||||
|
||||
|
|
@ -159,7 +184,26 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
OtherPromotion dco_decode_other_promotion(dynamic raw);
|
||||
|
||||
@protected
|
||||
TwonlyConfig dco_decode_twonly_config(dynamic raw);
|
||||
(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);
|
||||
|
|
@ -167,6 +211,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@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);
|
||||
|
||||
|
|
@ -179,6 +226,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@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,
|
||||
|
|
@ -190,6 +242,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
AnnouncedUser sse_decode_announced_user(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
BackupPasswordKeys sse_decode_backup_password_keys(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
bool sse_decode_bool(SseDeserializer deserializer);
|
||||
|
||||
|
|
@ -198,13 +255,16 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
BackupPasswordKeys sse_decode_box_autoadd_backup_password_keys(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
PlatformInt64 sse_decode_box_autoadd_i_64(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
TwonlyConfig sse_decode_box_autoadd_twonly_config(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
InitConfig sse_decode_box_autoadd_init_config(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
FlutterUserDiscovery sse_decode_flutter_user_discovery(
|
||||
|
|
@ -214,6 +274,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
PlatformInt64 sse_decode_i_64(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
InitConfig sse_decode_init_config(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
PlatformInt64 sse_decode_isize(SseDeserializer deserializer);
|
||||
|
||||
|
|
@ -233,6 +296,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@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,
|
||||
|
|
@ -258,7 +330,32 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
OtherPromotion sse_decode_other_promotion(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
TwonlyConfig sse_decode_twonly_config(SseDeserializer deserializer);
|
||||
(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);
|
||||
|
|
@ -266,6 +363,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@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);
|
||||
|
||||
|
|
@ -366,6 +466,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@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,
|
||||
|
|
@ -378,6 +484,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@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);
|
||||
|
||||
|
|
@ -387,6 +499,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
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,
|
||||
|
|
@ -394,8 +512,8 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_twonly_config(
|
||||
TwonlyConfig self,
|
||||
void sse_encode_box_autoadd_init_config(
|
||||
InitConfig self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
|
|
@ -408,6 +526,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@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);
|
||||
|
||||
|
|
@ -432,6 +553,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
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,
|
||||
|
|
@ -469,7 +599,40 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_twonly_config(TwonlyConfig self, SseSerializer serializer);
|
||||
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);
|
||||
|
|
@ -477,6 +640,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@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);
|
||||
|
||||
|
|
|
|||
|
|
@ -8,10 +8,14 @@
|
|||
|
||||
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> {
|
||||
|
|
@ -100,6 +104,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@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);
|
||||
|
||||
|
|
@ -109,17 +118,23 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@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
|
||||
TwonlyConfig dco_decode_box_autoadd_twonly_config(dynamic raw);
|
||||
InitConfig dco_decode_box_autoadd_init_config(dynamic raw);
|
||||
|
||||
@protected
|
||||
FlutterUserDiscovery dco_decode_flutter_user_discovery(dynamic raw);
|
||||
|
|
@ -127,6 +142,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
PlatformInt64 dco_decode_i_64(dynamic raw);
|
||||
|
||||
@protected
|
||||
InitConfig dco_decode_init_config(dynamic raw);
|
||||
|
||||
@protected
|
||||
PlatformInt64 dco_decode_isize(dynamic raw);
|
||||
|
||||
|
|
@ -142,6 +160,13 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@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);
|
||||
|
||||
|
|
@ -161,7 +186,26 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
OtherPromotion dco_decode_other_promotion(dynamic raw);
|
||||
|
||||
@protected
|
||||
TwonlyConfig dco_decode_twonly_config(dynamic raw);
|
||||
(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);
|
||||
|
|
@ -169,6 +213,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@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);
|
||||
|
||||
|
|
@ -181,6 +228,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@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,
|
||||
|
|
@ -192,6 +244,11 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
AnnouncedUser sse_decode_announced_user(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
BackupPasswordKeys sse_decode_backup_password_keys(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
bool sse_decode_bool(SseDeserializer deserializer);
|
||||
|
||||
|
|
@ -200,13 +257,16 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
BackupPasswordKeys sse_decode_box_autoadd_backup_password_keys(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
PlatformInt64 sse_decode_box_autoadd_i_64(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
TwonlyConfig sse_decode_box_autoadd_twonly_config(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
InitConfig sse_decode_box_autoadd_init_config(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
FlutterUserDiscovery sse_decode_flutter_user_discovery(
|
||||
|
|
@ -216,6 +276,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
PlatformInt64 sse_decode_i_64(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
InitConfig sse_decode_init_config(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
PlatformInt64 sse_decode_isize(SseDeserializer deserializer);
|
||||
|
||||
|
|
@ -235,6 +298,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@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,
|
||||
|
|
@ -260,7 +332,32 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
OtherPromotion sse_decode_other_promotion(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
TwonlyConfig sse_decode_twonly_config(SseDeserializer deserializer);
|
||||
(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);
|
||||
|
|
@ -268,6 +365,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@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);
|
||||
|
||||
|
|
@ -368,6 +468,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@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,
|
||||
|
|
@ -380,6 +486,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@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);
|
||||
|
||||
|
|
@ -389,6 +501,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
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,
|
||||
|
|
@ -396,8 +514,8 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_twonly_config(
|
||||
TwonlyConfig self,
|
||||
void sse_encode_box_autoadd_init_config(
|
||||
InitConfig self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
|
|
@ -410,6 +528,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@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);
|
||||
|
||||
|
|
@ -434,6 +555,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
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,
|
||||
|
|
@ -471,7 +601,40 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_twonly_config(TwonlyConfig self, SseSerializer serializer);
|
||||
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);
|
||||
|
|
@ -479,6 +642,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@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);
|
||||
|
||||
|
|
|
|||
29
lib/core/keys/backup_password_keys.dart
Normal file
29
lib/core/keys/backup_password_keys.dart
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
// 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;
|
||||
}
|
||||
20
lib/core/lib.dart
Normal file
20
lib/core/lib.dart
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
// 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,24 +1,29 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:camera/camera.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
|
||||
class AppEnvironment {
|
||||
static late final String cacheDir;
|
||||
static late final String supportDir;
|
||||
static late String cacheDir;
|
||||
static late String supportDir;
|
||||
|
||||
static bool _isInitialized = false;
|
||||
|
||||
// will be loaded in the main_camera_controller.dart
|
||||
static List<CameraDescription> cameras = [];
|
||||
|
||||
static Future<void> init() async {
|
||||
if (_isInitialized) return;
|
||||
cacheDir = (await getApplicationCacheDirectory()).path;
|
||||
supportDir = (await getApplicationSupportDirectory()).path;
|
||||
Log.init();
|
||||
_isInitialized = true;
|
||||
}
|
||||
|
||||
static void initTesting() {
|
||||
cacheDir = '/tmp/twonly_cache';
|
||||
supportDir = '/tmp/twonly_support';
|
||||
static void initTesting({String? customCacheDir, String? customSupportDir}) {
|
||||
cacheDir = customCacheDir ?? '/tmp/twonly_cache';
|
||||
supportDir = customSupportDir ?? '/tmp/twonly_support';
|
||||
_isInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -27,9 +32,6 @@ class AppState {
|
|||
static bool isInBackgroundTask = false;
|
||||
static bool allowErrorTrackingViaSentry = false;
|
||||
static bool gotMessageFromServer = false;
|
||||
static int latestAppVersionId = 110;
|
||||
}
|
||||
|
||||
class AppGlobalKeys {
|
||||
static final scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
|
||||
static int latestAppVersionId = 116;
|
||||
static bool hasCameraPermissions = false;
|
||||
}
|
||||
|
|
|
|||
188
lib/main.dart
188
lib/main.dart
|
|
@ -1,87 +1,112 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:mutex/mutex.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.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/locator.dart';
|
||||
import 'package:twonly/src/callbacks/callbacks.dart';
|
||||
import 'package:twonly/src/database/tables/contacts.table.dart';
|
||||
import 'package:twonly/src/providers/connection.provider.dart';
|
||||
import 'package:twonly/src/providers/image_editor.provider.dart';
|
||||
import 'package:twonly/src/providers/purchases.provider.dart';
|
||||
import 'package:twonly/src/providers/settings.provider.dart';
|
||||
import 'package:twonly/src/services/api/mediafiles/download.api.dart';
|
||||
import 'package:twonly/src/services/api/mediafiles/media_background.api.dart';
|
||||
import 'package:twonly/src/services/api/mediafiles/upload.api.dart';
|
||||
import 'package:twonly/src/services/background/callback_dispatcher.background.dart';
|
||||
import 'package:twonly/src/services/backup/create.backup.dart';
|
||||
import 'package:twonly/src/services/backup.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/setup.notifications.dart';
|
||||
import 'package:twonly/src/services/user.service.dart';
|
||||
import 'package:twonly/src/services/user_discovery.service.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/secure_storage.dart';
|
||||
import 'package:twonly/src/visual/views/onboarding/setup.view.dart';
|
||||
import 'package:twonly/src/utils/startup_guard.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<void> twonlyMinimumInitialization() async {
|
||||
SentryWidgetsFlutterBinding.ensureInitialized();
|
||||
Future<bool> twonlyMinimumInitialization() async {
|
||||
Log.info('twonlyMinimumInitialization: called');
|
||||
final hasStorageError = await exclusiveAccess(
|
||||
lockName: 'init',
|
||||
mutex: _initMutex,
|
||||
action: () async {
|
||||
Log.info('twonlyMinimumInitialization: started');
|
||||
setupLocator();
|
||||
|
||||
await AppEnvironment.init();
|
||||
Log.init();
|
||||
setupLocator();
|
||||
Log.info('twonlyMinimumInitialization: RustLib.init()');
|
||||
await RustLib.init();
|
||||
|
||||
await RustLib.init();
|
||||
Log.info('twonlyMinimumInitialization: initFlutterCallbacksForRust()');
|
||||
await initFlutterCallbacksForRust();
|
||||
|
||||
await initFlutterCallbacksForRust();
|
||||
|
||||
await bridge.initializeTwonlyFlutter(
|
||||
config: bridge.TwonlyConfig(
|
||||
databasePath: '${AppEnvironment.supportDir}/twonly.sqlite',
|
||||
dataDirectory: AppEnvironment.supportDir,
|
||||
),
|
||||
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 {
|
||||
await twonlyMinimumInitialization();
|
||||
final binding = SentryWidgetsFlutterBinding.ensureInitialized();
|
||||
await AppEnvironment.init();
|
||||
final stopwatch = Stopwatch()..start();
|
||||
|
||||
unawaited(initFCMService());
|
||||
unawaited(StartupGuard.markAppStartup());
|
||||
|
||||
var storageError = await twonlyMinimumInitialization();
|
||||
await initFCMService();
|
||||
|
||||
var userExists = false;
|
||||
var storageError = false;
|
||||
|
||||
try {
|
||||
userExists = await userService.tryInit();
|
||||
} catch (e) {
|
||||
Log.error('Failed to initialize user session due to storage error: $e');
|
||||
storageError = true;
|
||||
}
|
||||
var recoveryPossible = false;
|
||||
|
||||
final dbExists = File(
|
||||
'${AppEnvironment.supportDir}/twonly.sqlite',
|
||||
).existsSync();
|
||||
|
||||
if (Platform.isIOS && userExists) {
|
||||
if (!dbExists) {
|
||||
Log.error('[twonly] IOS: App was removed and then reinstalled again...');
|
||||
await SecureStorage.instance.deleteAll();
|
||||
userExists = false;
|
||||
if (!storageError) {
|
||||
try {
|
||||
userExists = await userService.tryInit();
|
||||
} catch (e) {
|
||||
Log.error('Failed to initialize user session due to storage error: $e');
|
||||
storageError = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!userExists && !storageError) {
|
||||
try {
|
||||
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.');
|
||||
|
||||
final settingsController = SettingsChangeProvider()..loadSettings();
|
||||
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
||||
await initFileDownloader();
|
||||
unawaited(initFileDownloader());
|
||||
|
||||
if (userExists) {
|
||||
if (userService.currentUser.allowErrorTrackingViaSentry) {
|
||||
|
|
@ -96,22 +121,22 @@ void main() async {
|
|||
}
|
||||
|
||||
await runMigrations();
|
||||
|
||||
await twonlyDB.messagesDao.purgeMessageTable();
|
||||
await twonlyDB.receiptsDao.purgeReceivedReceipts();
|
||||
await UserDiscoveryService.removeDeletedContacts();
|
||||
|
||||
unawaited(MediaFileService.purgeTempFolder());
|
||||
|
||||
unawaited(setupPushNotification());
|
||||
unawaited(finishStartedPreprocessing());
|
||||
unawaited(createPushAvatars());
|
||||
unawaited(performTwonlySafeBackup());
|
||||
unawaited(initializeBackgroundTaskManager());
|
||||
// We wait for the first frame to be rendered before starting heavy tasks.
|
||||
// This ensures the splash screen is dismissed on Android immediately.
|
||||
binding.addPostFrameCallback((_) async {
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
unawaited(postStartupTasks());
|
||||
unawaited(apiService.connect());
|
||||
});
|
||||
}
|
||||
|
||||
await apiService.listenToNetworkChanges();
|
||||
unawaited(apiService.connect());
|
||||
|
||||
stopwatch.stop();
|
||||
|
||||
Log.info(
|
||||
'Initialization finished after ${stopwatch.elapsed}. Calling runApp...',
|
||||
);
|
||||
|
||||
runApp(
|
||||
MultiProvider(
|
||||
|
|
@ -121,43 +146,34 @@ void main() async {
|
|||
ChangeNotifierProvider(create: (_) => ImageEditorProvider()),
|
||||
ChangeNotifierProvider(create: (_) => PurchasesProvider()),
|
||||
],
|
||||
child: App(storageError: storageError),
|
||||
child: App(
|
||||
storageError: storageError,
|
||||
recoveryPossible: recoveryPossible,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> runMigrations() async {
|
||||
if (userService.currentUser.appVersion < 90) {
|
||||
// BUG: Requested media files for reupload where not reuploaded because the wrong state...
|
||||
await twonlyDB.mediaFilesDao.updateAllRetransmissionUploadingState();
|
||||
await UserService.update((u) => u.appVersion = 90);
|
||||
}
|
||||
Future<void> postStartupTasks() async {
|
||||
Log.info('Post startup started.');
|
||||
unawaited(MemoriesService.prewarmCache());
|
||||
|
||||
if (userService.currentUser.appVersion < 91) {
|
||||
// BUG: Requested media files for reupload where not reuploaded because the wrong state...
|
||||
await makeMigrationToVersion91();
|
||||
await UserService.update((u) => u.appVersion = 91);
|
||||
}
|
||||
// 1. Immediate background cleanup (Non-blocking for UI)
|
||||
await twonlyDB.messagesDao.purgeMessageTable();
|
||||
unawaited(twonlyDB.receiptsDao.purgeReceivedReceipts());
|
||||
unawaited(MediaFileService.purgeTempFolder());
|
||||
|
||||
if (userService.currentUser.appVersion < 109) {
|
||||
final contacts = await twonlyDB.contactsDao.getAllContacts();
|
||||
for (final contact in contacts) {
|
||||
if (contact.verified) {
|
||||
await twonlyDB.keyVerificationDao.addKeyVerification(
|
||||
contact.userId,
|
||||
VerificationType.migratedFromOldVersion,
|
||||
);
|
||||
}
|
||||
}
|
||||
await UserService.update((u) {
|
||||
u
|
||||
..appVersion = 109
|
||||
..skipSetupPages = true;
|
||||
if (u.avatarSvg == null) {
|
||||
u.currentSetupPage = SetupPages.profile.name;
|
||||
} else {
|
||||
u.currentSetupPage = SetupPages.shareYourFriends.name;
|
||||
}
|
||||
});
|
||||
}
|
||||
// 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,4 +1,5 @@
|
|||
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';
|
||||
|
|
@ -15,11 +16,11 @@ class LoggingCallbacks {
|
|||
Log.info(log.split('INFO ')[1]);
|
||||
} else if (log.contains('DEBUG ')) {
|
||||
Log.info(log.split('DEBUG ')[1]);
|
||||
} else if (kDebugMode) {
|
||||
} else if (kDebugMode && !Platform.environment.containsKey('FLUTTER_TEST')) {
|
||||
// ignore: avoid_print
|
||||
print(log);
|
||||
}
|
||||
},
|
||||
onDone: () => Log.info('Log stream closed'),
|
||||
);
|
||||
timer.cancel();
|
||||
} catch (e) {
|
||||
|
|
|
|||
|
|
@ -16,8 +16,12 @@ class UserDiscoveryCallbacks {
|
|||
static Future<Uint8List?> signData(
|
||||
Uint8List inputData,
|
||||
) async {
|
||||
Log.info('UserDiscoveryCallbacks: signData started');
|
||||
var privKey = (await getSignalIdentityKeyPair())?.getPrivateKey();
|
||||
if (privKey == null) return null;
|
||||
if (privKey == null) {
|
||||
Log.error('UserDiscoveryCallbacks: signData failed, privKey is null');
|
||||
return null;
|
||||
}
|
||||
final random = getRandomUint8List(32);
|
||||
final signature = sign(
|
||||
privKey.serialize(),
|
||||
|
|
@ -25,6 +29,7 @@ class UserDiscoveryCallbacks {
|
|||
random,
|
||||
);
|
||||
privKey = null;
|
||||
Log.info('UserDiscoveryCallbacks: signData finished');
|
||||
return signature;
|
||||
}
|
||||
|
||||
|
|
@ -33,21 +38,29 @@ class UserDiscoveryCallbacks {
|
|||
Uint8List pubKey,
|
||||
Uint8List signature,
|
||||
) async {
|
||||
return Curve.verifySignature(
|
||||
IdentityKey.fromBytes(pubKey, 0).publicKey,
|
||||
inputData,
|
||||
signature,
|
||||
);
|
||||
try {
|
||||
return Curve.verifySignature(
|
||||
IdentityKey.fromBytes(pubKey, 0).publicKey,
|
||||
inputData,
|
||||
signature,
|
||||
);
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<bool> verifyStoredPubKey(
|
||||
int contactId,
|
||||
Uint8List pubKey,
|
||||
) async {
|
||||
final storedPublicKey = await getPublicKeyFromContact(contactId);
|
||||
if (storedPublicKey != null) {
|
||||
return storedPublicKey.equals(pubKey);
|
||||
} else {
|
||||
try {
|
||||
final storedPublicKey = await getPublicKeyFromContact(contactId);
|
||||
if (storedPublicKey != null) {
|
||||
return storedPublicKey.equals(pubKey);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
class KeyValueKeys {
|
||||
static const String lastPeriodicTaskExecution =
|
||||
'last_periodic_task_execution';
|
||||
static const String currentBackupState = 'current_backup_state';
|
||||
static const String backupRecoveryState = 'backup_recovery_state';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ class Routes {
|
|||
static const String settingsAccount = '/settings/account';
|
||||
static const String settingsSubscription = '/settings/subscription';
|
||||
static const String settingsBackup = '/settings/backup';
|
||||
static const String settingsBackupServer = '/settings/backup/server';
|
||||
static const String settingsBackupRecovery = '/settings/backup/recovery';
|
||||
static const String settingsBackupSetup = '/settings/backup/setup';
|
||||
static const String settingsAppearance = '/settings/appearance';
|
||||
|
|
@ -36,9 +35,14 @@ class Routes {
|
|||
'/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 settingsStorage = '/settings/storage_data';
|
||||
static const String settingsStorageManage = '/settings/storage_data/manage';
|
||||
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 settingsHelp = '/settings/help';
|
||||
static const String settingsHelpFaq = '/settings/help/faq';
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
class SecureStorageKeys {
|
||||
@Deprecated('Use the secure storage in rust')
|
||||
static const String signalIdentity = 'signal_identity';
|
||||
@Deprecated('Use the secure storage in rust')
|
||||
static const String signalSignedPreKey = 'signed_pre_key_store';
|
||||
@Deprecated('Use the login token')
|
||||
static const String apiAuthToken = 'api_auth_token';
|
||||
static const String googleFcm = 'google_fcm';
|
||||
static const String userData = 'userData';
|
||||
static const String twonlySafeLastBackupHash = 'twonly_safe_last_backup_hash';
|
||||
|
||||
@Deprecated('Use user.json file')
|
||||
static const String userData = 'userData';
|
||||
|
||||
// Not required for backup...
|
||||
static const String receivingPushKeys = 'push_keys_receiving';
|
||||
static const String sendingPushKeys = 'push_keys_sending';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,6 +103,13 @@ class ContactsDao extends DatabaseAccessor<TwonlyDB> with _$ContactsDaoMixin {
|
|||
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() {
|
||||
final count = contacts.userId.count();
|
||||
final query = selectOnly(contacts)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:drift/drift.dart';
|
||||
import 'package:hashlib/random.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||
import 'package:twonly/src/database/tables/groups.table.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/services/flame.service.dart';
|
||||
|
|
@ -139,15 +140,10 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
|
|||
}
|
||||
|
||||
Future<Group?> _insertGroup(GroupsCompanion group) async {
|
||||
try {
|
||||
await into(groups).insert(group);
|
||||
return await (select(
|
||||
groups,
|
||||
)..where((t) => t.groupId.equals(group.groupId.value))).getSingle();
|
||||
} catch (e) {
|
||||
Log.error('Could not insert group: $e');
|
||||
return null;
|
||||
}
|
||||
await into(groups).insertOnConflictUpdate(group);
|
||||
return (select(
|
||||
groups,
|
||||
)..where((t) => t.groupId.equals(group.groupId.value))).getSingleOrNull();
|
||||
}
|
||||
|
||||
Future<List<Contact>> getGroupContact(String groupId) async {
|
||||
|
|
@ -277,7 +273,7 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
|
|||
groups.groupId.equalsExp(groupMembers.groupId),
|
||||
),
|
||||
],
|
||||
)..where(groups.isDirectChat.isNull()));
|
||||
)..where(groups.isDirectChat.equals(false)));
|
||||
return query.map((row) => row.readTable(groupMembers)).get();
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
|
|
@ -297,6 +293,27 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
|
|||
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() {
|
||||
final query = selectOnly(groups)
|
||||
..addColumns([groups.totalMediaCounter.sum()]);
|
||||
|
|
@ -332,4 +349,18 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
|
|||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,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/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';
|
||||
|
||||
|
|
@ -26,7 +27,8 @@ class KeyVerificationDao extends DatabaseAccessor<TwonlyDB>
|
|||
KeyVerificationDao(super.db);
|
||||
|
||||
Future<List<VerificationToken>> getRecentVerificationTokens() {
|
||||
final cutoff = DateTime.now().subtract(const Duration(hours: 24));
|
||||
// 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();
|
||||
|
|
@ -82,17 +84,19 @@ class KeyVerificationDao extends DatabaseAccessor<TwonlyDB>
|
|||
|
||||
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(),
|
||||
);
|
||||
[
|
||||
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) {
|
||||
|
|
@ -103,6 +107,55 @@ class KeyVerificationDao extends DatabaseAccessor<TwonlyDB>
|
|||
});
|
||||
}
|
||||
|
||||
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');
|
||||
|
|
@ -154,17 +207,55 @@ class KeyVerificationDao extends DatabaseAccessor<TwonlyDB>
|
|||
}
|
||||
|
||||
Future<void> addKeyVerification(int contactId, VerificationType type) async {
|
||||
await into(keyVerifications).insertOnConflictUpdate(
|
||||
KeyVerificationsCompanion(
|
||||
contactId: Value(contactId),
|
||||
type: Value(type),
|
||||
),
|
||||
);
|
||||
if (userService.currentUser.isUserDiscoveryEnabled) {
|
||||
await FlutterUserDiscovery.updateVerificationStateForUser(
|
||||
contactId: contactId,
|
||||
publicKeyVerifiedTimestamp: clock.now().millisecondsSinceEpoch,
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,6 +65,10 @@ class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
|
|||
)..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 {
|
||||
final medias = await (select(
|
||||
mediaFiles,
|
||||
|
|
@ -110,9 +114,15 @@ class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
|
|||
.get();
|
||||
}
|
||||
|
||||
Future<List<MediaFile>> getAllNonHashedStoredMediaFiles() async {
|
||||
Future<List<MediaFile>> getAllMediaFilesPendingMigration() async {
|
||||
return (select(mediaFiles)..where(
|
||||
(t) => t.stored.equals(true) & t.storedFileHash.isNull(),
|
||||
(t) =>
|
||||
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();
|
||||
}
|
||||
|
|
@ -154,4 +164,43 @@ 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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
|||
t.openedAt.isNull() |
|
||||
t.mediaStored.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.text.name) &
|
||||
t.content.isNotNull()) |
|
||||
|
|
@ -140,24 +140,38 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
|||
Future<void> purgeMessageTable() async {
|
||||
final allGroups = await select(groups).get();
|
||||
|
||||
for (final group in allGroups) {
|
||||
final groupedByTime = <int, List<String>>{};
|
||||
for (final g in allGroups) {
|
||||
groupedByTime
|
||||
.putIfAbsent(g.deleteMessagesAfterMilliseconds, () => [])
|
||||
.add(g.groupId);
|
||||
}
|
||||
|
||||
for (final entry in groupedByTime.entries) {
|
||||
final deletionTime = clock.now().subtract(
|
||||
Duration(
|
||||
milliseconds: group.deleteMessagesAfterMilliseconds,
|
||||
),
|
||||
Duration(milliseconds: entry.key),
|
||||
);
|
||||
await (delete(messages)..where(
|
||||
(m) =>
|
||||
m.groupId.equals(group.groupId) &
|
||||
(m.mediaStored.equals(true) &
|
||||
m.isDeletedFromSender.equals(true) |
|
||||
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..
|
||||
(m.openedByAll.isSmallerThanValue(deletionTime) |
|
||||
(m.isDeletedFromSender.equals(true) &
|
||||
m.createdAt.isSmallerThanValue(deletionTime))),
|
||||
))
|
||||
.go();
|
||||
final groupIds = entry.value;
|
||||
|
||||
final deletedCount =
|
||||
await (delete(messages)..where(
|
||||
(m) =>
|
||||
m.groupId.isIn(groupIds) &
|
||||
((m.mediaStored.equals(true) &
|
||||
m.isDeletedFromSender.equals(true)) |
|
||||
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..
|
||||
(m.openedByAll.isSmallerThanValue(deletionTime) |
|
||||
(m.isDeletedFromSender.equals(true) &
|
||||
m.createdAt.isSmallerThanValue(deletionTime))),
|
||||
))
|
||||
.go();
|
||||
|
||||
if (deletedCount > 0) {
|
||||
Log.info(
|
||||
'Deleted $deletedCount messages for groups $groupIds due to retention policy.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -242,41 +256,70 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
|||
}
|
||||
|
||||
Future<void> handleMessagesOpened(
|
||||
int contactId,
|
||||
Value<int> contactId,
|
||||
List<String> messageIds,
|
||||
DateTime timestamp,
|
||||
) async {
|
||||
await batch((batch) async {
|
||||
for (final messageId in messageIds) {
|
||||
batch.insert(
|
||||
messageActions,
|
||||
MessageActionsCompanion(
|
||||
messageId: Value(messageId),
|
||||
contactId: Value(contactId),
|
||||
type: const Value(MessageActionType.openedAt),
|
||||
actionAt: Value(timestamp),
|
||||
),
|
||||
mode: InsertMode.insertOrReplace,
|
||||
);
|
||||
}
|
||||
for (final messageId in messageIds) {
|
||||
try {
|
||||
var actionTimestamp = timestamp;
|
||||
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;
|
||||
}
|
||||
|
||||
for (final messageId in messageIds) {
|
||||
final isOpenedByAll = await haveAllMembers(
|
||||
messageId,
|
||||
MessageActionType.openedAt,
|
||||
);
|
||||
final now = clock.now();
|
||||
final ts = actionTimestamp;
|
||||
await transaction(() async {
|
||||
await into(messageActions).insertOnConflictUpdate(
|
||||
MessageActionsCompanion(
|
||||
messageId: Value(messageId),
|
||||
contactId: contactId,
|
||||
type: const Value(MessageActionType.openedAt),
|
||||
actionAt: Value(ts),
|
||||
),
|
||||
);
|
||||
|
||||
batch.update(
|
||||
twonlyDB.messages,
|
||||
MessagesCompanion(
|
||||
openedAt: Value(now),
|
||||
openedByAll: Value(isOpenedByAll ? now : null),
|
||||
),
|
||||
where: (tbl) => tbl.messageId.equals(messageId),
|
||||
final isOpenedByAll = await haveAllMembers(
|
||||
messageId,
|
||||
MessageActionType.openedAt,
|
||||
);
|
||||
await (update(
|
||||
messages,
|
||||
)..where((tbl) => tbl.messageId.equals(messageId))).write(
|
||||
MessagesCompanion(
|
||||
openedAt: Value(ts),
|
||||
openedByAll: Value(isOpenedByAll ? ts : null),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
// 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(
|
||||
|
|
@ -284,57 +327,67 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
|||
String messageId,
|
||||
DateTime timestamp,
|
||||
) async {
|
||||
await into(messageActions).insertOnConflictUpdate(
|
||||
MessageActionsCompanion(
|
||||
messageId: Value(messageId),
|
||||
contactId: Value(contactId),
|
||||
type: const Value(MessageActionType.ackByServerAt),
|
||||
actionAt: Value(timestamp),
|
||||
),
|
||||
);
|
||||
await twonlyDB.messagesDao.updateMessageId(
|
||||
messageId,
|
||||
MessagesCompanion(ackByServer: Value(clock.now())),
|
||||
);
|
||||
await transaction(() async {
|
||||
await into(messageActions).insertOnConflictUpdate(
|
||||
MessageActionsCompanion(
|
||||
messageId: Value(messageId),
|
||||
contactId: Value(contactId),
|
||||
type: const Value(MessageActionType.ackByServerAt),
|
||||
actionAt: Value(timestamp),
|
||||
),
|
||||
);
|
||||
await twonlyDB.messagesDao.updateMessageId(
|
||||
messageId,
|
||||
MessagesCompanion(ackByServer: Value(timestamp)),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<bool> haveAllMembers(
|
||||
String messageId,
|
||||
MessageActionType action,
|
||||
) async {
|
||||
final message = await twonlyDB.messagesDao
|
||||
.getMessageById(messageId)
|
||||
.getSingleOrNull();
|
||||
if (message == null) return true;
|
||||
final members = await twonlyDB.groupsDao.getGroupNonLeftMembers(
|
||||
message.groupId,
|
||||
);
|
||||
try {
|
||||
final message = await twonlyDB.messagesDao
|
||||
.getMessageById(messageId)
|
||||
.getSingleOrNull();
|
||||
if (message == null) return true;
|
||||
final members = await twonlyDB.groupsDao.getGroupNonLeftMembers(
|
||||
message.groupId,
|
||||
);
|
||||
|
||||
final actions =
|
||||
await (select(messageActions)..where(
|
||||
(t) => t.type.equals(action.name) & t.messageId.equals(messageId),
|
||||
))
|
||||
.get();
|
||||
final actions =
|
||||
await (select(messageActions)..where(
|
||||
(t) =>
|
||||
t.type.equals(action.name) & t.messageId.equals(messageId),
|
||||
))
|
||||
.get();
|
||||
|
||||
return members.length == actions.length;
|
||||
return members.length == actions.length;
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateMessageId(
|
||||
String messageId,
|
||||
MessagesCompanion updatedValues,
|
||||
) async {
|
||||
await (update(
|
||||
final count = await (update(
|
||||
messages,
|
||||
)..where((c) => c.messageId.equals(messageId))).write(updatedValues);
|
||||
Log.info('Updated $count message(s) with messageId $messageId');
|
||||
}
|
||||
|
||||
Future<void> updateMessagesByMediaId(
|
||||
String mediaId,
|
||||
MessagesCompanion updatedValues,
|
||||
) {
|
||||
return (update(
|
||||
) async {
|
||||
final count = await (update(
|
||||
messages,
|
||||
)..where((c) => c.mediaId.equals(mediaId))).write(updatedValues);
|
||||
Log.info('Updated $count message(s) with mediaId $mediaId');
|
||||
}
|
||||
|
||||
Future<Message?> insertMessage(MessagesCompanion message) async {
|
||||
|
|
@ -404,6 +457,10 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
|||
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) {
|
||||
final query = (select(messageActions).join([
|
||||
leftOuterJoin(
|
||||
|
|
|
|||
|
|
@ -29,7 +29,14 @@ class ReactionsDao extends DatabaseAccessor<TwonlyDB> with _$ReactionsDaoMixin {
|
|||
final msg = await twonlyDB.messagesDao
|
||||
.getMessageById(messageId)
|
||||
.getSingleOrNull();
|
||||
if (msg == null || msg.groupId != groupId) return;
|
||||
if (msg == null) {
|
||||
Log.error('updateReaction: Message $messageId not found!');
|
||||
return;
|
||||
}
|
||||
if (msg.groupId != groupId) {
|
||||
Log.error('updateReaction: Message groupId ${msg.groupId} != $groupId');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (remove) {
|
||||
|
|
|
|||
|
|
@ -191,6 +191,23 @@ class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
|
|||
)..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(
|
||||
int contactId,
|
||||
String messageId,
|
||||
|
|
|
|||
79
lib/src/database/daos/shortcuts.dao.dart
Normal file
79
lib/src/database/daos/shortcuts.dao.dart
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
25
lib/src/database/daos/shortcuts.dao.g.dart
Normal file
25
lib/src/database/daos/shortcuts.dao.g.dart
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// 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,
|
||||
);
|
||||
}
|
||||
|
|
@ -228,6 +228,12 @@ class UserDiscoveryDao extends DatabaseAccessor<TwonlyDB>
|
|||
);
|
||||
}
|
||||
|
||||
Future<UserDiscoveryAnnouncedUser?> getAnnouncedUserById(int id) async {
|
||||
return (select(
|
||||
userDiscoveryAnnouncedUsers,
|
||||
)..where((tbl) => tbl.announcedUserId.equals(id))).getSingleOrNull();
|
||||
}
|
||||
|
||||
Stream<List<UserDiscoveryAnnouncedUser>> watchAllAnnouncedUsers() =>
|
||||
select(userDiscoveryAnnouncedUsers).watch();
|
||||
|
||||
|
|
|
|||
164
lib/src/database/drift_logging_interceptor.dart
Normal file
164
lib/src/database/drift_logging_interceptor.dart
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
2901
lib/src/database/schemas/twonly_db/drift_schema_v13.json
Normal file
2901
lib/src/database/schemas/twonly_db/drift_schema_v13.json
Normal file
File diff suppressed because it is too large
Load diff
2939
lib/src/database/schemas/twonly_db/drift_schema_v14.json
Normal file
2939
lib/src/database/schemas/twonly_db/drift_schema_v14.json
Normal file
File diff suppressed because it is too large
Load diff
2995
lib/src/database/schemas/twonly_db/drift_schema_v15.json
Normal file
2995
lib/src/database/schemas/twonly_db/drift_schema_v15.json
Normal file
File diff suppressed because it is too large
Load diff
3019
lib/src/database/schemas/twonly_db/drift_schema_v16.json
Normal file
3019
lib/src/database/schemas/twonly_db/drift_schema_v16.json
Normal file
File diff suppressed because it is too large
Load diff
3033
lib/src/database/schemas/twonly_db/drift_schema_v17.json
Normal file
3033
lib/src/database/schemas/twonly_db/drift_schema_v17.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,59 +1,50 @@
|
|||
import 'dart:collection';
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||
import 'package:twonly/src/constants/secure_storage.keys.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';
|
||||
|
||||
class SignalSignedPreKeyStore extends SignedPreKeyStore {
|
||||
Future<HashMap<int, Uint8List>> getStore() async {
|
||||
final storeSerialized = await SecureStorage.instance.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);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
Future<void> safeStore(HashMap<int, Uint8List> store) async {
|
||||
final storeHashMap = <List<dynamic>>[];
|
||||
for (final item in store.entries) {
|
||||
storeHashMap.add([item.key, base64Encode(item.value)]);
|
||||
}
|
||||
final storeSerialized = json.encode(storeHashMap);
|
||||
await SecureStorage.instance.write(
|
||||
key: SecureStorageKeys.signalSignedPreKey,
|
||||
value: storeSerialized,
|
||||
);
|
||||
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 store = await getStore();
|
||||
if (!store.containsKey(signedPreKeyId)) {
|
||||
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(store[signedPreKeyId]!);
|
||||
return SignedPreKeyRecord.fromSerialized(record.first.signedPreKey);
|
||||
}
|
||||
|
||||
@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;
|
||||
final records = await twonlyDB
|
||||
.select(twonlyDB.signalSignedPreKeyStores)
|
||||
.get();
|
||||
return records
|
||||
.map((r) => SignedPreKeyRecord.fromSerialized(r.signedPreKey))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -61,19 +52,32 @@ class SignalSignedPreKeyStore extends SignedPreKeyStore {
|
|||
int signedPreKeyId,
|
||||
SignedPreKeyRecord record,
|
||||
) async {
|
||||
final store = await getStore();
|
||||
store[signedPreKeyId] = record.serialize();
|
||||
await safeStore(store);
|
||||
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 =>
|
||||
(await getStore()).containsKey(signedPreKeyId);
|
||||
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 {
|
||||
final store = await getStore();
|
||||
store.remove(signedPreKeyId);
|
||||
await safeStore(store);
|
||||
await (twonlyDB.delete(
|
||||
twonlyDB.signalSignedPreKeyStores,
|
||||
)..where((tbl) => tbl.signedPreKeyId.equals(signedPreKeyId))).go();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,6 +83,8 @@ enum GroupActionType {
|
|||
demoteToMember,
|
||||
updatedGroupName,
|
||||
changeDisplayMaxTime,
|
||||
updatedContactUsername,
|
||||
updatedContactDisplayName,
|
||||
}
|
||||
|
||||
@DataClassName('GroupHistory')
|
||||
|
|
|
|||
|
|
@ -50,6 +50,9 @@ class MediaFiles extends Table {
|
|||
|
||||
BoolColumn get stored => 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()();
|
||||
|
||||
|
|
@ -66,7 +69,14 @@ class MediaFiles extends Table {
|
|||
|
||||
BlobColumn get storedFileHash => blob().nullable()();
|
||||
|
||||
BoolColumn get hasThumbnail =>
|
||||
boolean().withDefault(const Constant(false))();
|
||||
|
||||
IntColumn get sizeInBytes => integer().nullable()();
|
||||
|
||||
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
||||
TextColumn get createdAtMonth => text().nullable()();
|
||||
|
||||
|
||||
@override
|
||||
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/mediafiles.table.dart';
|
||||
|
||||
enum MessageType { media, text, contacts, restoreFlameCounter }
|
||||
enum MessageType { media, text, contacts, restoreFlameCounter, askAboutUser }
|
||||
|
||||
@DataClassName('Message')
|
||||
class Messages extends Table {
|
||||
|
|
|
|||
26
lib/src/database/tables/shortcuts.table.dart
Normal file
26
lib/src/database/tables/shortcuts.table.dart
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
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};
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
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};
|
||||
}
|
||||
|
|
@ -16,6 +16,8 @@ class UserDiscoveryAnnouncedUsers extends Table {
|
|||
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};
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import 'package:clock/clock.dart';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift_flutter/drift_flutter.dart'
|
||||
show DriftNativeOptions, driftDatabase;
|
||||
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/groups.dao.dart';
|
||||
import 'package:twonly/src/database/daos/key_verification.dao.dart';
|
||||
|
|
@ -10,17 +10,21 @@ import 'package:twonly/src/database/daos/mediafiles.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/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/groups.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/reactions.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_pre_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_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/utils/log.dart';
|
||||
|
|
@ -43,6 +47,7 @@ part 'twonly.db.g.dart';
|
|||
SignalPreKeyStores,
|
||||
SignalSenderKeyStores,
|
||||
SignalSessionStores,
|
||||
SignalSignedPreKeyStores,
|
||||
MessageActions,
|
||||
GroupHistories,
|
||||
KeyVerifications,
|
||||
|
|
@ -52,6 +57,8 @@ part 'twonly.db.g.dart';
|
|||
UserDiscoveryOtherPromotions,
|
||||
UserDiscoveryOwnPromotions,
|
||||
UserDiscoveryShares,
|
||||
Shortcuts,
|
||||
ShortcutMembers,
|
||||
],
|
||||
daos: [
|
||||
MessagesDao,
|
||||
|
|
@ -62,6 +69,7 @@ part 'twonly.db.g.dart';
|
|||
MediaFilesDao,
|
||||
UserDiscoveryDao,
|
||||
KeyVerificationDao,
|
||||
ShortcutsDao,
|
||||
],
|
||||
)
|
||||
class TwonlyDB extends _$TwonlyDB {
|
||||
|
|
@ -74,21 +82,29 @@ class TwonlyDB extends _$TwonlyDB {
|
|||
TwonlyDB.forTesting(DatabaseConnection super.connection);
|
||||
|
||||
@override
|
||||
int get schemaVersion => 12;
|
||||
int get schemaVersion => 17;
|
||||
|
||||
static QueryExecutor _openConnection() {
|
||||
return driftDatabase(
|
||||
final connection = driftDatabase(
|
||||
name: 'twonly',
|
||||
native: DriftNativeOptions(
|
||||
databaseDirectory: getApplicationSupportDirectory,
|
||||
shareAcrossIsolates: true,
|
||||
setup: (rawDb) {
|
||||
rawDb
|
||||
..execute('PRAGMA journal_mode=WAL;')
|
||||
..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
|
||||
|
|
@ -186,6 +202,37 @@ class TwonlyDB extends _$TwonlyDB {
|
|||
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);
|
||||
},
|
||||
);
|
||||
|
|
@ -207,38 +254,4 @@ class TwonlyDB extends _$TwonlyDB {
|
|||
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
|
|
@ -8,12 +8,9 @@ import 'app_localizations.dart';
|
|||
class AppLocalizationsDe extends AppLocalizations {
|
||||
AppLocalizationsDe([String locale = 'de']) : super(locale);
|
||||
|
||||
@override
|
||||
String get registerTitle => 'Willkommen bei twonly!';
|
||||
|
||||
@override
|
||||
String get registerSlogan =>
|
||||
'twonly, eine private und sichere Möglichkeit um mit Freunden in Kontakt zu bleiben.';
|
||||
'Privat und sicher mit Freunden in Kontakt bleiben.';
|
||||
|
||||
@override
|
||||
String get onboardingWelcomeTitle => 'Willkommen bei twonly!';
|
||||
|
|
@ -55,15 +52,13 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
String get onboardingGetStartedTitle => 'Auf geht\'s';
|
||||
|
||||
@override
|
||||
String get registerUsernameSlogan =>
|
||||
'Bitte wähle einen Benutzernamen, damit dich andere finden können!';
|
||||
String get registerUsernameSlogan => 'Dein öffentlicher Benutzername';
|
||||
|
||||
@override
|
||||
String get registerUsernameDecoration => 'Benutzername';
|
||||
|
||||
@override
|
||||
String get registerUsernameLimits =>
|
||||
'Der Benutzername muss mindestens 3 Zeichen lang sein.';
|
||||
String get registerUsernameLimits => 'Mindestens 3 Zeichen.';
|
||||
|
||||
@override
|
||||
String get registerProofOfWorkFailed =>
|
||||
|
|
@ -214,6 +209,14 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get settingsPreSelectedReactions => 'Vorgewählte Reaktions-Emojis';
|
||||
|
||||
@override
|
||||
String get settingsAutomaticallyMarkEqualMediaFilesAsOpenedTitle =>
|
||||
'Duplikate als geöffnet markieren';
|
||||
|
||||
@override
|
||||
String get settingsAutomaticallyMarkEqualMediaFilesAsOpenedSubtitle =>
|
||||
'Wenn du die selbe Mediendatei in mehreren Chats erhältst, markiert das Öffnen einer Kopie alle anderen als geöffnet.';
|
||||
|
||||
@override
|
||||
String get settingsPreSelectedReactionsError =>
|
||||
'Es können maximal 12 Reaktionen ausgewählt werden.';
|
||||
|
|
@ -241,6 +244,21 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get settingsStorageDataAutoDownWifi => 'Bei Nutzung von WLAN';
|
||||
|
||||
@override
|
||||
String get settingsStorageManageTitle => 'Speicher verwalten';
|
||||
|
||||
@override
|
||||
String get settingsStorageUsed => 'Speicherplatz belegt';
|
||||
|
||||
@override
|
||||
String get settingsStorageImages => 'Bilder';
|
||||
|
||||
@override
|
||||
String get settingsStorageVideos => 'Videos';
|
||||
|
||||
@override
|
||||
String get settingsStorageGifs => 'GIFs';
|
||||
|
||||
@override
|
||||
String get settingsProfileCustomizeAvatar => 'Avatar anpassen';
|
||||
|
||||
|
|
@ -274,9 +292,44 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
return '$len Kontakt(e)';
|
||||
}
|
||||
|
||||
@override
|
||||
String get settingsPrivacyProfileSelectionTitle => 'Sicherheitsprofil';
|
||||
|
||||
@override
|
||||
String get settingsPrivacyProfileSelectionDesc =>
|
||||
'Wähle deinen Setup-Pfad und deine Sicherheitskonfiguration';
|
||||
|
||||
@override
|
||||
String get securityProfileTitle => 'Sicherheitsprofil';
|
||||
|
||||
@override
|
||||
String get securityProfileSubtitle =>
|
||||
'Wähle das Schutzniveau, das zu deiner täglichen Nutzung passt. Dies kann jederzeit in den Einstellungen geändert werden.';
|
||||
|
||||
@override
|
||||
String get securityProfileNormalTitle => 'Normaler Schutz';
|
||||
|
||||
@override
|
||||
String get securityProfileNormalDesc =>
|
||||
'Gute Balance zwischen Komfort und Sicherheit, ohne dich zu sehr einzuschränken.';
|
||||
|
||||
@override
|
||||
String get securityProfileStrictTitle => 'Strikter Schutz';
|
||||
|
||||
@override
|
||||
String get securityProfileStrictDesc =>
|
||||
'Maximaler Schutz vor Phishing, kann aber unkomfortabel sein.';
|
||||
|
||||
@override
|
||||
String get settingsNotification => 'Benachrichtigung';
|
||||
|
||||
@override
|
||||
String get settingsNotifyPermission => 'Benachrichtigungsberechtigung';
|
||||
|
||||
@override
|
||||
String get settingsNotifyPermissionDesc =>
|
||||
'Systemeinstellungen öffnen, um Push-Benachrichtigungen zu erlauben.';
|
||||
|
||||
@override
|
||||
String get settingsNotifyTroubleshooting => 'Fehlersuche';
|
||||
|
||||
|
|
@ -422,8 +475,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
String get verificationTypeQrScanned => 'Du hast den QR-Code gescannt.';
|
||||
|
||||
@override
|
||||
String get verificationTypeSecretQrToken =>
|
||||
'Die andere Person hat deinen QR-Code gescannt.';
|
||||
String verificationTypeSecretQrToken(Object username) {
|
||||
return '$username hat deinen QR-Code gescannt.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get verificationTypeLink => 'Per Link verifiziert.';
|
||||
|
|
@ -594,9 +648,6 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
String get errorPlanUpgradeNotYearly =>
|
||||
'Das Upgrade des Plans muss jährlich bezahlt werden, da der aktuelle Plan ebenfalls jährlich abgerechnet wird.';
|
||||
|
||||
@override
|
||||
String get upgradeToPaidPlan => 'Upgrade auf einen kostenpflichtigen Plan.';
|
||||
|
||||
@override
|
||||
String upgradeToPaidPlanButton(Object planId, Object sufix) {
|
||||
return 'Auf $planId upgraden$sufix';
|
||||
|
|
@ -658,12 +709,6 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get open => 'Offene';
|
||||
|
||||
@override
|
||||
String get createVoucher => 'Gutschein kaufen';
|
||||
|
||||
@override
|
||||
String get redeemVoucher => 'Gutschein einlösen';
|
||||
|
||||
@override
|
||||
String get buy => 'Kaufen';
|
||||
|
||||
|
|
@ -676,15 +721,27 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get galleryDelete => 'Datei löschen';
|
||||
|
||||
@override
|
||||
String get galleryDetails => 'Details anzeigen';
|
||||
|
||||
@override
|
||||
String get galleryExport => 'In Galerie exportieren';
|
||||
|
||||
@override
|
||||
String get galleryExportSuccess => 'Erfolgreich in der Gallery gespeichert.';
|
||||
|
||||
@override
|
||||
String get gallerySelectAll => 'Alle auswählen';
|
||||
|
||||
@override
|
||||
String get galleryDeselectAll => 'Auswahl aufheben';
|
||||
|
||||
@override
|
||||
String get galleryFavorite => 'Als Favorit markieren';
|
||||
|
||||
@override
|
||||
String get galleryUnfavorite => 'Favorit entfernen';
|
||||
|
||||
@override
|
||||
String get galleryCancel => 'Abbrechen';
|
||||
|
||||
@override
|
||||
String get memoriesEmpty =>
|
||||
'Sobald du Bilder oder Videos speicherst, landen sie hier in deinen Erinnerungen.';
|
||||
|
|
@ -695,6 +752,17 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get deleteOkBtnForAll => 'Für alle löschen';
|
||||
|
||||
@override
|
||||
String memoriesDeleteSnackbarSuccess(num count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count Elemente erfolgreich gelöscht',
|
||||
one: '1 Element erfolgreich gelöscht',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get deleteOkBtnForMe => 'Für mich löschen';
|
||||
|
||||
|
|
@ -704,6 +772,17 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get deleteImageBody => 'Das Bild wird unwiderruflich gelöscht.';
|
||||
|
||||
@override
|
||||
String deleteMemoriesBody(num count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'Die $count Bilder werden unwiderruflich gelöscht.',
|
||||
one: 'Das Bild wird unwiderruflich gelöscht.',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get settingsBackup => 'Backup';
|
||||
|
||||
|
|
@ -725,13 +804,10 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
'Aufgrund des Sicherheitssystems von twonly gibt es (derzeit) keine Funktion zur Wiederherstellung des Passworts. Daher musst du dir dein Passwort merken oder, besser noch, aufschreiben.';
|
||||
|
||||
@override
|
||||
String get backupServer => 'Server';
|
||||
String get backupIdentityHeader => 'Identität';
|
||||
|
||||
@override
|
||||
String get backupMaxBackupSize => 'max. Backup-Größe';
|
||||
|
||||
@override
|
||||
String get backupStorageRetention => 'Speicheraufbewahrung';
|
||||
String get backupArchiveHeader => 'Kontakte, Einstellungen und Nachrichten';
|
||||
|
||||
@override
|
||||
String get backupLastBackupDate => 'Letztes Backup';
|
||||
|
|
@ -742,9 +818,6 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get backupLastBackupResult => 'Ergebnis';
|
||||
|
||||
@override
|
||||
String get backupData => 'Daten-Backup';
|
||||
|
||||
@override
|
||||
String get backupInsecurePassword => 'Unsicheres Passwort';
|
||||
|
||||
|
|
@ -777,24 +850,11 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get backupPasswordRequirement =>
|
||||
'Das Passwort muss mindestens 8 Zeichen lang sein.';
|
||||
|
||||
@override
|
||||
String get backupExpertSettings => 'Experteneinstellungen';
|
||||
'Das Passwort muss mindestens 10 Zeichen lang sein.';
|
||||
|
||||
@override
|
||||
String get backupEnableBackup => 'Automatische Sicherung aktivieren';
|
||||
|
||||
@override
|
||||
String get backupOwnServerDesc =>
|
||||
'Speichere dein twonly Backup auf einem Server deiner Wahl.';
|
||||
|
||||
@override
|
||||
String get backupUseOwnServer => 'Server verwenden';
|
||||
|
||||
@override
|
||||
String get backupResetServer => 'Standardserver verwenden';
|
||||
|
||||
@override
|
||||
String get backupTwonlySaveNow => 'Jetzt speichern';
|
||||
|
||||
|
|
@ -802,11 +862,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
String get backupChangePassword => 'Password ändern';
|
||||
|
||||
@override
|
||||
String get twonlySafeRecoverTitle => 'Recovery';
|
||||
|
||||
@override
|
||||
String get twonlySafeRecoverDesc =>
|
||||
'Wenn du ein Backup mit twonly Backup erstellt hast, kannst du es hier wiederherstellen.';
|
||||
String get twonlySafeRecoverTitle => 'Backup wiederherstellen';
|
||||
|
||||
@override
|
||||
String get twonlySafeRecoverBtn => 'Backup wiederherstellen';
|
||||
|
|
@ -1272,7 +1328,8 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
String get openYourOwnQRcode => 'Eigenen QR-Code öffnen';
|
||||
|
||||
@override
|
||||
String get skipForNow => 'Vorerst überspringen';
|
||||
String get addContactQrSheetSubtext =>
|
||||
'Lass einen Freund diesen QR-Code scannen, um dich hinzuzufügen';
|
||||
|
||||
@override
|
||||
String get finishSetupCardTitle => 'Profil vervollständigen';
|
||||
|
|
@ -1284,6 +1341,16 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get finishSetupCardAction => 'Setup fortsetzen';
|
||||
|
||||
@override
|
||||
String get missingBackupCardTitle => 'Backup einrichten';
|
||||
|
||||
@override
|
||||
String get missingBackupCardDesc =>
|
||||
'Wir haben den Backup-Mechanismus verbessert, weshalb du ihn erneut einrichten musst.';
|
||||
|
||||
@override
|
||||
String get missingBackupCardAction => 'Jetzt einrichten';
|
||||
|
||||
@override
|
||||
String get onboardingFinishLater => 'Später abschließen';
|
||||
|
||||
|
|
@ -1317,11 +1384,11 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
'Erfahre, wer dich anfragt';
|
||||
|
||||
@override
|
||||
String get userDiscoverySettingsManualApproval => 'Manuelle Zustimmung';
|
||||
String get userDiscoverySettingsManualApproval => 'Vor jedem Teilen fragen';
|
||||
|
||||
@override
|
||||
String get userDiscoverySettingsManualApprovalDesc =>
|
||||
'Bevor jemand geteilt wird, wirst du zuerst gefragt.';
|
||||
'Bevor einer deiner Freunde geteilt wird, wirst du jedes Mal gefragt.';
|
||||
|
||||
@override
|
||||
String get onboardingUserDiscoveryLetFriendsFindYou =>
|
||||
|
|
@ -1489,7 +1556,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get verificationBadgeGeneralDesc =>
|
||||
'Der Haken gibt dir die Sicherheit, dass du mit der richtigen Person schreibst. Scanne einen Kontakt, um diesen zu verifizieren.';
|
||||
'Der Haken gibt dir die Sicherheit, dass du mit der richtigen Person schreibst. Du kannst Kontakte jederzeit verifizieren, indem du deren QR-Code scannst.';
|
||||
|
||||
@override
|
||||
String get verificationBadgeGreenDesc =>
|
||||
|
|
@ -1503,6 +1570,40 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
String get verificationBadgeRedDesc =>
|
||||
'Ein Kontakt, dessen Identität noch *nicht überprüft* wurde.';
|
||||
|
||||
@override
|
||||
String get deleteVerificationTitle => 'Verifizierung löschen?';
|
||||
|
||||
@override
|
||||
String get deleteVerificationBody =>
|
||||
'Möchtest du diese Verifizierung wirklich löschen?';
|
||||
|
||||
@override
|
||||
String secretQrTokenVerifiedSnackbar(Object username) {
|
||||
return '$username hat deinen QR-Code gescannt und ist nun verifiziert.';
|
||||
}
|
||||
|
||||
@override
|
||||
String mutualGroupsTitle(num count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count gemeinsame Gruppen',
|
||||
one: '1 gemeinsame Gruppe',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String mutualGroupsSentMessages(num count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count Nachrichten gesendet',
|
||||
one: '1 Nachricht gesendet',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String chatEntryFlameRestored(Object count) {
|
||||
return '$count Flammen wiederhergestellt';
|
||||
|
|
@ -1582,6 +1683,12 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get userDiscoverySettingsTitle => 'Gemeinsame Freunde';
|
||||
|
||||
@override
|
||||
String get userDiscoveryWhyThisIsUsed => 'Warum dies verwendet wird';
|
||||
|
||||
@override
|
||||
String get userDiscoveryFeatureOffers => 'Dein Nutzen auf einen Blick';
|
||||
|
||||
@override
|
||||
String get userDiscoveryDisabledLearnMore => 'Mehr erfahren';
|
||||
|
||||
|
|
@ -1625,6 +1732,43 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get friendSuggestionsRequest => 'Anfragen';
|
||||
|
||||
@override
|
||||
String get friendSuggestionsAskFriend => 'Deine Freunde fragen';
|
||||
|
||||
@override
|
||||
String askFriendsDialogTitle(Object username) {
|
||||
return 'Nach $username fragen';
|
||||
}
|
||||
|
||||
@override
|
||||
String get askFriendsDialogDescription =>
|
||||
'Wähle die Freunde aus, die du zu diesem Nutzer fragen möchtest:';
|
||||
|
||||
@override
|
||||
String get askFriendsDialogConfirm => 'Fragen';
|
||||
|
||||
@override
|
||||
String get askFriendsDialogCancel => 'Abbrechen';
|
||||
|
||||
@override
|
||||
String get chatAskAFriendReceivedDescription =>
|
||||
'Dein Freund hat diesen Nutzer als Vorschlag erhalten und möchte wissen, ob er diese Person kennt.';
|
||||
|
||||
@override
|
||||
String get chatAskAFriendAddedDescription =>
|
||||
'Du hast diesen Nutzer zu deinen Kontakten hinzugefügt.';
|
||||
|
||||
@override
|
||||
String get chatAskAFriendHide => 'Ausblenden';
|
||||
|
||||
@override
|
||||
String get chatAskAFriendRequest => 'Anfragen';
|
||||
|
||||
@override
|
||||
String chatAskAFriendUnknownUser(Object userId) {
|
||||
return 'Nutzer $userId';
|
||||
}
|
||||
|
||||
@override
|
||||
String contactUserDiscoveryImagesLeft(Object imagesLeft, Object username) {
|
||||
return 'Es fehlen noch $imagesLeft Bilder bis deine Freunde mit $username geteilt werden.';
|
||||
|
|
@ -1669,4 +1813,318 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get contactUserDiscoveryManualApprovalApprove => 'Freigeben';
|
||||
|
||||
@override
|
||||
String get exampleUserName1 => 'max_mustermann';
|
||||
|
||||
@override
|
||||
String get exampleUserName2 => 'erika_musterfrau';
|
||||
|
||||
@override
|
||||
String get exampleUserName3 => 'hans';
|
||||
|
||||
@override
|
||||
String get exampleUserName4 => 'petra';
|
||||
|
||||
@override
|
||||
String get exampleUserName5 => 'klaus';
|
||||
|
||||
@override
|
||||
String get exampleUserName6 => 'sabine';
|
||||
|
||||
@override
|
||||
String get exampleUserName7 => 'stefan';
|
||||
|
||||
@override
|
||||
String get exampleUserName8 => 'monika';
|
||||
|
||||
@override
|
||||
String get exampleUserName9 => 'christian';
|
||||
|
||||
@override
|
||||
String get exampleUserName10 => 'lena';
|
||||
|
||||
@override
|
||||
String get exampleUserName11 => 'david';
|
||||
|
||||
@override
|
||||
String get exampleJane => 'erika';
|
||||
|
||||
@override
|
||||
String get back => 'Zurück';
|
||||
|
||||
@override
|
||||
String get onboardingExampleLabel => 'Beispiel';
|
||||
|
||||
@override
|
||||
String makerChangedUsername(Object maker, Object oldName, Object newName) {
|
||||
return '$maker hat den Benutzernamen von $oldName zu $newName geändert.';
|
||||
}
|
||||
|
||||
@override
|
||||
String makerChangedDisplayName(Object maker, Object oldName, Object newName) {
|
||||
return '$maker hat den Anzeigenamen von $oldName zu $newName geändert.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get recoverErrorNoInternet =>
|
||||
'Keine Internetverbindung. Bitte überprüfe deine Netzwerkverbindung und versuche es erneut.';
|
||||
|
||||
@override
|
||||
String get recoverErrorUsernameNotValid =>
|
||||
'Der eingegebene Benutzername ist ungültig oder existiert nicht.';
|
||||
|
||||
@override
|
||||
String get recoverErrorPasswordInvalid =>
|
||||
'Das eingegebene Passwort ist falsch.';
|
||||
|
||||
@override
|
||||
String get recoverErrorTryAgainLater =>
|
||||
'Der Server ist derzeit nicht erreichbar. Bitte versuche es später erneut.';
|
||||
|
||||
@override
|
||||
String get recoverErrorUnknown =>
|
||||
'Ein unbekannter Fehler ist aufgetreten. Bitte versuche es erneut.';
|
||||
|
||||
@override
|
||||
String get recoverSuccessTitle => 'Backup erfolgreich wiederhergestellt.';
|
||||
|
||||
@override
|
||||
String get recoverSuccessBody => 'Klicke hier, um die App wieder zu öffnen';
|
||||
|
||||
@override
|
||||
String get iosRecoveryWelcomeBack => 'Willkommen zurück';
|
||||
|
||||
@override
|
||||
String get iosRecoveryPrompt =>
|
||||
'Wir haben eine zuvor gesicherte twonly-Identität auf diesem Gerät erkannt. Möchtest du deine Kontakte, Nachrichten und Einstellungen automatisch aus deinem Cloud-Archiv herunterladen und wiederherstellen?';
|
||||
|
||||
@override
|
||||
String iosRecoveryNoBackupFound(Object error) {
|
||||
return 'Für dieses Gerät konnte kein Backup-Archiv vom Server abgerufen werden.\n\nFehler: $error\n\nBitte fahre mit der Registrierung eines neuen twonly-Kontos fort.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get registerNewAccount => 'Neues Konto registrieren';
|
||||
|
||||
@override
|
||||
String get tryRestoreAgain => 'Wiederherstellung erneut versuchen';
|
||||
|
||||
@override
|
||||
String get registeringNewAccount => 'Neues Konto wird registriert';
|
||||
|
||||
@override
|
||||
String get createShortcut => 'Shortcut erstellen';
|
||||
|
||||
@override
|
||||
String get editShortcut => 'Shortcut bearbeiten';
|
||||
|
||||
@override
|
||||
String get deleteShortcut => 'Shortcut löschen';
|
||||
|
||||
@override
|
||||
String get deleteShortcutBody =>
|
||||
'Bist du sicher, dass du diesen Shortcut löschen möchtest?';
|
||||
|
||||
@override
|
||||
String get updateShortcut => 'Shortcut aktualisieren';
|
||||
|
||||
@override
|
||||
String get selectEmoji => 'Emoji auswählen';
|
||||
|
||||
@override
|
||||
String get errorEmojiUsedOrInvalid =>
|
||||
'Emoji wird bereits verwendet oder ist ungültig';
|
||||
|
||||
@override
|
||||
String get subscriptionPledgeSecureTitle => 'Secure by Design';
|
||||
|
||||
@override
|
||||
String get subscriptionPledgeSecureDesc =>
|
||||
'Deine Nachrichten und Bilder sind immer vollständig Ende-zu-Ende verschlüsselt.';
|
||||
|
||||
@override
|
||||
String get subscriptionPledgeNoAdsTitle => 'Keine Werbung oder Datenverkauf';
|
||||
|
||||
@override
|
||||
String get subscriptionPledgeNoAdsDesc =>
|
||||
'twonly wird niemals Werbung anzeigen oder deine privaten Daten verkaufen.';
|
||||
|
||||
@override
|
||||
String get subscriptionPledgeSubtitle => 'Keine Werbung. Volle Privatsphäre.';
|
||||
|
||||
@override
|
||||
String get dragToZoom => 'Zum Zoomen ziehen';
|
||||
|
||||
@override
|
||||
String get showUsername => 'Benutzernamen anzeigen';
|
||||
|
||||
@override
|
||||
String get onboardingProfileSelectionTitle => 'Wähle deinen Setup-Weg';
|
||||
|
||||
@override
|
||||
String get onboardingProfileSelectionSubtitle =>
|
||||
'Wähle aus, wie du deine Sicherheits- und Privatsphäre-Einstellungen konfigurieren möchtest.';
|
||||
|
||||
@override
|
||||
String get onboardingProfileSelectionDefaultTitle => 'Standard';
|
||||
|
||||
@override
|
||||
String get onboardingProfileSelectionDefaultDesc =>
|
||||
'Wendet sofort die empfohlenen Einstellungen an, damit du die App direkt nutzen kannst.';
|
||||
|
||||
@override
|
||||
String get onboardingProfileSelectionDefaultBadge => 'Schnelles Setup';
|
||||
|
||||
@override
|
||||
String get onboardingProfileSelectionCustomizeTitle => 'Anpassen';
|
||||
|
||||
@override
|
||||
String get onboardingProfileSelectionCustomizeDesc =>
|
||||
'Schritt-für-Schritt-Einrichtung, damit du selbst entscheiden kannst.';
|
||||
|
||||
@override
|
||||
String get onboardingProfileSelectionStrictTitle => 'Erhöhter Schutz';
|
||||
|
||||
@override
|
||||
String get onboardingProfileSelectionStrictDesc =>
|
||||
'Maximaler Schutz vor Phishing. Empfohlen für *Journalisten & Personen des öffentlichen Lebens*.';
|
||||
|
||||
@override
|
||||
String get replyFlameRestored => 'Flammen wiederhergestellt';
|
||||
|
||||
@override
|
||||
String get replyAskAFriend => 'Einen Freund fragen';
|
||||
|
||||
@override
|
||||
String get unverifiedWarningDirectTitle =>
|
||||
'Identität nicht persönlich verifiziert';
|
||||
|
||||
@override
|
||||
String get unverifiedWarningGroupTitle =>
|
||||
'Nicht alle Mitglieder sind persönlich verifiziert';
|
||||
|
||||
@override
|
||||
String get unverifiedWarningBody =>
|
||||
'*Teile keine geheimen Daten*. Jemand könnte sich *als dein Freund ausgeben*.';
|
||||
|
||||
@override
|
||||
String get unverifiedWarningButton => 'Jetzt verifizieren';
|
||||
|
||||
@override
|
||||
String get today => 'Heute';
|
||||
|
||||
@override
|
||||
String get yesterday => 'Gestern';
|
||||
|
||||
@override
|
||||
String get galleryDisableWarningTitle => 'Galeriespeicherung deaktivieren?';
|
||||
|
||||
@override
|
||||
String get galleryDisableWarningBody =>
|
||||
'Wenn du dies deaktivierst, werden deine Mediendateien nicht in deiner Galerie gespeichert und könnten dauerhaft verloren gehen, wenn twonly deinstalliert wird oder ein Problem auftritt, da Mediendateien noch nicht in Backups enthalten sind.';
|
||||
|
||||
@override
|
||||
String get galleryDisableWarningConfirm => 'Deaktivieren';
|
||||
|
||||
@override
|
||||
String get settingsStorageScanGalleryTitle => 'Aus Galerie importieren';
|
||||
|
||||
@override
|
||||
String get importGalleryDeselectAll => 'Alle abwählen';
|
||||
|
||||
@override
|
||||
String get importGallerySelectAll => 'Alle auswählen';
|
||||
|
||||
@override
|
||||
String get importGalleryPermissionRequired =>
|
||||
'Zugriff auf deine Galerie ist erforderlich, um frühere twonly-Mediendateien zu importieren.';
|
||||
|
||||
@override
|
||||
String importGalleryPermissionError(Object error) {
|
||||
return 'Beim Anfordern der Berechtigung ist ein Fehler aufgetreten: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String importGalleryLoadError(Object error) {
|
||||
return 'Laden der Medien fehlgeschlagen: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String importGalleryImportingOf(Object current, Object total) {
|
||||
return '$current von $total wird importiert...';
|
||||
}
|
||||
|
||||
@override
|
||||
String get importGalleryStarting => 'Import wird gestartet...';
|
||||
|
||||
@override
|
||||
String importGalleryComplete(
|
||||
Object imported,
|
||||
Object duplicated,
|
||||
Object failed,
|
||||
) {
|
||||
return 'Import abgeschlossen: $imported erfolgreich importiert, $duplicated Duplikate und $failed fehlgeschlagen.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get importGalleryGrantAccess => 'Zugriff erlauben';
|
||||
|
||||
@override
|
||||
String get importGalleryOpenSettings => 'Einstellungen öffnen';
|
||||
|
||||
@override
|
||||
String get importGalleryPermissionDenied => 'Zugriff auf Galerie verweigert.';
|
||||
|
||||
@override
|
||||
String get importGalleryTryAgain => 'Erneut versuchen';
|
||||
|
||||
@override
|
||||
String get importGalleryAlbumNotFound => '\"twonly\"-Album nicht gefunden';
|
||||
|
||||
@override
|
||||
String get importGalleryAlbumNotFoundDesc =>
|
||||
'Falls du dieses Album noch nicht hast, kannst du es auch erstellen, um Fotos in twonly zu importieren.';
|
||||
|
||||
@override
|
||||
String get importGalleryNoImagesFound => 'Keine Bilder gefunden';
|
||||
|
||||
@override
|
||||
String get importGalleryNoImagesFoundDesc =>
|
||||
'Es befinden sich keine Bilder auf deinem Gerät.';
|
||||
|
||||
@override
|
||||
String get importGalleryShowAllImages => 'Alle Bilder anzeigen';
|
||||
|
||||
@override
|
||||
String get importGalleryShowTwonlyAlbum => 'twonly-Album anzeigen';
|
||||
|
||||
@override
|
||||
String get importGalleryToggleDescAll =>
|
||||
'Es werden alle Bilder auf deinem Gerät angezeigt.';
|
||||
|
||||
@override
|
||||
String get importGalleryToggleDescTwonly =>
|
||||
'Es wird das \"twonly\"-Album angezeigt.';
|
||||
|
||||
@override
|
||||
String get importGalleryFilterTwonly => 'Nur das twonly-Album anzeigen';
|
||||
|
||||
@override
|
||||
String get importGalleryRefresh => 'Aktualisieren';
|
||||
|
||||
@override
|
||||
String get importGallerySelectToImport =>
|
||||
'Elemente zum Importieren auswählen';
|
||||
|
||||
@override
|
||||
String importGalleryImportCount(num count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count Elemente importieren',
|
||||
one: '1 Element importieren',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,12 +8,9 @@ import 'app_localizations.dart';
|
|||
class AppLocalizationsEn extends AppLocalizations {
|
||||
AppLocalizationsEn([String locale = 'en']) : super(locale);
|
||||
|
||||
@override
|
||||
String get registerTitle => 'Welcome to twonly!';
|
||||
|
||||
@override
|
||||
String get registerSlogan =>
|
||||
'twonly, a privacy friendly way to connect with friends through secure, spontaneous image sharing';
|
||||
'Stay in touch with friends privately and securely.';
|
||||
|
||||
@override
|
||||
String get onboardingWelcomeTitle => 'Welcome to twonly!';
|
||||
|
|
@ -54,15 +51,13 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
String get onboardingGetStartedTitle => 'Let\'s go!';
|
||||
|
||||
@override
|
||||
String get registerUsernameSlogan =>
|
||||
'Please select a username so others can find you!';
|
||||
String get registerUsernameSlogan => 'Your public username';
|
||||
|
||||
@override
|
||||
String get registerUsernameDecoration => 'Username';
|
||||
|
||||
@override
|
||||
String get registerUsernameLimits =>
|
||||
'Your username must be at least 3 characters long.';
|
||||
String get registerUsernameLimits => 'At least 3 characters.';
|
||||
|
||||
@override
|
||||
String get registerProofOfWorkFailed =>
|
||||
|
|
@ -211,6 +206,14 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get settingsPreSelectedReactions => 'Preselected reaction emojis';
|
||||
|
||||
@override
|
||||
String get settingsAutomaticallyMarkEqualMediaFilesAsOpenedTitle =>
|
||||
'Mark duplicates as opened';
|
||||
|
||||
@override
|
||||
String get settingsAutomaticallyMarkEqualMediaFilesAsOpenedSubtitle =>
|
||||
'If you receive the same media in multiple chats, opening one marks all others as opened.';
|
||||
|
||||
@override
|
||||
String get settingsPreSelectedReactionsError =>
|
||||
'A maximum of 12 reactions can be selected.';
|
||||
|
|
@ -237,6 +240,21 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get settingsStorageDataAutoDownWifi => 'When using WI-FI';
|
||||
|
||||
@override
|
||||
String get settingsStorageManageTitle => 'Manage storage';
|
||||
|
||||
@override
|
||||
String get settingsStorageUsed => 'Storage used';
|
||||
|
||||
@override
|
||||
String get settingsStorageImages => 'Images';
|
||||
|
||||
@override
|
||||
String get settingsStorageVideos => 'Videos';
|
||||
|
||||
@override
|
||||
String get settingsStorageGifs => 'GIFs';
|
||||
|
||||
@override
|
||||
String get settingsProfileCustomizeAvatar => 'Customize your avatar';
|
||||
|
||||
|
|
@ -270,9 +288,44 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
return '$len contact(s)';
|
||||
}
|
||||
|
||||
@override
|
||||
String get settingsPrivacyProfileSelectionTitle => 'Security Profile';
|
||||
|
||||
@override
|
||||
String get settingsPrivacyProfileSelectionDesc =>
|
||||
'Choose your setup path and security configuration';
|
||||
|
||||
@override
|
||||
String get securityProfileTitle => 'Security Profile';
|
||||
|
||||
@override
|
||||
String get securityProfileSubtitle =>
|
||||
'Choose the level of protection that fits your daily use. This can be changed at any time in your settings.';
|
||||
|
||||
@override
|
||||
String get securityProfileNormalTitle => 'Normal Protection';
|
||||
|
||||
@override
|
||||
String get securityProfileNormalDesc =>
|
||||
'Good balance between a convenient mode without bothering you too much.';
|
||||
|
||||
@override
|
||||
String get securityProfileStrictTitle => 'Strict Protection';
|
||||
|
||||
@override
|
||||
String get securityProfileStrictDesc =>
|
||||
'Maximum anti-phishing protection but may be inconvenient.';
|
||||
|
||||
@override
|
||||
String get settingsNotification => 'Notification';
|
||||
|
||||
@override
|
||||
String get settingsNotifyPermission => 'Notification permissions';
|
||||
|
||||
@override
|
||||
String get settingsNotifyPermissionDesc =>
|
||||
'Open system settings to allow push notifications.';
|
||||
|
||||
@override
|
||||
String get settingsNotifyTroubleshooting => 'Troubleshooting';
|
||||
|
||||
|
|
@ -417,8 +470,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
String get verificationTypeQrScanned => 'You scanned their QR code.';
|
||||
|
||||
@override
|
||||
String get verificationTypeSecretQrToken =>
|
||||
'The other person scanned your QR code.';
|
||||
String verificationTypeSecretQrToken(Object username) {
|
||||
return '$username has scanned your QR code.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get verificationTypeLink => 'Verified via link.';
|
||||
|
|
@ -588,9 +642,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
String get errorPlanUpgradeNotYearly =>
|
||||
'The plan upgrade must be paid for annually, as the current plan is also billed annually.';
|
||||
|
||||
@override
|
||||
String get upgradeToPaidPlan => 'Upgrade to a paid plan.';
|
||||
|
||||
@override
|
||||
String upgradeToPaidPlanButton(Object planId, Object sufix) {
|
||||
return 'Upgrade to $planId$sufix';
|
||||
|
|
@ -652,12 +703,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get open => 'Open';
|
||||
|
||||
@override
|
||||
String get createVoucher => 'Buy voucher';
|
||||
|
||||
@override
|
||||
String get redeemVoucher => 'Redeem voucher';
|
||||
|
||||
@override
|
||||
String get buy => 'Buy';
|
||||
|
||||
|
|
@ -670,15 +715,27 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get galleryDelete => 'Delete file';
|
||||
|
||||
@override
|
||||
String get galleryDetails => 'Show details';
|
||||
|
||||
@override
|
||||
String get galleryExport => 'Export to gallery';
|
||||
|
||||
@override
|
||||
String get galleryExportSuccess => 'Successfully saved in the Gallery.';
|
||||
|
||||
@override
|
||||
String get gallerySelectAll => 'Select all';
|
||||
|
||||
@override
|
||||
String get galleryDeselectAll => 'Deselect all';
|
||||
|
||||
@override
|
||||
String get galleryFavorite => 'Favorite';
|
||||
|
||||
@override
|
||||
String get galleryUnfavorite => 'Unfavorite';
|
||||
|
||||
@override
|
||||
String get galleryCancel => 'Cancel';
|
||||
|
||||
@override
|
||||
String get memoriesEmpty =>
|
||||
'As soon as you save pictures or videos, they end up here in your memories.';
|
||||
|
|
@ -689,6 +746,17 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get deleteOkBtnForAll => 'Delete for all';
|
||||
|
||||
@override
|
||||
String memoriesDeleteSnackbarSuccess(num count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'Deleted $count items successfully',
|
||||
one: 'Deleted 1 item successfully',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get deleteOkBtnForMe => 'Delete for me';
|
||||
|
||||
|
|
@ -698,6 +766,17 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get deleteImageBody => 'The image will be irrevocably deleted.';
|
||||
|
||||
@override
|
||||
String deleteMemoriesBody(num count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'The $count images will be irrevocably deleted.',
|
||||
one: 'The image will be irrevocably deleted.',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get settingsBackup => 'Backup';
|
||||
|
||||
|
|
@ -719,13 +798,10 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
'Due to twonly\'s security system, there is (currently) no password recovery function. Therefore, you must remember your password or, better yet, write it down.';
|
||||
|
||||
@override
|
||||
String get backupServer => 'Server';
|
||||
String get backupIdentityHeader => 'Identity';
|
||||
|
||||
@override
|
||||
String get backupMaxBackupSize => 'max. backup size';
|
||||
|
||||
@override
|
||||
String get backupStorageRetention => 'Storage retention';
|
||||
String get backupArchiveHeader => 'Contacts, Settings and Messages';
|
||||
|
||||
@override
|
||||
String get backupLastBackupDate => 'Last backup';
|
||||
|
|
@ -736,9 +812,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get backupLastBackupResult => 'Result';
|
||||
|
||||
@override
|
||||
String get backupData => 'Data-Backup';
|
||||
|
||||
@override
|
||||
String get backupInsecurePassword => 'Insecure password';
|
||||
|
||||
|
|
@ -771,24 +844,11 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get backupPasswordRequirement =>
|
||||
'Password must be at least 8 characters long.';
|
||||
|
||||
@override
|
||||
String get backupExpertSettings => 'Expert settings';
|
||||
'Password must be at least 10 characters long.';
|
||||
|
||||
@override
|
||||
String get backupEnableBackup => 'Activate automatic backup';
|
||||
|
||||
@override
|
||||
String get backupOwnServerDesc =>
|
||||
'Save your twonly Backup at twonly or on any server of your choice.';
|
||||
|
||||
@override
|
||||
String get backupUseOwnServer => 'Use server';
|
||||
|
||||
@override
|
||||
String get backupResetServer => 'Use standard server';
|
||||
|
||||
@override
|
||||
String get backupTwonlySaveNow => 'Save now';
|
||||
|
||||
|
|
@ -796,11 +856,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
String get backupChangePassword => 'Change password';
|
||||
|
||||
@override
|
||||
String get twonlySafeRecoverTitle => 'Recovery';
|
||||
|
||||
@override
|
||||
String get twonlySafeRecoverDesc =>
|
||||
'If you have created a backup with twonly Backup, you can restore it here.';
|
||||
String get twonlySafeRecoverTitle => 'Restore backup';
|
||||
|
||||
@override
|
||||
String get twonlySafeRecoverBtn => 'Restore backup';
|
||||
|
|
@ -1263,7 +1319,8 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
String get openYourOwnQRcode => 'Open your own QR code';
|
||||
|
||||
@override
|
||||
String get skipForNow => 'Skip for now';
|
||||
String get addContactQrSheetSubtext =>
|
||||
'Let a friend scan this QR code to add you';
|
||||
|
||||
@override
|
||||
String get finishSetupCardTitle => 'Complete your profile';
|
||||
|
|
@ -1275,6 +1332,16 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get finishSetupCardAction => 'Resume Setup';
|
||||
|
||||
@override
|
||||
String get missingBackupCardTitle => 'Setup backup';
|
||||
|
||||
@override
|
||||
String get missingBackupCardDesc =>
|
||||
'We have improved the backup mechanism, which requires you to set it up again.';
|
||||
|
||||
@override
|
||||
String get missingBackupCardAction => 'Set up now';
|
||||
|
||||
@override
|
||||
String get onboardingFinishLater => 'Finish later';
|
||||
|
||||
|
|
@ -1308,11 +1375,12 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
'Be informed about who is requesting';
|
||||
|
||||
@override
|
||||
String get userDiscoverySettingsManualApproval => 'Manual approval';
|
||||
String get userDiscoverySettingsManualApproval =>
|
||||
'Ask every time before sharing';
|
||||
|
||||
@override
|
||||
String get userDiscoverySettingsManualApprovalDesc =>
|
||||
'Before someone is shared, you\'ll be asked first.';
|
||||
'Before one of your friends is shared, you will be asked every time.';
|
||||
|
||||
@override
|
||||
String get onboardingUserDiscoveryLetFriendsFindYou =>
|
||||
|
|
@ -1474,7 +1542,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get verificationBadgeGeneralDesc =>
|
||||
'The checkmark gives you the certainty that you are messaging the right person. Scan the contact\'s QR code to verify it.';
|
||||
'The checkmark gives you the certainty that you are messaging the right person. You can verify contacts at any time by scanning their QR code.';
|
||||
|
||||
@override
|
||||
String get verificationBadgeGreenDesc =>
|
||||
|
|
@ -1488,6 +1556,40 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
String get verificationBadgeRedDesc =>
|
||||
'A contact whose identity has *not* yet been verified.';
|
||||
|
||||
@override
|
||||
String get deleteVerificationTitle => 'Delete verification?';
|
||||
|
||||
@override
|
||||
String get deleteVerificationBody =>
|
||||
'Are you sure you want to delete this verification?';
|
||||
|
||||
@override
|
||||
String secretQrTokenVerifiedSnackbar(Object username) {
|
||||
return '$username has scanned your QR code and is now verified.';
|
||||
}
|
||||
|
||||
@override
|
||||
String mutualGroupsTitle(num count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count mutual groups',
|
||||
one: '1 mutual group',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String mutualGroupsSentMessages(num count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count messages sent',
|
||||
one: '1 message sent',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String chatEntryFlameRestored(Object count) {
|
||||
return '$count flames restored';
|
||||
|
|
@ -1567,6 +1669,12 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get userDiscoverySettingsTitle => 'Mutual Friends';
|
||||
|
||||
@override
|
||||
String get userDiscoveryWhyThisIsUsed => 'Why this is used';
|
||||
|
||||
@override
|
||||
String get userDiscoveryFeatureOffers => 'Your benefits at a glance';
|
||||
|
||||
@override
|
||||
String get userDiscoveryDisabledLearnMore => 'Learn more';
|
||||
|
||||
|
|
@ -1610,6 +1718,43 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get friendSuggestionsRequest => 'Request';
|
||||
|
||||
@override
|
||||
String get friendSuggestionsAskFriend => 'Ask your friends';
|
||||
|
||||
@override
|
||||
String askFriendsDialogTitle(Object username) {
|
||||
return 'Ask about $username';
|
||||
}
|
||||
|
||||
@override
|
||||
String get askFriendsDialogDescription =>
|
||||
'Select the friends you want to ask about this user:';
|
||||
|
||||
@override
|
||||
String get askFriendsDialogConfirm => 'Ask';
|
||||
|
||||
@override
|
||||
String get askFriendsDialogCancel => 'Cancel';
|
||||
|
||||
@override
|
||||
String get chatAskAFriendReceivedDescription =>
|
||||
'Your friend just got this as a suggestion and wants to know if he knows this person.';
|
||||
|
||||
@override
|
||||
String get chatAskAFriendAddedDescription =>
|
||||
'You have added this user to your contacts.';
|
||||
|
||||
@override
|
||||
String get chatAskAFriendHide => 'Hide';
|
||||
|
||||
@override
|
||||
String get chatAskAFriendRequest => 'Request';
|
||||
|
||||
@override
|
||||
String chatAskAFriendUnknownUser(Object userId) {
|
||||
return 'User $userId';
|
||||
}
|
||||
|
||||
@override
|
||||
String contactUserDiscoveryImagesLeft(Object imagesLeft, Object username) {
|
||||
return '$imagesLeft more images are needed until your friends are shared with $username.';
|
||||
|
|
@ -1654,4 +1799,314 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get contactUserDiscoveryManualApprovalApprove => 'Approve';
|
||||
|
||||
@override
|
||||
String get exampleUserName1 => 'james';
|
||||
|
||||
@override
|
||||
String get exampleUserName2 => 'mary';
|
||||
|
||||
@override
|
||||
String get exampleUserName3 => 'john';
|
||||
|
||||
@override
|
||||
String get exampleUserName4 => 'patricia';
|
||||
|
||||
@override
|
||||
String get exampleUserName5 => 'robert';
|
||||
|
||||
@override
|
||||
String get exampleUserName6 => 'jennifer';
|
||||
|
||||
@override
|
||||
String get exampleUserName7 => 'michael';
|
||||
|
||||
@override
|
||||
String get exampleUserName8 => 'linda';
|
||||
|
||||
@override
|
||||
String get exampleUserName9 => 'william';
|
||||
|
||||
@override
|
||||
String get exampleUserName10 => 'lena';
|
||||
|
||||
@override
|
||||
String get exampleUserName11 => 'david';
|
||||
|
||||
@override
|
||||
String get exampleJane => 'jane';
|
||||
|
||||
@override
|
||||
String get back => 'Back';
|
||||
|
||||
@override
|
||||
String get onboardingExampleLabel => 'Example';
|
||||
|
||||
@override
|
||||
String makerChangedUsername(Object maker, Object oldName, Object newName) {
|
||||
return '$maker changed their username from $oldName to $newName.';
|
||||
}
|
||||
|
||||
@override
|
||||
String makerChangedDisplayName(Object maker, Object oldName, Object newName) {
|
||||
return '$maker changed their display name from $oldName to $newName.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get recoverErrorNoInternet =>
|
||||
'No internet connection. Please check your network and try again.';
|
||||
|
||||
@override
|
||||
String get recoverErrorUsernameNotValid =>
|
||||
'The username provided is not valid or does not exist.';
|
||||
|
||||
@override
|
||||
String get recoverErrorPasswordInvalid =>
|
||||
'The password provided is incorrect.';
|
||||
|
||||
@override
|
||||
String get recoverErrorTryAgainLater =>
|
||||
'The server is currently unavailable. Please try again later.';
|
||||
|
||||
@override
|
||||
String get recoverErrorUnknown =>
|
||||
'An unknown error occurred. Please try again.';
|
||||
|
||||
@override
|
||||
String get recoverSuccessTitle => 'Backup successfully recovered.';
|
||||
|
||||
@override
|
||||
String get recoverSuccessBody => 'Click here to open the app again';
|
||||
|
||||
@override
|
||||
String get iosRecoveryWelcomeBack => 'Welcome Back';
|
||||
|
||||
@override
|
||||
String get iosRecoveryPrompt =>
|
||||
'We detected a previously secured twonly identity on this device. Would you like to automatically download and restore your contacts, messages, and settings from your cloud archive?';
|
||||
|
||||
@override
|
||||
String iosRecoveryNoBackupFound(Object error) {
|
||||
return 'No backup archive could be retrieved from the server for this device.\n\nError: $error\n\nPlease proceed to register a new twonly account.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get registerNewAccount => 'Register New Account';
|
||||
|
||||
@override
|
||||
String get tryRestoreAgain => 'Try Restore Again';
|
||||
|
||||
@override
|
||||
String get registeringNewAccount => 'Registering new account';
|
||||
|
||||
@override
|
||||
String get createShortcut => 'Create shortcut';
|
||||
|
||||
@override
|
||||
String get editShortcut => 'Edit shortcut';
|
||||
|
||||
@override
|
||||
String get deleteShortcut => 'Delete shortcut';
|
||||
|
||||
@override
|
||||
String get deleteShortcutBody =>
|
||||
'Are you sure you want to delete this shortcut?';
|
||||
|
||||
@override
|
||||
String get updateShortcut => 'Update shortcut';
|
||||
|
||||
@override
|
||||
String get selectEmoji => 'Select Emoji';
|
||||
|
||||
@override
|
||||
String get errorEmojiUsedOrInvalid => 'Emoji already used or invalid';
|
||||
|
||||
@override
|
||||
String get subscriptionPledgeSecureTitle => 'Secure by Design';
|
||||
|
||||
@override
|
||||
String get subscriptionPledgeSecureDesc =>
|
||||
'Your messages and shared moments are fully end-to-end encrypted.';
|
||||
|
||||
@override
|
||||
String get subscriptionPledgeNoAdsTitle => 'No Ads or Data selling';
|
||||
|
||||
@override
|
||||
String get subscriptionPledgeNoAdsDesc =>
|
||||
'twonly will never show advertisements or sell your private data.';
|
||||
|
||||
@override
|
||||
String get subscriptionPledgeSubtitle => 'Zero ads. Total privacy.';
|
||||
|
||||
@override
|
||||
String get dragToZoom => 'Drag to Zoom';
|
||||
|
||||
@override
|
||||
String get showUsername => 'Show username';
|
||||
|
||||
@override
|
||||
String get onboardingProfileSelectionTitle => 'Choose your setup path';
|
||||
|
||||
@override
|
||||
String get onboardingProfileSelectionSubtitle =>
|
||||
'Choose how you want to configure your security and privacy settings.';
|
||||
|
||||
@override
|
||||
String get onboardingProfileSelectionDefaultTitle => 'Default';
|
||||
|
||||
@override
|
||||
String get onboardingProfileSelectionDefaultDesc =>
|
||||
'Instantly applies recommended settings so you can start using the app.';
|
||||
|
||||
@override
|
||||
String get onboardingProfileSelectionDefaultBadge => 'Fast Setup';
|
||||
|
||||
@override
|
||||
String get onboardingProfileSelectionCustomizeTitle => 'Customize';
|
||||
|
||||
@override
|
||||
String get onboardingProfileSelectionCustomizeDesc =>
|
||||
'Step-by-step setup so you can decide for yourself.';
|
||||
|
||||
@override
|
||||
String get onboardingProfileSelectionStrictTitle => 'Enhanced Protection';
|
||||
|
||||
@override
|
||||
String get onboardingProfileSelectionStrictDesc =>
|
||||
'Maximum anti-phishing defense. Recommended for *journalists & public figures*.';
|
||||
|
||||
@override
|
||||
String get replyFlameRestored => 'Flames restored';
|
||||
|
||||
@override
|
||||
String get replyAskAFriend => 'Ask a friend';
|
||||
|
||||
@override
|
||||
String get unverifiedWarningDirectTitle => 'Identity not verified in person';
|
||||
|
||||
@override
|
||||
String get unverifiedWarningGroupTitle =>
|
||||
'Not all members are verified in person';
|
||||
|
||||
@override
|
||||
String get unverifiedWarningBody =>
|
||||
'*Avoid sharing sensitive data*. Risk of *impersonation* without manual verification.';
|
||||
|
||||
@override
|
||||
String get unverifiedWarningButton => 'Verify now';
|
||||
|
||||
@override
|
||||
String get today => 'Today';
|
||||
|
||||
@override
|
||||
String get yesterday => 'Yesterday';
|
||||
|
||||
@override
|
||||
String get galleryDisableWarningTitle => 'Disable gallery saving?';
|
||||
|
||||
@override
|
||||
String get galleryDisableWarningBody =>
|
||||
'If you disable this, your media files will not be saved to your gallery and could be permanently lost if twonly is removed or has an issue, as media files are not yet backed up.';
|
||||
|
||||
@override
|
||||
String get galleryDisableWarningConfirm => 'Disable';
|
||||
|
||||
@override
|
||||
String get settingsStorageScanGalleryTitle => 'Import from Gallery';
|
||||
|
||||
@override
|
||||
String get importGalleryDeselectAll => 'Deselect all';
|
||||
|
||||
@override
|
||||
String get importGallerySelectAll => 'Select all';
|
||||
|
||||
@override
|
||||
String get importGalleryPermissionRequired =>
|
||||
'Permission to access your gallery is required to import previous twonly media files.';
|
||||
|
||||
@override
|
||||
String importGalleryPermissionError(Object error) {
|
||||
return 'An error occurred while requesting permission: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String importGalleryLoadError(Object error) {
|
||||
return 'Failed to load assets: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String importGalleryImportingOf(Object current, Object total) {
|
||||
return 'Importing $current of $total...';
|
||||
}
|
||||
|
||||
@override
|
||||
String get importGalleryStarting => 'Starting import...';
|
||||
|
||||
@override
|
||||
String importGalleryComplete(
|
||||
Object imported,
|
||||
Object duplicated,
|
||||
Object failed,
|
||||
) {
|
||||
return 'Import complete: $imported successfully imported, $duplicated duplicated and $failed failed.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get importGalleryGrantAccess => 'Grant Access';
|
||||
|
||||
@override
|
||||
String get importGalleryOpenSettings => 'Open Settings';
|
||||
|
||||
@override
|
||||
String get importGalleryPermissionDenied =>
|
||||
'Permission to access gallery denied.';
|
||||
|
||||
@override
|
||||
String get importGalleryTryAgain => 'Try Again';
|
||||
|
||||
@override
|
||||
String get importGalleryAlbumNotFound => '\"twonly\" album not found';
|
||||
|
||||
@override
|
||||
String get importGalleryAlbumNotFoundDesc =>
|
||||
'If you don\'t have this album yet, you can also create it to import photos into twonly.';
|
||||
|
||||
@override
|
||||
String get importGalleryNoImagesFound => 'No images found';
|
||||
|
||||
@override
|
||||
String get importGalleryNoImagesFoundDesc =>
|
||||
'There are no images on your device.';
|
||||
|
||||
@override
|
||||
String get importGalleryShowAllImages => 'Show all images';
|
||||
|
||||
@override
|
||||
String get importGalleryShowTwonlyAlbum => 'Show twonly album';
|
||||
|
||||
@override
|
||||
String get importGalleryToggleDescAll => 'Viewing all images on your device.';
|
||||
|
||||
@override
|
||||
String get importGalleryToggleDescTwonly => 'Viewing the \"twonly\" album.';
|
||||
|
||||
@override
|
||||
String get importGalleryFilterTwonly => 'Only show the twonly-Album';
|
||||
|
||||
@override
|
||||
String get importGalleryRefresh => 'Refresh';
|
||||
|
||||
@override
|
||||
String get importGallerySelectToImport => 'Select items to import';
|
||||
|
||||
@override
|
||||
String importGalleryImportCount(num count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'Import $count items',
|
||||
one: 'Import 1 item',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 03bf220400bf35002c77e153768bd0f963a97d89
|
||||
Subproject commit 189bf8f4dbe2bee4f19a15b9640b8826e4f2e235
|
||||
51
lib/src/model/json/backup.model.dart
Normal file
51
lib/src/model/json/backup.model.dart
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
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);
|
||||
}
|
||||
65
lib/src/model/json/backup.model.g.dart
Normal file
65
lib/src/model/json/backup.model.g.dart
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
// 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,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:twonly/src/services/profile.service.dart';
|
||||
part 'userdata.model.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
|
|
@ -10,6 +11,7 @@ class UserData {
|
|||
required this.displayName,
|
||||
required this.subscriptionPlan,
|
||||
required this.currentSetupPage,
|
||||
required this.appVersion,
|
||||
});
|
||||
factory UserData.fromJson(Map<String, dynamic> json) =>
|
||||
_$UserDataFromJson(json);
|
||||
|
|
@ -35,6 +37,12 @@ class UserData {
|
|||
@JsonKey(defaultValue: 0)
|
||||
int deviceId = 0;
|
||||
|
||||
@JsonKey(defaultValue: SetupProfile.standard)
|
||||
SetupProfile setupProfile = SetupProfile.standard;
|
||||
|
||||
@JsonKey(defaultValue: SecurityProfile.normal)
|
||||
SecurityProfile securityProfile = SecurityProfile.normal;
|
||||
|
||||
// --- SUBSCRIPTION DTA ---
|
||||
|
||||
@JsonKey(defaultValue: 'Free')
|
||||
|
|
@ -57,6 +65,12 @@ class UserData {
|
|||
@JsonKey(defaultValue: false)
|
||||
bool requestedAudioPermission = false;
|
||||
|
||||
@JsonKey(defaultValue: false)
|
||||
bool enableDatabaseLogging = false;
|
||||
|
||||
@JsonKey(defaultValue: false)
|
||||
bool automaticallyMarkEqualMediaFilesAsOpened = false;
|
||||
|
||||
@JsonKey(defaultValue: true)
|
||||
bool videoStabilizationEnabled = true;
|
||||
|
||||
|
|
@ -73,8 +87,8 @@ class UserData {
|
|||
|
||||
Map<String, List<String>>? autoDownloadOptions;
|
||||
|
||||
@JsonKey(defaultValue: false)
|
||||
bool storeMediaFilesInGallery = false;
|
||||
@JsonKey(defaultValue: true)
|
||||
bool storeMediaFilesInGallery = true;
|
||||
|
||||
@JsonKey(defaultValue: false)
|
||||
bool autoStoreAllSendUnlimitedMediaFiles = false;
|
||||
|
|
@ -109,6 +123,9 @@ class UserData {
|
|||
@JsonKey(defaultValue: true)
|
||||
bool userDiscoverySharePromotion = true;
|
||||
|
||||
@JsonKey(defaultValue: false)
|
||||
bool userDiscoveryInitializationError = false;
|
||||
|
||||
// -- Custom DATA --
|
||||
|
||||
@JsonKey(defaultValue: 100_000)
|
||||
|
|
@ -125,12 +142,20 @@ class UserData {
|
|||
@JsonKey(defaultValue: true)
|
||||
bool updateFCMToken = true;
|
||||
|
||||
@JsonKey(defaultValue: true)
|
||||
bool canUseLoginTokenForAuth = true;
|
||||
|
||||
// --- BACKUP ---
|
||||
|
||||
DateTime? nextTimeToShowBackupNotice;
|
||||
BackupServer? backupServer;
|
||||
@Deprecated('Use the secure storage in rust')
|
||||
TwonlySafeBackup? twonlySafeBackup;
|
||||
|
||||
@JsonKey(defaultValue: false)
|
||||
bool isBackupEnabled = false;
|
||||
|
||||
// Used for push notifcation via FCM.
|
||||
String? fcmToken;
|
||||
|
||||
// For my master thesis I want to create a anonymous user study:
|
||||
// - users in the "Tester" Plan can, if they want, take part of the user study
|
||||
|
||||
|
|
@ -151,6 +176,9 @@ class UserData {
|
|||
@JsonKey(defaultValue: false)
|
||||
bool skipSetupPages = false;
|
||||
|
||||
@JsonKey(defaultValue: false)
|
||||
bool hasZoomed = false;
|
||||
|
||||
Map<String, dynamic> toJson() => _$UserDataToJson(this);
|
||||
}
|
||||
|
||||
|
|
@ -172,19 +200,3 @@ class TwonlySafeBackup {
|
|||
List<int> encryptionKey;
|
||||
Map<String, dynamic> toJson() => _$TwonlySafeBackupToJson(this);
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class BackupServer {
|
||||
BackupServer({
|
||||
required this.serverUrl,
|
||||
required this.retentionDays,
|
||||
required this.maxBackupBytes,
|
||||
});
|
||||
factory BackupServer.fromJson(Map<String, dynamic> json) =>
|
||||
_$BackupServerFromJson(json);
|
||||
|
||||
String serverUrl;
|
||||
int retentionDays;
|
||||
int maxBackupBytes;
|
||||
Map<String, dynamic> toJson() => _$BackupServerToJson(this);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,13 +13,22 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) =>
|
|||
displayName: json['displayName'] as String,
|
||||
subscriptionPlan: json['subscriptionPlan'] as String? ?? 'Free',
|
||||
currentSetupPage: json['currentSetupPage'] as String?,
|
||||
appVersion: (json['appVersion'] as num?)?.toInt() ?? 0,
|
||||
)
|
||||
..avatarSvg = json['avatarSvg'] as String?
|
||||
..avatarJson = json['avatarJson'] as String?
|
||||
..appVersion = (json['appVersion'] as num?)?.toInt() ?? 0
|
||||
..avatarCounter = (json['avatarCounter'] as num?)?.toInt() ?? 0
|
||||
..isDeveloper = json['isDeveloper'] as bool? ?? false
|
||||
..deviceId = (json['deviceId'] as num?)?.toInt() ?? 0
|
||||
..setupProfile =
|
||||
$enumDecodeNullable(_$SetupProfileEnumMap, json['setupProfile']) ??
|
||||
SetupProfile.standard
|
||||
..securityProfile =
|
||||
$enumDecodeNullable(
|
||||
_$SecurityProfileEnumMap,
|
||||
json['securityProfile'],
|
||||
) ??
|
||||
SecurityProfile.normal
|
||||
..subscriptionPlanIdStore = json['subscriptionPlanIdStore'] as String?
|
||||
..lastImageSend = json['lastImageSend'] == null
|
||||
? null
|
||||
|
|
@ -33,6 +42,9 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) =>
|
|||
..defaultShowTime = (json['defaultShowTime'] as num?)?.toInt()
|
||||
..requestedAudioPermission =
|
||||
json['requestedAudioPermission'] as bool? ?? false
|
||||
..enableDatabaseLogging = json['enableDatabaseLogging'] as bool? ?? false
|
||||
..automaticallyMarkEqualMediaFilesAsOpened =
|
||||
json['automaticallyMarkEqualMediaFilesAsOpened'] as bool? ?? false
|
||||
..videoStabilizationEnabled =
|
||||
json['videoStabilizationEnabled'] as bool? ?? true
|
||||
..showFeedbackShortcut = json['showFeedbackShortcut'] as bool? ?? true
|
||||
|
|
@ -71,6 +83,8 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) =>
|
|||
json['userDiscoveryRequiresManualApproval'] as bool? ?? false
|
||||
..userDiscoverySharePromotion =
|
||||
json['userDiscoverySharePromotion'] as bool? ?? true
|
||||
..userDiscoveryInitializationError =
|
||||
json['userDiscoveryInitializationError'] as bool? ?? false
|
||||
..currentPreKeyIndexStart =
|
||||
(json['currentPreKeyIndexStart'] as num?)?.toInt() ?? 100000
|
||||
..currentSignedPreKeyIndexStart =
|
||||
|
|
@ -80,17 +94,15 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) =>
|
|||
.toList()
|
||||
..hideChangeLog = json['hideChangeLog'] as bool? ?? true
|
||||
..updateFCMToken = json['updateFCMToken'] as bool? ?? true
|
||||
..nextTimeToShowBackupNotice = json['nextTimeToShowBackupNotice'] == null
|
||||
? null
|
||||
: DateTime.parse(json['nextTimeToShowBackupNotice'] as String)
|
||||
..backupServer = json['backupServer'] == null
|
||||
? null
|
||||
: BackupServer.fromJson(json['backupServer'] as Map<String, dynamic>)
|
||||
..canUseLoginTokenForAuth =
|
||||
json['canUseLoginTokenForAuth'] as bool? ?? true
|
||||
..twonlySafeBackup = json['twonlySafeBackup'] == null
|
||||
? null
|
||||
: TwonlySafeBackup.fromJson(
|
||||
json['twonlySafeBackup'] as Map<String, dynamic>,
|
||||
)
|
||||
..isBackupEnabled = json['isBackupEnabled'] as bool? ?? false
|
||||
..fcmToken = json['fcmToken'] as String?
|
||||
..askedForUserStudyPermission =
|
||||
json['askedForUserStudyPermission'] as bool? ?? false
|
||||
..userStudyParticipantsToken =
|
||||
|
|
@ -100,7 +112,8 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) =>
|
|||
..lastUserStudyDataUpload = json['lastUserStudyDataUpload'] == null
|
||||
? null
|
||||
: DateTime.parse(json['lastUserStudyDataUpload'] as String)
|
||||
..skipSetupPages = json['skipSetupPages'] as bool? ?? false;
|
||||
..skipSetupPages = json['skipSetupPages'] as bool? ?? false
|
||||
..hasZoomed = json['hasZoomed'] as bool? ?? false;
|
||||
|
||||
Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
||||
'userId': instance.userId,
|
||||
|
|
@ -112,6 +125,8 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
|||
'avatarCounter': instance.avatarCounter,
|
||||
'isDeveloper': instance.isDeveloper,
|
||||
'deviceId': instance.deviceId,
|
||||
'setupProfile': _$SetupProfileEnumMap[instance.setupProfile]!,
|
||||
'securityProfile': _$SecurityProfileEnumMap[instance.securityProfile]!,
|
||||
'subscriptionPlan': instance.subscriptionPlan,
|
||||
'subscriptionPlanIdStore': instance.subscriptionPlanIdStore,
|
||||
'lastImageSend': instance.lastImageSend?.toIso8601String(),
|
||||
|
|
@ -121,6 +136,9 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
|||
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
|
||||
'defaultShowTime': instance.defaultShowTime,
|
||||
'requestedAudioPermission': instance.requestedAudioPermission,
|
||||
'enableDatabaseLogging': instance.enableDatabaseLogging,
|
||||
'automaticallyMarkEqualMediaFilesAsOpened':
|
||||
instance.automaticallyMarkEqualMediaFilesAsOpened,
|
||||
'videoStabilizationEnabled': instance.videoStabilizationEnabled,
|
||||
'showFeedbackShortcut': instance.showFeedbackShortcut,
|
||||
'showShowImagePreviewWhenSending': instance.showShowImagePreviewWhenSending,
|
||||
|
|
@ -142,15 +160,16 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
|||
'userDiscoveryRequiresManualApproval':
|
||||
instance.userDiscoveryRequiresManualApproval,
|
||||
'userDiscoverySharePromotion': instance.userDiscoverySharePromotion,
|
||||
'userDiscoveryInitializationError': instance.userDiscoveryInitializationError,
|
||||
'currentPreKeyIndexStart': instance.currentPreKeyIndexStart,
|
||||
'currentSignedPreKeyIndexStart': instance.currentSignedPreKeyIndexStart,
|
||||
'lastChangeLogHash': instance.lastChangeLogHash,
|
||||
'hideChangeLog': instance.hideChangeLog,
|
||||
'updateFCMToken': instance.updateFCMToken,
|
||||
'nextTimeToShowBackupNotice': instance.nextTimeToShowBackupNotice
|
||||
?.toIso8601String(),
|
||||
'backupServer': instance.backupServer,
|
||||
'canUseLoginTokenForAuth': instance.canUseLoginTokenForAuth,
|
||||
'twonlySafeBackup': instance.twonlySafeBackup,
|
||||
'isBackupEnabled': instance.isBackupEnabled,
|
||||
'fcmToken': instance.fcmToken,
|
||||
'askedForUserStudyPermission': instance.askedForUserStudyPermission,
|
||||
'userStudyParticipantsToken': instance.userStudyParticipantsToken,
|
||||
'userStudyCountNewFriendsViaSuggestion':
|
||||
|
|
@ -159,6 +178,18 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
|||
?.toIso8601String(),
|
||||
'currentSetupPage': instance.currentSetupPage,
|
||||
'skipSetupPages': instance.skipSetupPages,
|
||||
'hasZoomed': instance.hasZoomed,
|
||||
};
|
||||
|
||||
const _$SetupProfileEnumMap = {
|
||||
SetupProfile.standard: 'standard',
|
||||
SetupProfile.customized: 'customized',
|
||||
SetupProfile.maximum: 'maximum',
|
||||
};
|
||||
|
||||
const _$SecurityProfileEnumMap = {
|
||||
SecurityProfile.normal: 'normal',
|
||||
SecurityProfile.strict: 'strict',
|
||||
};
|
||||
|
||||
const _$ThemeModeEnumMap = {
|
||||
|
|
@ -201,16 +232,3 @@ const _$LastBackupUploadStateEnumMap = {
|
|||
LastBackupUploadState.failed: 'failed',
|
||||
LastBackupUploadState.success: 'success',
|
||||
};
|
||||
|
||||
BackupServer _$BackupServerFromJson(Map<String, dynamic> json) => BackupServer(
|
||||
serverUrl: json['serverUrl'] as String,
|
||||
retentionDays: (json['retentionDays'] as num).toInt(),
|
||||
maxBackupBytes: (json['maxBackupBytes'] as num).toInt(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$BackupServerToJson(BackupServer instance) =>
|
||||
<String, dynamic>{
|
||||
'serverUrl': instance.serverUrl,
|
||||
'retentionDays': instance.retentionDays,
|
||||
'maxBackupBytes': instance.maxBackupBytes,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,9 +5,11 @@ class MemoryItem {
|
|||
MemoryItem({
|
||||
required this.mediaService,
|
||||
required this.messages,
|
||||
this.sender,
|
||||
});
|
||||
final List<Message> messages;
|
||||
final MediaFileService mediaService;
|
||||
final Contact? sender;
|
||||
|
||||
static Future<Map<String, MemoryItem>> convertFromMessages(
|
||||
List<Message> messages,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -134,13 +134,33 @@ const Handshake$json = {
|
|||
'9': 0,
|
||||
'10': 'requestPOW'
|
||||
},
|
||||
{
|
||||
'1': 'authenticate_with_login_token',
|
||||
'3': 6,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.Handshake.AuthenticateWithLoginToken',
|
||||
'9': 0,
|
||||
'10': 'authenticateWithLoginToken'
|
||||
},
|
||||
{
|
||||
'1': 'get_userid_by_username',
|
||||
'3': 7,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.Handshake.GetUserIdByUsername',
|
||||
'9': 0,
|
||||
'10': 'getUseridByUsername'
|
||||
},
|
||||
],
|
||||
'3': [
|
||||
Handshake_RequestPOW$json,
|
||||
Handshake_Register$json,
|
||||
Handshake_GetAuthChallenge$json,
|
||||
Handshake_GetUserIdByUsername$json,
|
||||
Handshake_GetAuthToken$json,
|
||||
Handshake_Authenticate$json
|
||||
Handshake_Authenticate$json,
|
||||
Handshake_AuthenticateWithLoginToken$json
|
||||
],
|
||||
'8': [
|
||||
{'1': 'Handshake'},
|
||||
|
|
@ -186,9 +206,19 @@ const Handshake_Register$json = {
|
|||
{'1': 'is_ios', '3': 8, '4': 1, '5': 8, '10': 'isIos'},
|
||||
{'1': 'lang_code', '3': 9, '4': 1, '5': 9, '10': 'langCode'},
|
||||
{'1': 'proof_of_work', '3': 10, '4': 1, '5': 3, '10': 'proofOfWork'},
|
||||
{
|
||||
'1': 'login_token',
|
||||
'3': 11,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'9': 1,
|
||||
'10': 'loginToken',
|
||||
'17': true
|
||||
},
|
||||
],
|
||||
'8': [
|
||||
{'1': '_invite_code'},
|
||||
{'1': '_login_token'},
|
||||
],
|
||||
};
|
||||
|
||||
|
|
@ -197,6 +227,14 @@ const Handshake_GetAuthChallenge$json = {
|
|||
'1': 'GetAuthChallenge',
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use handshakeDescriptor instead')
|
||||
const Handshake_GetUserIdByUsername$json = {
|
||||
'1': 'GetUserIdByUsername',
|
||||
'2': [
|
||||
{'1': 'username', '3': 1, '4': 1, '5': 9, '10': 'username'},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use handshakeDescriptor instead')
|
||||
const Handshake_GetAuthToken$json = {
|
||||
'1': 'GetAuthToken',
|
||||
|
|
@ -247,6 +285,24 @@ const Handshake_Authenticate$json = {
|
|||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use handshakeDescriptor instead')
|
||||
const Handshake_AuthenticateWithLoginToken$json = {
|
||||
'1': 'AuthenticateWithLoginToken',
|
||||
'2': [
|
||||
{'1': 'user_id', '3': 1, '4': 1, '5': 3, '10': 'userId'},
|
||||
{
|
||||
'1': 'secret_login_token',
|
||||
'3': 2,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'10': 'secretLoginToken'
|
||||
},
|
||||
{'1': 'app_version', '3': 3, '4': 1, '5': 9, '10': 'appVersion'},
|
||||
{'1': 'device_id', '3': 4, '4': 1, '5': 3, '10': 'deviceId'},
|
||||
{'1': 'in_background', '3': 5, '4': 1, '5': 8, '10': 'inBackground'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `Handshake`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List handshakeDescriptor = $convert.base64Decode(
|
||||
'CglIYW5kc2hha2USQgoIcmVnaXN0ZXIYASABKAsyJC5jbGllbnRfdG9fc2VydmVyLkhhbmRzaG'
|
||||
|
|
@ -256,20 +312,30 @@ final $typed_data.Uint8List handshakeDescriptor = $convert.base64Decode(
|
|||
'LkdldEF1dGhUb2tlbkgAUgxnZXRBdXRoVG9rZW4STgoMYXV0aGVudGljYXRlGAQgASgLMiguY2'
|
||||
'xpZW50X3RvX3NlcnZlci5IYW5kc2hha2UuQXV0aGVudGljYXRlSABSDGF1dGhlbnRpY2F0ZRJI'
|
||||
'CgpyZXF1ZXN0UE9XGAUgASgLMiYuY2xpZW50X3RvX3NlcnZlci5IYW5kc2hha2UuUmVxdWVzdF'
|
||||
'BPV0gAUgpyZXF1ZXN0UE9XGgwKClJlcXVlc3RQT1calAMKCFJlZ2lzdGVyEhoKCHVzZXJuYW1l'
|
||||
'GAEgASgJUgh1c2VybmFtZRIkCgtpbnZpdGVfY29kZRgCIAEoCUgAUgppbnZpdGVDb2RliAEBEi'
|
||||
'4KE3B1YmxpY19pZGVudGl0eV9rZXkYAyABKAxSEXB1YmxpY0lkZW50aXR5S2V5EiMKDXNpZ25l'
|
||||
'ZF9wcmVrZXkYBCABKAxSDHNpZ25lZFByZWtleRI2ChdzaWduZWRfcHJla2V5X3NpZ25hdHVyZR'
|
||||
'gFIAEoDFIVc2lnbmVkUHJla2V5U2lnbmF0dXJlEigKEHNpZ25lZF9wcmVrZXlfaWQYBiABKANS'
|
||||
'DnNpZ25lZFByZWtleUlkEicKD3JlZ2lzdHJhdGlvbl9pZBgHIAEoA1IOcmVnaXN0cmF0aW9uSW'
|
||||
'QSFQoGaXNfaW9zGAggASgIUgVpc0lvcxIbCglsYW5nX2NvZGUYCSABKAlSCGxhbmdDb2RlEiIK'
|
||||
'DXByb29mX29mX3dvcmsYCiABKANSC3Byb29mT2ZXb3JrQg4KDF9pbnZpdGVfY29kZRoSChBHZX'
|
||||
'RBdXRoQ2hhbGxlbmdlGkMKDEdldEF1dGhUb2tlbhIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQS'
|
||||
'GgoIcmVzcG9uc2UYAiABKAxSCHJlc3BvbnNlGugBCgxBdXRoZW50aWNhdGUSFwoHdXNlcl9pZB'
|
||||
'gBIAEoA1IGdXNlcklkEh0KCmF1dGhfdG9rZW4YAiABKAxSCWF1dGhUb2tlbhIkCgthcHBfdmVy'
|
||||
'c2lvbhgDIAEoCUgAUgphcHBWZXJzaW9uiAEBEiAKCWRldmljZV9pZBgEIAEoA0gBUghkZXZpY2'
|
||||
'VJZIgBARIoCg1pbl9iYWNrZ3JvdW5kGAUgASgISAJSDGluQmFja2dyb3VuZIgBAUIOCgxfYXBw'
|
||||
'X3ZlcnNpb25CDAoKX2RldmljZV9pZEIQCg5faW5fYmFja2dyb3VuZEILCglIYW5kc2hha2U=');
|
||||
'BPV0gAUgpyZXF1ZXN0UE9XEnsKHWF1dGhlbnRpY2F0ZV93aXRoX2xvZ2luX3Rva2VuGAYgASgL'
|
||||
'MjYuY2xpZW50X3RvX3NlcnZlci5IYW5kc2hha2UuQXV0aGVudGljYXRlV2l0aExvZ2luVG9rZW'
|
||||
'5IAFIaYXV0aGVudGljYXRlV2l0aExvZ2luVG9rZW4SZgoWZ2V0X3VzZXJpZF9ieV91c2VybmFt'
|
||||
'ZRgHIAEoCzIvLmNsaWVudF90b19zZXJ2ZXIuSGFuZHNoYWtlLkdldFVzZXJJZEJ5VXNlcm5hbW'
|
||||
'VIAFITZ2V0VXNlcmlkQnlVc2VybmFtZRoMCgpSZXF1ZXN0UE9XGsoDCghSZWdpc3RlchIaCgh1'
|
||||
'c2VybmFtZRgBIAEoCVIIdXNlcm5hbWUSJAoLaW52aXRlX2NvZGUYAiABKAlIAFIKaW52aXRlQ2'
|
||||
'9kZYgBARIuChNwdWJsaWNfaWRlbnRpdHlfa2V5GAMgASgMUhFwdWJsaWNJZGVudGl0eUtleRIj'
|
||||
'Cg1zaWduZWRfcHJla2V5GAQgASgMUgxzaWduZWRQcmVrZXkSNgoXc2lnbmVkX3ByZWtleV9zaW'
|
||||
'duYXR1cmUYBSABKAxSFXNpZ25lZFByZWtleVNpZ25hdHVyZRIoChBzaWduZWRfcHJla2V5X2lk'
|
||||
'GAYgASgDUg5zaWduZWRQcmVrZXlJZBInCg9yZWdpc3RyYXRpb25faWQYByABKANSDnJlZ2lzdH'
|
||||
'JhdGlvbklkEhUKBmlzX2lvcxgIIAEoCFIFaXNJb3MSGwoJbGFuZ19jb2RlGAkgASgJUghsYW5n'
|
||||
'Q29kZRIiCg1wcm9vZl9vZl93b3JrGAogASgDUgtwcm9vZk9mV29yaxIkCgtsb2dpbl90b2tlbh'
|
||||
'gLIAEoDEgBUgpsb2dpblRva2VuiAEBQg4KDF9pbnZpdGVfY29kZUIOCgxfbG9naW5fdG9rZW4a'
|
||||
'EgoQR2V0QXV0aENoYWxsZW5nZRoxChNHZXRVc2VySWRCeVVzZXJuYW1lEhoKCHVzZXJuYW1lGA'
|
||||
'EgASgJUgh1c2VybmFtZRpDCgxHZXRBdXRoVG9rZW4SFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklk'
|
||||
'EhoKCHJlc3BvbnNlGAIgASgMUghyZXNwb25zZRroAQoMQXV0aGVudGljYXRlEhcKB3VzZXJfaW'
|
||||
'QYASABKANSBnVzZXJJZBIdCgphdXRoX3Rva2VuGAIgASgMUglhdXRoVG9rZW4SJAoLYXBwX3Zl'
|
||||
'cnNpb24YAyABKAlIAFIKYXBwVmVyc2lvbogBARIgCglkZXZpY2VfaWQYBCABKANIAVIIZGV2aW'
|
||||
'NlSWSIAQESKAoNaW5fYmFja2dyb3VuZBgFIAEoCEgCUgxpbkJhY2tncm91bmSIAQFCDgoMX2Fw'
|
||||
'cF92ZXJzaW9uQgwKCl9kZXZpY2VfaWRCEAoOX2luX2JhY2tncm91bmQaxgEKGkF1dGhlbnRpY2'
|
||||
'F0ZVdpdGhMb2dpblRva2VuEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBIsChJzZWNyZXRfbG9n'
|
||||
'aW5fdG9rZW4YAiABKAxSEHNlY3JldExvZ2luVG9rZW4SHwoLYXBwX3ZlcnNpb24YAyABKAlSCm'
|
||||
'FwcFZlcnNpb24SGwoJZGV2aWNlX2lkGAQgASgDUghkZXZpY2VJZBIjCg1pbl9iYWNrZ3JvdW5k'
|
||||
'GAUgASgIUgxpbkJhY2tncm91bmRCCwoJSGFuZHNoYWtl');
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
const ApplicationData$json = {
|
||||
|
|
@ -321,13 +387,13 @@ const ApplicationData$json = {
|
|||
'10': 'updateGoogleFcmToken'
|
||||
},
|
||||
{
|
||||
'1': 'getLocation',
|
||||
'1': 'deprecated_9',
|
||||
'3': 9,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.ApplicationData.GetLocation',
|
||||
'6': '.client_to_server.ApplicationData.Deprecated',
|
||||
'9': 0,
|
||||
'10': 'getLocation'
|
||||
'10': 'deprecated9'
|
||||
},
|
||||
{
|
||||
'1': 'getCurrentPlanInfos',
|
||||
|
|
@ -339,13 +405,13 @@ const ApplicationData$json = {
|
|||
'10': 'getCurrentPlanInfos'
|
||||
},
|
||||
{
|
||||
'1': 'redeemVoucher',
|
||||
'1': 'deprecated_11',
|
||||
'3': 11,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.ApplicationData.RedeemVoucher',
|
||||
'6': '.client_to_server.ApplicationData.Deprecated',
|
||||
'9': 0,
|
||||
'10': 'redeemVoucher'
|
||||
'10': 'deprecated11'
|
||||
},
|
||||
{
|
||||
'1': 'getAvailablePlans',
|
||||
|
|
@ -357,58 +423,58 @@ const ApplicationData$json = {
|
|||
'10': 'getAvailablePlans'
|
||||
},
|
||||
{
|
||||
'1': 'createVoucher',
|
||||
'1': 'deprecated_13',
|
||||
'3': 13,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.ApplicationData.CreateVoucher',
|
||||
'6': '.client_to_server.ApplicationData.Deprecated',
|
||||
'9': 0,
|
||||
'10': 'createVoucher'
|
||||
'10': 'deprecated13'
|
||||
},
|
||||
{
|
||||
'1': 'getVouchers',
|
||||
'1': 'deprecated_14',
|
||||
'3': 14,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.ApplicationData.GetVouchers',
|
||||
'6': '.client_to_server.ApplicationData.Deprecated',
|
||||
'9': 0,
|
||||
'10': 'getVouchers'
|
||||
'10': 'deprecated14'
|
||||
},
|
||||
{
|
||||
'1': 'switchtoPayedPlan',
|
||||
'1': 'deprecated_15',
|
||||
'3': 15,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.ApplicationData.SwitchToPayedPlan',
|
||||
'6': '.client_to_server.ApplicationData.Deprecated',
|
||||
'9': 0,
|
||||
'10': 'switchtoPayedPlan'
|
||||
'10': 'deprecated15'
|
||||
},
|
||||
{
|
||||
'1': 'getAddaccountsInvites',
|
||||
'1': 'deprecated_16',
|
||||
'3': 16,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.ApplicationData.GetAddAccountsInvites',
|
||||
'6': '.client_to_server.ApplicationData.Deprecated',
|
||||
'9': 0,
|
||||
'10': 'getAddaccountsInvites'
|
||||
'10': 'deprecated16'
|
||||
},
|
||||
{
|
||||
'1': 'redeemAdditionalCode',
|
||||
'1': 'deprecated_17',
|
||||
'3': 17,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.ApplicationData.RedeemAdditionalCode',
|
||||
'6': '.client_to_server.ApplicationData.Deprecated',
|
||||
'9': 0,
|
||||
'10': 'redeemAdditionalCode'
|
||||
'10': 'deprecated17'
|
||||
},
|
||||
{
|
||||
'1': 'updatePlanOptions',
|
||||
'1': 'deprecated_19',
|
||||
'3': 19,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.ApplicationData.UpdatePlanOptions',
|
||||
'6': '.client_to_server.ApplicationData.Deprecated',
|
||||
'9': 0,
|
||||
'10': 'updatePlanOptions'
|
||||
'10': 'deprecated19'
|
||||
},
|
||||
{
|
||||
'1': 'downloadDone',
|
||||
|
|
@ -500,6 +566,15 @@ const ApplicationData$json = {
|
|||
'9': 0,
|
||||
'10': 'addAdditionalUser'
|
||||
},
|
||||
{
|
||||
'1': 'set_login_token',
|
||||
'3': 30,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.ApplicationData.SetLoginToken',
|
||||
'9': 0,
|
||||
'10': 'setLoginToken'
|
||||
},
|
||||
],
|
||||
'3': [
|
||||
ApplicationData_TextMessage$json,
|
||||
|
|
@ -507,16 +582,9 @@ const ApplicationData$json = {
|
|||
ApplicationData_ChangeUsername$json,
|
||||
ApplicationData_UpdateGoogleFcmToken$json,
|
||||
ApplicationData_GetUserById$json,
|
||||
ApplicationData_RedeemVoucher$json,
|
||||
ApplicationData_SwitchToPayedPlan$json,
|
||||
ApplicationData_UpdatePlanOptions$json,
|
||||
ApplicationData_CreateVoucher$json,
|
||||
ApplicationData_GetLocation$json,
|
||||
ApplicationData_GetVouchers$json,
|
||||
ApplicationData_GetAvailablePlans$json,
|
||||
ApplicationData_GetAddAccountsInvites$json,
|
||||
ApplicationData_GetCurrentPlanInfos$json,
|
||||
ApplicationData_RedeemAdditionalCode$json,
|
||||
ApplicationData_RemoveAdditionalUser$json,
|
||||
ApplicationData_GetPrekeysByUserId$json,
|
||||
ApplicationData_GetSignedPreKeyByUserId$json,
|
||||
|
|
@ -526,7 +594,9 @@ const ApplicationData$json = {
|
|||
ApplicationData_IPAPurchase$json,
|
||||
ApplicationData_IPAForceCheck$json,
|
||||
ApplicationData_DeleteAccount$json,
|
||||
ApplicationData_AddAdditionalUser$json
|
||||
ApplicationData_AddAdditionalUser$json,
|
||||
ApplicationData_SetLoginToken$json,
|
||||
ApplicationData_Deprecated$json
|
||||
],
|
||||
'8': [
|
||||
{'1': 'ApplicationData'},
|
||||
|
|
@ -586,50 +656,6 @@ const ApplicationData_GetUserById$json = {
|
|||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
const ApplicationData_RedeemVoucher$json = {
|
||||
'1': 'RedeemVoucher',
|
||||
'2': [
|
||||
{'1': 'voucher', '3': 1, '4': 1, '5': 9, '10': 'voucher'},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
const ApplicationData_SwitchToPayedPlan$json = {
|
||||
'1': 'SwitchToPayedPlan',
|
||||
'2': [
|
||||
{'1': 'plan_id', '3': 1, '4': 1, '5': 9, '10': 'planId'},
|
||||
{'1': 'pay_monthly', '3': 2, '4': 1, '5': 8, '10': 'payMonthly'},
|
||||
{'1': 'auto_renewal', '3': 3, '4': 1, '5': 8, '10': 'autoRenewal'},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
const ApplicationData_UpdatePlanOptions$json = {
|
||||
'1': 'UpdatePlanOptions',
|
||||
'2': [
|
||||
{'1': 'auto_renewal', '3': 1, '4': 1, '5': 8, '10': 'autoRenewal'},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
const ApplicationData_CreateVoucher$json = {
|
||||
'1': 'CreateVoucher',
|
||||
'2': [
|
||||
{'1': 'value_cents', '3': 1, '4': 1, '5': 13, '10': 'valueCents'},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
const ApplicationData_GetLocation$json = {
|
||||
'1': 'GetLocation',
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
const ApplicationData_GetVouchers$json = {
|
||||
'1': 'GetVouchers',
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
const ApplicationData_GetAvailablePlans$json = {
|
||||
'1': 'GetAvailablePlans',
|
||||
|
|
@ -645,14 +671,6 @@ const ApplicationData_GetCurrentPlanInfos$json = {
|
|||
'1': 'GetCurrentPlanInfos',
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
const ApplicationData_RedeemAdditionalCode$json = {
|
||||
'1': 'RedeemAdditionalCode',
|
||||
'2': [
|
||||
{'1': 'invite_code', '3': 2, '4': 1, '5': 9, '10': 'inviteCode'},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
const ApplicationData_RemoveAdditionalUser$json = {
|
||||
'1': 'RemoveAdditionalUser',
|
||||
|
|
@ -744,6 +762,19 @@ const ApplicationData_AddAdditionalUser$json = {
|
|||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
const ApplicationData_SetLoginToken$json = {
|
||||
'1': 'SetLoginToken',
|
||||
'2': [
|
||||
{'1': 'login_token', '3': 1, '4': 1, '5': 12, '10': 'loginToken'},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
const ApplicationData_Deprecated$json = {
|
||||
'1': 'Deprecated',
|
||||
};
|
||||
|
||||
/// Descriptor for `ApplicationData`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List applicationDataDescriptor = $convert.base64Decode(
|
||||
'Cg9BcHBsaWNhdGlvbkRhdGESUQoLdGV4dE1lc3NhZ2UYASABKAsyLS5jbGllbnRfdG9fc2Vydm'
|
||||
|
|
@ -755,66 +786,61 @@ final $typed_data.Uint8List applicationDataDescriptor = $convert.base64Decode(
|
|||
'bnRfdG9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5HZXRVc2VyQnlJZEgAUgtnZXRVc2VyQnlJZB'
|
||||
'JsChR1cGRhdGVHb29nbGVGY21Ub2tlbhgIIAEoCzI2LmNsaWVudF90b19zZXJ2ZXIuQXBwbGlj'
|
||||
'YXRpb25EYXRhLlVwZGF0ZUdvb2dsZUZjbVRva2VuSABSFHVwZGF0ZUdvb2dsZUZjbVRva2VuEl'
|
||||
'EKC2dldExvY2F0aW9uGAkgASgLMi0uY2xpZW50X3RvX3NlcnZlci5BcHBsaWNhdGlvbkRhdGEu'
|
||||
'R2V0TG9jYXRpb25IAFILZ2V0TG9jYXRpb24SaQoTZ2V0Q3VycmVudFBsYW5JbmZvcxgKIAEoCz'
|
||||
'EKDGRlcHJlY2F0ZWRfORgJIAEoCzIsLmNsaWVudF90b19zZXJ2ZXIuQXBwbGljYXRpb25EYXRh'
|
||||
'LkRlcHJlY2F0ZWRIAFILZGVwcmVjYXRlZDkSaQoTZ2V0Q3VycmVudFBsYW5JbmZvcxgKIAEoCz'
|
||||
'I1LmNsaWVudF90b19zZXJ2ZXIuQXBwbGljYXRpb25EYXRhLkdldEN1cnJlbnRQbGFuSW5mb3NI'
|
||||
'AFITZ2V0Q3VycmVudFBsYW5JbmZvcxJXCg1yZWRlZW1Wb3VjaGVyGAsgASgLMi8uY2xpZW50X3'
|
||||
'RvX3NlcnZlci5BcHBsaWNhdGlvbkRhdGEuUmVkZWVtVm91Y2hlckgAUg1yZWRlZW1Wb3VjaGVy'
|
||||
'EmMKEWdldEF2YWlsYWJsZVBsYW5zGAwgASgLMjMuY2xpZW50X3RvX3NlcnZlci5BcHBsaWNhdG'
|
||||
'lvbkRhdGEuR2V0QXZhaWxhYmxlUGxhbnNIAFIRZ2V0QXZhaWxhYmxlUGxhbnMSVwoNY3JlYXRl'
|
||||
'Vm91Y2hlchgNIAEoCzIvLmNsaWVudF90b19zZXJ2ZXIuQXBwbGljYXRpb25EYXRhLkNyZWF0ZV'
|
||||
'ZvdWNoZXJIAFINY3JlYXRlVm91Y2hlchJRCgtnZXRWb3VjaGVycxgOIAEoCzItLmNsaWVudF90'
|
||||
'b19zZXJ2ZXIuQXBwbGljYXRpb25EYXRhLkdldFZvdWNoZXJzSABSC2dldFZvdWNoZXJzEmMKEX'
|
||||
'N3aXRjaHRvUGF5ZWRQbGFuGA8gASgLMjMuY2xpZW50X3RvX3NlcnZlci5BcHBsaWNhdGlvbkRh'
|
||||
'dGEuU3dpdGNoVG9QYXllZFBsYW5IAFIRc3dpdGNodG9QYXllZFBsYW4SbwoVZ2V0QWRkYWNjb3'
|
||||
'VudHNJbnZpdGVzGBAgASgLMjcuY2xpZW50X3RvX3NlcnZlci5BcHBsaWNhdGlvbkRhdGEuR2V0'
|
||||
'QWRkQWNjb3VudHNJbnZpdGVzSABSFWdldEFkZGFjY291bnRzSW52aXRlcxJsChRyZWRlZW1BZG'
|
||||
'RpdGlvbmFsQ29kZRgRIAEoCzI2LmNsaWVudF90b19zZXJ2ZXIuQXBwbGljYXRpb25EYXRhLlJl'
|
||||
'ZGVlbUFkZGl0aW9uYWxDb2RlSABSFHJlZGVlbUFkZGl0aW9uYWxDb2RlEmMKEXVwZGF0ZVBsYW'
|
||||
'5PcHRpb25zGBMgASgLMjMuY2xpZW50X3RvX3NlcnZlci5BcHBsaWNhdGlvbkRhdGEuVXBkYXRl'
|
||||
'UGxhbk9wdGlvbnNIAFIRdXBkYXRlUGxhbk9wdGlvbnMSVAoMZG93bmxvYWREb25lGBQgASgLMi'
|
||||
'4uY2xpZW50X3RvX3NlcnZlci5BcHBsaWNhdGlvbkRhdGEuRG93bmxvYWREb25lSABSDGRvd25s'
|
||||
'b2FkRG9uZRJ1ChdnZXRTaWduZWRQcmVrZXlCeVVzZXJpZBgWIAEoCzI5LmNsaWVudF90b19zZX'
|
||||
'J2ZXIuQXBwbGljYXRpb25EYXRhLkdldFNpZ25lZFByZUtleUJ5VXNlcklkSABSF2dldFNpZ25l'
|
||||
'ZFByZWtleUJ5VXNlcmlkEmYKEnVwZGF0ZVNpZ25lZFByZWtleRgXIAEoCzI0LmNsaWVudF90b1'
|
||||
'9zZXJ2ZXIuQXBwbGljYXRpb25EYXRhLlVwZGF0ZVNpZ25lZFByZUtleUgAUhJ1cGRhdGVTaWdu'
|
||||
'ZWRQcmVrZXkSVwoNZGVsZXRlQWNjb3VudBgYIAEoCzIvLmNsaWVudF90b19zZXJ2ZXIuQXBwbG'
|
||||
'ljYXRpb25EYXRhLkRlbGV0ZUFjY291bnRIAFINZGVsZXRlQWNjb3VudBJOCgpyZXBvcnRVc2Vy'
|
||||
'GBkgASgLMiwuY2xpZW50X3RvX3NlcnZlci5BcHBsaWNhdGlvbkRhdGEuUmVwb3J0VXNlckgAUg'
|
||||
'pyZXBvcnRVc2VyEloKDmNoYW5nZVVzZXJuYW1lGBogASgLMjAuY2xpZW50X3RvX3NlcnZlci5B'
|
||||
'cHBsaWNhdGlvbkRhdGEuQ2hhbmdlVXNlcm5hbWVIAFIOY2hhbmdlVXNlcm5hbWUSUQoLaXBhUH'
|
||||
'VyY2hhc2UYGyABKAsyLS5jbGllbnRfdG9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5JUEFQdXJj'
|
||||
'aGFzZUgAUgtpcGFQdXJjaGFzZRJXCg1pcGFGb3JjZUNoZWNrGBwgASgLMi8uY2xpZW50X3RvX3'
|
||||
'NlcnZlci5BcHBsaWNhdGlvbkRhdGEuSVBBRm9yY2VDaGVja0gAUg1pcGFGb3JjZUNoZWNrEmwK'
|
||||
'FHJlbW92ZUFkZGl0aW9uYWxVc2VyGBIgASgLMjYuY2xpZW50X3RvX3NlcnZlci5BcHBsaWNhdG'
|
||||
'lvbkRhdGEuUmVtb3ZlQWRkaXRpb25hbFVzZXJIAFIUcmVtb3ZlQWRkaXRpb25hbFVzZXISYwoR'
|
||||
'YWRkQWRkaXRpb25hbFVzZXIYHSABKAsyMy5jbGllbnRfdG9fc2VydmVyLkFwcGxpY2F0aW9uRG'
|
||||
'F0YS5BZGRBZGRpdGlvbmFsVXNlckgAUhFhZGRBZGRpdGlvbmFsVXNlchpqCgtUZXh0TWVzc2Fn'
|
||||
'AFITZ2V0Q3VycmVudFBsYW5JbmZvcxJTCg1kZXByZWNhdGVkXzExGAsgASgLMiwuY2xpZW50X3'
|
||||
'RvX3NlcnZlci5BcHBsaWNhdGlvbkRhdGEuRGVwcmVjYXRlZEgAUgxkZXByZWNhdGVkMTESYwoR'
|
||||
'Z2V0QXZhaWxhYmxlUGxhbnMYDCABKAsyMy5jbGllbnRfdG9fc2VydmVyLkFwcGxpY2F0aW9uRG'
|
||||
'F0YS5HZXRBdmFpbGFibGVQbGFuc0gAUhFnZXRBdmFpbGFibGVQbGFucxJTCg1kZXByZWNhdGVk'
|
||||
'XzEzGA0gASgLMiwuY2xpZW50X3RvX3NlcnZlci5BcHBsaWNhdGlvbkRhdGEuRGVwcmVjYXRlZE'
|
||||
'gAUgxkZXByZWNhdGVkMTMSUwoNZGVwcmVjYXRlZF8xNBgOIAEoCzIsLmNsaWVudF90b19zZXJ2'
|
||||
'ZXIuQXBwbGljYXRpb25EYXRhLkRlcHJlY2F0ZWRIAFIMZGVwcmVjYXRlZDE0ElMKDWRlcHJlY2'
|
||||
'F0ZWRfMTUYDyABKAsyLC5jbGllbnRfdG9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5EZXByZWNh'
|
||||
'dGVkSABSDGRlcHJlY2F0ZWQxNRJTCg1kZXByZWNhdGVkXzE2GBAgASgLMiwuY2xpZW50X3RvX3'
|
||||
'NlcnZlci5BcHBsaWNhdGlvbkRhdGEuRGVwcmVjYXRlZEgAUgxkZXByZWNhdGVkMTYSUwoNZGVw'
|
||||
'cmVjYXRlZF8xNxgRIAEoCzIsLmNsaWVudF90b19zZXJ2ZXIuQXBwbGljYXRpb25EYXRhLkRlcH'
|
||||
'JlY2F0ZWRIAFIMZGVwcmVjYXRlZDE3ElMKDWRlcHJlY2F0ZWRfMTkYEyABKAsyLC5jbGllbnRf'
|
||||
'dG9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5EZXByZWNhdGVkSABSDGRlcHJlY2F0ZWQxORJUCg'
|
||||
'xkb3dubG9hZERvbmUYFCABKAsyLi5jbGllbnRfdG9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5E'
|
||||
'b3dubG9hZERvbmVIAFIMZG93bmxvYWREb25lEnUKF2dldFNpZ25lZFByZWtleUJ5VXNlcmlkGB'
|
||||
'YgASgLMjkuY2xpZW50X3RvX3NlcnZlci5BcHBsaWNhdGlvbkRhdGEuR2V0U2lnbmVkUHJlS2V5'
|
||||
'QnlVc2VySWRIAFIXZ2V0U2lnbmVkUHJla2V5QnlVc2VyaWQSZgoSdXBkYXRlU2lnbmVkUHJla2'
|
||||
'V5GBcgASgLMjQuY2xpZW50X3RvX3NlcnZlci5BcHBsaWNhdGlvbkRhdGEuVXBkYXRlU2lnbmVk'
|
||||
'UHJlS2V5SABSEnVwZGF0ZVNpZ25lZFByZWtleRJXCg1kZWxldGVBY2NvdW50GBggASgLMi8uY2'
|
||||
'xpZW50X3RvX3NlcnZlci5BcHBsaWNhdGlvbkRhdGEuRGVsZXRlQWNjb3VudEgAUg1kZWxldGVB'
|
||||
'Y2NvdW50Ek4KCnJlcG9ydFVzZXIYGSABKAsyLC5jbGllbnRfdG9fc2VydmVyLkFwcGxpY2F0aW'
|
||||
'9uRGF0YS5SZXBvcnRVc2VySABSCnJlcG9ydFVzZXISWgoOY2hhbmdlVXNlcm5hbWUYGiABKAsy'
|
||||
'MC5jbGllbnRfdG9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5DaGFuZ2VVc2VybmFtZUgAUg5jaG'
|
||||
'FuZ2VVc2VybmFtZRJRCgtpcGFQdXJjaGFzZRgbIAEoCzItLmNsaWVudF90b19zZXJ2ZXIuQXBw'
|
||||
'bGljYXRpb25EYXRhLklQQVB1cmNoYXNlSABSC2lwYVB1cmNoYXNlElcKDWlwYUZvcmNlQ2hlY2'
|
||||
'sYHCABKAsyLy5jbGllbnRfdG9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5JUEFGb3JjZUNoZWNr'
|
||||
'SABSDWlwYUZvcmNlQ2hlY2sSbAoUcmVtb3ZlQWRkaXRpb25hbFVzZXIYEiABKAsyNi5jbGllbn'
|
||||
'RfdG9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5SZW1vdmVBZGRpdGlvbmFsVXNlckgAUhRyZW1v'
|
||||
'dmVBZGRpdGlvbmFsVXNlchJjChFhZGRBZGRpdGlvbmFsVXNlchgdIAEoCzIzLmNsaWVudF90b1'
|
||||
'9zZXJ2ZXIuQXBwbGljYXRpb25EYXRhLkFkZEFkZGl0aW9uYWxVc2VySABSEWFkZEFkZGl0aW9u'
|
||||
'YWxVc2VyElkKD3NldF9sb2dpbl90b2tlbhgeIAEoCzIvLmNsaWVudF90b19zZXJ2ZXIuQXBwbG'
|
||||
'ljYXRpb25EYXRhLlNldExvZ2luVG9rZW5IAFINc2V0TG9naW5Ub2tlbhpqCgtUZXh0TWVzc2Fn'
|
||||
'ZRIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQSEgoEYm9keRgDIAEoDFIEYm9keRIgCglwdXNoX2'
|
||||
'RhdGEYBCABKAxIAFIIcHVzaERhdGGIAQFCDAoKX3B1c2hfZGF0YRovChFHZXRVc2VyQnlVc2Vy'
|
||||
'bmFtZRIaCgh1c2VybmFtZRgBIAEoCVIIdXNlcm5hbWUaLAoOQ2hhbmdlVXNlcm5hbWUSGgoIdX'
|
||||
'Nlcm5hbWUYASABKAlSCHVzZXJuYW1lGjUKFFVwZGF0ZUdvb2dsZUZjbVRva2VuEh0KCmdvb2ds'
|
||||
'ZV9mY20YASABKAlSCWdvb2dsZUZjbRomCgtHZXRVc2VyQnlJZBIXCgd1c2VyX2lkGAEgASgDUg'
|
||||
'Z1c2VySWQaKQoNUmVkZWVtVm91Y2hlchIYCgd2b3VjaGVyGAEgASgJUgd2b3VjaGVyGnAKEVN3'
|
||||
'aXRjaFRvUGF5ZWRQbGFuEhcKB3BsYW5faWQYASABKAlSBnBsYW5JZBIfCgtwYXlfbW9udGhseR'
|
||||
'gCIAEoCFIKcGF5TW9udGhseRIhCgxhdXRvX3JlbmV3YWwYAyABKAhSC2F1dG9SZW5ld2FsGjYK'
|
||||
'EVVwZGF0ZVBsYW5PcHRpb25zEiEKDGF1dG9fcmVuZXdhbBgBIAEoCFILYXV0b1JlbmV3YWwaMA'
|
||||
'oNQ3JlYXRlVm91Y2hlchIfCgt2YWx1ZV9jZW50cxgBIAEoDVIKdmFsdWVDZW50cxoNCgtHZXRM'
|
||||
'b2NhdGlvbhoNCgtHZXRWb3VjaGVycxoTChFHZXRBdmFpbGFibGVQbGFucxoXChVHZXRBZGRBY2'
|
||||
'NvdW50c0ludml0ZXMaFQoTR2V0Q3VycmVudFBsYW5JbmZvcxo3ChRSZWRlZW1BZGRpdGlvbmFs'
|
||||
'Q29kZRIfCgtpbnZpdGVfY29kZRgCIAEoCVIKaW52aXRlQ29kZRovChRSZW1vdmVBZGRpdGlvbm'
|
||||
'FsVXNlchIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQaLQoSR2V0UHJla2V5c0J5VXNlcklkEhcK'
|
||||
'B3VzZXJfaWQYASABKANSBnVzZXJJZBoyChdHZXRTaWduZWRQcmVLZXlCeVVzZXJJZBIXCgd1c2'
|
||||
'VyX2lkGAEgASgDUgZ1c2VySWQamwEKElVwZGF0ZVNpZ25lZFByZUtleRIoChBzaWduZWRfcHJl'
|
||||
'a2V5X2lkGAEgASgDUg5zaWduZWRQcmVrZXlJZBIjCg1zaWduZWRfcHJla2V5GAIgASgMUgxzaW'
|
||||
'duZWRQcmVrZXkSNgoXc2lnbmVkX3ByZWtleV9zaWduYXR1cmUYAyABKAxSFXNpZ25lZFByZWtl'
|
||||
'eVNpZ25hdHVyZRo1CgxEb3dubG9hZERvbmUSJQoOZG93bmxvYWRfdG9rZW4YASABKAxSDWRvd2'
|
||||
'5sb2FkVG9rZW4aTgoKUmVwb3J0VXNlchIoChByZXBvcnRlZF91c2VyX2lkGAEgASgDUg5yZXBv'
|
||||
'cnRlZFVzZXJJZBIWCgZyZWFzb24YAiABKAlSBnJlYXNvbhpxCgtJUEFQdXJjaGFzZRIdCgpwcm'
|
||||
'9kdWN0X2lkGAEgASgJUglwcm9kdWN0SWQSFgoGc291cmNlGAIgASgJUgZzb3VyY2USKwoRdmVy'
|
||||
'aWZpY2F0aW9uX2RhdGEYAyABKAlSEHZlcmlmaWNhdGlvbkRhdGEaDwoNSVBBRm9yY2VDaGVjax'
|
||||
'oPCg1EZWxldGVBY2NvdW50GiwKEUFkZEFkZGl0aW9uYWxVc2VyEhcKB3VzZXJfaWQYASABKANS'
|
||||
'BnVzZXJJZEIRCg9BcHBsaWNhdGlvbkRhdGE=');
|
||||
'Z1c2VySWQaEwoRR2V0QXZhaWxhYmxlUGxhbnMaFwoVR2V0QWRkQWNjb3VudHNJbnZpdGVzGhUK'
|
||||
'E0dldEN1cnJlbnRQbGFuSW5mb3MaLwoUUmVtb3ZlQWRkaXRpb25hbFVzZXISFwoHdXNlcl9pZB'
|
||||
'gBIAEoA1IGdXNlcklkGi0KEkdldFByZWtleXNCeVVzZXJJZBIXCgd1c2VyX2lkGAEgASgDUgZ1'
|
||||
'c2VySWQaMgoXR2V0U2lnbmVkUHJlS2V5QnlVc2VySWQSFwoHdXNlcl9pZBgBIAEoA1IGdXNlck'
|
||||
'lkGpsBChJVcGRhdGVTaWduZWRQcmVLZXkSKAoQc2lnbmVkX3ByZWtleV9pZBgBIAEoA1IOc2ln'
|
||||
'bmVkUHJla2V5SWQSIwoNc2lnbmVkX3ByZWtleRgCIAEoDFIMc2lnbmVkUHJla2V5EjYKF3NpZ2'
|
||||
'5lZF9wcmVrZXlfc2lnbmF0dXJlGAMgASgMUhVzaWduZWRQcmVrZXlTaWduYXR1cmUaNQoMRG93'
|
||||
'bmxvYWREb25lEiUKDmRvd25sb2FkX3Rva2VuGAEgASgMUg1kb3dubG9hZFRva2VuGk4KClJlcG'
|
||||
'9ydFVzZXISKAoQcmVwb3J0ZWRfdXNlcl9pZBgBIAEoA1IOcmVwb3J0ZWRVc2VySWQSFgoGcmVh'
|
||||
'c29uGAIgASgJUgZyZWFzb24acQoLSVBBUHVyY2hhc2USHQoKcHJvZHVjdF9pZBgBIAEoCVIJcH'
|
||||
'JvZHVjdElkEhYKBnNvdXJjZRgCIAEoCVIGc291cmNlEisKEXZlcmlmaWNhdGlvbl9kYXRhGAMg'
|
||||
'ASgJUhB2ZXJpZmljYXRpb25EYXRhGg8KDUlQQUZvcmNlQ2hlY2saDwoNRGVsZXRlQWNjb3VudB'
|
||||
'osChFBZGRBZGRpdGlvbmFsVXNlchIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQaMAoNU2V0TG9n'
|
||||
'aW5Ub2tlbhIfCgtsb2dpbl90b2tlbhgBIAEoDFIKbG9naW5Ub2tlbhoMCgpEZXByZWNhdGVkQh'
|
||||
'EKD0FwcGxpY2F0aW9uRGF0YQ==');
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response$json = {
|
||||
|
|
|
|||
|
|
@ -16,12 +16,9 @@ import 'package:fixnum/fixnum.dart' as $fixnum;
|
|||
import 'package:protobuf/protobuf.dart' as $pb;
|
||||
|
||||
import 'error.pbenum.dart' as $0;
|
||||
import 'server_to_client.pbenum.dart';
|
||||
|
||||
export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions;
|
||||
|
||||
export 'server_to_client.pbenum.dart';
|
||||
|
||||
enum ServerToClient_V { v0, notSet }
|
||||
|
||||
class ServerToClient extends $pb.GeneratedMessage {
|
||||
|
|
@ -752,90 +749,6 @@ class Response_AddAccountsInvites extends $pb.GeneratedMessage {
|
|||
$pb.PbList<Response_AddAccountsInvite> get invites => $_getList(0);
|
||||
}
|
||||
|
||||
class Response_Transaction extends $pb.GeneratedMessage {
|
||||
factory Response_Transaction({
|
||||
$fixnum.Int64? depositCents,
|
||||
Response_TransactionTypes? transactionType,
|
||||
$fixnum.Int64? createdAtUnixTimestamp,
|
||||
}) {
|
||||
final result = create();
|
||||
if (depositCents != null) result.depositCents = depositCents;
|
||||
if (transactionType != null) result.transactionType = transactionType;
|
||||
if (createdAtUnixTimestamp != null)
|
||||
result.createdAtUnixTimestamp = createdAtUnixTimestamp;
|
||||
return result;
|
||||
}
|
||||
|
||||
Response_Transaction._();
|
||||
|
||||
factory Response_Transaction.fromBuffer($core.List<$core.int> data,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromBuffer(data, registry);
|
||||
factory Response_Transaction.fromJson($core.String json,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromJson(json, registry);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
_omitMessageNames ? '' : 'Response.Transaction',
|
||||
package:
|
||||
const $pb.PackageName(_omitMessageNames ? '' : 'server_to_client'),
|
||||
createEmptyInstance: create)
|
||||
..aInt64(1, _omitFieldNames ? '' : 'depositCents')
|
||||
..aE<Response_TransactionTypes>(2, _omitFieldNames ? '' : 'transactionType',
|
||||
enumValues: Response_TransactionTypes.values)
|
||||
..aInt64(3, _omitFieldNames ? '' : 'createdAtUnixTimestamp')
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
Response_Transaction clone() => deepCopy();
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
Response_Transaction copyWith(void Function(Response_Transaction) updates) =>
|
||||
super.copyWith((message) => updates(message as Response_Transaction))
|
||||
as Response_Transaction;
|
||||
|
||||
@$core.override
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Response_Transaction create() => Response_Transaction._();
|
||||
@$core.override
|
||||
Response_Transaction createEmptyInstance() => create();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Response_Transaction getDefault() => _defaultInstance ??=
|
||||
$pb.GeneratedMessage.$_defaultFor<Response_Transaction>(create);
|
||||
static Response_Transaction? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$fixnum.Int64 get depositCents => $_getI64(0);
|
||||
@$pb.TagNumber(1)
|
||||
set depositCents($fixnum.Int64 value) => $_setInt64(0, value);
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasDepositCents() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearDepositCents() => $_clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
Response_TransactionTypes get transactionType => $_getN(1);
|
||||
@$pb.TagNumber(2)
|
||||
set transactionType(Response_TransactionTypes value) => $_setField(2, value);
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasTransactionType() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearTransactionType() => $_clearField(2);
|
||||
|
||||
/// Represents seconds of UTC time since Unix epoch
|
||||
/// 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
|
||||
/// 9999-12-31T23:59:59Z inclusive.
|
||||
@$pb.TagNumber(3)
|
||||
$fixnum.Int64 get createdAtUnixTimestamp => $_getI64(2);
|
||||
@$pb.TagNumber(3)
|
||||
set createdAtUnixTimestamp($fixnum.Int64 value) => $_setInt64(2, value);
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasCreatedAtUnixTimestamp() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearCreatedAtUnixTimestamp() => $_clearField(3);
|
||||
}
|
||||
|
||||
class Response_AdditionalAccount extends $pb.GeneratedMessage {
|
||||
factory Response_AdditionalAccount({
|
||||
$fixnum.Int64? userId,
|
||||
|
|
@ -905,158 +818,82 @@ class Response_AdditionalAccount extends $pb.GeneratedMessage {
|
|||
void clearPlanId() => $_clearField(3);
|
||||
}
|
||||
|
||||
class Response_Voucher extends $pb.GeneratedMessage {
|
||||
factory Response_Voucher({
|
||||
$core.String? voucherId,
|
||||
$fixnum.Int64? valueCents,
|
||||
$core.bool? redeemed,
|
||||
$core.bool? requested,
|
||||
$fixnum.Int64? createdAtUnixTimestamp,
|
||||
}) {
|
||||
final result = create();
|
||||
if (voucherId != null) result.voucherId = voucherId;
|
||||
if (valueCents != null) result.valueCents = valueCents;
|
||||
if (redeemed != null) result.redeemed = redeemed;
|
||||
if (requested != null) result.requested = requested;
|
||||
if (createdAtUnixTimestamp != null)
|
||||
result.createdAtUnixTimestamp = createdAtUnixTimestamp;
|
||||
return result;
|
||||
}
|
||||
class Response_Deprecated extends $pb.GeneratedMessage {
|
||||
factory Response_Deprecated() => create();
|
||||
|
||||
Response_Voucher._();
|
||||
Response_Deprecated._();
|
||||
|
||||
factory Response_Voucher.fromBuffer($core.List<$core.int> data,
|
||||
factory Response_Deprecated.fromBuffer($core.List<$core.int> data,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromBuffer(data, registry);
|
||||
factory Response_Voucher.fromJson($core.String json,
|
||||
factory Response_Deprecated.fromJson($core.String json,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromJson(json, registry);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
_omitMessageNames ? '' : 'Response.Voucher',
|
||||
_omitMessageNames ? '' : 'Response.Deprecated',
|
||||
package:
|
||||
const $pb.PackageName(_omitMessageNames ? '' : 'server_to_client'),
|
||||
createEmptyInstance: create)
|
||||
..aOS(1, _omitFieldNames ? '' : 'voucherId')
|
||||
..aInt64(2, _omitFieldNames ? '' : 'valueCents')
|
||||
..aOB(3, _omitFieldNames ? '' : 'redeemed')
|
||||
..aOB(4, _omitFieldNames ? '' : 'requested')
|
||||
..aInt64(5, _omitFieldNames ? '' : 'createdAtUnixTimestamp')
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
Response_Voucher clone() => deepCopy();
|
||||
Response_Deprecated clone() => deepCopy();
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
Response_Voucher copyWith(void Function(Response_Voucher) updates) =>
|
||||
super.copyWith((message) => updates(message as Response_Voucher))
|
||||
as Response_Voucher;
|
||||
Response_Deprecated copyWith(void Function(Response_Deprecated) updates) =>
|
||||
super.copyWith((message) => updates(message as Response_Deprecated))
|
||||
as Response_Deprecated;
|
||||
|
||||
@$core.override
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Response_Voucher create() => Response_Voucher._();
|
||||
static Response_Deprecated create() => Response_Deprecated._();
|
||||
@$core.override
|
||||
Response_Voucher createEmptyInstance() => create();
|
||||
Response_Deprecated createEmptyInstance() => create();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Response_Voucher getDefault() => _defaultInstance ??=
|
||||
$pb.GeneratedMessage.$_defaultFor<Response_Voucher>(create);
|
||||
static Response_Voucher? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.String get voucherId => $_getSZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set voucherId($core.String value) => $_setString(0, value);
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasVoucherId() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearVoucherId() => $_clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$fixnum.Int64 get valueCents => $_getI64(1);
|
||||
@$pb.TagNumber(2)
|
||||
set valueCents($fixnum.Int64 value) => $_setInt64(1, value);
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasValueCents() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearValueCents() => $_clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool get redeemed => $_getBF(2);
|
||||
@$pb.TagNumber(3)
|
||||
set redeemed($core.bool value) => $_setBool(2, value);
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasRedeemed() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearRedeemed() => $_clearField(3);
|
||||
|
||||
@$pb.TagNumber(4)
|
||||
$core.bool get requested => $_getBF(3);
|
||||
@$pb.TagNumber(4)
|
||||
set requested($core.bool value) => $_setBool(3, value);
|
||||
@$pb.TagNumber(4)
|
||||
$core.bool hasRequested() => $_has(3);
|
||||
@$pb.TagNumber(4)
|
||||
void clearRequested() => $_clearField(4);
|
||||
|
||||
@$pb.TagNumber(5)
|
||||
$fixnum.Int64 get createdAtUnixTimestamp => $_getI64(4);
|
||||
@$pb.TagNumber(5)
|
||||
set createdAtUnixTimestamp($fixnum.Int64 value) => $_setInt64(4, value);
|
||||
@$pb.TagNumber(5)
|
||||
$core.bool hasCreatedAtUnixTimestamp() => $_has(4);
|
||||
@$pb.TagNumber(5)
|
||||
void clearCreatedAtUnixTimestamp() => $_clearField(5);
|
||||
static Response_Deprecated getDefault() => _defaultInstance ??=
|
||||
$pb.GeneratedMessage.$_defaultFor<Response_Deprecated>(create);
|
||||
static Response_Deprecated? _defaultInstance;
|
||||
}
|
||||
|
||||
class Response_Vouchers extends $pb.GeneratedMessage {
|
||||
factory Response_Vouchers({
|
||||
$core.Iterable<Response_Voucher>? vouchers,
|
||||
}) {
|
||||
final result = create();
|
||||
if (vouchers != null) result.vouchers.addAll(vouchers);
|
||||
return result;
|
||||
}
|
||||
class Response_Transaction extends $pb.GeneratedMessage {
|
||||
factory Response_Transaction() => create();
|
||||
|
||||
Response_Vouchers._();
|
||||
Response_Transaction._();
|
||||
|
||||
factory Response_Vouchers.fromBuffer($core.List<$core.int> data,
|
||||
factory Response_Transaction.fromBuffer($core.List<$core.int> data,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromBuffer(data, registry);
|
||||
factory Response_Vouchers.fromJson($core.String json,
|
||||
factory Response_Transaction.fromJson($core.String json,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromJson(json, registry);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
_omitMessageNames ? '' : 'Response.Vouchers',
|
||||
_omitMessageNames ? '' : 'Response.Transaction',
|
||||
package:
|
||||
const $pb.PackageName(_omitMessageNames ? '' : 'server_to_client'),
|
||||
createEmptyInstance: create)
|
||||
..pPM<Response_Voucher>(1, _omitFieldNames ? '' : 'vouchers',
|
||||
subBuilder: Response_Voucher.create)
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
Response_Vouchers clone() => deepCopy();
|
||||
Response_Transaction clone() => deepCopy();
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
Response_Vouchers copyWith(void Function(Response_Vouchers) updates) =>
|
||||
super.copyWith((message) => updates(message as Response_Vouchers))
|
||||
as Response_Vouchers;
|
||||
Response_Transaction copyWith(void Function(Response_Transaction) updates) =>
|
||||
super.copyWith((message) => updates(message as Response_Transaction))
|
||||
as Response_Transaction;
|
||||
|
||||
@$core.override
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Response_Vouchers create() => Response_Vouchers._();
|
||||
static Response_Transaction create() => Response_Transaction._();
|
||||
@$core.override
|
||||
Response_Vouchers createEmptyInstance() => create();
|
||||
Response_Transaction createEmptyInstance() => create();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Response_Vouchers getDefault() => _defaultInstance ??=
|
||||
$pb.GeneratedMessage.$_defaultFor<Response_Vouchers>(create);
|
||||
static Response_Vouchers? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$pb.PbList<Response_Voucher> get vouchers => $_getList(0);
|
||||
static Response_Transaction getDefault() => _defaultInstance ??=
|
||||
$pb.GeneratedMessage.$_defaultFor<Response_Transaction>(create);
|
||||
static Response_Transaction? _defaultInstance;
|
||||
}
|
||||
|
||||
class Response_PlanBallance extends $pb.GeneratedMessage {
|
||||
|
|
@ -1195,85 +1032,6 @@ class Response_PlanBallance extends $pb.GeneratedMessage {
|
|||
void clearAdditionalAccountOwnerId() => $_clearField(8);
|
||||
}
|
||||
|
||||
class Response_Location extends $pb.GeneratedMessage {
|
||||
factory Response_Location({
|
||||
$core.String? county,
|
||||
$core.String? region,
|
||||
$core.String? city,
|
||||
}) {
|
||||
final result = create();
|
||||
if (county != null) result.county = county;
|
||||
if (region != null) result.region = region;
|
||||
if (city != null) result.city = city;
|
||||
return result;
|
||||
}
|
||||
|
||||
Response_Location._();
|
||||
|
||||
factory Response_Location.fromBuffer($core.List<$core.int> data,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromBuffer(data, registry);
|
||||
factory Response_Location.fromJson($core.String json,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromJson(json, registry);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
_omitMessageNames ? '' : 'Response.Location',
|
||||
package:
|
||||
const $pb.PackageName(_omitMessageNames ? '' : 'server_to_client'),
|
||||
createEmptyInstance: create)
|
||||
..aOS(1, _omitFieldNames ? '' : 'county')
|
||||
..aOS(2, _omitFieldNames ? '' : 'region')
|
||||
..aOS(3, _omitFieldNames ? '' : 'city')
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
Response_Location clone() => deepCopy();
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
Response_Location copyWith(void Function(Response_Location) updates) =>
|
||||
super.copyWith((message) => updates(message as Response_Location))
|
||||
as Response_Location;
|
||||
|
||||
@$core.override
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Response_Location create() => Response_Location._();
|
||||
@$core.override
|
||||
Response_Location createEmptyInstance() => create();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Response_Location getDefault() => _defaultInstance ??=
|
||||
$pb.GeneratedMessage.$_defaultFor<Response_Location>(create);
|
||||
static Response_Location? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.String get county => $_getSZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set county($core.String value) => $_setString(0, value);
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasCounty() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearCounty() => $_clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.String get region => $_getSZ(1);
|
||||
@$pb.TagNumber(2)
|
||||
set region($core.String value) => $_setString(1, value);
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasRegion() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearRegion() => $_clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.String get city => $_getSZ(2);
|
||||
@$pb.TagNumber(3)
|
||||
set city($core.String value) => $_setString(2, value);
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasCity() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearCity() => $_clearField(3);
|
||||
}
|
||||
|
||||
class Response_PreKey extends $pb.GeneratedMessage {
|
||||
factory Response_PreKey({
|
||||
$fixnum.Int64? id,
|
||||
|
|
@ -1754,11 +1512,11 @@ enum Response_Ok_Ok {
|
|||
uploadtoken,
|
||||
userdata,
|
||||
authtoken,
|
||||
location,
|
||||
deprecated7,
|
||||
authenticated,
|
||||
plans,
|
||||
planballance,
|
||||
vouchers,
|
||||
deprecated11,
|
||||
addaccountsinvites,
|
||||
downloadtokens,
|
||||
signedprekey,
|
||||
|
|
@ -1774,11 +1532,11 @@ class Response_Ok extends $pb.GeneratedMessage {
|
|||
Response_UploadToken? uploadtoken,
|
||||
Response_UserData? userdata,
|
||||
$core.List<$core.int>? authtoken,
|
||||
Response_Location? location,
|
||||
Response_Deprecated? deprecated7,
|
||||
Response_Authenticated? authenticated,
|
||||
Response_Plans? plans,
|
||||
Response_PlanBallance? planballance,
|
||||
Response_Vouchers? vouchers,
|
||||
Response_Deprecated? deprecated11,
|
||||
Response_AddAccountsInvites? addaccountsinvites,
|
||||
Response_DownloadTokens? downloadtokens,
|
||||
Response_SignedPreKey? signedprekey,
|
||||
|
|
@ -1791,11 +1549,11 @@ class Response_Ok extends $pb.GeneratedMessage {
|
|||
if (uploadtoken != null) result.uploadtoken = uploadtoken;
|
||||
if (userdata != null) result.userdata = userdata;
|
||||
if (authtoken != null) result.authtoken = authtoken;
|
||||
if (location != null) result.location = location;
|
||||
if (deprecated7 != null) result.deprecated7 = deprecated7;
|
||||
if (authenticated != null) result.authenticated = authenticated;
|
||||
if (plans != null) result.plans = plans;
|
||||
if (planballance != null) result.planballance = planballance;
|
||||
if (vouchers != null) result.vouchers = vouchers;
|
||||
if (deprecated11 != null) result.deprecated11 = deprecated11;
|
||||
if (addaccountsinvites != null)
|
||||
result.addaccountsinvites = addaccountsinvites;
|
||||
if (downloadtokens != null) result.downloadtokens = downloadtokens;
|
||||
|
|
@ -1820,11 +1578,11 @@ class Response_Ok extends $pb.GeneratedMessage {
|
|||
4: Response_Ok_Ok.uploadtoken,
|
||||
5: Response_Ok_Ok.userdata,
|
||||
6: Response_Ok_Ok.authtoken,
|
||||
7: Response_Ok_Ok.location,
|
||||
7: Response_Ok_Ok.deprecated7,
|
||||
8: Response_Ok_Ok.authenticated,
|
||||
9: Response_Ok_Ok.plans,
|
||||
10: Response_Ok_Ok.planballance,
|
||||
11: Response_Ok_Ok.vouchers,
|
||||
11: Response_Ok_Ok.deprecated11,
|
||||
12: Response_Ok_Ok.addaccountsinvites,
|
||||
13: Response_Ok_Ok.downloadtokens,
|
||||
14: Response_Ok_Ok.signedprekey,
|
||||
|
|
@ -1847,16 +1605,16 @@ class Response_Ok extends $pb.GeneratedMessage {
|
|||
subBuilder: Response_UserData.create)
|
||||
..a<$core.List<$core.int>>(
|
||||
6, _omitFieldNames ? '' : 'authtoken', $pb.PbFieldType.OY)
|
||||
..aOM<Response_Location>(7, _omitFieldNames ? '' : 'location',
|
||||
subBuilder: Response_Location.create)
|
||||
..aOM<Response_Deprecated>(7, _omitFieldNames ? '' : 'deprecated7',
|
||||
protoName: 'deprecated_7', subBuilder: Response_Deprecated.create)
|
||||
..aOM<Response_Authenticated>(8, _omitFieldNames ? '' : 'authenticated',
|
||||
subBuilder: Response_Authenticated.create)
|
||||
..aOM<Response_Plans>(9, _omitFieldNames ? '' : 'plans',
|
||||
subBuilder: Response_Plans.create)
|
||||
..aOM<Response_PlanBallance>(10, _omitFieldNames ? '' : 'planballance',
|
||||
subBuilder: Response_PlanBallance.create)
|
||||
..aOM<Response_Vouchers>(11, _omitFieldNames ? '' : 'vouchers',
|
||||
subBuilder: Response_Vouchers.create)
|
||||
..aOM<Response_Deprecated>(11, _omitFieldNames ? '' : 'deprecated11',
|
||||
protoName: 'deprecated_11', subBuilder: Response_Deprecated.create)
|
||||
..aOM<Response_AddAccountsInvites>(
|
||||
12, _omitFieldNames ? '' : 'addaccountsinvites',
|
||||
subBuilder: Response_AddAccountsInvites.create)
|
||||
|
|
@ -1979,15 +1737,15 @@ class Response_Ok extends $pb.GeneratedMessage {
|
|||
void clearAuthtoken() => $_clearField(6);
|
||||
|
||||
@$pb.TagNumber(7)
|
||||
Response_Location get location => $_getN(6);
|
||||
Response_Deprecated get deprecated7 => $_getN(6);
|
||||
@$pb.TagNumber(7)
|
||||
set location(Response_Location value) => $_setField(7, value);
|
||||
set deprecated7(Response_Deprecated value) => $_setField(7, value);
|
||||
@$pb.TagNumber(7)
|
||||
$core.bool hasLocation() => $_has(6);
|
||||
$core.bool hasDeprecated7() => $_has(6);
|
||||
@$pb.TagNumber(7)
|
||||
void clearLocation() => $_clearField(7);
|
||||
void clearDeprecated7() => $_clearField(7);
|
||||
@$pb.TagNumber(7)
|
||||
Response_Location ensureLocation() => $_ensure(6);
|
||||
Response_Deprecated ensureDeprecated7() => $_ensure(6);
|
||||
|
||||
@$pb.TagNumber(8)
|
||||
Response_Authenticated get authenticated => $_getN(7);
|
||||
|
|
@ -2023,15 +1781,15 @@ class Response_Ok extends $pb.GeneratedMessage {
|
|||
Response_PlanBallance ensurePlanballance() => $_ensure(9);
|
||||
|
||||
@$pb.TagNumber(11)
|
||||
Response_Vouchers get vouchers => $_getN(10);
|
||||
Response_Deprecated get deprecated11 => $_getN(10);
|
||||
@$pb.TagNumber(11)
|
||||
set vouchers(Response_Vouchers value) => $_setField(11, value);
|
||||
set deprecated11(Response_Deprecated value) => $_setField(11, value);
|
||||
@$pb.TagNumber(11)
|
||||
$core.bool hasVouchers() => $_has(10);
|
||||
$core.bool hasDeprecated11() => $_has(10);
|
||||
@$pb.TagNumber(11)
|
||||
void clearVouchers() => $_clearField(11);
|
||||
void clearDeprecated11() => $_clearField(11);
|
||||
@$pb.TagNumber(11)
|
||||
Response_Vouchers ensureVouchers() => $_ensure(10);
|
||||
Response_Deprecated ensureDeprecated11() => $_ensure(10);
|
||||
|
||||
@$pb.TagNumber(12)
|
||||
Response_AddAccountsInvites get addaccountsinvites => $_getN(11);
|
||||
|
|
|
|||
|
|
@ -9,48 +9,3 @@
|
|||
// ignore_for_file: curly_braces_in_flow_control_structures
|
||||
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports
|
||||
|
||||
import 'dart:core' as $core;
|
||||
|
||||
import 'package:protobuf/protobuf.dart' as $pb;
|
||||
|
||||
class Response_TransactionTypes extends $pb.ProtobufEnum {
|
||||
static const Response_TransactionTypes Refund =
|
||||
Response_TransactionTypes._(0, _omitEnumNames ? '' : 'Refund');
|
||||
static const Response_TransactionTypes VoucherRedeemed =
|
||||
Response_TransactionTypes._(1, _omitEnumNames ? '' : 'VoucherRedeemed');
|
||||
static const Response_TransactionTypes VoucherCreated =
|
||||
Response_TransactionTypes._(2, _omitEnumNames ? '' : 'VoucherCreated');
|
||||
static const Response_TransactionTypes Cash =
|
||||
Response_TransactionTypes._(3, _omitEnumNames ? '' : 'Cash');
|
||||
static const Response_TransactionTypes PlanUpgrade =
|
||||
Response_TransactionTypes._(4, _omitEnumNames ? '' : 'PlanUpgrade');
|
||||
static const Response_TransactionTypes Unknown =
|
||||
Response_TransactionTypes._(5, _omitEnumNames ? '' : 'Unknown');
|
||||
static const Response_TransactionTypes ThanksForTesting =
|
||||
Response_TransactionTypes._(6, _omitEnumNames ? '' : 'ThanksForTesting');
|
||||
static const Response_TransactionTypes AutoRenewal =
|
||||
Response_TransactionTypes._(7, _omitEnumNames ? '' : 'AutoRenewal');
|
||||
|
||||
static const $core.List<Response_TransactionTypes> values =
|
||||
<Response_TransactionTypes>[
|
||||
Refund,
|
||||
VoucherRedeemed,
|
||||
VoucherCreated,
|
||||
Cash,
|
||||
PlanUpgrade,
|
||||
Unknown,
|
||||
ThanksForTesting,
|
||||
AutoRenewal,
|
||||
];
|
||||
|
||||
static final $core.List<Response_TransactionTypes?> _byValue =
|
||||
$pb.ProtobufEnum.$_initByValueList(values, 7);
|
||||
static Response_TransactionTypes? valueOf($core.int value) =>
|
||||
value < 0 || value >= _byValue.length ? null : _byValue[value];
|
||||
|
||||
const Response_TransactionTypes._(super.value, super.name);
|
||||
}
|
||||
|
||||
const $core.bool _omitEnumNames =
|
||||
$core.bool.fromEnvironment('protobuf.omit_enum_names');
|
||||
|
|
|
|||
|
|
@ -166,12 +166,10 @@ const Response$json = {
|
|||
Response_Plans$json,
|
||||
Response_AddAccountsInvite$json,
|
||||
Response_AddAccountsInvites$json,
|
||||
Response_Transaction$json,
|
||||
Response_AdditionalAccount$json,
|
||||
Response_Voucher$json,
|
||||
Response_Vouchers$json,
|
||||
Response_Deprecated$json,
|
||||
Response_Transaction$json,
|
||||
Response_PlanBallance$json,
|
||||
Response_Location$json,
|
||||
Response_PreKey$json,
|
||||
Response_SignedPreKey$json,
|
||||
Response_UserData$json,
|
||||
|
|
@ -180,7 +178,6 @@ const Response$json = {
|
|||
Response_ProofOfWork$json,
|
||||
Response_Ok$json
|
||||
],
|
||||
'4': [Response_TransactionTypes$json],
|
||||
'8': [
|
||||
{'1': 'Response'},
|
||||
],
|
||||
|
|
@ -285,29 +282,6 @@ const Response_AddAccountsInvites$json = {
|
|||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response_Transaction$json = {
|
||||
'1': 'Transaction',
|
||||
'2': [
|
||||
{'1': 'deposit_cents', '3': 1, '4': 1, '5': 3, '10': 'depositCents'},
|
||||
{
|
||||
'1': 'transaction_type',
|
||||
'3': 2,
|
||||
'4': 1,
|
||||
'5': 14,
|
||||
'6': '.server_to_client.Response.TransactionTypes',
|
||||
'10': 'transactionType'
|
||||
},
|
||||
{
|
||||
'1': 'created_at_unix_timestamp',
|
||||
'3': 3,
|
||||
'4': 1,
|
||||
'5': 3,
|
||||
'10': 'createdAtUnixTimestamp'
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response_AdditionalAccount$json = {
|
||||
'1': 'AdditionalAccount',
|
||||
|
|
@ -318,36 +292,13 @@ const Response_AdditionalAccount$json = {
|
|||
};
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response_Voucher$json = {
|
||||
'1': 'Voucher',
|
||||
'2': [
|
||||
{'1': 'voucher_id', '3': 1, '4': 1, '5': 9, '10': 'voucherId'},
|
||||
{'1': 'value_cents', '3': 2, '4': 1, '5': 3, '10': 'valueCents'},
|
||||
{'1': 'redeemed', '3': 3, '4': 1, '5': 8, '10': 'redeemed'},
|
||||
{'1': 'requested', '3': 4, '4': 1, '5': 8, '10': 'requested'},
|
||||
{
|
||||
'1': 'created_at_unix_timestamp',
|
||||
'3': 5,
|
||||
'4': 1,
|
||||
'5': 3,
|
||||
'10': 'createdAtUnixTimestamp'
|
||||
},
|
||||
],
|
||||
const Response_Deprecated$json = {
|
||||
'1': 'Deprecated',
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response_Vouchers$json = {
|
||||
'1': 'Vouchers',
|
||||
'2': [
|
||||
{
|
||||
'1': 'vouchers',
|
||||
'3': 1,
|
||||
'4': 3,
|
||||
'5': 11,
|
||||
'6': '.server_to_client.Response.Voucher',
|
||||
'10': 'vouchers'
|
||||
},
|
||||
],
|
||||
const Response_Transaction$json = {
|
||||
'1': 'Transaction',
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
|
|
@ -429,16 +380,6 @@ const Response_PlanBallance$json = {
|
|||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response_Location$json = {
|
||||
'1': 'Location',
|
||||
'2': [
|
||||
{'1': 'county', '3': 1, '4': 1, '5': 9, '10': 'county'},
|
||||
{'1': 'region', '3': 2, '4': 1, '5': 9, '10': 'region'},
|
||||
{'1': 'city', '3': 3, '4': 1, '5': 9, '10': 'city'},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response_PreKey$json = {
|
||||
'1': 'PreKey',
|
||||
|
|
@ -602,13 +543,13 @@ const Response_Ok$json = {
|
|||
},
|
||||
{'1': 'authtoken', '3': 6, '4': 1, '5': 12, '9': 0, '10': 'authtoken'},
|
||||
{
|
||||
'1': 'location',
|
||||
'1': 'deprecated_7',
|
||||
'3': 7,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.server_to_client.Response.Location',
|
||||
'6': '.server_to_client.Response.Deprecated',
|
||||
'9': 0,
|
||||
'10': 'location'
|
||||
'10': 'deprecated7'
|
||||
},
|
||||
{
|
||||
'1': 'authenticated',
|
||||
|
|
@ -638,13 +579,13 @@ const Response_Ok$json = {
|
|||
'10': 'planballance'
|
||||
},
|
||||
{
|
||||
'1': 'vouchers',
|
||||
'1': 'deprecated_11',
|
||||
'3': 11,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.server_to_client.Response.Vouchers',
|
||||
'6': '.server_to_client.Response.Deprecated',
|
||||
'9': 0,
|
||||
'10': 'vouchers'
|
||||
'10': 'deprecated11'
|
||||
},
|
||||
{
|
||||
'1': 'addaccountsinvites',
|
||||
|
|
@ -688,21 +629,6 @@ const Response_Ok$json = {
|
|||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response_TransactionTypes$json = {
|
||||
'1': 'TransactionTypes',
|
||||
'2': [
|
||||
{'1': 'Refund', '2': 0},
|
||||
{'1': 'VoucherRedeemed', '2': 1},
|
||||
{'1': 'VoucherCreated', '2': 2},
|
||||
{'1': 'Cash', '2': 3},
|
||||
{'1': 'PlanUpgrade', '2': 4},
|
||||
{'1': 'Unknown', '2': 5},
|
||||
{'1': 'ThanksForTesting', '2': 6},
|
||||
{'1': 'AutoRenewal', '2': 7},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `Response`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List responseDescriptor = $convert.base64Decode(
|
||||
'CghSZXNwb25zZRIvCgJvaxgBIAEoCzIdLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuT2tIAF'
|
||||
|
|
@ -720,64 +646,53 @@ final $typed_data.Uint8List responseDescriptor = $convert.base64Decode(
|
|||
'bnQuUmVzcG9uc2UuUGxhblIFcGxhbnMaTQoRQWRkQWNjb3VudHNJbnZpdGUSFwoHcGxhbl9pZB'
|
||||
'gBIAEoCVIGcGxhbklkEh8KC2ludml0ZV9jb2RlGAIgASgJUgppbnZpdGVDb2RlGlwKEkFkZEFj'
|
||||
'Y291bnRzSW52aXRlcxJGCgdpbnZpdGVzGAEgAygLMiwuc2VydmVyX3RvX2NsaWVudC5SZXNwb2'
|
||||
'5zZS5BZGRBY2NvdW50c0ludml0ZVIHaW52aXRlcxrFAQoLVHJhbnNhY3Rpb24SIwoNZGVwb3Np'
|
||||
'dF9jZW50cxgBIAEoA1IMZGVwb3NpdENlbnRzElYKEHRyYW5zYWN0aW9uX3R5cGUYAiABKA4yKy'
|
||||
'5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLlRyYW5zYWN0aW9uVHlwZXNSD3RyYW5zYWN0aW9u'
|
||||
'VHlwZRI5ChljcmVhdGVkX2F0X3VuaXhfdGltZXN0YW1wGAMgASgDUhZjcmVhdGVkQXRVbml4VG'
|
||||
'ltZXN0YW1wGkUKEUFkZGl0aW9uYWxBY2NvdW50EhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBIX'
|
||||
'CgdwbGFuX2lkGAMgASgJUgZwbGFuSWQavgEKB1ZvdWNoZXISHQoKdm91Y2hlcl9pZBgBIAEoCV'
|
||||
'IJdm91Y2hlcklkEh8KC3ZhbHVlX2NlbnRzGAIgASgDUgp2YWx1ZUNlbnRzEhoKCHJlZGVlbWVk'
|
||||
'GAMgASgIUghyZWRlZW1lZBIcCglyZXF1ZXN0ZWQYBCABKAhSCXJlcXVlc3RlZBI5ChljcmVhdG'
|
||||
'VkX2F0X3VuaXhfdGltZXN0YW1wGAUgASgDUhZjcmVhdGVkQXRVbml4VGltZXN0YW1wGkoKCFZv'
|
||||
'dWNoZXJzEj4KCHZvdWNoZXJzGAEgAygLMiIuc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5Wb3'
|
||||
'VjaGVyUgh2b3VjaGVycxqXBQoMUGxhbkJhbGxhbmNlEkAKHXVzZWRfZGFpbHlfbWVkaWFfdXBs'
|
||||
'b2FkX2xpbWl0GAEgASgDUhl1c2VkRGFpbHlNZWRpYVVwbG9hZExpbWl0Ej4KHHVzZWRfdXBsb2'
|
||||
'FkX21lZGlhX3NpemVfbGltaXQYAiABKANSGHVzZWRVcGxvYWRNZWRpYVNpemVMaW1pdBIzChNw'
|
||||
'YXltZW50X3BlcmlvZF9kYXlzGAMgASgDSABSEXBheW1lbnRQZXJpb2REYXlziAEBEksKIGxhc3'
|
||||
'RfcGF5bWVudF9kb25lX3VuaXhfdGltZXN0YW1wGAQgASgDSAFSHGxhc3RQYXltZW50RG9uZVVu'
|
||||
'aXhUaW1lc3RhbXCIAQESSgoMdHJhbnNhY3Rpb25zGAUgAygLMiYuc2VydmVyX3RvX2NsaWVudC'
|
||||
'5SZXNwb25zZS5UcmFuc2FjdGlvblIMdHJhbnNhY3Rpb25zEl0KE2FkZGl0aW9uYWxfYWNjb3Vu'
|
||||
'dHMYBiADKAsyLC5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLkFkZGl0aW9uYWxBY2NvdW50Uh'
|
||||
'JhZGRpdGlvbmFsQWNjb3VudHMSJgoMYXV0b19yZW5ld2FsGAcgASgISAJSC2F1dG9SZW5ld2Fs'
|
||||
'iAEBEkIKG2FkZGl0aW9uYWxfYWNjb3VudF9vd25lcl9pZBgIIAEoA0gDUhhhZGRpdGlvbmFsQW'
|
||||
'Njb3VudE93bmVySWSIAQFCFgoUX3BheW1lbnRfcGVyaW9kX2RheXNCIwohX2xhc3RfcGF5bWVu'
|
||||
'dF9kb25lX3VuaXhfdGltZXN0YW1wQg8KDV9hdXRvX3JlbmV3YWxCHgocX2FkZGl0aW9uYWxfYW'
|
||||
'Njb3VudF9vd25lcl9pZBpOCghMb2NhdGlvbhIWCgZjb3VudHkYASABKAlSBmNvdW50eRIWCgZy'
|
||||
'ZWdpb24YAiABKAlSBnJlZ2lvbhISCgRjaXR5GAMgASgJUgRjaXR5GjAKBlByZUtleRIOCgJpZB'
|
||||
'gBIAEoA1ICaWQSFgoGcHJla2V5GAIgASgMUgZwcmVrZXkalQEKDFNpZ25lZFByZUtleRIoChBz'
|
||||
'aWduZWRfcHJla2V5X2lkGAEgASgDUg5zaWduZWRQcmVrZXlJZBIjCg1zaWduZWRfcHJla2V5GA'
|
||||
'IgASgMUgxzaWduZWRQcmVrZXkSNgoXc2lnbmVkX3ByZWtleV9zaWduYXR1cmUYAyABKAxSFXNp'
|
||||
'Z25lZFByZWtleVNpZ25hdHVyZRr2AwoIVXNlckRhdGESFwoHdXNlcl9pZBgBIAEoA1IGdXNlck'
|
||||
'lkEjsKB3ByZWtleXMYAiADKAsyIS5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLlByZUtleVIH'
|
||||
'cHJla2V5cxIfCgh1c2VybmFtZRgHIAEoDEgAUgh1c2VybmFtZYgBARIzChNwdWJsaWNfaWRlbn'
|
||||
'RpdHlfa2V5GAMgASgMSAFSEXB1YmxpY0lkZW50aXR5S2V5iAEBEigKDXNpZ25lZF9wcmVrZXkY'
|
||||
'BCABKAxIAlIMc2lnbmVkUHJla2V5iAEBEjsKF3NpZ25lZF9wcmVrZXlfc2lnbmF0dXJlGAUgAS'
|
||||
'gMSANSFXNpZ25lZFByZWtleVNpZ25hdHVyZYgBARItChBzaWduZWRfcHJla2V5X2lkGAYgASgD'
|
||||
'SARSDnNpZ25lZFByZWtleUlkiAEBEiwKD3JlZ2lzdHJhdGlvbl9pZBgIIAEoA0gFUg5yZWdpc3'
|
||||
'RyYXRpb25JZIgBAUILCglfdXNlcm5hbWVCFgoUX3B1YmxpY19pZGVudGl0eV9rZXlCEAoOX3Np'
|
||||
'Z25lZF9wcmVrZXlCGgoYX3NpZ25lZF9wcmVrZXlfc2lnbmF0dXJlQhMKEV9zaWduZWRfcHJla2'
|
||||
'V5X2lkQhIKEF9yZWdpc3RyYXRpb25faWQaWQoLVXBsb2FkVG9rZW4SIQoMdXBsb2FkX3Rva2Vu'
|
||||
'GAEgASgMUgt1cGxvYWRUb2tlbhInCg9kb3dubG9hZF90b2tlbnMYAiADKAxSDmRvd25sb2FkVG'
|
||||
'9rZW5zGjkKDkRvd25sb2FkVG9rZW5zEicKD2Rvd25sb2FkX3Rva2VucxgBIAMoDFIOZG93bmxv'
|
||||
'YWRUb2tlbnMaRQoLUHJvb2ZPZldvcmsSFgoGcHJlZml4GAEgASgJUgZwcmVmaXgSHgoKZGlmZm'
|
||||
'ljdWx0eRgCIAEoA1IKZGlmZmljdWx0eRrDBwoCT2sSFAoETm9uZRgBIAEoCEgAUgROb25lEhgK'
|
||||
'BnVzZXJpZBgCIAEoA0gAUgZ1c2VyaWQSJgoNYXV0aGNoYWxsZW5nZRgDIAEoDEgAUg1hdXRoY2'
|
||||
'hhbGxlbmdlEkoKC3VwbG9hZHRva2VuGAQgASgLMiYuc2VydmVyX3RvX2NsaWVudC5SZXNwb25z'
|
||||
'ZS5VcGxvYWRUb2tlbkgAUgt1cGxvYWR0b2tlbhJBCgh1c2VyZGF0YRgFIAEoCzIjLnNlcnZlcl'
|
||||
'90b19jbGllbnQuUmVzcG9uc2UuVXNlckRhdGFIAFIIdXNlcmRhdGESHgoJYXV0aHRva2VuGAYg'
|
||||
'ASgMSABSCWF1dGh0b2tlbhJBCghsb2NhdGlvbhgHIAEoCzIjLnNlcnZlcl90b19jbGllbnQuUm'
|
||||
'VzcG9uc2UuTG9jYXRpb25IAFIIbG9jYXRpb24SUAoNYXV0aGVudGljYXRlZBgIIAEoCzIoLnNl'
|
||||
'cnZlcl90b19jbGllbnQuUmVzcG9uc2UuQXV0aGVudGljYXRlZEgAUg1hdXRoZW50aWNhdGVkEj'
|
||||
'gKBXBsYW5zGAkgASgLMiAuc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5QbGFuc0gAUgVwbGFu'
|
||||
'cxJNCgxwbGFuYmFsbGFuY2UYCiABKAsyJy5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLlBsYW'
|
||||
'5CYWxsYW5jZUgAUgxwbGFuYmFsbGFuY2USQQoIdm91Y2hlcnMYCyABKAsyIy5zZXJ2ZXJfdG9f'
|
||||
'Y2xpZW50LlJlc3BvbnNlLlZvdWNoZXJzSABSCHZvdWNoZXJzEl8KEmFkZGFjY291bnRzaW52aX'
|
||||
'RlcxgMIAEoCzItLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuQWRkQWNjb3VudHNJbnZpdGVz'
|
||||
'SABSEmFkZGFjY291bnRzaW52aXRlcxJTCg5kb3dubG9hZHRva2VucxgNIAEoCzIpLnNlcnZlcl'
|
||||
'90b19jbGllbnQuUmVzcG9uc2UuRG93bmxvYWRUb2tlbnNIAFIOZG93bmxvYWR0b2tlbnMSTQoM'
|
||||
'c2lnbmVkcHJla2V5GA4gASgLMicuc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5TaWduZWRQcm'
|
||||
'VLZXlIAFIMc2lnbmVkcHJla2V5EkoKC3Byb29mT2ZXb3JrGA8gASgLMiYuc2VydmVyX3RvX2Ns'
|
||||
'aWVudC5SZXNwb25zZS5Qcm9vZk9mV29ya0gAUgtwcm9vZk9mV29ya0IECgJPayKWAQoQVHJhbn'
|
||||
'NhY3Rpb25UeXBlcxIKCgZSZWZ1bmQQABITCg9Wb3VjaGVyUmVkZWVtZWQQARISCg5Wb3VjaGVy'
|
||||
'Q3JlYXRlZBACEggKBENhc2gQAxIPCgtQbGFuVXBncmFkZRAEEgsKB1Vua25vd24QBRIUChBUaG'
|
||||
'Fua3NGb3JUZXN0aW5nEAYSDwoLQXV0b1JlbmV3YWwQB0IKCghSZXNwb25zZQ==');
|
||||
'5zZS5BZGRBY2NvdW50c0ludml0ZVIHaW52aXRlcxpFChFBZGRpdGlvbmFsQWNjb3VudBIXCgd1'
|
||||
'c2VyX2lkGAEgASgDUgZ1c2VySWQSFwoHcGxhbl9pZBgDIAEoCVIGcGxhbklkGgwKCkRlcHJlY2'
|
||||
'F0ZWQaDQoLVHJhbnNhY3Rpb24alwUKDFBsYW5CYWxsYW5jZRJACh11c2VkX2RhaWx5X21lZGlh'
|
||||
'X3VwbG9hZF9saW1pdBgBIAEoA1IZdXNlZERhaWx5TWVkaWFVcGxvYWRMaW1pdBI+Chx1c2VkX3'
|
||||
'VwbG9hZF9tZWRpYV9zaXplX2xpbWl0GAIgASgDUhh1c2VkVXBsb2FkTWVkaWFTaXplTGltaXQS'
|
||||
'MwoTcGF5bWVudF9wZXJpb2RfZGF5cxgDIAEoA0gAUhFwYXltZW50UGVyaW9kRGF5c4gBARJLCi'
|
||||
'BsYXN0X3BheW1lbnRfZG9uZV91bml4X3RpbWVzdGFtcBgEIAEoA0gBUhxsYXN0UGF5bWVudERv'
|
||||
'bmVVbml4VGltZXN0YW1wiAEBEkoKDHRyYW5zYWN0aW9ucxgFIAMoCzImLnNlcnZlcl90b19jbG'
|
||||
'llbnQuUmVzcG9uc2UuVHJhbnNhY3Rpb25SDHRyYW5zYWN0aW9ucxJdChNhZGRpdGlvbmFsX2Fj'
|
||||
'Y291bnRzGAYgAygLMiwuc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5BZGRpdGlvbmFsQWNjb3'
|
||||
'VudFISYWRkaXRpb25hbEFjY291bnRzEiYKDGF1dG9fcmVuZXdhbBgHIAEoCEgCUgthdXRvUmVu'
|
||||
'ZXdhbIgBARJCChthZGRpdGlvbmFsX2FjY291bnRfb3duZXJfaWQYCCABKANIA1IYYWRkaXRpb2'
|
||||
'5hbEFjY291bnRPd25lcklkiAEBQhYKFF9wYXltZW50X3BlcmlvZF9kYXlzQiMKIV9sYXN0X3Bh'
|
||||
'eW1lbnRfZG9uZV91bml4X3RpbWVzdGFtcEIPCg1fYXV0b19yZW5ld2FsQh4KHF9hZGRpdGlvbm'
|
||||
'FsX2FjY291bnRfb3duZXJfaWQaMAoGUHJlS2V5Eg4KAmlkGAEgASgDUgJpZBIWCgZwcmVrZXkY'
|
||||
'AiABKAxSBnByZWtleRqVAQoMU2lnbmVkUHJlS2V5EigKEHNpZ25lZF9wcmVrZXlfaWQYASABKA'
|
||||
'NSDnNpZ25lZFByZWtleUlkEiMKDXNpZ25lZF9wcmVrZXkYAiABKAxSDHNpZ25lZFByZWtleRI2'
|
||||
'ChdzaWduZWRfcHJla2V5X3NpZ25hdHVyZRgDIAEoDFIVc2lnbmVkUHJla2V5U2lnbmF0dXJlGv'
|
||||
'YDCghVc2VyRGF0YRIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQSOwoHcHJla2V5cxgCIAMoCzIh'
|
||||
'LnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuUHJlS2V5UgdwcmVrZXlzEh8KCHVzZXJuYW1lGA'
|
||||
'cgASgMSABSCHVzZXJuYW1liAEBEjMKE3B1YmxpY19pZGVudGl0eV9rZXkYAyABKAxIAVIRcHVi'
|
||||
'bGljSWRlbnRpdHlLZXmIAQESKAoNc2lnbmVkX3ByZWtleRgEIAEoDEgCUgxzaWduZWRQcmVrZX'
|
||||
'mIAQESOwoXc2lnbmVkX3ByZWtleV9zaWduYXR1cmUYBSABKAxIA1IVc2lnbmVkUHJla2V5U2ln'
|
||||
'bmF0dXJliAEBEi0KEHNpZ25lZF9wcmVrZXlfaWQYBiABKANIBFIOc2lnbmVkUHJla2V5SWSIAQ'
|
||||
'ESLAoPcmVnaXN0cmF0aW9uX2lkGAggASgDSAVSDnJlZ2lzdHJhdGlvbklkiAEBQgsKCV91c2Vy'
|
||||
'bmFtZUIWChRfcHVibGljX2lkZW50aXR5X2tleUIQCg5fc2lnbmVkX3ByZWtleUIaChhfc2lnbm'
|
||||
'VkX3ByZWtleV9zaWduYXR1cmVCEwoRX3NpZ25lZF9wcmVrZXlfaWRCEgoQX3JlZ2lzdHJhdGlv'
|
||||
'bl9pZBpZCgtVcGxvYWRUb2tlbhIhCgx1cGxvYWRfdG9rZW4YASABKAxSC3VwbG9hZFRva2VuEi'
|
||||
'cKD2Rvd25sb2FkX3Rva2VucxgCIAMoDFIOZG93bmxvYWRUb2tlbnMaOQoORG93bmxvYWRUb2tl'
|
||||
'bnMSJwoPZG93bmxvYWRfdG9rZW5zGAEgAygMUg5kb3dubG9hZFRva2VucxpFCgtQcm9vZk9mV2'
|
||||
'9yaxIWCgZwcmVmaXgYASABKAlSBnByZWZpeBIeCgpkaWZmaWN1bHR5GAIgASgDUgpkaWZmaWN1'
|
||||
'bHR5GtcHCgJPaxIUCgROb25lGAEgASgISABSBE5vbmUSGAoGdXNlcmlkGAIgASgDSABSBnVzZX'
|
||||
'JpZBImCg1hdXRoY2hhbGxlbmdlGAMgASgMSABSDWF1dGhjaGFsbGVuZ2USSgoLdXBsb2FkdG9r'
|
||||
'ZW4YBCABKAsyJi5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLlVwbG9hZFRva2VuSABSC3VwbG'
|
||||
'9hZHRva2VuEkEKCHVzZXJkYXRhGAUgASgLMiMuc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5V'
|
||||
'c2VyRGF0YUgAUgh1c2VyZGF0YRIeCglhdXRodG9rZW4YBiABKAxIAFIJYXV0aHRva2VuEkoKDG'
|
||||
'RlcHJlY2F0ZWRfNxgHIAEoCzIlLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuRGVwcmVjYXRl'
|
||||
'ZEgAUgtkZXByZWNhdGVkNxJQCg1hdXRoZW50aWNhdGVkGAggASgLMiguc2VydmVyX3RvX2NsaW'
|
||||
'VudC5SZXNwb25zZS5BdXRoZW50aWNhdGVkSABSDWF1dGhlbnRpY2F0ZWQSOAoFcGxhbnMYCSAB'
|
||||
'KAsyIC5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLlBsYW5zSABSBXBsYW5zEk0KDHBsYW5iYW'
|
||||
'xsYW5jZRgKIAEoCzInLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuUGxhbkJhbGxhbmNlSABS'
|
||||
'DHBsYW5iYWxsYW5jZRJMCg1kZXByZWNhdGVkXzExGAsgASgLMiUuc2VydmVyX3RvX2NsaWVudC'
|
||||
'5SZXNwb25zZS5EZXByZWNhdGVkSABSDGRlcHJlY2F0ZWQxMRJfChJhZGRhY2NvdW50c2ludml0'
|
||||
'ZXMYDCABKAsyLS5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLkFkZEFjY291bnRzSW52aXRlc0'
|
||||
'gAUhJhZGRhY2NvdW50c2ludml0ZXMSUwoOZG93bmxvYWR0b2tlbnMYDSABKAsyKS5zZXJ2ZXJf'
|
||||
'dG9fY2xpZW50LlJlc3BvbnNlLkRvd25sb2FkVG9rZW5zSABSDmRvd25sb2FkdG9rZW5zEk0KDH'
|
||||
'NpZ25lZHByZWtleRgOIAEoCzInLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuU2lnbmVkUHJl'
|
||||
'S2V5SABSDHNpZ25lZHByZWtleRJKCgtwcm9vZk9mV29yaxgPIAEoCzImLnNlcnZlcl90b19jbG'
|
||||
'llbnQuUmVzcG9uc2UuUHJvb2ZPZldvcmtIAFILcHJvb2ZPZldvcmtCBAoCT2tCCgoIUmVzcG9u'
|
||||
'c2U=');
|
||||
|
|
|
|||
|
|
@ -11,10 +11,12 @@ message AdditionalMessageData {
|
|||
LINK = 0;
|
||||
CONTACTS = 1;
|
||||
RESTORED_FLAME_COUNTER = 2;
|
||||
ASK_ABOUT_USER = 3;
|
||||
}
|
||||
Type type = 1;
|
||||
|
||||
optional string link = 2;
|
||||
repeated SharedContact contacts = 3;
|
||||
optional int64 restored_flame_counter = 4;
|
||||
optional int64 ask_about_user_id = 5;
|
||||
}
|
||||
|
|
@ -105,6 +105,7 @@ class AdditionalMessageData extends $pb.GeneratedMessage {
|
|||
$core.String? link,
|
||||
$core.Iterable<SharedContact>? contacts,
|
||||
$fixnum.Int64? restoredFlameCounter,
|
||||
$fixnum.Int64? askAboutUserId,
|
||||
}) {
|
||||
final result = create();
|
||||
if (type != null) result.type = type;
|
||||
|
|
@ -112,6 +113,7 @@ class AdditionalMessageData extends $pb.GeneratedMessage {
|
|||
if (contacts != null) result.contacts.addAll(contacts);
|
||||
if (restoredFlameCounter != null)
|
||||
result.restoredFlameCounter = restoredFlameCounter;
|
||||
if (askAboutUserId != null) result.askAboutUserId = askAboutUserId;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -133,6 +135,7 @@ class AdditionalMessageData extends $pb.GeneratedMessage {
|
|||
..pPM<SharedContact>(3, _omitFieldNames ? '' : 'contacts',
|
||||
subBuilder: SharedContact.create)
|
||||
..aInt64(4, _omitFieldNames ? '' : 'restoredFlameCounter')
|
||||
..aInt64(5, _omitFieldNames ? '' : 'askAboutUserId')
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
|
|
@ -184,6 +187,15 @@ class AdditionalMessageData extends $pb.GeneratedMessage {
|
|||
$core.bool hasRestoredFlameCounter() => $_has(3);
|
||||
@$pb.TagNumber(4)
|
||||
void clearRestoredFlameCounter() => $_clearField(4);
|
||||
|
||||
@$pb.TagNumber(5)
|
||||
$fixnum.Int64 get askAboutUserId => $_getI64(4);
|
||||
@$pb.TagNumber(5)
|
||||
set askAboutUserId($fixnum.Int64 value) => $_setInt64(4, value);
|
||||
@$pb.TagNumber(5)
|
||||
$core.bool hasAskAboutUserId() => $_has(4);
|
||||
@$pb.TagNumber(5)
|
||||
void clearAskAboutUserId() => $_clearField(5);
|
||||
}
|
||||
|
||||
const $core.bool _omitFieldNames =
|
||||
|
|
|
|||
|
|
@ -22,16 +22,19 @@ class AdditionalMessageData_Type extends $pb.ProtobufEnum {
|
|||
static const AdditionalMessageData_Type RESTORED_FLAME_COUNTER =
|
||||
AdditionalMessageData_Type._(
|
||||
2, _omitEnumNames ? '' : 'RESTORED_FLAME_COUNTER');
|
||||
static const AdditionalMessageData_Type ASK_ABOUT_USER =
|
||||
AdditionalMessageData_Type._(3, _omitEnumNames ? '' : 'ASK_ABOUT_USER');
|
||||
|
||||
static const $core.List<AdditionalMessageData_Type> values =
|
||||
<AdditionalMessageData_Type>[
|
||||
LINK,
|
||||
CONTACTS,
|
||||
RESTORED_FLAME_COUNTER,
|
||||
ASK_ABOUT_USER,
|
||||
];
|
||||
|
||||
static final $core.List<AdditionalMessageData_Type?> _byValue =
|
||||
$pb.ProtobufEnum.$_initByValueList(values, 2);
|
||||
$pb.ProtobufEnum.$_initByValueList(values, 3);
|
||||
static AdditionalMessageData_Type? valueOf($core.int value) =>
|
||||
value < 0 || value >= _byValue.length ? null : _byValue[value];
|
||||
|
||||
|
|
|
|||
|
|
@ -67,11 +67,21 @@ const AdditionalMessageData$json = {
|
|||
'10': 'restoredFlameCounter',
|
||||
'17': true
|
||||
},
|
||||
{
|
||||
'1': 'ask_about_user_id',
|
||||
'3': 5,
|
||||
'4': 1,
|
||||
'5': 3,
|
||||
'9': 2,
|
||||
'10': 'askAboutUserId',
|
||||
'17': true
|
||||
},
|
||||
],
|
||||
'4': [AdditionalMessageData_Type$json],
|
||||
'8': [
|
||||
{'1': '_link'},
|
||||
{'1': '_restored_flame_counter'},
|
||||
{'1': '_ask_about_user_id'},
|
||||
],
|
||||
};
|
||||
|
||||
|
|
@ -82,6 +92,7 @@ const AdditionalMessageData_Type$json = {
|
|||
{'1': 'LINK', '2': 0},
|
||||
{'1': 'CONTACTS', '2': 1},
|
||||
{'1': 'RESTORED_FLAME_COUNTER', '2': 2},
|
||||
{'1': 'ASK_ABOUT_USER', '2': 3},
|
||||
],
|
||||
};
|
||||
|
||||
|
|
@ -90,6 +101,7 @@ final $typed_data.Uint8List additionalMessageDataDescriptor = $convert.base64Dec
|
|||
'ChVBZGRpdGlvbmFsTWVzc2FnZURhdGESLwoEdHlwZRgBIAEoDjIbLkFkZGl0aW9uYWxNZXNzYW'
|
||||
'dlRGF0YS5UeXBlUgR0eXBlEhcKBGxpbmsYAiABKAlIAFIEbGlua4gBARIqCghjb250YWN0cxgD'
|
||||
'IAMoCzIOLlNoYXJlZENvbnRhY3RSCGNvbnRhY3RzEjkKFnJlc3RvcmVkX2ZsYW1lX2NvdW50ZX'
|
||||
'IYBCABKANIAVIUcmVzdG9yZWRGbGFtZUNvdW50ZXKIAQEiOgoEVHlwZRIICgRMSU5LEAASDAoI'
|
||||
'Q09OVEFDVFMQARIaChZSRVNUT1JFRF9GTEFNRV9DT1VOVEVSEAJCBwoFX2xpbmtCGQoXX3Jlc3'
|
||||
'RvcmVkX2ZsYW1lX2NvdW50ZXI=');
|
||||
'IYBCABKANIAVIUcmVzdG9yZWRGbGFtZUNvdW50ZXKIAQESLgoRYXNrX2Fib3V0X3VzZXJfaWQY'
|
||||
'BSABKANIAlIOYXNrQWJvdXRVc2VySWSIAQEiTgoEVHlwZRIICgRMSU5LEAASDAoIQ09OVEFDVF'
|
||||
'MQARIaChZSRVNUT1JFRF9GTEFNRV9DT1VOVEVSEAISEgoOQVNLX0FCT1VUX1VTRVIQA0IHCgVf'
|
||||
'bGlua0IZChdfcmVzdG9yZWRfZmxhbWVfY291bnRlckIUChJfYXNrX2Fib3V0X3VzZXJfaWQ=');
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ class PublicProfile extends $pb.GeneratedMessage {
|
|||
$core.List<$core.int>? signedPrekeySignature,
|
||||
$fixnum.Int64? signedPrekeyId,
|
||||
$core.List<$core.int>? secretVerificationToken,
|
||||
$fixnum.Int64? timestamp,
|
||||
}) {
|
||||
final result = create();
|
||||
if (userId != null) result.userId = userId;
|
||||
|
|
@ -109,6 +110,7 @@ class PublicProfile extends $pb.GeneratedMessage {
|
|||
if (signedPrekeyId != null) result.signedPrekeyId = signedPrekeyId;
|
||||
if (secretVerificationToken != null)
|
||||
result.secretVerificationToken = secretVerificationToken;
|
||||
if (timestamp != null) result.timestamp = timestamp;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -136,6 +138,7 @@ class PublicProfile extends $pb.GeneratedMessage {
|
|||
..aInt64(7, _omitFieldNames ? '' : 'signedPrekeyId')
|
||||
..a<$core.List<$core.int>>(
|
||||
8, _omitFieldNames ? '' : 'secretVerificationToken', $pb.PbFieldType.OY)
|
||||
..aInt64(9, _omitFieldNames ? '' : 'timestamp')
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
|
|
@ -230,6 +233,15 @@ class PublicProfile extends $pb.GeneratedMessage {
|
|||
$core.bool hasSecretVerificationToken() => $_has(7);
|
||||
@$pb.TagNumber(8)
|
||||
void clearSecretVerificationToken() => $_clearField(8);
|
||||
|
||||
@$pb.TagNumber(9)
|
||||
$fixnum.Int64 get timestamp => $_getI64(8);
|
||||
@$pb.TagNumber(9)
|
||||
set timestamp($fixnum.Int64 value) => $_setInt64(8, value);
|
||||
@$pb.TagNumber(9)
|
||||
$core.bool hasTimestamp() => $_has(8);
|
||||
@$pb.TagNumber(9)
|
||||
void clearTimestamp() => $_clearField(9);
|
||||
}
|
||||
|
||||
const $core.bool _omitFieldNames =
|
||||
|
|
|
|||
|
|
@ -77,9 +77,19 @@ const PublicProfile$json = {
|
|||
'10': 'secretVerificationToken',
|
||||
'17': true
|
||||
},
|
||||
{
|
||||
'1': 'timestamp',
|
||||
'3': 9,
|
||||
'4': 1,
|
||||
'5': 3,
|
||||
'9': 1,
|
||||
'10': 'timestamp',
|
||||
'17': true
|
||||
},
|
||||
],
|
||||
'8': [
|
||||
{'1': '_secret_verification_token'},
|
||||
{'1': '_timestamp'},
|
||||
],
|
||||
};
|
||||
|
||||
|
|
@ -91,4 +101,5 @@ final $typed_data.Uint8List publicProfileDescriptor = $convert.base64Decode(
|
|||
'lvbl9pZBgFIAEoA1IOcmVnaXN0cmF0aW9uSWQSNgoXc2lnbmVkX3ByZWtleV9zaWduYXR1cmUY'
|
||||
'BiABKAxSFXNpZ25lZFByZWtleVNpZ25hdHVyZRIoChBzaWduZWRfcHJla2V5X2lkGAcgASgDUg'
|
||||
'5zaWduZWRQcmVrZXlJZBI/ChlzZWNyZXRfdmVyaWZpY2F0aW9uX3Rva2VuGAggASgMSABSF3Nl'
|
||||
'Y3JldFZlcmlmaWNhdGlvblRva2VuiAEBQhwKGl9zZWNyZXRfdmVyaWZpY2F0aW9uX3Rva2Vu');
|
||||
'Y3JldFZlcmlmaWNhdGlvblRva2VuiAEBEiEKCXRpbWVzdGFtcBgJIAEoA0gBUgl0aW1lc3RhbX'
|
||||
'CIAQFCHAoaX3NlY3JldF92ZXJpZmljYXRpb25fdG9rZW5CDAoKX3RpbWVzdGFtcA==');
|
||||
|
|
|
|||
|
|
@ -17,4 +17,5 @@ message PublicProfile {
|
|||
bytes signed_prekey_signature = 6;
|
||||
int64 signed_prekey_id = 7;
|
||||
optional bytes secret_verification_token = 8;
|
||||
optional int64 timestamp = 9;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ class PurchasesProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
|||
}
|
||||
|
||||
loadPurchases();
|
||||
Log.info('PurchasesProvider: constructor finished');
|
||||
}
|
||||
|
||||
SubscriptionPlan plan = SubscriptionPlan.Free;
|
||||
|
|
@ -74,6 +75,7 @@ class PurchasesProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
|||
|
||||
Future<void> loadPurchases() async {
|
||||
final available = await iapConnection.isAvailable();
|
||||
Log.info('PurchasesProvider: IAP available: $available');
|
||||
if (!available) {
|
||||
storeState = StoreState.notAvailable;
|
||||
Log.warn('Store is not available');
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:twonly/app.dart';
|
||||
import 'package:twonly/src/constants/routes.keys.dart';
|
||||
|
|
@ -16,14 +17,13 @@ import 'package:twonly/src/visual/views/onboarding/recover.view.dart';
|
|||
import 'package:twonly/src/visual/views/public_profile.view.dart';
|
||||
import 'package:twonly/src/visual/views/settings/account.view.dart';
|
||||
import 'package:twonly/src/visual/views/settings/appearance.view.dart';
|
||||
import 'package:twonly/src/visual/views/settings/backup/backup_server.view.dart';
|
||||
import 'package:twonly/src/visual/views/settings/backup/backup_settings.view.dart';
|
||||
import 'package:twonly/src/visual/views/settings/backup/backup_setup.view.dart';
|
||||
import 'package:twonly/src/visual/views/settings/chat/chat_reactions.view.dart';
|
||||
import 'package:twonly/src/visual/views/settings/chat/chat_settings.view.dart';
|
||||
import 'package:twonly/src/visual/views/settings/data_and_storage.view.dart';
|
||||
import 'package:twonly/src/visual/views/settings/data_and_storage/export_media.view.dart';
|
||||
import 'package:twonly/src/visual/views/settings/data_and_storage/import_media.view.dart';
|
||||
import 'package:twonly/src/visual/views/settings/data_and_storage/import_from_gallery.view.dart';
|
||||
import 'package:twonly/src/visual/views/settings/data_and_storage/manage_storage.view.dart';
|
||||
import 'package:twonly/src/visual/views/settings/developer/automated_testing.view.dart';
|
||||
import 'package:twonly/src/visual/views/settings/developer/developer.view.dart';
|
||||
import 'package:twonly/src/visual/views/settings/developer/reduce_flames.view.dart';
|
||||
|
|
@ -38,6 +38,7 @@ import 'package:twonly/src/visual/views/settings/help/help.view.dart';
|
|||
import 'package:twonly/src/visual/views/settings/notification.view.dart';
|
||||
import 'package:twonly/src/visual/views/settings/privacy.view.dart';
|
||||
import 'package:twonly/src/visual/views/settings/privacy/block_users.view.dart';
|
||||
import 'package:twonly/src/visual/views/settings/privacy/profile_selection.view.dart';
|
||||
import 'package:twonly/src/visual/views/settings/privacy/user_discovery.view.dart';
|
||||
import 'package:twonly/src/visual/views/settings/profile/modify_avatar.view.dart';
|
||||
import 'package:twonly/src/visual/views/settings/profile/profile.view.dart';
|
||||
|
|
@ -47,7 +48,10 @@ import 'package:twonly/src/visual/views/settings/subscription/subscription.view.
|
|||
import 'package:twonly/src/visual/views/user_study/user_study_questionnaire.view.dart';
|
||||
import 'package:twonly/src/visual/views/user_study/user_study_welcome.view.dart';
|
||||
|
||||
final GlobalKey<NavigatorState> rootNavigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
final routerProvider = GoRouter(
|
||||
navigatorKey: rootNavigatorKey,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: Routes.home,
|
||||
|
|
@ -165,10 +169,6 @@ final routerProvider = GoRouter(
|
|||
path: 'backup',
|
||||
builder: (context, state) => const BackupView(),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'server',
|
||||
builder: (context, state) => const BackupServerView(),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'recovery',
|
||||
builder: (context, state) => const BackupRecoveryView(),
|
||||
|
|
@ -205,6 +205,10 @@ final routerProvider = GoRouter(
|
|||
path: 'user_discovery',
|
||||
builder: (context, state) => const UserDiscoverySettingsView(),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'profile_selection',
|
||||
builder: (context, state) => const ProfileSelectionSettingsView(),
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
|
|
@ -216,12 +220,12 @@ final routerProvider = GoRouter(
|
|||
builder: (context, state) => const DataAndStorageView(),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'import',
|
||||
builder: (context, state) => const ImportMediaView(),
|
||||
path: 'manage',
|
||||
builder: (context, state) => const ManageStorageView(),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'export',
|
||||
builder: (context, state) => const ExportMediaView(),
|
||||
path: 'import_gallery',
|
||||
builder: (context, state) => const ImportFromGalleryView(),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
25
lib/src/services/android_photo_picker.service.dart
Normal file
25
lib/src/services/android_photo_picker.service.dart
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import 'package:flutter/services.dart';
|
||||
|
||||
class AndroidPhotoPickerService {
|
||||
static const MethodChannel _channel = MethodChannel('eu.twonly/photo_picker');
|
||||
|
||||
/// Launches the native Android Photo Picker and returns a list of URIs.
|
||||
static Future<List<String>> pickImages() async {
|
||||
try {
|
||||
final result = await _channel.invokeListMethod<String>('pickImages');
|
||||
return result ?? [];
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads the raw bytes from a content URI using the Android ContentResolver.
|
||||
static Future<Uint8List?> getUriBytes(String uri) async {
|
||||
try {
|
||||
final bytes = await _channel.invokeMethod<Uint8List>('getUriBytes', {'uri': uri});
|
||||
return bytes;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,14 +15,13 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:libsignal_protocol_dart/src/ecc/ed25519.dart';
|
||||
import 'package:mutex/mutex.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:twonly/core/bridge/wrapper/key_manager.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/constants/secure_storage.keys.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/model/protobuf/api/websocket/client_to_server.pbserver.dart';
|
||||
import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart';
|
||||
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart'
|
||||
as server;
|
||||
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart' as server;
|
||||
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pbserver.dart';
|
||||
import 'package:twonly/src/services/api/client2client/user_discovery.c2c.dart';
|
||||
import 'package:twonly/src/services/api/mediafiles/download.api.dart';
|
||||
|
|
@ -31,7 +30,7 @@ import 'package:twonly/src/services/api/messages.api.dart';
|
|||
import 'package:twonly/src/services/api/server_messages.api.dart';
|
||||
import 'package:twonly/src/services/api/utils.api.dart';
|
||||
import 'package:twonly/src/services/flame.service.dart';
|
||||
import 'package:twonly/src/services/group.services.dart';
|
||||
import 'package:twonly/src/services/group.service.dart';
|
||||
import 'package:twonly/src/services/notifications/fcm.notifications.dart';
|
||||
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
|
||||
import 'package:twonly/src/services/signal/identity.signal.dart';
|
||||
|
|
@ -60,19 +59,19 @@ class ApiService {
|
|||
// final String apiHost = kReleaseMode ? 'api.twonly.eu' : 'dev.twonly.eu';
|
||||
final String apiSecure = kReleaseMode ? 's' : 's';
|
||||
|
||||
String get apiEndpoint => 'http$apiSecure://$apiHost/api/';
|
||||
|
||||
final _planUpdateController = StreamController<SubscriptionPlan>.broadcast();
|
||||
Stream<SubscriptionPlan> get onPlanUpdated => _planUpdateController.stream;
|
||||
|
||||
final _connectionStateController = StreamController<bool>.broadcast();
|
||||
Stream<bool> get onConnectionStateUpdated =>
|
||||
_connectionStateController.stream;
|
||||
Stream<bool> get onConnectionStateUpdated => _connectionStateController.stream;
|
||||
|
||||
final _appOutdatedController = StreamController<void>.broadcast();
|
||||
Stream<void> get onAppOutdated => _appOutdatedController.stream;
|
||||
|
||||
final _newDeviceRegisteredController = StreamController<void>.broadcast();
|
||||
Stream<void> get onNewDeviceRegistered =>
|
||||
_newDeviceRegisteredController.stream;
|
||||
Stream<void> get onNewDeviceRegistered => _newDeviceRegisteredController.stream;
|
||||
|
||||
bool appIsOutdated = false;
|
||||
bool isAuthenticated = false;
|
||||
|
|
@ -81,8 +80,7 @@ class ApiService {
|
|||
Timer? reconnectionTimer;
|
||||
int _reconnectionDelay = 5;
|
||||
|
||||
final HashMap<Int64, Completer<server.ServerToClient?>> _pendingRequests =
|
||||
HashMap();
|
||||
final HashMap<Int64, Completer<server.ServerToClient?>> _pendingRequests = HashMap();
|
||||
IOWebSocketChannel? _channel;
|
||||
// ignore: cancel_subscriptions
|
||||
StreamSubscription<List<ConnectivityResult>>? _connectivitySubscription;
|
||||
|
|
@ -92,13 +90,22 @@ class ApiService {
|
|||
try {
|
||||
final channel = IOWebSocketChannel.connect(
|
||||
Uri.parse(apiUrl),
|
||||
pingInterval: const Duration(seconds: 30),
|
||||
);
|
||||
|
||||
try {
|
||||
await channel.ready.timeout(const Duration(seconds: 10));
|
||||
} catch (e) {
|
||||
channel.sink.close().ignore();
|
||||
rethrow;
|
||||
}
|
||||
|
||||
_channel = channel;
|
||||
_channel!.stream.listen(_onData, onDone: _onDone, onError: _onError);
|
||||
await _channel!.ready;
|
||||
Log.info('websocket connected to $apiUrl');
|
||||
return true;
|
||||
} catch (_) {
|
||||
} catch (e) {
|
||||
_channel = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -122,7 +129,7 @@ class ApiService {
|
|||
twonlyDB.markUpdated();
|
||||
unawaited(syncFlameCounters());
|
||||
unawaited(setupNotificationWithUsers());
|
||||
unawaited(signalHandleNewServerConnection());
|
||||
unawaited(SignalIdentityService.onAuthenticated());
|
||||
resetResyncedUsers();
|
||||
resetUserDiscoveryRequestUpdates();
|
||||
unawaited(fetchGroupStatesForUnjoinedGroups());
|
||||
|
|
@ -145,6 +152,8 @@ class ApiService {
|
|||
}
|
||||
|
||||
Future<void> onClosed() async {
|
||||
if (_channel == null) return;
|
||||
Log.info('websocket connection closed');
|
||||
_channel = null;
|
||||
isAuthenticated = false;
|
||||
_connectionStateController.add(false);
|
||||
|
|
@ -175,15 +184,19 @@ class ApiService {
|
|||
_reconnectionDelay = 3;
|
||||
}
|
||||
|
||||
Future<void> close(Function callback) async {
|
||||
Future<void> close(Function? callback) async {
|
||||
Log.info('closing websocket connection');
|
||||
if (_channel != null) {
|
||||
await _channel!.sink.close();
|
||||
try {
|
||||
await _channel!.sink.close().timeout(const Duration(seconds: 2));
|
||||
} catch (e) {
|
||||
Log.warn('Timeout or error closing websocket: $e');
|
||||
}
|
||||
await onClosed();
|
||||
callback();
|
||||
callback?.call();
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
callback?.call();
|
||||
}
|
||||
|
||||
Future<void> listenToNetworkChanges() async {
|
||||
|
|
@ -241,15 +254,18 @@ class ApiService {
|
|||
|
||||
Future<void> _onData(dynamic msgBuffer) async {
|
||||
try {
|
||||
final msg = server.ServerToClient.fromBuffer(msgBuffer as Uint8List);
|
||||
if (msgBuffer is! Uint8List) {
|
||||
msgBuffer = Uint8List.fromList(msgBuffer as List<int>);
|
||||
}
|
||||
final msg = server.ServerToClient.fromBuffer(msgBuffer);
|
||||
if (msg.v0.hasResponse()) {
|
||||
await removeFromRetransmissionBuffer(msg.v0.seq);
|
||||
final completer = _pendingRequests.remove(msg.v0.seq);
|
||||
if (completer != null && !completer.isCompleted) {
|
||||
completer.complete(msg);
|
||||
}
|
||||
unawaited(removeFromRetransmissionBuffer(msg.v0.seq));
|
||||
} else {
|
||||
await handleServerMessage(msg);
|
||||
unawaited(handleServerMessage(msg));
|
||||
}
|
||||
} catch (e) {
|
||||
Log.error('Error parsing the servers message: $e');
|
||||
|
|
@ -402,9 +418,7 @@ class ApiService {
|
|||
}
|
||||
if (res.error == ErrorCode.UserIdNotFound && contactId != null) {
|
||||
Log.warn('Contact deleted their account $contactId.');
|
||||
final contact = await twonlyDB.contactsDao
|
||||
.getContactByUserId(contactId)
|
||||
.getSingleOrNull();
|
||||
final contact = await twonlyDB.contactsDao.getContactByUserId(contactId).getSingleOrNull();
|
||||
if (contact != null) {
|
||||
await twonlyDB.contactsDao.updateContact(
|
||||
contactId,
|
||||
|
|
@ -413,6 +427,7 @@ class ApiService {
|
|||
),
|
||||
);
|
||||
}
|
||||
await twonlyDB.receiptsDao.deleteReceiptForUser(contactId);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
|
|
@ -420,7 +435,7 @@ class ApiService {
|
|||
|
||||
Future<bool> tryAuthenticateWithToken() async {
|
||||
final apiAuthToken = await SecureStorage.instance.read(
|
||||
key: SecureStorageKeys.apiAuthToken,
|
||||
key: 'api_auth_token',
|
||||
);
|
||||
|
||||
if (apiAuthToken != null) {
|
||||
|
|
@ -449,12 +464,26 @@ class ApiService {
|
|||
await onAuthenticated();
|
||||
} else {
|
||||
unawaited(onAuthenticated());
|
||||
|
||||
try {
|
||||
Log.info('Switching authentication to login token');
|
||||
final loginToken = await RustKeyManager.getLoginToken();
|
||||
final res = await _setLoginToken(loginToken);
|
||||
if (res.isSuccess) {
|
||||
Log.info('Switch was successfully.');
|
||||
await UserService.update((u) => u.canUseLoginTokenForAuth = true);
|
||||
await SecureStorage.instance.delete(
|
||||
key: 'api_auth_token',
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (result.isError) {
|
||||
if (result.error != ErrorCode.AuthTokenNotValid &&
|
||||
result.error != ErrorCode.ForegroundSessionConnected) {
|
||||
if (result.error != ErrorCode.AuthTokenNotValid && result.error != ErrorCode.ForegroundSessionConnected) {
|
||||
Log.error(
|
||||
'got error while authenticating to the server: ${result.error}',
|
||||
);
|
||||
|
|
@ -465,22 +494,66 @@ class ApiService {
|
|||
return false;
|
||||
}
|
||||
|
||||
Future<bool> tryAuthenticateWithLoginToken() async {
|
||||
try {
|
||||
final loginToken = await RustKeyManager.getLoginToken();
|
||||
|
||||
final authenticate = Handshake_AuthenticateWithLoginToken()
|
||||
..userId = Int64(userService.currentUser.userId)
|
||||
..appVersion = (await PackageInfo.fromPlatform()).version
|
||||
..deviceId = Int64(userService.currentUser.deviceId)
|
||||
..inBackground = AppState.isInBackgroundTask
|
||||
..secretLoginToken = loginToken.toList();
|
||||
|
||||
final handshake = Handshake()..authenticateWithLoginToken = authenticate;
|
||||
final req = createClientToServerFromHandshake(handshake);
|
||||
|
||||
final result = await sendRequestSync(req, authenticated: false);
|
||||
|
||||
if (result.isSuccess) {
|
||||
Log.info('websocket is authenticated');
|
||||
isAuthenticated = true;
|
||||
if (AppState.isInBackgroundTask) {
|
||||
await onAuthenticated();
|
||||
} else {
|
||||
unawaited(onAuthenticated());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (result.isError) {
|
||||
if (result.error != ErrorCode.AuthTokenNotValid && result.error != ErrorCode.ForegroundSessionConnected) {
|
||||
Log.error(
|
||||
'got error while authenticating to the server: ${result.error}',
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<void> authenticate() async {
|
||||
return lockAuthentication.protect(() async {
|
||||
if (isAuthenticated) return;
|
||||
if (await getSignalIdentity() == null) {
|
||||
Log.error('Signal identity not found.');
|
||||
|
||||
if (!userService.isUserCreated) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!userService.isUserCreated) return;
|
||||
|
||||
if (userService.currentUser.canUseLoginTokenForAuth) {
|
||||
await tryAuthenticateWithLoginToken();
|
||||
return;
|
||||
}
|
||||
|
||||
if (await tryAuthenticateWithToken()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final handshake = Handshake()
|
||||
..getAuthChallenge = Handshake_GetAuthChallenge();
|
||||
final handshake = Handshake()..getAuthChallenge = Handshake_GetAuthChallenge();
|
||||
final req = createClientToServerFromHandshake(handshake);
|
||||
|
||||
final result = await sendRequestSync(req, authenticated: false);
|
||||
|
|
@ -519,7 +592,7 @@ class ApiService {
|
|||
final apiAuthTokenB64 = base64Encode(apiAuthToken);
|
||||
|
||||
await SecureStorage.instance.write(
|
||||
key: SecureStorageKeys.apiAuthToken,
|
||||
key: 'api_auth_token',
|
||||
value: apiAuthTokenB64,
|
||||
);
|
||||
|
||||
|
|
@ -541,16 +614,17 @@ class ApiService {
|
|||
|
||||
final signedPreKey = (await signalStore.loadSignedPreKeys())[0];
|
||||
|
||||
final loginToken = await RustKeyManager.getLoginToken();
|
||||
|
||||
final register = Handshake_Register()
|
||||
..username = username
|
||||
..publicIdentityKey = (await signalStore.getIdentityKeyPair())
|
||||
.getPublicKey()
|
||||
.serialize()
|
||||
..publicIdentityKey = (await signalStore.getIdentityKeyPair()).getPublicKey().serialize()
|
||||
..registrationId = Int64(signalIdentity.registrationId)
|
||||
..signedPrekey = signedPreKey.getKeyPair().publicKey.serialize()
|
||||
..signedPrekeySignature = signedPreKey.signature
|
||||
..signedPrekeyId = Int64(signedPreKey.id)
|
||||
..langCode = ui.PlatformDispatcher.instance.locale.languageCode
|
||||
..loginToken = loginToken
|
||||
..proofOfWork = Int64(proofOfWorkResult)
|
||||
..isIos = Platform.isIOS;
|
||||
|
||||
|
|
@ -616,13 +690,28 @@ class ApiService {
|
|||
return sendRequestSync(req, ensureRetransmission: true);
|
||||
}
|
||||
|
||||
Future<Result> getCurrentLocation() async {
|
||||
final get = ApplicationData_GetLocation();
|
||||
final appData = ApplicationData()..getLocation = get;
|
||||
Future<Result> _setLoginToken(List<int> token) async {
|
||||
final get = ApplicationData_SetLoginToken()..loginToken = token;
|
||||
final appData = ApplicationData()..setLoginToken = get;
|
||||
final req = createClientToServerFromApplicationData(appData);
|
||||
return sendRequestSync(req);
|
||||
}
|
||||
|
||||
Future<int?> getUserIdFromUsername(String username) async {
|
||||
final appData = Handshake(
|
||||
getUseridByUsername: Handshake_GetUserIdByUsername(username: username),
|
||||
);
|
||||
final req = createClientToServerFromHandshake(appData);
|
||||
final res = await sendRequestSync(req);
|
||||
if (res.isSuccess) {
|
||||
final ok = res.value as server.Response_Ok;
|
||||
if (ok.hasUserid()) {
|
||||
return ok.userid.toInt();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<Response_UserData?> getUserData(String username) async {
|
||||
final get = ApplicationData_GetUserByUsername()..username = username;
|
||||
final appData = ApplicationData()..getUserByUsername = get;
|
||||
|
|
@ -651,27 +740,6 @@ class ApiService {
|
|||
return null;
|
||||
}
|
||||
|
||||
Future<Response_Vouchers?> getVoucherList() async {
|
||||
final get = ApplicationData_GetVouchers();
|
||||
final appData = ApplicationData()..getVouchers = get;
|
||||
final req = createClientToServerFromApplicationData(appData);
|
||||
final res = await sendRequestSync(req);
|
||||
if (res.isSuccess) {
|
||||
final ok = res.value as server.Response_Ok;
|
||||
if (ok.hasVouchers()) {
|
||||
return ok.vouchers;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<Result> updatePlanOptions(bool autoRenewal) async {
|
||||
final get = ApplicationData_UpdatePlanOptions()..autoRenewal = autoRenewal;
|
||||
final appData = ApplicationData()..updatePlanOptions = get;
|
||||
final req = createClientToServerFromApplicationData(appData);
|
||||
return sendRequestSync(req);
|
||||
}
|
||||
|
||||
Future<Result> removeAdditionalUser(Int64 userId) async {
|
||||
final get = ApplicationData_RemoveAdditionalUser()..userId = userId;
|
||||
final appData = ApplicationData()..removeAdditionalUser = get;
|
||||
|
|
@ -686,34 +754,6 @@ class ApiService {
|
|||
return sendRequestSync(req, contactId: userId.toInt());
|
||||
}
|
||||
|
||||
Future<Result> buyVoucher(int valueInCents) async {
|
||||
final get = ApplicationData_CreateVoucher()..valueCents = valueInCents;
|
||||
final appData = ApplicationData()..createVoucher = get;
|
||||
final req = createClientToServerFromApplicationData(appData);
|
||||
return sendRequestSync(req);
|
||||
}
|
||||
|
||||
Future<Result> switchToPayedPlan(
|
||||
String planId,
|
||||
bool payMonthly,
|
||||
bool autoRenewal,
|
||||
) async {
|
||||
final get = ApplicationData_SwitchToPayedPlan()
|
||||
..planId = planId
|
||||
..payMonthly = payMonthly
|
||||
..autoRenewal = autoRenewal;
|
||||
final appData = ApplicationData()..switchtoPayedPlan = get;
|
||||
final req = createClientToServerFromApplicationData(appData);
|
||||
return sendRequestSync(req);
|
||||
}
|
||||
|
||||
Future<Result> redeemVoucher(String voucher) async {
|
||||
final get = ApplicationData_RedeemVoucher()..voucher = voucher;
|
||||
final appData = ApplicationData()..redeemVoucher = get;
|
||||
final req = createClientToServerFromApplicationData(appData);
|
||||
return sendRequestSync(req);
|
||||
}
|
||||
|
||||
Future<Result> reportUser(int userId, String reason) async {
|
||||
final get = ApplicationData_ReportUser()
|
||||
..reportedUserId = Int64(userId)
|
||||
|
|
@ -730,13 +770,6 @@ class ApiService {
|
|||
return sendRequestSync(req);
|
||||
}
|
||||
|
||||
Future<Result> redeemUserInviteCode(String inviteCode) async {
|
||||
final get = ApplicationData_RedeemAdditionalCode()..inviteCode = inviteCode;
|
||||
final appData = ApplicationData()..redeemAdditionalCode = get;
|
||||
final req = createClientToServerFromApplicationData(appData);
|
||||
return sendRequestSync(req);
|
||||
}
|
||||
|
||||
Future<Result> updateFCMToken(String googleFcm) async {
|
||||
final get = ApplicationData_UpdateGoogleFcmToken()..googleFcm = googleFcm;
|
||||
final appData = ApplicationData()..updateGoogleFcmToken = get;
|
||||
|
|
|
|||
|
|
@ -10,9 +10,10 @@ Future<void> handleAdditionalDataMessage(
|
|||
int fromUserId,
|
||||
String groupId,
|
||||
EncryptedContent_AdditionalDataMessage message,
|
||||
String receiptId,
|
||||
) async {
|
||||
Log.info(
|
||||
'Got a additional data message: ${message.senderMessageId} from $groupId',
|
||||
'[$receiptId] Got a additional data message: ${message.senderMessageId} from $groupId',
|
||||
);
|
||||
|
||||
// Prevent message overwrite: reject if a message with this ID already
|
||||
|
|
@ -22,7 +23,7 @@ Future<void> handleAdditionalDataMessage(
|
|||
.getSingleOrNull();
|
||||
if (existing != null && existing.senderId != fromUserId) {
|
||||
Log.warn(
|
||||
'$fromUserId tried to overwrite message from ${existing.senderId}. Dropping.',
|
||||
'[$receiptId] $fromUserId tried to overwrite message from ${existing.senderId}. Dropping.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
|
@ -45,6 +46,6 @@ Future<void> handleAdditionalDataMessage(
|
|||
fromTimestamp(message.timestamp),
|
||||
);
|
||||
if (msg != null) {
|
||||
Log.info('Inserted a new text message with ID: ${msg.messageId}');
|
||||
Log.info('[$receiptId] Inserted a new text message with ID: ${msg.messageId}');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import 'dart:convert';
|
|||
import 'package:drift/drift.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||
import 'package:twonly/src/database/tables/groups.table.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart' hide Message;
|
||||
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
|
||||
import 'package:twonly/src/services/api/messages.api.dart';
|
||||
|
|
@ -27,7 +28,6 @@ Future<bool> handleNewContactRequest(int fromUserId) async {
|
|||
await handleContactAccept(fromUserId);
|
||||
}
|
||||
|
||||
// contact was already accepted, so just accept the request in the background.
|
||||
await sendCipherText(
|
||||
contact.userId,
|
||||
EncryptedContent(
|
||||
|
|
@ -35,6 +35,7 @@ Future<bool> handleNewContactRequest(int fromUserId) async {
|
|||
type: EncryptedContent_ContactRequest_Type.ACCEPT,
|
||||
),
|
||||
),
|
||||
blocking: false,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -59,6 +60,15 @@ Future<bool> handleNewContactRequest(int fromUserId) async {
|
|||
}
|
||||
|
||||
Future<void> handleContactAccept(int fromUserId) async {
|
||||
final contact = await twonlyDB.contactsDao
|
||||
.getContactByUserId(fromUserId)
|
||||
.getSingleOrNull();
|
||||
if (contact == null) return;
|
||||
if (contact.requested || contact.deletedByUser) {
|
||||
Log.error('User has never send an request. So ignore the Accept.');
|
||||
return;
|
||||
}
|
||||
|
||||
await twonlyDB.contactsDao.updateContact(
|
||||
fromUserId,
|
||||
const ContactsCompanion(
|
||||
|
|
@ -67,32 +77,28 @@ Future<void> handleContactAccept(int fromUserId) async {
|
|||
deletedByUser: Value(false),
|
||||
),
|
||||
);
|
||||
final contact = await twonlyDB.contactsDao
|
||||
.getContactByUserId(fromUserId)
|
||||
.getSingleOrNull();
|
||||
if (contact != null) {
|
||||
await twonlyDB.groupsDao.createNewDirectChat(
|
||||
fromUserId,
|
||||
GroupsCompanion(
|
||||
groupName: Value(getContactDisplayName(contact)),
|
||||
),
|
||||
);
|
||||
}
|
||||
await twonlyDB.groupsDao.createNewDirectChat(
|
||||
fromUserId,
|
||||
GroupsCompanion(
|
||||
groupName: Value(getContactDisplayName(contact)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<bool> handleContactRequest(
|
||||
int fromUserId,
|
||||
EncryptedContent_ContactRequest contactRequest,
|
||||
String receiptId,
|
||||
) async {
|
||||
switch (contactRequest.type) {
|
||||
case EncryptedContent_ContactRequest_Type.REQUEST:
|
||||
Log.info('Got a contact request from $fromUserId');
|
||||
Log.info('[$receiptId] Got a contact request from $fromUserId');
|
||||
return handleNewContactRequest(fromUserId);
|
||||
case EncryptedContent_ContactRequest_Type.ACCEPT:
|
||||
Log.info('Got a contact accept from $fromUserId');
|
||||
Log.info('[$receiptId] Got a contact accept from $fromUserId');
|
||||
await handleContactAccept(fromUserId);
|
||||
case EncryptedContent_ContactRequest_Type.REJECT:
|
||||
Log.info('Got a contact reject from $fromUserId');
|
||||
Log.info('[$receiptId] Got a contact reject from $fromUserId');
|
||||
await twonlyDB.contactsDao.updateContact(
|
||||
fromUserId,
|
||||
const ContactsCompanion(
|
||||
|
|
@ -109,14 +115,15 @@ Future<void> handleContactUpdate(
|
|||
int fromUserId,
|
||||
EncryptedContent_ContactUpdate contactUpdate,
|
||||
int? senderProfileCounter,
|
||||
String receiptId,
|
||||
) async {
|
||||
switch (contactUpdate.type) {
|
||||
case EncryptedContent_ContactUpdate_Type.REQUEST:
|
||||
Log.info('Got a contact update request from $fromUserId');
|
||||
Log.info('[$receiptId] Got a contact update request from $fromUserId');
|
||||
await sendContactMyProfileData(fromUserId);
|
||||
|
||||
case EncryptedContent_ContactUpdate_Type.UPDATE:
|
||||
Log.info('Got a contact update $fromUserId');
|
||||
Log.info('[$receiptId] Got a contact update $fromUserId');
|
||||
Uint8List? avatarSvgCompressed;
|
||||
if (contactUpdate.hasAvatarSvgCompressed()) {
|
||||
avatarSvgCompressed = Uint8List.fromList(
|
||||
|
|
@ -126,6 +133,44 @@ Future<void> handleContactUpdate(
|
|||
if (contactUpdate.hasDisplayName() &&
|
||||
contactUpdate.hasUsername() &&
|
||||
senderProfileCounter != null) {
|
||||
final contact = await twonlyDB.contactsDao
|
||||
.getContactByUserId(fromUserId)
|
||||
.getSingleOrNull();
|
||||
|
||||
if (contact != null) {
|
||||
final sharedGroups = await twonlyDB.groupsDao.getGroupsForMember(
|
||||
fromUserId,
|
||||
);
|
||||
|
||||
if (contact.username != contactUpdate.username) {
|
||||
for (final group in sharedGroups) {
|
||||
await twonlyDB.groupsDao.insertGroupAction(
|
||||
GroupHistoriesCompanion(
|
||||
groupId: Value(group.groupId),
|
||||
type: const Value(GroupActionType.updatedContactUsername),
|
||||
contactId: Value(fromUserId),
|
||||
oldGroupName: Value(contact.username),
|
||||
newGroupName: Value(contactUpdate.username),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (contact.displayName != contactUpdate.displayName) {
|
||||
for (final group in sharedGroups) {
|
||||
await twonlyDB.groupsDao.insertGroupAction(
|
||||
GroupHistoriesCompanion(
|
||||
groupId: Value(group.groupId),
|
||||
type: const Value(GroupActionType.updatedContactDisplayName),
|
||||
contactId: Value(fromUserId),
|
||||
oldGroupName: Value(contact.displayName ?? contact.username),
|
||||
newGroupName: Value(contactUpdate.displayName),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await twonlyDB.contactsDao.updateContact(
|
||||
fromUserId,
|
||||
ContactsCompanion(
|
||||
|
|
@ -145,8 +190,9 @@ Future<void> handleContactUpdate(
|
|||
Future<void> handleFlameSync(
|
||||
String groupId,
|
||||
EncryptedContent_FlameSync flameSync,
|
||||
String receiptId,
|
||||
) async {
|
||||
Log.info('Got a flameSync for group $groupId');
|
||||
Log.info('[$receiptId] Got a flameSync for group $groupId');
|
||||
|
||||
final group = await twonlyDB.groupsDao.getGroup(groupId);
|
||||
if (group == null || group.lastFlameCounterChange == null) return;
|
||||
|
|
@ -192,6 +238,7 @@ Future<int?> checkForProfileUpdate(
|
|||
type: EncryptedContent_ContactUpdate_Type.REQUEST,
|
||||
),
|
||||
),
|
||||
blocking: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,9 @@ import 'package:twonly/src/utils/log.dart';
|
|||
Future<void> handleErrorMessage(
|
||||
int fromUserId,
|
||||
EncryptedContent_ErrorMessages error,
|
||||
String receiptId,
|
||||
) async {
|
||||
Log.error('Got error from $fromUserId: $error');
|
||||
Log.error('[$receiptId] Got error from $fromUserId: $error');
|
||||
|
||||
switch (error.type) {
|
||||
case EncryptedContent_ErrorMessages_Type
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue