mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-09-01 09:24:08 +00:00
Compare commits
No commits in common. "main" and "v0.4.1" have entirely different histories.
240 changed files with 5125 additions and 65646 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -56,4 +56,3 @@ devtools_options.yaml
|
|||
rust/target
|
||||
rust_dependencies/target
|
||||
fastlane/repo/status/running.json
|
||||
.cache/
|
||||
267
CHANGELOG.md
267
CHANGELOG.md
|
|
@ -1,31 +1,5 @@
|
|||
# Changelog
|
||||
|
||||
## 0.5.2
|
||||
|
||||
- Hotfix: Fixed an infinite loop involving delivery receipts
|
||||
|
||||
## 0.5.1
|
||||
|
||||
- Improve: Show delivery and read receipt indicators for text messages
|
||||
- Improve: Backup screen clearer and easier to understand
|
||||
- Improve: Show username above messages in group chats
|
||||
- Fix: Background audio correctly pauses and resumes when viewing videos
|
||||
- Fix: Sometimes old messages were being received as duplicates
|
||||
- Fix: Multiple smaller bug fixes
|
||||
|
||||
## 0.5.0
|
||||
|
||||
- New: Update to Signal's new PQC-ready key agreement PQXDH
|
||||
- New: Contact labels
|
||||
- Fix: Multiple bug fixes
|
||||
- Fix: Multiple black screens
|
||||
|
||||
## 0.4.0
|
||||
|
||||
- New: Encrypted Cloud Backup of Memories
|
||||
- New: Passwordless Backup
|
||||
- Fix: Performance issues
|
||||
|
||||
## 0.3.7
|
||||
|
||||
- Fix: Multiple UI issues
|
||||
|
|
@ -62,8 +36,8 @@
|
|||
|
||||
## 0.3.0
|
||||
|
||||
- Improve: Design of some UI components
|
||||
- Improve: Memories viewer shows state for batch operations and has improved performance
|
||||
- Improved: Design of some UI components
|
||||
- Improved: Memories viewer shows state for batch operations and has improved performance
|
||||
- Fix: Issue with background notifications on Android
|
||||
- Fix: Changed minimum threshold for the user discovery to 3
|
||||
- Fix: Multiple UI issues
|
||||
|
|
@ -72,24 +46,24 @@
|
|||
## 0.2.26
|
||||
|
||||
- New: Import images from the gallery
|
||||
- Improve: Media files are now stored in the dedicated "twonly" album
|
||||
- Improve: UI components adapt to native styling (iOS/Android)
|
||||
- Fix: Migration issue that resulted in a corrupted backup mechanism
|
||||
- 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
|
||||
|
||||
- Improve: Smaller UI changes
|
||||
- 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.
|
||||
- Improve: Onboarding flow for new users.
|
||||
- Improve: Flame restore experience.
|
||||
- Improve: The blue verification checkmark now displays the total number of verifications.
|
||||
- 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
|
||||
|
|
@ -106,36 +80,36 @@
|
|||
|
||||
## 0.2.13
|
||||
|
||||
- New: Tutorial on how to use zoom.
|
||||
- New: Tutorial on how to use zoom.
|
||||
- New: Manage storage view.
|
||||
- Improve: Media thumbnails for faster loading.
|
||||
- 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).
|
||||
- Improve: Memories viewer redesigned with smoother animations and new quick-action controls.
|
||||
- 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
|
||||
- Improve: Redesigned snackbar notifications
|
||||
- Improve: New backup mechanism to allow larger backup files
|
||||
- Improve: Move keys into a centralized Rust-owned structure stored in secure storage
|
||||
- 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
|
||||
- Fix: Issue with push notifications on Android
|
||||
|
||||
## 0.2.9
|
||||
|
||||
- Improve: Make contact avatars clickable
|
||||
- Improved: Make contact avatars clickable
|
||||
- Fix: Messages occasionally not received until app restart
|
||||
- Fix: Complete setup would sometimes get stuck
|
||||
- Fix: Complete setup would sometimes get stuck
|
||||
|
||||
## 0.2.8
|
||||
|
||||
|
|
@ -146,29 +120,29 @@
|
|||
- New: Feature to find friends without a phone number
|
||||
- New: The verification state is now transferred to the scanned user
|
||||
- New: Registration setup to configure the most important configurations
|
||||
- Improve: Show ⌛ instead of the flame icon when it is about to expire
|
||||
- Improve: FAQ is now in the app rather than opening in the browser
|
||||
- Improve: Videos can now be paused
|
||||
- Improve: Lock to record hands-free
|
||||
- Improved: Show ⌛ instead of the flame icon when it is about to expire
|
||||
- Improved: FAQ is now in the app rather than opening in the browser
|
||||
- Improved: Videos can now be paused
|
||||
- Improved: Lock to record hands-free
|
||||
- Fix: Many smaller issues
|
||||
|
||||
## 0.1.8
|
||||
|
||||
- Improve: Typos and grammar issues thanks to @AlbertUnruh
|
||||
- Improved: Typos and grammar issues thanks to @AlbertUnruh
|
||||
- Fix: App becomes unresponsive when clicking notifications.
|
||||
|
||||
## 0.1.7
|
||||
|
||||
- Improve: Show input indicator in the chat overview as well
|
||||
- Improve: Username change error handling
|
||||
- Fix: Phantom push notification
|
||||
- Improved: Show input indicator in the chat overview as well
|
||||
- Improved: Username change error handling
|
||||
- Fix: Phantom push notification
|
||||
- Fix: Start in chat, if configured
|
||||
- Fix: Smaller UI fixes
|
||||
|
||||
## 0.1.5
|
||||
|
||||
- Fix: Reupload of media files was not working properly
|
||||
- Fix: Chats were sometimes ordered wrongly
|
||||
- Fix: Chats were sometimes ordered wrongly
|
||||
- Fix: Typing indicator was not always shown
|
||||
- Fix: Multiple smaller issues
|
||||
|
||||
|
|
@ -176,7 +150,7 @@
|
|||
|
||||
- New: Typing and chat open indicator
|
||||
- New: Screen lock for twonly (Can be enabled in the settings.)
|
||||
- Improve: Visual indication when connected to the server
|
||||
- Improve: Visual indication when connected to the server
|
||||
- Improve: Several minor issues with the user interface
|
||||
- Fix: Poor audio quality and edge distortions in videos sent from Android
|
||||
|
||||
|
|
@ -186,23 +160,23 @@
|
|||
- New: Crop or rotate images before sharing them.
|
||||
- New: Clicking on “Text Notifications” will now open the chat directly (Android only)
|
||||
- New: Developer settings to reduce flames
|
||||
- Improve: Improved troubleshooting for issues with push notifications
|
||||
- Improve: Improved troubleshooting for issues with push notifications
|
||||
- Improve: A message appears if someone has deleted their account.
|
||||
- Improve: Make the verification badge more visible.
|
||||
- Fix: Flash not activated when starting a video recording
|
||||
- Fix: Problem sending media when a recipient has deleted their account.
|
||||
- Fix: Receive push notifications without receiving an in-app message (Android)
|
||||
- Fix: Issue with sending GIFs from Memories
|
||||
- Fix: Incorrect processing of messages that have already been fetched from the server causes the UI to freeze
|
||||
- Fix: Incorrect processing of messages that have already been fetched from the server causes the UI to freeze
|
||||
|
||||
## 0.1.1
|
||||
|
||||
- New: Groups can now collect flames as well
|
||||
- New: Background execution to pre-load messages
|
||||
- New: Background execution to pre-load messages
|
||||
- New: Adds a link if the image contains a QR code
|
||||
- Improve: Video compression with progress updates
|
||||
- Improve: Show message "Flames restored"
|
||||
- Improve: Show toast message if user was added via QR
|
||||
- Improve: Show toast message if user was added via QR
|
||||
- Fix: Media file appears as a white square and is not listed.
|
||||
- Fix: Issue with media files required to be reuploaded
|
||||
- Fix: Problem during contact requests
|
||||
|
|
@ -212,17 +186,17 @@
|
|||
|
||||
## 0.0.96
|
||||
|
||||
- New: Show link in chat if the saved media file contains one
|
||||
- Improve: Verification badge for groups
|
||||
- Feature: Show link in chat if the saved media file contains one
|
||||
- Improve: Verification badge for groups
|
||||
- Improve: Huge reduction in app size
|
||||
- Fix: Crash on older devices when compressing a video
|
||||
- Fix: Problem with decrypting messages fixed
|
||||
|
||||
## 0.0.93
|
||||
|
||||
- New: Verification checkmark for friends
|
||||
- Feature: Verification checkmark for friends
|
||||
- Fix: Added contacts in contact sharing that were not clickable.
|
||||
- Fix: Open chat after the image expires in case a draft message exists
|
||||
- Fix: Open chat after the image expires in case a draft message exists
|
||||
- Fix: Restore flames as a plus user
|
||||
- Fix: Route not found when sharing image
|
||||
- Fix: Increase recent limit in emoji keyboard
|
||||
|
|
@ -233,138 +207,139 @@
|
|||
|
||||
## 0.0.92
|
||||
|
||||
- New: The option to share contacts
|
||||
- New: Option to zoom in received images / videos
|
||||
- Fix: Issue with "reuploaded requested" not working
|
||||
- Fix: Race condition while writing to the log file
|
||||
- Adds the option to share contacts
|
||||
- Adds option to zoom in received images / videos
|
||||
- Fixes issue with "reuploaded requested" not working
|
||||
- Fixes race condition while writing to the log file
|
||||
|
||||
## 0.0.91
|
||||
|
||||
- Fix: Link preview on iOS
|
||||
- Fix: Sharing images from other apps on iOS
|
||||
- Fixes link preview on iOS
|
||||
- Fixes sharing images from other apps on iOS
|
||||
|
||||
## 0.0.90
|
||||
|
||||
- Fix: Issue that media files were not reuploaded
|
||||
- Fix: iOS zooming issue when switching between .5 and x1
|
||||
- Fix: Biometric auth bypass when opening a twonly/reopen send image
|
||||
- Fix: That media files could not be downloaded in case the contact deleted his account
|
||||
- Fix: Database issue in case twonly is opened multiple times
|
||||
- Fix: Typos in translation
|
||||
- Fixes issue that media files where not reuploaded
|
||||
- Fixes iOS zooming issue when switching between .5 and x1
|
||||
- Fixes biometric auth bypass when opening a twonly/reopen send image
|
||||
- Fixes that media files could not be downloaded in case the contact deleted his account
|
||||
- Fixes database issue in case twonly is opened multiple times
|
||||
- Fixes typos in translation
|
||||
|
||||
## 0.0.87
|
||||
|
||||
- New: Link preview to shared links
|
||||
- New: Option to manual focus in the camera
|
||||
- New: Support to switch between front and back cameras during video recording
|
||||
- New: Basic face filters
|
||||
- Improve: Image editor, like emojis or text under a drawing can be moved
|
||||
- Improve: Speed after taking a picture
|
||||
- Fix: Issue with emojis disappearing in the image editor
|
||||
- Adds link preview to shared links
|
||||
- Adds option to manual focus in the camera
|
||||
- Adds support to switch between front and back cameras during video recording
|
||||
- Adds basic face filters
|
||||
- Improves image editor, like emojis or text under a drawing can be moved
|
||||
- Improves speed after taking a picture
|
||||
- Fixes issue with emojis disappearing in the image editor
|
||||
|
||||
## 0.0.86
|
||||
|
||||
- New: Allows to reopen send images (if send without time limit or enabled auth)
|
||||
- New: Support for front camera zoom
|
||||
- Fix: Several bug fixes
|
||||
- Allows to reopen send images (if send without time limit or enabled auth)
|
||||
- Added support for front camera zoom
|
||||
- Several bug fixes
|
||||
|
||||
## 0.0.83
|
||||
|
||||
- Improve: View of the diagnostic log
|
||||
- Fix: Several bug fixes
|
||||
- Improved view of the diagnostic log
|
||||
- Several bug fixes
|
||||
|
||||
## 0.0.82
|
||||
|
||||
- New: An option in the settings to automatically save all sent images
|
||||
- New: Hides duplicate images in the memory
|
||||
- Improve: Several other minor improvements
|
||||
- Fix: A bug where messages were not being received
|
||||
- Added an option in the settings to automatically save all sent images
|
||||
- Hides duplicate images in the memory
|
||||
- Fixes a bug where messages were not being received
|
||||
- Several other minor improvements
|
||||
|
||||
## 0.0.81
|
||||
|
||||
- Fix: The issue where black/blank images were sometimes received
|
||||
- Fix: An issue in the image editor
|
||||
- Fixes the issue where black/blank images were sometimes received
|
||||
- Fixes an issue in the image editor
|
||||
|
||||
## 0.0.80
|
||||
|
||||
- New: Share images/videos directly from other applications
|
||||
- New: More customization options in the appearance settings
|
||||
- Improve: UI for changing the display time of images
|
||||
- Improve: Several minor UI improvements
|
||||
- Fix: Several bug fixes
|
||||
- Share images/videos directly from other applications
|
||||
- More customization options in the appearance settings
|
||||
- Improved UI for changing the display time of images
|
||||
- Several minor UI improvements
|
||||
- Several bug fixes
|
||||
|
||||
## 0.0.74
|
||||
|
||||
- Improve: Uploading speed
|
||||
- Fix: Issue with ffmpeg for android
|
||||
- Improving uploading speed
|
||||
- Fixing issue with ffmpeg for android
|
||||
|
||||
## 0.0.73
|
||||
|
||||
- New: Integrated QR code scanner in the main camera
|
||||
- New: Profile share page
|
||||
- New: Workflow for checking the security number
|
||||
- Improve: User interface for creating voice messages
|
||||
- Integrated QR code scanner in the main camera
|
||||
- New profile share page
|
||||
- New workflow for checking the security number
|
||||
- Improved user interface for creating voice messages
|
||||
|
||||
## 0.0.69
|
||||
|
||||
- New: Option to export and import memories
|
||||
- New: iOS support for ultra-wide-angle camera
|
||||
- New: Support Android Monochrome Icon
|
||||
- Fix: Multiple layout issues fixed
|
||||
- Fix: Multiple bug fixes
|
||||
- Option to export and import memories
|
||||
- iOS support for ultra-wide-angle camera
|
||||
- Support Android Monochrome Icon
|
||||
- Multiple layout issues fixed
|
||||
- Multiple bug fixes
|
||||
|
||||
## 0.0.67
|
||||
|
||||
- New: Crash reports (optional). Please consider enabling this under Settings > Help > “Share errors and crashes with us.”
|
||||
- Fix: Bug when saving images to the gallery
|
||||
- Fix: Multiple layout issues fixed
|
||||
- Fix: Multiple bug fixes
|
||||
- Adds crash reports (optional). Please consider enabling this under Settings > Help > “Share errors and crashes with us.”
|
||||
- Fixes bug when saving images to the gallery
|
||||
- Multiple layout issues fixed
|
||||
- Multiple bug fixes
|
||||
|
||||
## 0.0.62
|
||||
|
||||
- New: Support for groups with multiple administrators
|
||||
- New: Edit and delete messages
|
||||
- New: Create images using volume buttons
|
||||
- New: Removing audio after recording is possible
|
||||
- New: Edited image is now embedded into the video
|
||||
- New: Video max length increased to 60 seconds
|
||||
- New: Context menu and other UI enhancements
|
||||
- New: Client-to-client protocol migrated to Protocol Buffers (Protobuf)
|
||||
- New: Database identifiers converted to UUIDs and the database schema completely redesigned
|
||||
- Improve: Emoji picker
|
||||
- Improve: Switched to FFmpeg for improved video compression
|
||||
- Improve: Reliability of client-to-client messaging
|
||||
- Fix: Multiple bug fixes
|
||||
- Support for groups with multiple administrators
|
||||
- Edit and delete messages
|
||||
- Create images using volume buttons
|
||||
- New and improved emoji picker
|
||||
- Removing audio after recording is possible
|
||||
- Edited image is now embedded into the video
|
||||
- Video max length increased to 60 seconds
|
||||
- Switched to FFmpeg for improved video compression
|
||||
- New context menu and other UI enhancements
|
||||
- Client-to-client protocol migrated to Protocol Buffers (Protobuf)
|
||||
- Database identifiers converted to UUIDs and the database schema completely redesigned
|
||||
- Improved reliability of client-to-client messaging
|
||||
- Multiple bug fixes
|
||||
|
||||
## 0.0.61
|
||||
|
||||
- New: Image editor when changing colors
|
||||
- New: Dependency and Flutter upgrade
|
||||
- Fix: Message decryption error
|
||||
- Fix: Issue with user deletion
|
||||
- Fix: Issue with flame counter sync
|
||||
- Improving image editor when changing colors
|
||||
- Fixing message decryption error
|
||||
- Fixing issue with user deletion
|
||||
- Fixing issue with flame counter sync
|
||||
- Dependency and Flutter upgrade
|
||||
|
||||
## 0.0.60
|
||||
|
||||
- New: Display your own avatar in the title bar of the chat list.
|
||||
- New: Created a default avatar image in case none was set.
|
||||
- New: Flutter SDK and dependencies upgraded.
|
||||
- Improve: Logging to debug the 'Tap to load' issue.
|
||||
- Improve: UI handling when requesting microphone access for the first time.
|
||||
- Fix: Multiple bug fixes.
|
||||
- Improved logging to debug the 'Tap to load' issue.
|
||||
- Display your own avatar in the title bar of the chat list.
|
||||
- Created a default avatar image in case none was set.
|
||||
- Improved UI handling when requesting microphone access for the first time.
|
||||
- Flutter SDK and dependencies upgraded.
|
||||
- Multiple bug fixes.
|
||||
|
||||
## 0.0.59
|
||||
|
||||
- New: Location Filter are now stored as WebP instead of PNG
|
||||
- Fix: Media download error
|
||||
- Fix: Issue with video recording
|
||||
- Fixing media download error
|
||||
- Fixing issue with video recording
|
||||
- Location Filter are now stored as WebP instead of PNG
|
||||
|
||||
## 0.0.58
|
||||
|
||||
- New: iOS gestures to close images
|
||||
- New: Onboarding screens updated and registration view simplified
|
||||
- New: The sender is displayed in the top right corner when a media file is opened
|
||||
- New: Images are now stored as WebP to save storage
|
||||
- New: Button to report users
|
||||
- Improve: Chat messages view, including better citation view and display times
|
||||
- Fix: Multiple bug fixes
|
||||
- twonly now has a free plan and is now financed by donations and an optional subscription with more features (coming soon)
|
||||
- iOS gestures to close images
|
||||
- Improved chat messages view, including better citation view and display times
|
||||
- Onboarding screens updated and registration view simplified
|
||||
- The sender is displayed in the top right corner when a media file is opened
|
||||
- Images are now stored as WebP to save storage
|
||||
- Button to report users
|
||||
- Multiple bug fixes
|
||||
21
README.md
21
README.md
|
|
@ -32,20 +32,27 @@ If you decide to give twonly a try, please keep in mind that it is still in its
|
|||
## Features
|
||||
|
||||
- Offer a Snapchat™ like experience
|
||||
- End-to-End encryption using the [Signal Protocol](https://signal.org/docs/specifications/pqxdh/)
|
||||
- End-to-End encryption using the [Signal Protocol](https://de.wikipedia.org/wiki/Signal-Protokoll)
|
||||
- Open Source and can be downloaded directly from GitHub
|
||||
- No email or phone number required to register
|
||||
- The backend is hosted exclusively in Europe
|
||||
- [Passwordless Backup](https://twonly.eu/en/blog/2026-passwordless-backup.html) without a phone number
|
||||
- [User Discovery](https://twonly.eu/en/blog/2026-mutual-friends.html) without a phone number
|
||||
- E2EE cloud backup of memories
|
||||
|
||||
## Next on the bucket list
|
||||
## Roadmap
|
||||
|
||||
### Currently
|
||||
|
||||
- Focus on user-friendliness so that people enjoy using the app
|
||||
- Passwordless recovery without a phone number
|
||||
- Implementation of features so that Snapchat can actually be replaced
|
||||
- E2EE cloud backup of memories
|
||||
- Importing memories from Snapchat
|
||||
|
||||
### Next on the bucket list
|
||||
|
||||
- Importing memories from Snapchat
|
||||
- For Android: Support for [UnifiedPush] (https://unifiedpush.org/)
|
||||
- For Android: Reproducible builds
|
||||
- Implementation of [Sealed Sender](https://signal.org/blog/sealed-sender/) (or a similar protocol) to minimize metadata
|
||||
- Switch from the Signal protocol to [MLS](https://github.com/openmls/openmls) for post-quantum crypto support
|
||||
- Decentralize the server so that anyone can run their own server
|
||||
|
||||
## Security Issues
|
||||
|
|
@ -56,7 +63,7 @@ guarantee a bounty currently :/
|
|||
|
||||
## Contribution
|
||||
|
||||
Currently there are still some core features and rewrites open I want to do involving a huge rewrite in Rust. Because of this, contributions are currently not wanted. You can still view the code (if you find any security issues please contact me!). Also, issues are currently closed. If you find a bug, please use the in-app option as there you can upload your debug log which helps a lot.
|
||||
Currently there are still some core features and rewrites open I want to do like the switch to the MLS protocol involving a huge rewrite in Rust. Because of this, contributions are currently not wanted. You can still view the code (if you find any security issues please contact me!). Also, issues are currently closed. If you find a bug, please use the in-app option as there you can upload your debug log which helps a lot.
|
||||
|
||||
## Signing Keys
|
||||
|
||||
|
|
|
|||
|
|
@ -26,13 +26,6 @@ analyzer:
|
|||
- "**.arb"
|
||||
- "test/drift/**"
|
||||
- "**.g.dart"
|
||||
- build/**
|
||||
- android/**
|
||||
- ios/**
|
||||
- web/**
|
||||
- windows/**
|
||||
- macos/**
|
||||
- linux/**
|
||||
|
||||
linter:
|
||||
rules:
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@ android {
|
|||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = flutter.versionCode
|
||||
versionName = flutter.versionName
|
||||
manifestPlaceholders = [appName: "twonly"]
|
||||
}
|
||||
signingConfigs {
|
||||
release {
|
||||
|
|
@ -54,7 +53,6 @@ android {
|
|||
buildTypes {
|
||||
debug {
|
||||
applicationIdSuffix ".testing"
|
||||
manifestPlaceholders = [appName: "twonly [dev]"]
|
||||
}
|
||||
// profile {
|
||||
// applicationIdSuffix ".STOP"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
|
||||
<application
|
||||
android:label="${appName}"
|
||||
android:label="twonly"
|
||||
android:name=".MyApplication"
|
||||
android:allowBackup="false"
|
||||
android:fullBackupContent="false"
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit b874b9cc309822d90b940fdfbc8fd1ad3afc2450
|
||||
Subproject commit 72d9bd6320bca1f1d29c6e61c3821fed326c0abe
|
||||
204
dependencies.py
204
dependencies.py
|
|
@ -1,204 +0,0 @@
|
|||
import yaml
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
def print_blue(text):
|
||||
BLUE = '\x1b[34m'
|
||||
RESET = '\x1b[0m'
|
||||
print(f"{BLUE}{text}{RESET}")
|
||||
|
||||
def print_yellow(text):
|
||||
YELLOW = '\x1b[33m'
|
||||
RESET = '\x1b[0m'
|
||||
print(f"{YELLOW}{text}{RESET}")
|
||||
|
||||
def get_git_head(repo_path='.'):
|
||||
result = subprocess.run(
|
||||
['git', 'rev-parse', 'HEAD'],
|
||||
cwd=repo_path,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
check=False
|
||||
)
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError(f"git error: {result.stderr.strip()}")
|
||||
return result.stdout.strip()
|
||||
|
||||
def get_default_branch(repo_path='.'):
|
||||
result = subprocess.run(
|
||||
['git', 'symbolic-ref', 'refs/remotes/origin/HEAD'],
|
||||
cwd=repo_path,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
check=False
|
||||
)
|
||||
if result.returncode == 0:
|
||||
return result.stdout.strip().split('/')[-1]
|
||||
return 'main'
|
||||
|
||||
def integrate_package(folder_name, data, cache_dir, out_dir):
|
||||
repo_url = data['git']
|
||||
keep_list = ["lib", "LICENSE", "pubspec.yaml", "android", "ios", "darwin"]
|
||||
if "keep" in data:
|
||||
keep_list += [item.rstrip('/') for item in data['keep']]
|
||||
|
||||
print(f"Processing {folder_name}...")
|
||||
|
||||
cache_path = os.path.join(cache_dir, folder_name)
|
||||
if not os.path.exists(cache_path):
|
||||
subprocess.run(["git", "clone", repo_url, cache_path], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
else:
|
||||
result = subprocess.run(["git", "fetch", "--all"], check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, cwd=cache_path)
|
||||
if result.returncode != 0:
|
||||
print_yellow(f"Warning: Could not fetch updates for {folder_name}. You might be offline.")
|
||||
|
||||
if "commit" in data:
|
||||
commit_hash = data["commit"]
|
||||
subprocess.run(["git", "checkout", commit_hash], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, cwd=cache_path)
|
||||
elif "tag" in data:
|
||||
tag_name = data["tag"]
|
||||
subprocess.run(["git", "checkout", tag_name], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, cwd=cache_path)
|
||||
else:
|
||||
print_yellow(f"Warning: No commit or tag specified for {folder_name}. Using default branch.")
|
||||
default_branch = get_default_branch(cache_path)
|
||||
subprocess.run(["git", "checkout", default_branch], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, cwd=cache_path)
|
||||
subprocess.run(["git", "pull"], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, cwd=cache_path)
|
||||
last_commit_hash = get_git_head(cache_path)
|
||||
data["commit"] = last_commit_hash
|
||||
print_blue(f"Recorded commit {last_commit_hash} for {folder_name}")
|
||||
|
||||
results = [] # List of (pkg_name, version)
|
||||
|
||||
if "subpackages" in data:
|
||||
packages_to_extract = data["subpackages"]
|
||||
else:
|
||||
packages_to_extract = [{"name": folder_name, "path": data.get("path", "")}]
|
||||
|
||||
for pkg in packages_to_extract:
|
||||
pkg_name = pkg["name"]
|
||||
subpath = pkg.get("path", "")
|
||||
|
||||
out_path = os.path.join(out_dir, pkg_name)
|
||||
if os.path.exists(out_path):
|
||||
shutil.rmtree(out_path)
|
||||
os.makedirs(out_path)
|
||||
|
||||
package_src_path = os.path.join(cache_path, subpath) if subpath else cache_path
|
||||
|
||||
for item in keep_list:
|
||||
src_item = os.path.join(package_src_path, item)
|
||||
dst_item = os.path.join(out_path, item)
|
||||
|
||||
if os.path.exists(src_item):
|
||||
os.makedirs(os.path.dirname(dst_item), exist_ok=True)
|
||||
if os.path.isdir(src_item):
|
||||
shutil.copytree(src_item, dst_item, dirs_exist_ok=True)
|
||||
else:
|
||||
shutil.copy2(src_item, dst_item)
|
||||
|
||||
version = "any"
|
||||
try:
|
||||
pubspec_path = os.path.join(package_src_path, "pubspec.yaml")
|
||||
if os.path.exists(pubspec_path):
|
||||
with open(pubspec_path, "r") as f:
|
||||
ps = yaml.safe_load(f)
|
||||
if ps and isinstance(ps, dict):
|
||||
version = ps.get("version", "any")
|
||||
except Exception as e:
|
||||
print_yellow(f"Warning: Could not read version from {pkg_name}/pubspec.yaml")
|
||||
|
||||
results.append((pkg_name, version))
|
||||
|
||||
return results
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Update specific or all repositories.")
|
||||
parser.add_argument('repo_name', nargs='?', default=None, help="Name of the repository to update (optional)")
|
||||
args = parser.parse_args()
|
||||
|
||||
with open("dependencies.yaml", "r") as f:
|
||||
config = yaml.safe_load(f)
|
||||
|
||||
cache_dir = config.get('cache', './.cache')
|
||||
out_dir = config.get('outdir', './dependencies')
|
||||
deps = config.get('dependencies', {})
|
||||
|
||||
if not os.path.exists(cache_dir):
|
||||
os.makedirs(cache_dir)
|
||||
if not os.path.exists(out_dir):
|
||||
os.makedirs(out_dir)
|
||||
|
||||
repos_to_update = [args.repo_name] if args.repo_name else list(deps.keys())
|
||||
|
||||
pubspec_overrides = []
|
||||
pubspec_deps = []
|
||||
|
||||
def process_deps_recursive(deps_dict, to_update=None):
|
||||
for name, data in deps_dict.items():
|
||||
if to_update is None or name in to_update:
|
||||
extracted_packages = integrate_package(name, data, cache_dir, out_dir)
|
||||
for pkg_name, version in extracted_packages:
|
||||
pubspec_overrides.append(f" {pkg_name}:\n path: {out_dir}/{pkg_name}\n")
|
||||
if version and version != "any":
|
||||
pubspec_deps.append(f" {pkg_name}: ^{version}\n")
|
||||
else:
|
||||
pubspec_deps.append(f" {pkg_name}: any\n")
|
||||
if "dependencies" in data:
|
||||
# If we updated the parent, we should update children? Or if no args provided, update all.
|
||||
# Actually, the original logic updated children automatically if parent is updated.
|
||||
process_deps_recursive(data["dependencies"], None if (to_update is None or name in to_update) else [])
|
||||
|
||||
process_deps_recursive(deps, repos_to_update if args.repo_name else None)
|
||||
|
||||
def sort_dependencies(d):
|
||||
sorted_d = {k: d[k] for k in sorted(d.keys())}
|
||||
for k, v in sorted_d.items():
|
||||
if "dependencies" in v and isinstance(v["dependencies"], dict):
|
||||
v["dependencies"] = sort_dependencies(v["dependencies"])
|
||||
return sorted_d
|
||||
|
||||
if "dependencies" in config:
|
||||
config["dependencies"] = sort_dependencies(config["dependencies"])
|
||||
|
||||
with open("dependencies.yaml", "w") as f:
|
||||
yaml.safe_dump(config, f, sort_keys=False)
|
||||
|
||||
# Update pubspec.yaml
|
||||
if not args.repo_name:
|
||||
with open("pubspec.yaml", "r") as f:
|
||||
pubspec_lines = f.readlines()
|
||||
|
||||
start_marker_overrides = "## --- Start Managed Dependency Overrides ---"
|
||||
end_marker_overrides = "## --- End Managed Dependency Overrides ---"
|
||||
|
||||
start_marker_deps = "## --- Start Managed Dependencies ---"
|
||||
end_marker_deps = "## --- End Managed Dependencies ---"
|
||||
|
||||
try:
|
||||
start_idx_overrides = next(i for i, line in enumerate(pubspec_lines) if line.strip() == start_marker_overrides)
|
||||
end_idx_overrides = next(i for i, line in enumerate(pubspec_lines) if line.strip() == end_marker_overrides)
|
||||
|
||||
# Update overrides section
|
||||
new_lines = pubspec_lines[:start_idx_overrides + 1] + pubspec_overrides + pubspec_lines[end_idx_overrides:]
|
||||
|
||||
# Now find the deps markers in the updated lines
|
||||
start_idx_deps = next(i for i, line in enumerate(new_lines) if line.strip() == start_marker_deps)
|
||||
end_idx_deps = next(i for i, line in enumerate(new_lines) if line.strip() == end_marker_deps)
|
||||
|
||||
# Update dependencies section
|
||||
final_lines = new_lines[:start_idx_deps + 1] + pubspec_deps + new_lines[end_idx_deps:]
|
||||
|
||||
with open("pubspec.yaml", "w") as f:
|
||||
f.writelines(final_lines)
|
||||
print_blue("Updated pubspec.yaml successfully.")
|
||||
except ValueError as e:
|
||||
print_yellow("Error: Could not find professional markers in pubspec.yaml.")
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -1,109 +0,0 @@
|
|||
cache: ./.cache
|
||||
outdir: ./dependencies
|
||||
dependencies:
|
||||
audio_waveforms:
|
||||
git: https://github.com/SimformSolutionsPvtLtd/audio_waveforms.git
|
||||
tag: 2.0.2
|
||||
avatar_maker:
|
||||
git: https://github.com/RoadTripMoustache/avatar_maker.git
|
||||
tag: 1.5.0
|
||||
keep:
|
||||
- assets/icons
|
||||
blurhash_dart:
|
||||
git: https://github.com/justacid/blurhash-dart.git
|
||||
tag: v1.2.1
|
||||
exif:
|
||||
git: https://github.com/bigflood/dartexif.git
|
||||
dependencies:
|
||||
sprintf:
|
||||
git: https://github.com/Naddiseo/dart-sprintf.git
|
||||
commit: f1e74f2f4c339d983f9d011b4ba1df4ec8b8857c
|
||||
commit: bf170d5639f0b6fcb0947060cf8bd7b623df9069
|
||||
flutter_blurhash:
|
||||
git: https://github.com/fluttercommunity/flutter_blurhash.git
|
||||
commit: c5cdec4986432e835bb91f5ce00564534450cdc7
|
||||
flutter_markdown_plus:
|
||||
git: https://github.com/foresightmobile/flutter_markdown_plus.git
|
||||
commit: dc1185c933fbf9dba559ef6c91586ff1503be3ee
|
||||
flutter_packages:
|
||||
git: https://github.com/otsmr/flutter-packages.git
|
||||
subpackages:
|
||||
- name: video_player
|
||||
path: packages/video_player/video_player
|
||||
- name: video_player_android
|
||||
path: packages/video_player/video_player_android
|
||||
- name: video_player_avfoundation
|
||||
path: packages/video_player/video_player_avfoundation
|
||||
- name: camera_android_camerax
|
||||
path: packages/camera/camera_android_camerax
|
||||
commit: b4f7d807a822a8ce327aae21353ac2217e928f6d
|
||||
flutter_sharing_intent:
|
||||
git: https://github.com/bhagat-techind/flutter_sharing_intent.git
|
||||
commit: aa1672f547d6579585fa27df0b28ffa2a2544aaa
|
||||
hand_signature:
|
||||
git: https://github.com/RomanBase/hand_signature.git
|
||||
commit: 1beedb164d093643365b0832277c377353c7464f
|
||||
hashlib:
|
||||
git: https://github.com/bitanon/hashlib.git
|
||||
replace:
|
||||
- - abstract class MACHashBase
|
||||
- abstract mixin class MACHashBase
|
||||
dependencies:
|
||||
hashlib_codecs:
|
||||
git: https://github.com/bitanon/hashlib_codecs.git
|
||||
commit: 2a966c37c3b9b1f5541ae88e99ab34acf3fc968b
|
||||
commit: bc9c2f8dd7bbc72f47ccab0ce1111d40259c49bc
|
||||
image:
|
||||
git: https://github.com/brendan-duncan/image.git
|
||||
tag: v4.9.2
|
||||
introduction_screen:
|
||||
git: https://github.com/Pyozer/introduction_screen.git
|
||||
dependencies:
|
||||
dots_indicator:
|
||||
git: https://github.com/Pyozer/dots_indicator.git
|
||||
commit: 508f5883ac79bdbc10254092de3f28f571d261cd
|
||||
commit: 4a90e557630b28834479ed9c64a9d2d0185d8e48
|
||||
libsignal_protocol_dart:
|
||||
git: https://github.com/MixinNetwork/libsignal_protocol_dart.git
|
||||
dependencies:
|
||||
adaptive_number:
|
||||
git: https://github.com/lemoony/adaptive_number_dart
|
||||
commit: ea9178fdd4d82ac45cf0ec966ac870dae661124f
|
||||
ed25519_edwards:
|
||||
git: https://github.com/Tougee/ed25519.git
|
||||
commit: 7353ba759ea9f4646cbf481c2ef949625c8ce4cf
|
||||
optional:
|
||||
git: https://github.com/tonio-ramirez/optional.dart.git
|
||||
commit: 71c638891ce4f2aff35c7387727989f31f9d877d
|
||||
pointycastle:
|
||||
git: https://github.com/bcgit/pc-dart.git
|
||||
commit: bbd8569f68a7fccbdf0b92d0b44a9219c126c8dd
|
||||
x25519:
|
||||
git: https://github.com/Tougee/curve25519.git
|
||||
commit: ecb1d357714537bba6e276ef45f093846d4beaee
|
||||
commit: c95a1586057022acdbb9c76b1692d94cc549bcc7
|
||||
lottie:
|
||||
git: https://github.com/xvrh/lottie-flutter.git
|
||||
commit: 127bc29f2c6bd8b32ec4064a09e54e6b31cd0a88
|
||||
mutex:
|
||||
git: https://github.com/hoylen/dart-mutex.git
|
||||
commit: 84ca903a3ac863735e3228c75a212133621f680f
|
||||
photo_view:
|
||||
git: https://github.com/bluefireteam/photo_view.git
|
||||
commit: a13ca2fc387a3fb1276126959e092c44d0029987
|
||||
pro_video_editor:
|
||||
git: https://github.com/hm21/pro_video_editor.git
|
||||
tag: v2.11.3
|
||||
qr_flutter:
|
||||
git: https://github.com/theyakka/qr.flutter.git
|
||||
dependencies:
|
||||
qr:
|
||||
git: https://github.com/kevmoo/qr.dart.git
|
||||
commit: 7b1e9665ca976f484e7975356cf26fc7a0ccf02e
|
||||
commit: d5e7206396105d643113618290bbcc755d05f492
|
||||
restart_app:
|
||||
git: https://github.com/gabrimatic/restart_app
|
||||
commit: 66897cb67e235bab85421647bfae036acb4438cb
|
||||
screen_protector:
|
||||
git: https://github.com/prongbang/screen_protector.git
|
||||
commit: 019c04d622d7b610d2903d3a347edc3ba76a6ed0
|
||||
|
|
@ -20,12 +20,12 @@ import SwiftProtobuf
|
|||
// incompatible with the version of SwiftProtobuf to which you are linking.
|
||||
// Please ensure that you are building against the same version of the API
|
||||
// that was used to generate this file.
|
||||
fileprivate nonisolated struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
|
||||
fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
|
||||
struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}
|
||||
typealias Version = _2
|
||||
}
|
||||
|
||||
nonisolated enum PushKind: SwiftProtobuf.Enum, Swift.CaseIterable {
|
||||
enum PushKind: SwiftProtobuf.Enum, Swift.CaseIterable {
|
||||
typealias RawValue = Int
|
||||
case reaction // = 0
|
||||
case response // = 1
|
||||
|
|
@ -119,7 +119,7 @@ nonisolated enum PushKind: SwiftProtobuf.Enum, Swift.CaseIterable {
|
|||
|
||||
}
|
||||
|
||||
nonisolated struct EncryptedPushNotification: Sendable {
|
||||
struct EncryptedPushNotification: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -137,7 +137,7 @@ nonisolated struct EncryptedPushNotification: Sendable {
|
|||
init() {}
|
||||
}
|
||||
|
||||
nonisolated struct PushNotification: Sendable {
|
||||
struct PushNotification: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -170,7 +170,7 @@ nonisolated struct PushNotification: Sendable {
|
|||
fileprivate var _additionalContent: String? = nil
|
||||
}
|
||||
|
||||
nonisolated struct PushUsers: Sendable {
|
||||
struct PushUsers: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -182,7 +182,7 @@ nonisolated struct PushUsers: Sendable {
|
|||
init() {}
|
||||
}
|
||||
|
||||
nonisolated struct PushUser: Sendable {
|
||||
struct PushUser: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -211,7 +211,7 @@ nonisolated struct PushUser: Sendable {
|
|||
fileprivate var _lastMessageID: String? = nil
|
||||
}
|
||||
|
||||
nonisolated struct PushKey: Sendable {
|
||||
struct PushKey: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
|
@ -229,11 +229,11 @@ nonisolated struct PushKey: Sendable {
|
|||
|
||||
// MARK: - Code below here is support for the SwiftProtobuf runtime.
|
||||
|
||||
nonisolated extension PushKind: SwiftProtobuf._ProtoNameProviding {
|
||||
extension PushKind: SwiftProtobuf._ProtoNameProviding {
|
||||
static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0REACTION\0\u{1}RESPONSE\0\u{1}TEXT\0\u{1}VIDEO\0\u{1}TWONLY\0\u{1}IMAGE\0\u{1}CONTACT_REQUEST\0\u{1}ACCEPT_REQUEST\0\u{1}STORED_MEDIA_FILE\0\u{1}TEST_NOTIFICATION\0\u{1}REOPENED_MEDIA\0\u{1}REACTION_TO_VIDEO\0\u{1}REACTION_TO_TEXT\0\u{1}REACTION_TO_IMAGE\0\u{1}REACTION_TO_AUDIO\0\u{1}ADDED_TO_GROUP\0\u{1}AUDIO\0")
|
||||
}
|
||||
|
||||
nonisolated extension EncryptedPushNotification: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
extension EncryptedPushNotification: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
static let protoMessageName: String = "EncryptedPushNotification"
|
||||
static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}key_id\0\u{1}nonce\0\u{1}ciphertext\0\u{1}mac\0")
|
||||
|
||||
|
|
@ -278,7 +278,7 @@ nonisolated extension EncryptedPushNotification: SwiftProtobuf.Message, SwiftPro
|
|||
}
|
||||
}
|
||||
|
||||
nonisolated extension PushNotification: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
extension PushNotification: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
static let protoMessageName: String = "PushNotification"
|
||||
static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}kind\0\u{3}message_id\0\u{3}additional_content\0")
|
||||
|
||||
|
|
@ -322,7 +322,7 @@ nonisolated extension PushNotification: SwiftProtobuf.Message, SwiftProtobuf._Me
|
|||
}
|
||||
}
|
||||
|
||||
nonisolated extension PushUsers: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
extension PushUsers: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
static let protoMessageName: String = "PushUsers"
|
||||
static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}users\0")
|
||||
|
||||
|
|
@ -352,7 +352,7 @@ nonisolated extension PushUsers: SwiftProtobuf.Message, SwiftProtobuf._MessageIm
|
|||
}
|
||||
}
|
||||
|
||||
nonisolated extension PushUser: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
extension PushUser: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
static let protoMessageName: String = "PushUser"
|
||||
static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}user_id\0\u{3}display_name\0\u{1}blocked\0\u{3}last_message_id\0\u{3}push_keys\0")
|
||||
|
||||
|
|
@ -406,7 +406,7 @@ nonisolated extension PushUser: SwiftProtobuf.Message, SwiftProtobuf._MessageImp
|
|||
}
|
||||
}
|
||||
|
||||
nonisolated extension PushKey: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
extension PushKey: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
static let protoMessageName: String = "PushKey"
|
||||
static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}id\0\u{1}key\0\u{3}created_at_unix_timestamp\0")
|
||||
|
||||
|
|
|
|||
|
|
@ -92,6 +92,8 @@ PODS:
|
|||
- nanopb/encode (3.30910.0)
|
||||
- permission_handler_apple (9.3.0):
|
||||
- Flutter
|
||||
- pro_video_editor (0.0.1):
|
||||
- Flutter
|
||||
- PromisesObjC (2.4.0)
|
||||
- rust_lib_twonly (0.0.1):
|
||||
- Flutter
|
||||
|
|
@ -120,6 +122,7 @@ DEPENDENCIES:
|
|||
- google_mlkit_commons (from `.symlinks/plugins/google_mlkit_commons/ios`)
|
||||
- google_mlkit_face_detection (from `.symlinks/plugins/google_mlkit_face_detection/ios`)
|
||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||
- pro_video_editor (from `.symlinks/plugins/pro_video_editor/ios`)
|
||||
- rust_lib_twonly (from `.symlinks/plugins/rust_lib_twonly/ios`)
|
||||
- screen_protector (from `.symlinks/plugins/screen_protector/ios`)
|
||||
- SwiftProtobuf
|
||||
|
|
@ -167,6 +170,8 @@ EXTERNAL SOURCES:
|
|||
:path: ".symlinks/plugins/google_mlkit_face_detection/ios"
|
||||
permission_handler_apple:
|
||||
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||
pro_video_editor:
|
||||
:path: ".symlinks/plugins/pro_video_editor/ios"
|
||||
rust_lib_twonly:
|
||||
:path: ".symlinks/plugins/rust_lib_twonly/ios"
|
||||
screen_protector:
|
||||
|
|
@ -177,7 +182,7 @@ EXTERNAL SOURCES:
|
|||
SPEC CHECKSUMS:
|
||||
audio_waveforms: a6dde7fe7c0ea05f06ffbdb0f7c1b2b2ba6cedcf
|
||||
cryptography_flutter_plus: 44f4e9e4079395fcbb3e7809c0ac2c6ae2d9576f
|
||||
Flutter: 71a624a5bc0c04062bf19101d501e466baf2fb47
|
||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||
flutter_image_compress_common: 1697a328fd72bfb335507c6bca1a65fa5ad87df1
|
||||
flutter_sharing_intent: 0c1e53949f09fa8df8ac2268505687bde8ff264c
|
||||
flutter_volume_controller: c2be490cb0487e8b88d0d9fc2b7e1c139a4ebccb
|
||||
|
|
@ -198,6 +203,7 @@ SPEC CHECKSUMS:
|
|||
MLKitVision: 39a5a812db83c4a0794445088e567f3631c11961
|
||||
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
||||
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
|
||||
pro_video_editor: 44ef9a6d48dbd757ed428cf35396dd05f35c7830
|
||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||
rust_lib_twonly: 73165b05d0cda50db45852db63f49caa7f319520
|
||||
screen_protector: 18c6aca2dc5d2a832f6787a5318f97f03e9d3150
|
||||
|
|
@ -209,4 +215,4 @@ SPEC CHECKSUMS:
|
|||
|
||||
PODFILE CHECKSUM: 245e6d5f26c858edb6b99a7d972cc93ead4d55cf
|
||||
|
||||
COCOAPODS: 1.17.0
|
||||
COCOAPODS: 1.16.2
|
||||
|
|
|
|||
|
|
@ -742,7 +742,7 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
|
|
@ -886,7 +886,7 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
|
|
@ -939,7 +939,7 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
|
|
|
|||
72
lib/app.dart
72
lib/app.dart
|
|
@ -1,21 +1,16 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:app_links/app_links.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_sharing_intent/model/sharing_file.dart' show SharedFile;
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/constants/keyvalue.keys.dart';
|
||||
import 'package:twonly/src/constants/routes.keys.dart';
|
||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||
import 'package:twonly/src/localization/generated/app_localizations.dart';
|
||||
import 'package:twonly/src/model/json/onboarding_state.model.dart';
|
||||
import 'package:twonly/src/providers/routing.provider.dart';
|
||||
import 'package:twonly/src/providers/settings.provider.dart';
|
||||
import 'package:twonly/src/services/intent/links.intent.dart';
|
||||
import 'package:twonly/src/utils/keyvalue.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:twonly/src/utils/pow.dart';
|
||||
|
|
@ -90,17 +85,15 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
|||
Locale('de', ''),
|
||||
];
|
||||
|
||||
final settings = context.watch<SettingsChangeProvider>();
|
||||
|
||||
if (widget.storageError) {
|
||||
return MaterialApp(
|
||||
localizationsDelegates: localizationsDelegates,
|
||||
debugShowCheckedModeBanner: false,
|
||||
supportedLocales: supportedLocales,
|
||||
title: 'twonly',
|
||||
theme: getLightTheme(settings.primaryColor),
|
||||
darkTheme: getDarkTheme(settings.primaryColor),
|
||||
themeMode: settings.themeMode,
|
||||
theme: lightTheme,
|
||||
darkTheme: darkTheme,
|
||||
themeMode: context.read<SettingsChangeProvider>().themeMode,
|
||||
home: const CriticalErrorView(),
|
||||
);
|
||||
}
|
||||
|
|
@ -111,9 +104,9 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
|||
debugShowCheckedModeBanner: false,
|
||||
supportedLocales: supportedLocales,
|
||||
title: 'twonly',
|
||||
theme: getLightTheme(settings.primaryColor),
|
||||
darkTheme: getDarkTheme(settings.primaryColor),
|
||||
themeMode: settings.themeMode,
|
||||
theme: lightTheme,
|
||||
darkTheme: darkTheme,
|
||||
themeMode: context.read<SettingsChangeProvider>().themeMode,
|
||||
home: const RecoveryView(),
|
||||
);
|
||||
}
|
||||
|
|
@ -124,9 +117,9 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
|||
debugShowCheckedModeBanner: false,
|
||||
supportedLocales: supportedLocales,
|
||||
title: 'twonly',
|
||||
theme: getLightTheme(settings.primaryColor),
|
||||
darkTheme: getDarkTheme(settings.primaryColor),
|
||||
themeMode: settings.themeMode,
|
||||
theme: lightTheme,
|
||||
darkTheme: darkTheme,
|
||||
themeMode: context.read<SettingsChangeProvider>().themeMode,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
@ -150,10 +143,6 @@ class _AppMainWidgetState extends State<AppMainWidget> {
|
|||
bool _wasLogged = true;
|
||||
late int _initialPage;
|
||||
|
||||
StreamSubscription<Uri>? _deepLinkSub;
|
||||
StreamSubscription<List<SharedFile>>? _intentStreamSub;
|
||||
StreamSubscription<String>? _emailTokenSub;
|
||||
|
||||
(Future<int>?, bool) _proofOfWork = (null, false);
|
||||
|
||||
@override
|
||||
|
|
@ -162,37 +151,6 @@ class _AppMainWidgetState extends State<AppMainWidget> {
|
|||
_initialPage = widget.initialPage;
|
||||
Log.info('AppWidgetState: initState started');
|
||||
initAsync();
|
||||
|
||||
void handleShareLink(Uri uri) {
|
||||
HomeViewState.pendingSharedLink = uri;
|
||||
routerProvider.go(Routes.home);
|
||||
HomeViewState.streamHomeViewPageIndex.add(1);
|
||||
HomeViewState.streamSharedLink.add(uri);
|
||||
}
|
||||
|
||||
// Subscribe to all events (initial link and further)
|
||||
_deepLinkSub = AppLinks().uriLinkStream.listen((uri) async {
|
||||
if (!mounted) return;
|
||||
Log.info('Got link via app links: ${uri.scheme}');
|
||||
if (!await handleIntentUrl(context, uri)) {
|
||||
if (uri.scheme.startsWith('http')) {
|
||||
handleShareLink(uri);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
void handleShareMedia(String path, MediaType type) {
|
||||
HomeViewState.pendingSharedMedia = (path, type);
|
||||
routerProvider.go(Routes.home);
|
||||
HomeViewState.streamHomeViewPageIndex.add(0);
|
||||
HomeViewState.streamSharedMedia.add((path, type));
|
||||
}
|
||||
|
||||
_intentStreamSub = initIntentStreams(
|
||||
context,
|
||||
handleShareLink,
|
||||
handleShareMedia,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> initAsync() async {
|
||||
|
|
@ -239,6 +197,10 @@ class _AppMainWidgetState extends State<AppMainWidget> {
|
|||
}
|
||||
}
|
||||
|
||||
// await PasswordlessRecoveryService.handleRecoveryLink(
|
||||
// 'https://me.twonly.eu/r/#7fdb8f08-0927-4e44-8761-038993414e48/1p7SKEzpxE3wSW9FQw60EUI4OpSW2U4EskdXLw8xg48',
|
||||
// );
|
||||
|
||||
setState(() {
|
||||
_isLoaded = true;
|
||||
});
|
||||
|
|
@ -302,12 +264,4 @@ class _AppMainWidgetState extends State<AppMainWidget> {
|
|||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_deepLinkSub?.cancel();
|
||||
_intentStreamSub?.cancel();
|
||||
_emailTokenSub?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,24 +9,6 @@ import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
|||
class RustKeyManager {
|
||||
const RustKeyManager();
|
||||
|
||||
static Future<Uint8List> decryptCloudMediaKey({
|
||||
required List<int> encryptedMediaKey,
|
||||
required String addition,
|
||||
}) => RustLib.instance.api
|
||||
.crateBridgeWrapperKeyManagerRustKeyManagerDecryptCloudMediaKey(
|
||||
encryptedMediaKey: encryptedMediaKey,
|
||||
addition: addition,
|
||||
);
|
||||
|
||||
static Future<Uint8List> encryptCloudMediaKey({
|
||||
required List<int> mediaKey,
|
||||
required String addition,
|
||||
}) => RustLib.instance.api
|
||||
.crateBridgeWrapperKeyManagerRustKeyManagerEncryptCloudMediaKey(
|
||||
mediaKey: mediaKey,
|
||||
addition: addition,
|
||||
);
|
||||
|
||||
static Future<Uint8List> getLoginToken() => RustLib.instance.api
|
||||
.crateBridgeWrapperKeyManagerRustKeyManagerGetLoginToken();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,57 +0,0 @@
|
|||
// This file is automatically generated, so please do not edit it.
|
||||
// @generated by `flutter_rust_bridge`@ 2.12.0.
|
||||
|
||||
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
|
||||
|
||||
import '../../frb_generated.dart';
|
||||
import '../../signal/engine.dart';
|
||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
||||
|
||||
class RustSignal {
|
||||
const RustSignal();
|
||||
|
||||
static Future<Uint8List> decrypt({
|
||||
required String name,
|
||||
required int deviceId,
|
||||
required List<int> ciphertext,
|
||||
}) => RustLib.instance.api.crateBridgeWrapperSignalRustSignalDecrypt(
|
||||
name: name,
|
||||
deviceId: deviceId,
|
||||
ciphertext: ciphertext,
|
||||
);
|
||||
|
||||
static Future<Uint8List> encrypt({
|
||||
required String name,
|
||||
required int deviceId,
|
||||
required List<int> plaintext,
|
||||
}) => RustLib.instance.api.crateBridgeWrapperSignalRustSignalEncrypt(
|
||||
name: name,
|
||||
deviceId: deviceId,
|
||||
plaintext: plaintext,
|
||||
);
|
||||
|
||||
static Future<FrbPreKeyBundle> generateBundle() =>
|
||||
RustLib.instance.api.crateBridgeWrapperSignalRustSignalGenerateBundle();
|
||||
|
||||
static Future<List<FrbPqcPreKey>> generatePqcPrekeys() => RustLib.instance.api
|
||||
.crateBridgeWrapperSignalRustSignalGeneratePqcPrekeys();
|
||||
|
||||
static Future<void> processPrekeyBundle({
|
||||
required String name,
|
||||
required int deviceId,
|
||||
required FrbPreKeyBundle bundle,
|
||||
}) => RustLib.instance.api
|
||||
.crateBridgeWrapperSignalRustSignalProcessPrekeyBundle(
|
||||
name: name,
|
||||
deviceId: deviceId,
|
||||
bundle: bundle,
|
||||
);
|
||||
|
||||
@override
|
||||
int get hashCode => 0;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is RustSignal && runtimeType == other.runtimeType;
|
||||
}
|
||||
|
|
@ -9,7 +9,6 @@ import 'bridge/callbacks/user_discovery.dart';
|
|||
import 'bridge/wrapper.dart';
|
||||
import 'bridge/wrapper/backup.dart';
|
||||
import 'bridge/wrapper/key_manager.dart';
|
||||
import 'bridge/wrapper/signal.dart';
|
||||
import 'bridge/wrapper/user_discovery.dart';
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
|
@ -19,7 +18,6 @@ import 'frb_generated.io.dart'
|
|||
import 'keys/backup_password_keys.dart';
|
||||
import 'lib.dart';
|
||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
||||
import 'signal/engine.dart';
|
||||
|
||||
/// Main entrypoint of the Rust API
|
||||
class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
|
||||
|
|
@ -78,7 +76,7 @@ class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
|
|||
String get codegenVersion => '2.12.0';
|
||||
|
||||
@override
|
||||
int get rustContentHash => 1109927244;
|
||||
int get rustContentHash => 340781866;
|
||||
|
||||
static const kDefaultExternalLibraryLoaderConfig =
|
||||
ExternalLibraryLoaderConfig(
|
||||
|
|
@ -207,18 +205,6 @@ abstract class RustLibApi extends BaseApi {
|
|||
required String password,
|
||||
});
|
||||
|
||||
Future<Uint8List>
|
||||
crateBridgeWrapperKeyManagerRustKeyManagerDecryptCloudMediaKey({
|
||||
required List<int> encryptedMediaKey,
|
||||
required String addition,
|
||||
});
|
||||
|
||||
Future<Uint8List>
|
||||
crateBridgeWrapperKeyManagerRustKeyManagerEncryptCloudMediaKey({
|
||||
required List<int> mediaKey,
|
||||
required String addition,
|
||||
});
|
||||
|
||||
Future<Uint8List> crateBridgeWrapperKeyManagerRustKeyManagerGetLoginToken();
|
||||
|
||||
Future<(Uint8List, PlatformInt64)>
|
||||
|
|
@ -261,29 +247,6 @@ abstract class RustLibApi extends BaseApi {
|
|||
required List<int> record,
|
||||
});
|
||||
|
||||
Future<Uint8List> crateBridgeWrapperSignalRustSignalDecrypt({
|
||||
required String name,
|
||||
required int deviceId,
|
||||
required List<int> ciphertext,
|
||||
});
|
||||
|
||||
Future<Uint8List> crateBridgeWrapperSignalRustSignalEncrypt({
|
||||
required String name,
|
||||
required int deviceId,
|
||||
required List<int> plaintext,
|
||||
});
|
||||
|
||||
Future<FrbPreKeyBundle> crateBridgeWrapperSignalRustSignalGenerateBundle();
|
||||
|
||||
Future<List<FrbPqcPreKey>>
|
||||
crateBridgeWrapperSignalRustSignalGeneratePqcPrekeys();
|
||||
|
||||
Future<void> crateBridgeWrapperSignalRustSignalProcessPrekeyBundle({
|
||||
required String name,
|
||||
required int deviceId,
|
||||
required FrbPreKeyBundle bundle,
|
||||
});
|
||||
|
||||
Future<List<Uint8List>> crateBridgeWrapperRustUtilsGenerateShares({
|
||||
required List<int> secret,
|
||||
required int total,
|
||||
|
|
@ -1178,82 +1141,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
argNames: ["userId", "password"],
|
||||
);
|
||||
|
||||
@override
|
||||
Future<Uint8List>
|
||||
crateBridgeWrapperKeyManagerRustKeyManagerDecryptCloudMediaKey({
|
||||
required List<int> encryptedMediaKey,
|
||||
required String addition,
|
||||
}) {
|
||||
return handler.executeNormal(
|
||||
NormalTask(
|
||||
callFfi: (port_) {
|
||||
final serializer = SseSerializer(generalizedFrbRustBinding);
|
||||
sse_encode_list_prim_u_8_loose(encryptedMediaKey, serializer);
|
||||
sse_encode_String(addition, serializer);
|
||||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 18,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
codec: SseCodec(
|
||||
decodeSuccessData: sse_decode_list_prim_u_8_strict,
|
||||
decodeErrorData: sse_decode_AnyhowException,
|
||||
),
|
||||
constMeta:
|
||||
kCrateBridgeWrapperKeyManagerRustKeyManagerDecryptCloudMediaKeyConstMeta,
|
||||
argValues: [encryptedMediaKey, addition],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TaskConstMeta
|
||||
get kCrateBridgeWrapperKeyManagerRustKeyManagerDecryptCloudMediaKeyConstMeta =>
|
||||
const TaskConstMeta(
|
||||
debugName: "rust_key_manager_decrypt_cloud_media_key",
|
||||
argNames: ["encryptedMediaKey", "addition"],
|
||||
);
|
||||
|
||||
@override
|
||||
Future<Uint8List>
|
||||
crateBridgeWrapperKeyManagerRustKeyManagerEncryptCloudMediaKey({
|
||||
required List<int> mediaKey,
|
||||
required String addition,
|
||||
}) {
|
||||
return handler.executeNormal(
|
||||
NormalTask(
|
||||
callFfi: (port_) {
|
||||
final serializer = SseSerializer(generalizedFrbRustBinding);
|
||||
sse_encode_list_prim_u_8_loose(mediaKey, serializer);
|
||||
sse_encode_String(addition, serializer);
|
||||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 19,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
codec: SseCodec(
|
||||
decodeSuccessData: sse_decode_list_prim_u_8_strict,
|
||||
decodeErrorData: sse_decode_AnyhowException,
|
||||
),
|
||||
constMeta:
|
||||
kCrateBridgeWrapperKeyManagerRustKeyManagerEncryptCloudMediaKeyConstMeta,
|
||||
argValues: [mediaKey, addition],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TaskConstMeta
|
||||
get kCrateBridgeWrapperKeyManagerRustKeyManagerEncryptCloudMediaKeyConstMeta =>
|
||||
const TaskConstMeta(
|
||||
debugName: "rust_key_manager_encrypt_cloud_media_key",
|
||||
argNames: ["mediaKey", "addition"],
|
||||
);
|
||||
|
||||
@override
|
||||
Future<Uint8List> crateBridgeWrapperKeyManagerRustKeyManagerGetLoginToken() {
|
||||
return handler.executeNormal(
|
||||
|
|
@ -1263,7 +1150,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 20,
|
||||
funcId: 18,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -1296,7 +1183,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 21,
|
||||
funcId: 19,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -1328,7 +1215,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 22,
|
||||
funcId: 20,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -1363,7 +1250,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 23,
|
||||
funcId: 21,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -1405,7 +1292,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 24,
|
||||
funcId: 22,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -1449,7 +1336,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 25,
|
||||
funcId: 23,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -1482,7 +1369,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 26,
|
||||
funcId: 24,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -1514,7 +1401,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 27,
|
||||
funcId: 25,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -1549,7 +1436,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 28,
|
||||
funcId: 26,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -1581,7 +1468,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 29,
|
||||
funcId: 27,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -1616,7 +1503,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 30,
|
||||
funcId: 28,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -1653,7 +1540,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 31,
|
||||
funcId: 29,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -1676,183 +1563,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
argNames: ["signedPreKeyId", "record"],
|
||||
);
|
||||
|
||||
@override
|
||||
Future<Uint8List> crateBridgeWrapperSignalRustSignalDecrypt({
|
||||
required String name,
|
||||
required int deviceId,
|
||||
required List<int> ciphertext,
|
||||
}) {
|
||||
return handler.executeNormal(
|
||||
NormalTask(
|
||||
callFfi: (port_) {
|
||||
final serializer = SseSerializer(generalizedFrbRustBinding);
|
||||
sse_encode_String(name, serializer);
|
||||
sse_encode_u_32(deviceId, serializer);
|
||||
sse_encode_list_prim_u_8_loose(ciphertext, serializer);
|
||||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 32,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
codec: SseCodec(
|
||||
decodeSuccessData: sse_decode_list_prim_u_8_strict,
|
||||
decodeErrorData: sse_decode_AnyhowException,
|
||||
),
|
||||
constMeta: kCrateBridgeWrapperSignalRustSignalDecryptConstMeta,
|
||||
argValues: [name, deviceId, ciphertext],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TaskConstMeta get kCrateBridgeWrapperSignalRustSignalDecryptConstMeta =>
|
||||
const TaskConstMeta(
|
||||
debugName: "rust_signal_decrypt",
|
||||
argNames: ["name", "deviceId", "ciphertext"],
|
||||
);
|
||||
|
||||
@override
|
||||
Future<Uint8List> crateBridgeWrapperSignalRustSignalEncrypt({
|
||||
required String name,
|
||||
required int deviceId,
|
||||
required List<int> plaintext,
|
||||
}) {
|
||||
return handler.executeNormal(
|
||||
NormalTask(
|
||||
callFfi: (port_) {
|
||||
final serializer = SseSerializer(generalizedFrbRustBinding);
|
||||
sse_encode_String(name, serializer);
|
||||
sse_encode_u_32(deviceId, serializer);
|
||||
sse_encode_list_prim_u_8_loose(plaintext, serializer);
|
||||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 33,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
codec: SseCodec(
|
||||
decodeSuccessData: sse_decode_list_prim_u_8_strict,
|
||||
decodeErrorData: sse_decode_AnyhowException,
|
||||
),
|
||||
constMeta: kCrateBridgeWrapperSignalRustSignalEncryptConstMeta,
|
||||
argValues: [name, deviceId, plaintext],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TaskConstMeta get kCrateBridgeWrapperSignalRustSignalEncryptConstMeta =>
|
||||
const TaskConstMeta(
|
||||
debugName: "rust_signal_encrypt",
|
||||
argNames: ["name", "deviceId", "plaintext"],
|
||||
);
|
||||
|
||||
@override
|
||||
Future<FrbPreKeyBundle> crateBridgeWrapperSignalRustSignalGenerateBundle() {
|
||||
return handler.executeNormal(
|
||||
NormalTask(
|
||||
callFfi: (port_) {
|
||||
final serializer = SseSerializer(generalizedFrbRustBinding);
|
||||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 34,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
codec: SseCodec(
|
||||
decodeSuccessData: sse_decode_frb_pre_key_bundle,
|
||||
decodeErrorData: sse_decode_AnyhowException,
|
||||
),
|
||||
constMeta: kCrateBridgeWrapperSignalRustSignalGenerateBundleConstMeta,
|
||||
argValues: [],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TaskConstMeta
|
||||
get kCrateBridgeWrapperSignalRustSignalGenerateBundleConstMeta =>
|
||||
const TaskConstMeta(
|
||||
debugName: "rust_signal_generate_bundle",
|
||||
argNames: [],
|
||||
);
|
||||
|
||||
@override
|
||||
Future<List<FrbPqcPreKey>>
|
||||
crateBridgeWrapperSignalRustSignalGeneratePqcPrekeys() {
|
||||
return handler.executeNormal(
|
||||
NormalTask(
|
||||
callFfi: (port_) {
|
||||
final serializer = SseSerializer(generalizedFrbRustBinding);
|
||||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 35,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
codec: SseCodec(
|
||||
decodeSuccessData: sse_decode_list_frb_pqc_pre_key,
|
||||
decodeErrorData: sse_decode_AnyhowException,
|
||||
),
|
||||
constMeta:
|
||||
kCrateBridgeWrapperSignalRustSignalGeneratePqcPrekeysConstMeta,
|
||||
argValues: [],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TaskConstMeta
|
||||
get kCrateBridgeWrapperSignalRustSignalGeneratePqcPrekeysConstMeta =>
|
||||
const TaskConstMeta(
|
||||
debugName: "rust_signal_generate_pqc_prekeys",
|
||||
argNames: [],
|
||||
);
|
||||
|
||||
@override
|
||||
Future<void> crateBridgeWrapperSignalRustSignalProcessPrekeyBundle({
|
||||
required String name,
|
||||
required int deviceId,
|
||||
required FrbPreKeyBundle bundle,
|
||||
}) {
|
||||
return handler.executeNormal(
|
||||
NormalTask(
|
||||
callFfi: (port_) {
|
||||
final serializer = SseSerializer(generalizedFrbRustBinding);
|
||||
sse_encode_String(name, serializer);
|
||||
sse_encode_u_32(deviceId, serializer);
|
||||
sse_encode_box_autoadd_frb_pre_key_bundle(bundle, serializer);
|
||||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 36,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
codec: SseCodec(
|
||||
decodeSuccessData: sse_decode_unit,
|
||||
decodeErrorData: sse_decode_AnyhowException,
|
||||
),
|
||||
constMeta:
|
||||
kCrateBridgeWrapperSignalRustSignalProcessPrekeyBundleConstMeta,
|
||||
argValues: [name, deviceId, bundle],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TaskConstMeta
|
||||
get kCrateBridgeWrapperSignalRustSignalProcessPrekeyBundleConstMeta =>
|
||||
const TaskConstMeta(
|
||||
debugName: "rust_signal_process_prekey_bundle",
|
||||
argNames: ["name", "deviceId", "bundle"],
|
||||
);
|
||||
|
||||
@override
|
||||
Future<List<Uint8List>> crateBridgeWrapperRustUtilsGenerateShares({
|
||||
required List<int> secret,
|
||||
|
|
@ -1869,7 +1579,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 37,
|
||||
funcId: 30,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -1904,7 +1614,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 38,
|
||||
funcId: 31,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -1940,7 +1650,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 39,
|
||||
funcId: 32,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -1977,7 +1687,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 40,
|
||||
funcId: 33,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -2015,7 +1725,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 41,
|
||||
funcId: 34,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -2053,7 +1763,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 42,
|
||||
funcId: 35,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -2091,7 +1801,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 43,
|
||||
funcId: 36,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -2130,7 +1840,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 44,
|
||||
funcId: 37,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -2169,7 +1879,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 45,
|
||||
funcId: 38,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -2214,7 +1924,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 46,
|
||||
funcId: 39,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -2266,7 +1976,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 47,
|
||||
funcId: 40,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -2307,7 +2017,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 48,
|
||||
funcId: 41,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -2345,7 +2055,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 49,
|
||||
funcId: 42,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -2383,7 +2093,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 50,
|
||||
funcId: 43,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -2421,7 +2131,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 51,
|
||||
funcId: 44,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -2459,7 +2169,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 52,
|
||||
funcId: 45,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -2501,7 +2211,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 53,
|
||||
funcId: 46,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -2541,7 +2251,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 54,
|
||||
funcId: 47,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -3180,12 +2890,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
return dco_decode_backup_password_keys(raw);
|
||||
}
|
||||
|
||||
@protected
|
||||
FrbPreKeyBundle dco_decode_box_autoadd_frb_pre_key_bundle(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
return dco_decode_frb_pre_key_bundle(raw);
|
||||
}
|
||||
|
||||
@protected
|
||||
PlatformInt64 dco_decode_box_autoadd_i_64(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
|
|
@ -3204,12 +2908,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
return dco_decode_other_promotion(raw);
|
||||
}
|
||||
|
||||
@protected
|
||||
int dco_decode_box_autoadd_u_32(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
return raw as int;
|
||||
}
|
||||
|
||||
@protected
|
||||
UserDiscoveryStoreFlutter dco_decode_box_autoadd_user_discovery_store_flutter(
|
||||
dynamic raw,
|
||||
|
|
@ -3235,42 +2933,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
return FlutterUserDiscovery();
|
||||
}
|
||||
|
||||
@protected
|
||||
FrbPqcPreKey dco_decode_frb_pqc_pre_key(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
final arr = raw as List<dynamic>;
|
||||
if (arr.length != 5)
|
||||
throw Exception('unexpected arr length: expect 5 but see ${arr.length}');
|
||||
return FrbPqcPreKey(
|
||||
eccPreKeyId: dco_decode_u_32(arr[0]),
|
||||
eccPreKey: dco_decode_list_prim_u_8_strict(arr[1]),
|
||||
kyberPreKeyId: dco_decode_u_32(arr[2]),
|
||||
kyberPreKey: dco_decode_list_prim_u_8_strict(arr[3]),
|
||||
kyberPreKeySignature: dco_decode_list_prim_u_8_strict(arr[4]),
|
||||
);
|
||||
}
|
||||
|
||||
@protected
|
||||
FrbPreKeyBundle dco_decode_frb_pre_key_bundle(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
final arr = raw as List<dynamic>;
|
||||
if (arr.length != 11)
|
||||
throw Exception('unexpected arr length: expect 11 but see ${arr.length}');
|
||||
return FrbPreKeyBundle(
|
||||
registrationId: dco_decode_u_32(arr[0]),
|
||||
deviceId: dco_decode_u_32(arr[1]),
|
||||
preKeyId: dco_decode_opt_box_autoadd_u_32(arr[2]),
|
||||
preKeyPublic: dco_decode_opt_list_prim_u_8_strict(arr[3]),
|
||||
signedPreKeyId: dco_decode_u_32(arr[4]),
|
||||
signedPreKeyPublic: dco_decode_list_prim_u_8_strict(arr[5]),
|
||||
signedPreKeySignature: dco_decode_list_prim_u_8_strict(arr[6]),
|
||||
kyberPreKeyId: dco_decode_u_32(arr[7]),
|
||||
kyberPreKeyPublic: dco_decode_list_prim_u_8_strict(arr[8]),
|
||||
kyberPreKeySignature: dco_decode_list_prim_u_8_strict(arr[9]),
|
||||
identityKey: dco_decode_list_prim_u_8_strict(arr[10]),
|
||||
);
|
||||
}
|
||||
|
||||
@protected
|
||||
PlatformInt64 dco_decode_i_64(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
|
|
@ -3295,12 +2957,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
return dcoDecodeI64(raw);
|
||||
}
|
||||
|
||||
@protected
|
||||
List<FrbPqcPreKey> dco_decode_list_frb_pqc_pre_key(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
return (raw as List<dynamic>).map(dco_decode_frb_pqc_pre_key).toList();
|
||||
}
|
||||
|
||||
@protected
|
||||
List<Uint8List> dco_decode_list_list_prim_u_8_strict(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
|
|
@ -3352,12 +3008,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
return raw == null ? null : dco_decode_box_autoadd_i_64(raw);
|
||||
}
|
||||
|
||||
@protected
|
||||
int? dco_decode_opt_box_autoadd_u_32(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
return raw == null ? null : dco_decode_box_autoadd_u_32(raw);
|
||||
}
|
||||
|
||||
@protected
|
||||
List<Uint8List>? dco_decode_opt_list_list_prim_u_8_strict(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
|
|
@ -3462,15 +3112,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
return RustKeyManager();
|
||||
}
|
||||
|
||||
@protected
|
||||
RustSignal dco_decode_rust_signal(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
final arr = raw as List<dynamic>;
|
||||
if (arr.length != 0)
|
||||
throw Exception('unexpected arr length: expect 0 but see ${arr.length}');
|
||||
return RustSignal();
|
||||
}
|
||||
|
||||
@protected
|
||||
RustUtils dco_decode_rust_utils(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
|
|
@ -3618,14 +3259,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
return (sse_decode_backup_password_keys(deserializer));
|
||||
}
|
||||
|
||||
@protected
|
||||
FrbPreKeyBundle sse_decode_box_autoadd_frb_pre_key_bundle(
|
||||
SseDeserializer deserializer,
|
||||
) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
return (sse_decode_frb_pre_key_bundle(deserializer));
|
||||
}
|
||||
|
||||
@protected
|
||||
PlatformInt64 sse_decode_box_autoadd_i_64(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
|
|
@ -3646,12 +3279,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
return (sse_decode_other_promotion(deserializer));
|
||||
}
|
||||
|
||||
@protected
|
||||
int sse_decode_box_autoadd_u_32(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
return (sse_decode_u_32(deserializer));
|
||||
}
|
||||
|
||||
@protected
|
||||
UserDiscoveryStoreFlutter sse_decode_box_autoadd_user_discovery_store_flutter(
|
||||
SseDeserializer deserializer,
|
||||
|
|
@ -3676,58 +3303,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
return FlutterUserDiscovery();
|
||||
}
|
||||
|
||||
@protected
|
||||
FrbPqcPreKey sse_decode_frb_pqc_pre_key(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
var var_eccPreKeyId = sse_decode_u_32(deserializer);
|
||||
var var_eccPreKey = sse_decode_list_prim_u_8_strict(deserializer);
|
||||
var var_kyberPreKeyId = sse_decode_u_32(deserializer);
|
||||
var var_kyberPreKey = sse_decode_list_prim_u_8_strict(deserializer);
|
||||
var var_kyberPreKeySignature = sse_decode_list_prim_u_8_strict(
|
||||
deserializer,
|
||||
);
|
||||
return FrbPqcPreKey(
|
||||
eccPreKeyId: var_eccPreKeyId,
|
||||
eccPreKey: var_eccPreKey,
|
||||
kyberPreKeyId: var_kyberPreKeyId,
|
||||
kyberPreKey: var_kyberPreKey,
|
||||
kyberPreKeySignature: var_kyberPreKeySignature,
|
||||
);
|
||||
}
|
||||
|
||||
@protected
|
||||
FrbPreKeyBundle sse_decode_frb_pre_key_bundle(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
var var_registrationId = sse_decode_u_32(deserializer);
|
||||
var var_deviceId = sse_decode_u_32(deserializer);
|
||||
var var_preKeyId = sse_decode_opt_box_autoadd_u_32(deserializer);
|
||||
var var_preKeyPublic = sse_decode_opt_list_prim_u_8_strict(deserializer);
|
||||
var var_signedPreKeyId = sse_decode_u_32(deserializer);
|
||||
var var_signedPreKeyPublic = sse_decode_list_prim_u_8_strict(deserializer);
|
||||
var var_signedPreKeySignature = sse_decode_list_prim_u_8_strict(
|
||||
deserializer,
|
||||
);
|
||||
var var_kyberPreKeyId = sse_decode_u_32(deserializer);
|
||||
var var_kyberPreKeyPublic = sse_decode_list_prim_u_8_strict(deserializer);
|
||||
var var_kyberPreKeySignature = sse_decode_list_prim_u_8_strict(
|
||||
deserializer,
|
||||
);
|
||||
var var_identityKey = sse_decode_list_prim_u_8_strict(deserializer);
|
||||
return FrbPreKeyBundle(
|
||||
registrationId: var_registrationId,
|
||||
deviceId: var_deviceId,
|
||||
preKeyId: var_preKeyId,
|
||||
preKeyPublic: var_preKeyPublic,
|
||||
signedPreKeyId: var_signedPreKeyId,
|
||||
signedPreKeyPublic: var_signedPreKeyPublic,
|
||||
signedPreKeySignature: var_signedPreKeySignature,
|
||||
kyberPreKeyId: var_kyberPreKeyId,
|
||||
kyberPreKeyPublic: var_kyberPreKeyPublic,
|
||||
kyberPreKeySignature: var_kyberPreKeySignature,
|
||||
identityKey: var_identityKey,
|
||||
);
|
||||
}
|
||||
|
||||
@protected
|
||||
PlatformInt64 sse_decode_i_64(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
|
|
@ -3748,20 +3323,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
return deserializer.buffer.getPlatformInt64();
|
||||
}
|
||||
|
||||
@protected
|
||||
List<FrbPqcPreKey> sse_decode_list_frb_pqc_pre_key(
|
||||
SseDeserializer deserializer,
|
||||
) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
|
||||
var len_ = sse_decode_i_32(deserializer);
|
||||
var ans_ = <FrbPqcPreKey>[];
|
||||
for (var idx_ = 0; idx_ < len_; ++idx_) {
|
||||
ans_.add(sse_decode_frb_pqc_pre_key(deserializer));
|
||||
}
|
||||
return ans_;
|
||||
}
|
||||
|
||||
@protected
|
||||
List<Uint8List> sse_decode_list_list_prim_u_8_strict(
|
||||
SseDeserializer deserializer,
|
||||
|
|
@ -3854,17 +3415,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
}
|
||||
}
|
||||
|
||||
@protected
|
||||
int? sse_decode_opt_box_autoadd_u_32(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
|
||||
if (sse_decode_bool(deserializer)) {
|
||||
return (sse_decode_box_autoadd_u_32(deserializer));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@protected
|
||||
List<Uint8List>? sse_decode_opt_list_list_prim_u_8_strict(
|
||||
SseDeserializer deserializer,
|
||||
|
|
@ -3975,12 +3525,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
return RustKeyManager();
|
||||
}
|
||||
|
||||
@protected
|
||||
RustSignal sse_decode_rust_signal(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
return RustSignal();
|
||||
}
|
||||
|
||||
@protected
|
||||
RustUtils sse_decode_rust_utils(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
|
|
@ -4314,15 +3858,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
sse_encode_backup_password_keys(self, serializer);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_frb_pre_key_bundle(
|
||||
FrbPreKeyBundle self,
|
||||
SseSerializer serializer,
|
||||
) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
sse_encode_frb_pre_key_bundle(self, serializer);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_i_64(
|
||||
PlatformInt64 self,
|
||||
|
|
@ -4350,12 +3885,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
sse_encode_other_promotion(self, serializer);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_u_32(int self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
sse_encode_u_32(self, serializer);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_user_discovery_store_flutter(
|
||||
UserDiscoveryStoreFlutter self,
|
||||
|
|
@ -4382,35 +3911,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_frb_pqc_pre_key(FrbPqcPreKey self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
sse_encode_u_32(self.eccPreKeyId, serializer);
|
||||
sse_encode_list_prim_u_8_strict(self.eccPreKey, serializer);
|
||||
sse_encode_u_32(self.kyberPreKeyId, serializer);
|
||||
sse_encode_list_prim_u_8_strict(self.kyberPreKey, serializer);
|
||||
sse_encode_list_prim_u_8_strict(self.kyberPreKeySignature, serializer);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_frb_pre_key_bundle(
|
||||
FrbPreKeyBundle self,
|
||||
SseSerializer serializer,
|
||||
) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
sse_encode_u_32(self.registrationId, serializer);
|
||||
sse_encode_u_32(self.deviceId, serializer);
|
||||
sse_encode_opt_box_autoadd_u_32(self.preKeyId, serializer);
|
||||
sse_encode_opt_list_prim_u_8_strict(self.preKeyPublic, serializer);
|
||||
sse_encode_u_32(self.signedPreKeyId, serializer);
|
||||
sse_encode_list_prim_u_8_strict(self.signedPreKeyPublic, serializer);
|
||||
sse_encode_list_prim_u_8_strict(self.signedPreKeySignature, serializer);
|
||||
sse_encode_u_32(self.kyberPreKeyId, serializer);
|
||||
sse_encode_list_prim_u_8_strict(self.kyberPreKeyPublic, serializer);
|
||||
sse_encode_list_prim_u_8_strict(self.kyberPreKeySignature, serializer);
|
||||
sse_encode_list_prim_u_8_strict(self.identityKey, serializer);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
|
|
@ -4430,18 +3930,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
serializer.buffer.putPlatformInt64(self);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_list_frb_pqc_pre_key(
|
||||
List<FrbPqcPreKey> self,
|
||||
SseSerializer serializer,
|
||||
) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
sse_encode_i_32(self.length, serializer);
|
||||
for (final item in self) {
|
||||
sse_encode_frb_pqc_pre_key(item, serializer);
|
||||
}
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_list_list_prim_u_8_strict(
|
||||
List<Uint8List> self,
|
||||
|
|
@ -4536,16 +4024,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
}
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_opt_box_autoadd_u_32(int? self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
|
||||
sse_encode_bool(self != null, serializer);
|
||||
if (self != null) {
|
||||
sse_encode_box_autoadd_u_32(self, serializer);
|
||||
}
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_opt_list_list_prim_u_8_strict(
|
||||
List<Uint8List>? self,
|
||||
|
|
@ -4656,11 +4134,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_rust_signal(RustSignal self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_rust_utils(RustUtils self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import 'bridge/callbacks/user_discovery.dart';
|
|||
import 'bridge/wrapper.dart';
|
||||
import 'bridge/wrapper/backup.dart';
|
||||
import 'bridge/wrapper/key_manager.dart';
|
||||
import 'bridge/wrapper/signal.dart';
|
||||
import 'bridge/wrapper/user_discovery.dart';
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
|
@ -18,7 +17,6 @@ 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';
|
||||
import 'signal/engine.dart';
|
||||
|
||||
abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
RustLibApiImplPlatform({
|
||||
|
|
@ -132,9 +130,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
BackupPasswordKeys dco_decode_box_autoadd_backup_password_keys(dynamic raw);
|
||||
|
||||
@protected
|
||||
FrbPreKeyBundle dco_decode_box_autoadd_frb_pre_key_bundle(dynamic raw);
|
||||
|
||||
@protected
|
||||
PlatformInt64 dco_decode_box_autoadd_i_64(dynamic raw);
|
||||
|
||||
|
|
@ -144,9 +139,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
OtherPromotion dco_decode_box_autoadd_other_promotion(dynamic raw);
|
||||
|
||||
@protected
|
||||
int dco_decode_box_autoadd_u_32(dynamic raw);
|
||||
|
||||
@protected
|
||||
UserDiscoveryStoreFlutter dco_decode_box_autoadd_user_discovery_store_flutter(
|
||||
dynamic raw,
|
||||
|
|
@ -160,12 +152,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
FlutterUserDiscovery dco_decode_flutter_user_discovery(dynamic raw);
|
||||
|
||||
@protected
|
||||
FrbPqcPreKey dco_decode_frb_pqc_pre_key(dynamic raw);
|
||||
|
||||
@protected
|
||||
FrbPreKeyBundle dco_decode_frb_pre_key_bundle(dynamic raw);
|
||||
|
||||
@protected
|
||||
PlatformInt64 dco_decode_i_64(dynamic raw);
|
||||
|
||||
|
|
@ -175,9 +161,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
PlatformInt64 dco_decode_isize(dynamic raw);
|
||||
|
||||
@protected
|
||||
List<FrbPqcPreKey> dco_decode_list_frb_pqc_pre_key(dynamic raw);
|
||||
|
||||
@protected
|
||||
List<Uint8List> dco_decode_list_list_prim_u_8_strict(dynamic raw);
|
||||
|
||||
|
|
@ -203,9 +186,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
PlatformInt64? dco_decode_opt_box_autoadd_i_64(dynamic raw);
|
||||
|
||||
@protected
|
||||
int? dco_decode_opt_box_autoadd_u_32(dynamic raw);
|
||||
|
||||
@protected
|
||||
List<Uint8List>? dco_decode_opt_list_list_prim_u_8_strict(dynamic raw);
|
||||
|
||||
|
|
@ -240,9 +220,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
RustKeyManager dco_decode_rust_key_manager(dynamic raw);
|
||||
|
||||
@protected
|
||||
RustSignal dco_decode_rust_signal(dynamic raw);
|
||||
|
||||
@protected
|
||||
RustUtils dco_decode_rust_utils(dynamic raw);
|
||||
|
||||
|
|
@ -311,11 +288,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
FrbPreKeyBundle sse_decode_box_autoadd_frb_pre_key_bundle(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
PlatformInt64 sse_decode_box_autoadd_i_64(SseDeserializer deserializer);
|
||||
|
||||
|
|
@ -327,9 +299,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
int sse_decode_box_autoadd_u_32(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
UserDiscoveryStoreFlutter sse_decode_box_autoadd_user_discovery_store_flutter(
|
||||
SseDeserializer deserializer,
|
||||
|
|
@ -345,12 +314,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
FrbPqcPreKey sse_decode_frb_pqc_pre_key(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
FrbPreKeyBundle sse_decode_frb_pre_key_bundle(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
PlatformInt64 sse_decode_i_64(SseDeserializer deserializer);
|
||||
|
||||
|
|
@ -360,11 +323,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
PlatformInt64 sse_decode_isize(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
List<FrbPqcPreKey> sse_decode_list_frb_pqc_pre_key(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
List<Uint8List> sse_decode_list_list_prim_u_8_strict(
|
||||
SseDeserializer deserializer,
|
||||
|
|
@ -398,9 +356,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
PlatformInt64? sse_decode_opt_box_autoadd_i_64(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
int? sse_decode_opt_box_autoadd_u_32(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
List<Uint8List>? sse_decode_opt_list_list_prim_u_8_strict(
|
||||
SseDeserializer deserializer,
|
||||
|
|
@ -445,9 +400,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
RustKeyManager sse_decode_rust_key_manager(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RustSignal sse_decode_rust_signal(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RustUtils sse_decode_rust_utils(SseDeserializer deserializer);
|
||||
|
||||
|
|
@ -609,12 +561,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_frb_pre_key_bundle(
|
||||
FrbPreKeyBundle self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_i_64(
|
||||
PlatformInt64 self,
|
||||
|
|
@ -633,9 +579,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_u_32(int self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_user_discovery_store_flutter(
|
||||
UserDiscoveryStoreFlutter self,
|
||||
|
|
@ -654,15 +597,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_frb_pqc_pre_key(FrbPqcPreKey self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_frb_pre_key_bundle(
|
||||
FrbPreKeyBundle self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer);
|
||||
|
||||
|
|
@ -672,12 +606,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
void sse_encode_isize(PlatformInt64 self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_list_frb_pqc_pre_key(
|
||||
List<FrbPqcPreKey> self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_list_list_prim_u_8_strict(
|
||||
List<Uint8List> self,
|
||||
|
|
@ -720,9 +648,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_opt_box_autoadd_u_32(int? self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_opt_list_list_prim_u_8_strict(
|
||||
List<Uint8List>? self,
|
||||
|
|
@ -783,9 +708,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_rust_signal(RustSignal self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_rust_utils(RustUtils self, SseSerializer serializer);
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import 'bridge/callbacks/user_discovery.dart';
|
|||
import 'bridge/wrapper.dart';
|
||||
import 'bridge/wrapper/backup.dart';
|
||||
import 'bridge/wrapper/key_manager.dart';
|
||||
import 'bridge/wrapper/signal.dart';
|
||||
import 'bridge/wrapper/user_discovery.dart';
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
|
@ -20,7 +19,6 @@ 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';
|
||||
import 'signal/engine.dart';
|
||||
|
||||
abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
RustLibApiImplPlatform({
|
||||
|
|
@ -134,9 +132,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
BackupPasswordKeys dco_decode_box_autoadd_backup_password_keys(dynamic raw);
|
||||
|
||||
@protected
|
||||
FrbPreKeyBundle dco_decode_box_autoadd_frb_pre_key_bundle(dynamic raw);
|
||||
|
||||
@protected
|
||||
PlatformInt64 dco_decode_box_autoadd_i_64(dynamic raw);
|
||||
|
||||
|
|
@ -146,9 +141,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
OtherPromotion dco_decode_box_autoadd_other_promotion(dynamic raw);
|
||||
|
||||
@protected
|
||||
int dco_decode_box_autoadd_u_32(dynamic raw);
|
||||
|
||||
@protected
|
||||
UserDiscoveryStoreFlutter dco_decode_box_autoadd_user_discovery_store_flutter(
|
||||
dynamic raw,
|
||||
|
|
@ -162,12 +154,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
FlutterUserDiscovery dco_decode_flutter_user_discovery(dynamic raw);
|
||||
|
||||
@protected
|
||||
FrbPqcPreKey dco_decode_frb_pqc_pre_key(dynamic raw);
|
||||
|
||||
@protected
|
||||
FrbPreKeyBundle dco_decode_frb_pre_key_bundle(dynamic raw);
|
||||
|
||||
@protected
|
||||
PlatformInt64 dco_decode_i_64(dynamic raw);
|
||||
|
||||
|
|
@ -177,9 +163,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
PlatformInt64 dco_decode_isize(dynamic raw);
|
||||
|
||||
@protected
|
||||
List<FrbPqcPreKey> dco_decode_list_frb_pqc_pre_key(dynamic raw);
|
||||
|
||||
@protected
|
||||
List<Uint8List> dco_decode_list_list_prim_u_8_strict(dynamic raw);
|
||||
|
||||
|
|
@ -205,9 +188,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
PlatformInt64? dco_decode_opt_box_autoadd_i_64(dynamic raw);
|
||||
|
||||
@protected
|
||||
int? dco_decode_opt_box_autoadd_u_32(dynamic raw);
|
||||
|
||||
@protected
|
||||
List<Uint8List>? dco_decode_opt_list_list_prim_u_8_strict(dynamic raw);
|
||||
|
||||
|
|
@ -242,9 +222,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
RustKeyManager dco_decode_rust_key_manager(dynamic raw);
|
||||
|
||||
@protected
|
||||
RustSignal dco_decode_rust_signal(dynamic raw);
|
||||
|
||||
@protected
|
||||
RustUtils dco_decode_rust_utils(dynamic raw);
|
||||
|
||||
|
|
@ -313,11 +290,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
FrbPreKeyBundle sse_decode_box_autoadd_frb_pre_key_bundle(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
PlatformInt64 sse_decode_box_autoadd_i_64(SseDeserializer deserializer);
|
||||
|
||||
|
|
@ -329,9 +301,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
int sse_decode_box_autoadd_u_32(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
UserDiscoveryStoreFlutter sse_decode_box_autoadd_user_discovery_store_flutter(
|
||||
SseDeserializer deserializer,
|
||||
|
|
@ -347,12 +316,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
FrbPqcPreKey sse_decode_frb_pqc_pre_key(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
FrbPreKeyBundle sse_decode_frb_pre_key_bundle(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
PlatformInt64 sse_decode_i_64(SseDeserializer deserializer);
|
||||
|
||||
|
|
@ -362,11 +325,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
PlatformInt64 sse_decode_isize(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
List<FrbPqcPreKey> sse_decode_list_frb_pqc_pre_key(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
List<Uint8List> sse_decode_list_list_prim_u_8_strict(
|
||||
SseDeserializer deserializer,
|
||||
|
|
@ -400,9 +358,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
PlatformInt64? sse_decode_opt_box_autoadd_i_64(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
int? sse_decode_opt_box_autoadd_u_32(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
List<Uint8List>? sse_decode_opt_list_list_prim_u_8_strict(
|
||||
SseDeserializer deserializer,
|
||||
|
|
@ -447,9 +402,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
RustKeyManager sse_decode_rust_key_manager(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RustSignal sse_decode_rust_signal(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
RustUtils sse_decode_rust_utils(SseDeserializer deserializer);
|
||||
|
||||
|
|
@ -611,12 +563,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_frb_pre_key_bundle(
|
||||
FrbPreKeyBundle self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_i_64(
|
||||
PlatformInt64 self,
|
||||
|
|
@ -635,9 +581,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_u_32(int self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_user_discovery_store_flutter(
|
||||
UserDiscoveryStoreFlutter self,
|
||||
|
|
@ -656,15 +599,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_frb_pqc_pre_key(FrbPqcPreKey self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_frb_pre_key_bundle(
|
||||
FrbPreKeyBundle self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer);
|
||||
|
||||
|
|
@ -674,12 +608,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
void sse_encode_isize(PlatformInt64 self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_list_frb_pqc_pre_key(
|
||||
List<FrbPqcPreKey> self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_list_list_prim_u_8_strict(
|
||||
List<Uint8List> self,
|
||||
|
|
@ -722,9 +650,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_opt_box_autoadd_u_32(int? self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_opt_list_list_prim_u_8_strict(
|
||||
List<Uint8List>? self,
|
||||
|
|
@ -785,9 +710,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_rust_signal(RustSignal self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_rust_utils(RustUtils self, SseSerializer serializer);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,101 +0,0 @@
|
|||
// This file is automatically generated, so please do not edit it.
|
||||
// @generated by `flutter_rust_bridge`@ 2.12.0.
|
||||
|
||||
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
|
||||
|
||||
import '../frb_generated.dart';
|
||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
||||
|
||||
class FrbPqcPreKey {
|
||||
final int eccPreKeyId;
|
||||
final Uint8List eccPreKey;
|
||||
final int kyberPreKeyId;
|
||||
final Uint8List kyberPreKey;
|
||||
final Uint8List kyberPreKeySignature;
|
||||
|
||||
const FrbPqcPreKey({
|
||||
required this.eccPreKeyId,
|
||||
required this.eccPreKey,
|
||||
required this.kyberPreKeyId,
|
||||
required this.kyberPreKey,
|
||||
required this.kyberPreKeySignature,
|
||||
});
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
eccPreKeyId.hashCode ^
|
||||
eccPreKey.hashCode ^
|
||||
kyberPreKeyId.hashCode ^
|
||||
kyberPreKey.hashCode ^
|
||||
kyberPreKeySignature.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is FrbPqcPreKey &&
|
||||
runtimeType == other.runtimeType &&
|
||||
eccPreKeyId == other.eccPreKeyId &&
|
||||
eccPreKey == other.eccPreKey &&
|
||||
kyberPreKeyId == other.kyberPreKeyId &&
|
||||
kyberPreKey == other.kyberPreKey &&
|
||||
kyberPreKeySignature == other.kyberPreKeySignature;
|
||||
}
|
||||
|
||||
class FrbPreKeyBundle {
|
||||
final int registrationId;
|
||||
final int deviceId;
|
||||
final int? preKeyId;
|
||||
final Uint8List? preKeyPublic;
|
||||
final int signedPreKeyId;
|
||||
final Uint8List signedPreKeyPublic;
|
||||
final Uint8List signedPreKeySignature;
|
||||
final int kyberPreKeyId;
|
||||
final Uint8List kyberPreKeyPublic;
|
||||
final Uint8List kyberPreKeySignature;
|
||||
final Uint8List identityKey;
|
||||
|
||||
const FrbPreKeyBundle({
|
||||
required this.registrationId,
|
||||
required this.deviceId,
|
||||
this.preKeyId,
|
||||
this.preKeyPublic,
|
||||
required this.signedPreKeyId,
|
||||
required this.signedPreKeyPublic,
|
||||
required this.signedPreKeySignature,
|
||||
required this.kyberPreKeyId,
|
||||
required this.kyberPreKeyPublic,
|
||||
required this.kyberPreKeySignature,
|
||||
required this.identityKey,
|
||||
});
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
registrationId.hashCode ^
|
||||
deviceId.hashCode ^
|
||||
preKeyId.hashCode ^
|
||||
preKeyPublic.hashCode ^
|
||||
signedPreKeyId.hashCode ^
|
||||
signedPreKeyPublic.hashCode ^
|
||||
signedPreKeySignature.hashCode ^
|
||||
kyberPreKeyId.hashCode ^
|
||||
kyberPreKeyPublic.hashCode ^
|
||||
kyberPreKeySignature.hashCode ^
|
||||
identityKey.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is FrbPreKeyBundle &&
|
||||
runtimeType == other.runtimeType &&
|
||||
registrationId == other.registrationId &&
|
||||
deviceId == other.deviceId &&
|
||||
preKeyId == other.preKeyId &&
|
||||
preKeyPublic == other.preKeyPublic &&
|
||||
signedPreKeyId == other.signedPreKeyId &&
|
||||
signedPreKeyPublic == other.signedPreKeyPublic &&
|
||||
signedPreKeySignature == other.signedPreKeySignature &&
|
||||
kyberPreKeyId == other.kyberPreKeyId &&
|
||||
kyberPreKeyPublic == other.kyberPreKeyPublic &&
|
||||
kyberPreKeySignature == other.kyberPreKeySignature &&
|
||||
identityKey == other.identityKey;
|
||||
}
|
||||
|
|
@ -35,6 +35,6 @@ class AppState {
|
|||
static bool isInBackgroundTask = false;
|
||||
static bool allowErrorTrackingViaSentry = false;
|
||||
static bool gotMessageFromServer = false;
|
||||
static int latestAppVersionId = 118;
|
||||
static int latestAppVersionId = 117;
|
||||
static bool hasCameraPermissions = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import 'package:get_it/get_it.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/services/api/api.service.dart';
|
||||
import 'package:twonly/src/services/news.service.dart';
|
||||
import 'package:twonly/src/services/api.service.dart';
|
||||
import 'package:twonly/src/services/user.service.dart';
|
||||
|
||||
final GetIt locator = GetIt.instance;
|
||||
|
|
@ -10,11 +9,9 @@ void setupLocator() {
|
|||
locator
|
||||
..registerLazySingleton<UserService>(UserService.new)
|
||||
..registerLazySingleton<ApiService>(ApiService.new)
|
||||
..registerLazySingleton<TwonlyDB>(TwonlyDB.new)
|
||||
..registerLazySingleton<NewsService>(NewsService.new);
|
||||
..registerLazySingleton<TwonlyDB>(TwonlyDB.new);
|
||||
}
|
||||
|
||||
UserService get userService => locator<UserService>();
|
||||
ApiService get apiService => locator<ApiService>();
|
||||
TwonlyDB get twonlyDB => locator<TwonlyDB>();
|
||||
NewsService get newsService => locator<NewsService>();
|
||||
|
|
|
|||
|
|
@ -175,15 +175,6 @@ Future<void> postStartupTasks() async {
|
|||
// 2. Service initializations
|
||||
unawaited(finishStartedPreprocessing());
|
||||
unawaited(createPushAvatars());
|
||||
unawaited(
|
||||
newsService.init().then((_) {
|
||||
final lastDownload = newsService.lastDownloadedAt;
|
||||
if (lastDownload == null ||
|
||||
DateTime.now().difference(lastDownload) >= const Duration(days: 7)) {
|
||||
newsService.fetchFeed();
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
unawaited(UserDiscoveryService.verifyInitializationOnStartup());
|
||||
|
||||
|
|
|
|||
|
|
@ -48,7 +48,6 @@ class Routes {
|
|||
static const String settingsHelpFaqVerifyBadge =
|
||||
'/settings/help/faq/verifybadge';
|
||||
static const String settingsHelpContactUs = '/settings/help/contact_us';
|
||||
static const String settingsHelpNews = '/settings/help/news';
|
||||
static const String settingsHelpDiagnostics = '/settings/help/diagnostics';
|
||||
static const String settingsHelpUserStudy = '/settings/help/user_study';
|
||||
static const String settingsHelpUserStudyQuestionnaire =
|
||||
|
|
|
|||
|
|
@ -275,7 +275,7 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
|
|||
),
|
||||
],
|
||||
)..where(groups.isDirectChat.equals(false)));
|
||||
return await query.map((row) => row.readTable(groupMembers)).get();
|
||||
return query.map((row) => row.readTable(groupMembers)).get();
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
return [];
|
||||
|
|
@ -340,25 +340,6 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
|
|||
.write(GroupsCompanion(lastMessageExchange: Value(clampedLastMessage)));
|
||||
}
|
||||
|
||||
Future<void> increaseMemberLastMessage(
|
||||
String groupId,
|
||||
int contactId,
|
||||
DateTime newLastMessage,
|
||||
) async {
|
||||
final now = clock.now();
|
||||
final clampedLastMessage = newLastMessage.isAfter(now)
|
||||
? now
|
||||
: newLastMessage;
|
||||
await (update(groupMembers)..where(
|
||||
(t) =>
|
||||
t.groupId.equals(groupId) &
|
||||
t.contactId.equals(contactId) &
|
||||
(t.lastMessage.isNull() |
|
||||
t.lastMessage.isSmallerThanValue(clampedLastMessage)),
|
||||
))
|
||||
.write(GroupMembersCompanion(lastMessage: Value(clampedLastMessage)));
|
||||
}
|
||||
|
||||
Stream<List<Group>> watchNonDirectGroupsForMember(int contactId) {
|
||||
final query =
|
||||
select(groups).join([
|
||||
|
|
|
|||
|
|
@ -1,91 +0,0 @@
|
|||
import 'package:drift/drift.dart';
|
||||
import 'package:twonly/src/database/tables/labels.table.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
|
||||
part 'labels.dao.g.dart';
|
||||
|
||||
@DriftAccessor(
|
||||
tables: [
|
||||
Labels,
|
||||
ContactLabels,
|
||||
],
|
||||
)
|
||||
class LabelsDao extends DatabaseAccessor<TwonlyDB> with _$LabelsDaoMixin {
|
||||
LabelsDao(super.db);
|
||||
|
||||
Stream<List<Label>> watchAllLabels() {
|
||||
return (select(labels)..orderBy([(t) => OrderingTerm(expression: t.name)])).watch();
|
||||
}
|
||||
|
||||
Future<List<Label>> getAllLabels() {
|
||||
return (select(labels)..orderBy([(t) => OrderingTerm(expression: t.name)])).get();
|
||||
}
|
||||
|
||||
Stream<List<Label>> watchContactLabels(int contactId) {
|
||||
final query = select(contactLabels).join([
|
||||
innerJoin(labels, labels.id.equalsExp(contactLabels.labelId)),
|
||||
])..where(contactLabels.contactId.equals(contactId));
|
||||
|
||||
return query.watch().map(
|
||||
(rows) => rows.map((row) => row.readTable(labels)).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<Label>> getContactLabels(int contactId) {
|
||||
final query = select(contactLabels).join([
|
||||
innerJoin(labels, labels.id.equalsExp(contactLabels.labelId)),
|
||||
])..where(contactLabels.contactId.equals(contactId));
|
||||
|
||||
return query.get().then(
|
||||
(rows) => rows.map((row) => row.readTable(labels)).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> setContactLabels(int contactId, List<int> labelIds) async {
|
||||
final sanitizedLabelIds = labelIds.take(3).toList();
|
||||
await transaction(() async {
|
||||
await (delete(contactLabels)..where((t) => t.contactId.equals(contactId))).go();
|
||||
if (sanitizedLabelIds.isNotEmpty) {
|
||||
await batch((b) {
|
||||
b.insertAll(
|
||||
contactLabels,
|
||||
sanitizedLabelIds.map(
|
||||
(lId) => ContactLabelsCompanion.insert(
|
||||
contactId: contactId,
|
||||
labelId: lId,
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<int> createLabel(String name, int textColor, int backgroundColor) {
|
||||
final sanitizedName = name.length > 8 ? name.substring(0, 8) : name;
|
||||
return into(labels).insert(
|
||||
LabelsCompanion.insert(
|
||||
name: sanitizedName,
|
||||
textColor: textColor,
|
||||
backgroundColor: backgroundColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<bool> updateLabel(int id, String name, int textColor, int backgroundColor) {
|
||||
final sanitizedName = name.length > 8 ? name.substring(0, 8) : name;
|
||||
return (update(labels)..where((t) => t.id.equals(id)))
|
||||
.write(
|
||||
LabelsCompanion(
|
||||
name: Value(sanitizedName),
|
||||
textColor: Value(textColor),
|
||||
backgroundColor: Value(backgroundColor),
|
||||
),
|
||||
)
|
||||
.then((rows) => rows > 0);
|
||||
}
|
||||
|
||||
Future<int> deleteLabel(int id) {
|
||||
return (delete(labels)..where((t) => t.id.equals(id))).go();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'labels.dao.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
mixin _$LabelsDaoMixin on DatabaseAccessor<TwonlyDB> {
|
||||
$LabelsTable get labels => attachedDatabase.labels;
|
||||
$ContactsTable get contacts => attachedDatabase.contacts;
|
||||
$ContactLabelsTable get contactLabels => attachedDatabase.contactLabels;
|
||||
LabelsDaoManager get managers => LabelsDaoManager(this);
|
||||
}
|
||||
|
||||
class LabelsDaoManager {
|
||||
final _$LabelsDaoMixin _db;
|
||||
LabelsDaoManager(this._db);
|
||||
$$LabelsTableTableManager get labels =>
|
||||
$$LabelsTableTableManager(_db.attachedDatabase, _db.labels);
|
||||
$$ContactsTableTableManager get contacts =>
|
||||
$$ContactsTableTableManager(_db.attachedDatabase, _db.contacts);
|
||||
$$ContactLabelsTableTableManager get contactLabels =>
|
||||
$$ContactLabelsTableTableManager(_db.attachedDatabase, _db.contactLabels);
|
||||
}
|
||||
|
|
@ -1,11 +1,8 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:hashlib/random.dart';
|
||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||
import 'package:twonly/src/database/tables/messages.table.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
|
||||
part 'mediafiles.dao.g.dart';
|
||||
|
|
@ -123,7 +120,6 @@ class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
|
|||
(t) =>
|
||||
t.stored.equals(true) &
|
||||
(t.storedFileHash.isNull() |
|
||||
t.blurhash.isNull() |
|
||||
t.hasCropAnalyzed.equals(false) |
|
||||
(t.hasThumbnail.equals(false) &
|
||||
t.type.equals(MediaType.audio.name).not()) |
|
||||
|
|
@ -145,12 +141,7 @@ class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
|
|||
|
||||
Stream<List<MediaFile>> watchAllStoredMediaFiles() {
|
||||
final query =
|
||||
(select(mediaFiles)..where(
|
||||
(t) =>
|
||||
t.stored.equals(true) |
|
||||
t.cloudState.equals(CloudState.uploaded.name),
|
||||
))
|
||||
.join([])
|
||||
(select(mediaFiles)..where((t) => t.stored.equals(true))).join([])
|
||||
..groupBy([
|
||||
const CustomExpression<Object>(
|
||||
'COALESCE(stored_file_hash, media_id)',
|
||||
|
|
@ -220,59 +211,12 @@ class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
|
|||
final rows = await select(mediaFiles).get();
|
||||
final stats = <MediaType, int>{};
|
||||
|
||||
final Set<String> existingPaths;
|
||||
if (rows.isNotEmpty) {
|
||||
final dummyMs = MediaFileService(rows.first);
|
||||
final storedDir = dummyMs.storedPath.parent;
|
||||
if (storedDir.existsSync()) {
|
||||
existingPaths = storedDir
|
||||
.listSync()
|
||||
.whereType<File>()
|
||||
.map((f) => f.path)
|
||||
.toSet();
|
||||
} else {
|
||||
existingPaths = <String>{};
|
||||
}
|
||||
} else {
|
||||
existingPaths = <String>{};
|
||||
}
|
||||
|
||||
for (final row in rows) {
|
||||
final type = row.type;
|
||||
final ms = MediaFileService(row);
|
||||
if (existingPaths.contains(ms.storedPath.path)) {
|
||||
final size = row.sizeInBytes ?? 0;
|
||||
stats[type] = (stats[type] ?? 0) + size;
|
||||
}
|
||||
final size = row.sizeInBytes ?? 0;
|
||||
stats[type] = (stats[type] ?? 0) + size;
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
Future<List<MediaFile>> getMemoriesToBackup() async {
|
||||
return (select(mediaFiles)..where(
|
||||
(t) =>
|
||||
t.stored.equals(true) & t.cloudState.equals(CloudState.none.name),
|
||||
))
|
||||
.get();
|
||||
}
|
||||
|
||||
Future<int> getCloudOnlyMemoriesCount() async {
|
||||
final query = select(mediaFiles)
|
||||
..where(
|
||||
(t) =>
|
||||
t.stored.equals(true) &
|
||||
t.cloudState.equals(CloudState.uploaded.name),
|
||||
);
|
||||
final rows = await query.get();
|
||||
|
||||
var count = 0;
|
||||
for (final row in rows) {
|
||||
final ms = MediaFileService(row);
|
||||
if (!(ms.storedPath.existsSync() && ms.storedPath.lengthSync() > 0)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -489,28 +489,22 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
|||
|
||||
await into(messages).insertOnConflictUpdate(insertMessage);
|
||||
|
||||
final msgTime = insertMessage.createdAt.present
|
||||
? insertMessage.createdAt.value
|
||||
: clock.now();
|
||||
|
||||
await twonlyDB.groupsDao.updateGroup(
|
||||
message.groupId.value,
|
||||
const GroupsCompanion(
|
||||
archived: Value(false),
|
||||
deletedContent: Value(false),
|
||||
GroupsCompanion(
|
||||
lastMessageExchange: Value(clock.now()),
|
||||
archived: const Value(false),
|
||||
deletedContent: const Value(false),
|
||||
),
|
||||
);
|
||||
|
||||
await twonlyDB.groupsDao.increaseLastMessageExchange(
|
||||
message.groupId.value,
|
||||
msgTime,
|
||||
);
|
||||
|
||||
if (message.senderId.present && message.senderId.value != null) {
|
||||
await twonlyDB.groupsDao.increaseMemberLastMessage(
|
||||
if (message.senderId.present) {
|
||||
await twonlyDB.groupsDao.updateMember(
|
||||
message.groupId.value,
|
||||
message.senderId.value!,
|
||||
msgTime,
|
||||
GroupMembersCompanion(
|
||||
lastMessage: Value(clock.now()),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
|
|||
await (delete(receivedReceipts)..where(
|
||||
(t) => (t.createdAt.isSmallerThanValue(
|
||||
clock.now().subtract(
|
||||
const Duration(days: 45),
|
||||
const Duration(days: 25),
|
||||
),
|
||||
)),
|
||||
))
|
||||
|
|
@ -250,25 +250,6 @@ class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
|
|||
null;
|
||||
}
|
||||
|
||||
/// Claims a new delivery-receipt attempt after [cooldown] has elapsed.
|
||||
///
|
||||
/// Updating the timestamp before sending prevents repeated server batches from
|
||||
/// starting multiple delivery-receipt attempts during the same cooldown.
|
||||
Future<bool> claimDuplicateReceiptResend(
|
||||
String receiptId,
|
||||
Duration cooldown,
|
||||
) async {
|
||||
final now = clock.now();
|
||||
final updated =
|
||||
await (update(receivedReceipts)..where(
|
||||
(t) =>
|
||||
t.receiptId.equals(receiptId) &
|
||||
t.createdAt.isSmallerOrEqualValue(now.subtract(cooldown)),
|
||||
))
|
||||
.write(ReceivedReceiptsCompanion(createdAt: Value(now)));
|
||||
return updated > 0;
|
||||
}
|
||||
|
||||
Future<void> gotReceipt(String receiptId) async {
|
||||
await into(
|
||||
receivedReceipts,
|
||||
|
|
|
|||
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
|
|
@ -63,7 +63,6 @@ class SignalSignedPreKeyStore extends SignedPreKeyStore {
|
|||
.insert(companion, mode: InsertMode.insertOrReplace);
|
||||
} catch (e) {
|
||||
Log.error('$e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
import 'package:drift/drift.dart';
|
||||
|
||||
|
||||
enum SignalVersion { v1, v2 }
|
||||
|
||||
@DataClassName('Contact')
|
||||
class Contacts extends Table {
|
||||
IntColumn get userId => integer()();
|
||||
|
|
@ -26,9 +23,6 @@ class Contacts extends Table {
|
|||
|
||||
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
||||
|
||||
TextColumn get signalVersion =>
|
||||
textEnum<SignalVersion>().withDefault(const Constant('v1'))();
|
||||
|
||||
// User Discovery
|
||||
BlobColumn get userDiscoveryVersion => blob().nullable()();
|
||||
BoolColumn get userDiscoveryExcluded =>
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ class Groups extends Table {
|
|||
boolean().withDefault(const Constant(false))();
|
||||
|
||||
IntColumn get deleteMessagesAfterMilliseconds => integer().withDefault(
|
||||
const Constant(1000 * 60 * 60 * 24),
|
||||
const Constant(defaultDeleteMessagesAfterMilliseconds),
|
||||
)();
|
||||
|
||||
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
import 'package:drift/drift.dart';
|
||||
import 'package:twonly/src/database/tables/contacts.table.dart';
|
||||
|
||||
@DataClassName('Label')
|
||||
class Labels extends Table {
|
||||
IntColumn get id => integer().autoIncrement()();
|
||||
TextColumn get name => text()();
|
||||
IntColumn get textColor => integer()();
|
||||
IntColumn get backgroundColor => integer()();
|
||||
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
||||
}
|
||||
|
||||
@DataClassName('ContactLabel')
|
||||
class ContactLabels extends Table {
|
||||
IntColumn get contactId => integer().references(
|
||||
Contacts,
|
||||
#userId,
|
||||
onDelete: KeyAction.cascade,
|
||||
)();
|
||||
IntColumn get labelId => integer().references(
|
||||
Labels,
|
||||
#id,
|
||||
onDelete: KeyAction.cascade,
|
||||
)();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {contactId, labelId};
|
||||
}
|
||||
|
|
@ -8,12 +8,6 @@ enum MediaType {
|
|||
audio,
|
||||
}
|
||||
|
||||
enum CloudState {
|
||||
none,
|
||||
pending,
|
||||
uploaded,
|
||||
}
|
||||
|
||||
enum UploadState {
|
||||
// Image/Video was taken. A database entry was created to track it...
|
||||
initialized,
|
||||
|
|
@ -49,9 +43,6 @@ class MediaFiles extends Table {
|
|||
TextColumn get type => textEnum<MediaType>()();
|
||||
|
||||
TextColumn get uploadState => textEnum<UploadState>().nullable()();
|
||||
TextColumn get cloudState =>
|
||||
textEnum<CloudState>().withDefault(const Constant('none'))();
|
||||
TextColumn get blurhash => text().nullable()();
|
||||
TextColumn get downloadState => textEnum<DownloadState>().nullable()();
|
||||
|
||||
BoolColumn get requiresAuthentication =>
|
||||
|
|
@ -78,13 +69,15 @@ class MediaFiles extends Table {
|
|||
|
||||
BlobColumn get storedFileHash => blob().nullable()();
|
||||
|
||||
BoolColumn get hasThumbnail => boolean().withDefault(const Constant(false))();
|
||||
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};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
|||
enum MessageType { media, text, contacts, restoreFlameCounter, askAboutUser }
|
||||
|
||||
@DataClassName('Message')
|
||||
@TableIndex(name: 'idx_messages_group_id_created_at', columns: {#groupId, #createdAt})
|
||||
class Messages extends Table {
|
||||
TextColumn get groupId =>
|
||||
text().references(Groups, #groupId, onDelete: KeyAction.cascade)();
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import 'package:twonly/src/database/tables/contacts.table.dart';
|
|||
import 'package:twonly/src/database/tables/messages.table.dart';
|
||||
|
||||
@DataClassName('Receipt')
|
||||
@TableIndex(name: 'idx_receipts_message_id', columns: {#messageId})
|
||||
class Receipts extends Table {
|
||||
TextColumn get receiptId => text()();
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ 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';
|
||||
import 'package:twonly/src/database/daos/labels.dao.dart';
|
||||
import 'package:twonly/src/database/daos/mediafiles.dao.dart';
|
||||
import 'package:twonly/src/database/daos/messages.dao.dart';
|
||||
import 'package:twonly/src/database/daos/reactions.dao.dart';
|
||||
|
|
@ -16,7 +15,6 @@ 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/labels.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';
|
||||
|
|
@ -61,8 +59,6 @@ part 'twonly.db.g.dart';
|
|||
UserDiscoveryShares,
|
||||
Shortcuts,
|
||||
ShortcutMembers,
|
||||
Labels,
|
||||
ContactLabels,
|
||||
],
|
||||
daos: [
|
||||
MessagesDao,
|
||||
|
|
@ -74,7 +70,6 @@ part 'twonly.db.g.dart';
|
|||
UserDiscoveryDao,
|
||||
KeyVerificationDao,
|
||||
ShortcutsDao,
|
||||
LabelsDao,
|
||||
],
|
||||
)
|
||||
class TwonlyDB extends _$TwonlyDB {
|
||||
|
|
@ -87,7 +82,7 @@ class TwonlyDB extends _$TwonlyDB {
|
|||
TwonlyDB.forTesting(DatabaseConnection super.connection);
|
||||
|
||||
@override
|
||||
int get schemaVersion => 25;
|
||||
int get schemaVersion => 22;
|
||||
|
||||
static QueryExecutor _openConnection() {
|
||||
final connection = driftDatabase(
|
||||
|
|
@ -280,23 +275,6 @@ class TwonlyDB extends _$TwonlyDB {
|
|||
schema.contacts.recoveryContactsThreshold,
|
||||
);
|
||||
},
|
||||
from22To23: (m, schema) async {
|
||||
await m.addColumn(
|
||||
schema.mediaFiles,
|
||||
schema.mediaFiles.cloudState,
|
||||
);
|
||||
await m.addColumn(
|
||||
schema.mediaFiles,
|
||||
schema.mediaFiles.blurhash,
|
||||
);
|
||||
},
|
||||
from23To24: (m, schema) async {
|
||||
await m.createTable(schema.labels);
|
||||
await m.createTable(schema.contactLabels);
|
||||
},
|
||||
from24To25: (m, schema) async {
|
||||
await m.addColumn(schema.contacts, schema.contacts.signalVersion);
|
||||
},
|
||||
)(m, from, to);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -806,11 +806,11 @@ abstract class AppLocalizations {
|
|||
/// **'Feedback submitted successfully!'**
|
||||
String get contactUsSuccess;
|
||||
|
||||
/// No description provided for @hideNewsIcon.
|
||||
/// No description provided for @contactUsShortcut.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Hide News Icon'**
|
||||
String get hideNewsIcon;
|
||||
/// **'Hide Feedback Icon'**
|
||||
String get contactUsShortcut;
|
||||
|
||||
/// No description provided for @settingsHelpTerms.
|
||||
///
|
||||
|
|
@ -926,6 +926,12 @@ abstract class AppLocalizations {
|
|||
/// **'Verified via link.'**
|
||||
String get verificationTypeLink;
|
||||
|
||||
/// No description provided for @verificationTypeContactSharedByVerified.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Contact received from a verified contact.'**
|
||||
String get verificationTypeContactSharedByVerified;
|
||||
|
||||
/// No description provided for @verificationTypeMigratedFromOldVersion.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -1265,13 +1271,13 @@ abstract class AppLocalizations {
|
|||
/// No description provided for @proFeature3.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'✓ 25GB Memories storage'**
|
||||
/// **'✓ Restore flames'**
|
||||
String get proFeature3;
|
||||
|
||||
/// No description provided for @proFeature4.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'✓ Restore flames'**
|
||||
/// **'✓ Support twonly'**
|
||||
String get proFeature4;
|
||||
|
||||
/// No description provided for @familyFeature1.
|
||||
|
|
@ -1289,7 +1295,7 @@ abstract class AppLocalizations {
|
|||
/// No description provided for @familyFeature3.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'✓ 50GB Memories storage'**
|
||||
/// **'✓ Restore flames'**
|
||||
String get familyFeature3;
|
||||
|
||||
/// No description provided for @familyFeature4.
|
||||
|
|
@ -1424,12 +1430,6 @@ abstract class AppLocalizations {
|
|||
/// **'Delete for me'**
|
||||
String get deleteOkBtnForMe;
|
||||
|
||||
/// No description provided for @deleteOnlyForMe.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Delete only for me'**
|
||||
String get deleteOnlyForMe;
|
||||
|
||||
/// No description provided for @deleteImageTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -1448,60 +1448,6 @@ abstract class AppLocalizations {
|
|||
/// **'{count, plural, =1 {The image will be irrevocably deleted.} other {The {count} images will be irrevocably deleted.}}'**
|
||||
String deleteMemoriesBody(num count);
|
||||
|
||||
/// No description provided for @deleteMemoriesLocalOnly.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Local Only'**
|
||||
String get deleteMemoriesLocalOnly;
|
||||
|
||||
/// No description provided for @deleteMemoriesCompletely.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Completely'**
|
||||
String get deleteMemoriesCompletely;
|
||||
|
||||
/// No description provided for @memoriesSelectedCount.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'{count, plural, =1 {1 Element} other {{count} Elements}}'**
|
||||
String memoriesSelectedCount(num count);
|
||||
|
||||
/// No description provided for @memoriesMenuSelectAll.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Select all'**
|
||||
String get memoriesMenuSelectAll;
|
||||
|
||||
/// No description provided for @memoriesMenuDeselectAll.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Deselect all'**
|
||||
String get memoriesMenuDeselectAll;
|
||||
|
||||
/// No description provided for @memoriesMenuExport.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Export to gallery'**
|
||||
String get memoriesMenuExport;
|
||||
|
||||
/// No description provided for @memoriesMenuFavorite.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Favorite'**
|
||||
String get memoriesMenuFavorite;
|
||||
|
||||
/// No description provided for @memoriesMenuDelete.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Delete completely'**
|
||||
String get memoriesMenuDelete;
|
||||
|
||||
/// No description provided for @memoriesMenuDeleteLocal.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Delete locally'**
|
||||
String get memoriesMenuDeleteLocal;
|
||||
|
||||
/// No description provided for @settingsBackup.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -1547,7 +1493,7 @@ abstract class AppLocalizations {
|
|||
/// No description provided for @backupArchiveHeader.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Contacts & Messages'**
|
||||
/// **'Contacts, Settings and Messages'**
|
||||
String get backupArchiveHeader;
|
||||
|
||||
/// No description provided for @backupLastBackupDate.
|
||||
|
|
@ -1601,7 +1547,7 @@ abstract class AppLocalizations {
|
|||
/// No description provided for @backupSelectStrongPassword.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Select a secure password. It is recommended to generate and store it via your password manager of choice.'**
|
||||
/// **'Choose a secure password. This is required if you want to restore your twonly Backup.'**
|
||||
String get backupSelectStrongPassword;
|
||||
|
||||
/// No description provided for @password.
|
||||
|
|
@ -1631,7 +1577,7 @@ abstract class AppLocalizations {
|
|||
/// No description provided for @backupEnableBackup.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Enable Password Backup'**
|
||||
/// **'Activate automatic backup'**
|
||||
String get backupEnableBackup;
|
||||
|
||||
/// No description provided for @backupTwonlySaveNow.
|
||||
|
|
@ -1658,36 +1604,6 @@ abstract class AppLocalizations {
|
|||
/// **'You can only change your password after you have authenticated!'**
|
||||
String get backupChangePasswordAuthFailed;
|
||||
|
||||
/// No description provided for @backupFreeSpaceWithCloud.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Free Space with Cloud Backup'**
|
||||
String get backupFreeSpaceWithCloud;
|
||||
|
||||
/// No description provided for @backupMemoriesNotEnabled.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Not enabled'**
|
||||
String get backupMemoriesNotEnabled;
|
||||
|
||||
/// No description provided for @backupMemoriesUpgradeRequired.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Upgrade required'**
|
||||
String get backupMemoriesUpgradeRequired;
|
||||
|
||||
/// No description provided for @todayAt.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Today at {time}'**
|
||||
String todayAt(Object time);
|
||||
|
||||
/// No description provided for @yesterdayAt.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Yesterday at {time}'**
|
||||
String yesterdayAt(Object time);
|
||||
|
||||
/// No description provided for @twonlySafeRecoverTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -1754,6 +1670,12 @@ abstract class AppLocalizations {
|
|||
/// **'Retransmission requested'**
|
||||
String get retransmissionRequested;
|
||||
|
||||
/// No description provided for @openChangeLog.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Open changelog automatically'**
|
||||
String get openChangeLog;
|
||||
|
||||
/// No description provided for @reportUserTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -2162,6 +2084,12 @@ abstract class AppLocalizations {
|
|||
/// **'wants to connect with you.'**
|
||||
String get notificationContactRequest;
|
||||
|
||||
/// No description provided for @notificationContactRequestUnknownUser.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'have received a new contact request.'**
|
||||
String get notificationContactRequestUnknownUser;
|
||||
|
||||
/// No description provided for @notificationAcceptRequest.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -2216,6 +2144,18 @@ abstract class AppLocalizations {
|
|||
/// **'has responded{inGroup}.'**
|
||||
String notificationResponse(Object inGroup);
|
||||
|
||||
/// No description provided for @notificationTitleUnknown.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'You have a new message.'**
|
||||
String get notificationTitleUnknown;
|
||||
|
||||
/// No description provided for @notificationBodyUnknown.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Open twonly to learn more.'**
|
||||
String get notificationBodyUnknown;
|
||||
|
||||
/// No description provided for @notificationCategoryMessageTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -2498,6 +2438,12 @@ abstract class AppLocalizations {
|
|||
/// **'Back up your twonly identity, as this is the only way to restore your account if you uninstall the app or lose your phone.'**
|
||||
String get onboardingBackupBody;
|
||||
|
||||
/// No description provided for @onboardingVerificationBadgeTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Verification Badge'**
|
||||
String get onboardingVerificationBadgeTitle;
|
||||
|
||||
/// No description provided for @onboardingUserDiscoveryShareFriends.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -3440,12 +3386,6 @@ abstract class AppLocalizations {
|
|||
/// **'Zero ads. Total privacy.'**
|
||||
String get subscriptionPledgeSubtitle;
|
||||
|
||||
/// No description provided for @subscriptionManage.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Manage subscription'**
|
||||
String get subscriptionManage;
|
||||
|
||||
/// No description provided for @dragToZoom.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -3494,6 +3434,18 @@ abstract class AppLocalizations {
|
|||
/// **'Step-by-step setup so you can decide for yourself.'**
|
||||
String get onboardingProfileSelectionCustomizeDesc;
|
||||
|
||||
/// No description provided for @onboardingProfileSelectionStrictTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Enhanced Protection'**
|
||||
String get onboardingProfileSelectionStrictTitle;
|
||||
|
||||
/// No description provided for @onboardingProfileSelectionStrictDesc.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Maximum anti-phishing defense. Recommended for *journalists & public figures*.'**
|
||||
String get onboardingProfileSelectionStrictDesc;
|
||||
|
||||
/// No description provided for @replyFlameRestored.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -3506,6 +3458,24 @@ abstract class AppLocalizations {
|
|||
/// **'Ask a friend'**
|
||||
String get replyAskAFriend;
|
||||
|
||||
/// No description provided for @unverifiedWarningDirectTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Identity not verified in person'**
|
||||
String get unverifiedWarningDirectTitle;
|
||||
|
||||
/// No description provided for @unverifiedWarningGroupTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Not all members are verified in person'**
|
||||
String get unverifiedWarningGroupTitle;
|
||||
|
||||
/// No description provided for @unverifiedWarningBody.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'*Avoid sharing sensitive data*. Risk of *impersonation* without manual verification.'**
|
||||
String get unverifiedWarningBody;
|
||||
|
||||
/// No description provided for @unverifiedWarningButton.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -3593,8 +3563,12 @@ abstract class AppLocalizations {
|
|||
/// No description provided for @importGalleryComplete.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Import complete: {imported} successfully imported and {failed} failed.'**
|
||||
String importGalleryComplete(Object imported, Object failed);
|
||||
/// **'Import complete: {imported} successfully imported, {duplicated} duplicated and {failed} failed.'**
|
||||
String importGalleryComplete(
|
||||
Object imported,
|
||||
Object duplicated,
|
||||
Object failed,
|
||||
);
|
||||
|
||||
/// No description provided for @importGalleryGrantAccess.
|
||||
///
|
||||
|
|
@ -3713,7 +3687,7 @@ abstract class AppLocalizations {
|
|||
/// No description provided for @passwordlessRecovery.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Trusted Friends'**
|
||||
/// **'Passwordless Recovery'**
|
||||
String get passwordlessRecovery;
|
||||
|
||||
/// No description provided for @passwordlessRecoveryNotConfigured.
|
||||
|
|
@ -3830,18 +3804,54 @@ abstract class AppLocalizations {
|
|||
/// **'These friends either have not yet received their share or do not use twonly actively anymore, which could mean that they maybe cannot help.'**
|
||||
String get passwordlessRecoveryInactiveFriendsDesc;
|
||||
|
||||
/// No description provided for @passwordlessRecoveryNotEnoughFriends.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Not enough friends selected. You need at least 3.'**
|
||||
String get passwordlessRecoveryNotEnoughFriends;
|
||||
|
||||
/// No description provided for @passwordlessRecoveryLoading.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Loading...'**
|
||||
String get passwordlessRecoveryLoading;
|
||||
|
||||
/// No description provided for @passwordlessRecoveryEnableSuccess.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Trusted Friends successfully set up!'**
|
||||
/// **'Passwordless recovery successfully enabled!'**
|
||||
String get passwordlessRecoveryEnableSuccess;
|
||||
|
||||
/// No description provided for @passwordlessRecoveryEnableFailed.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Failed to enable passwordless recovery.'**
|
||||
String get passwordlessRecoveryEnableFailed;
|
||||
|
||||
/// No description provided for @passwordlessRecoveryNeedAtLeast3.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'You need at least 3 trusted friends.'**
|
||||
String get passwordlessRecoveryNeedAtLeast3;
|
||||
|
||||
/// No description provided for @passwordlessRecoveryInvalidPin.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Invalid PIN'**
|
||||
String get passwordlessRecoveryInvalidPin;
|
||||
|
||||
/// No description provided for @passwordlessRecoveryEnterPin.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Please enter a PIN.'**
|
||||
String get passwordlessRecoveryEnterPin;
|
||||
|
||||
/// No description provided for @passwordlessRecoveryPinMinLength.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'PIN must be at least 4 digits.'**
|
||||
String get passwordlessRecoveryPinMinLength;
|
||||
|
||||
/// No description provided for @passwordlessRecoveryEnterEmail.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -3851,19 +3861,19 @@ abstract class AppLocalizations {
|
|||
/// No description provided for @passwordlessRecoveryEnableBtn.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Enable Trusted Friends'**
|
||||
/// **'Enable Passwordless Recovery'**
|
||||
String get passwordlessRecoveryEnableBtn;
|
||||
|
||||
/// No description provided for @passwordlessRecoveryRecoverBtn.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Recover via Trusted Friends'**
|
||||
/// **'Recover passwordless'**
|
||||
String get passwordlessRecoveryRecoverBtn;
|
||||
|
||||
/// No description provided for @passwordlessRecoveryModifyBtn.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Modify Trusted Friends'**
|
||||
/// **'Modify Passwordless Recovery'**
|
||||
String get passwordlessRecoveryModifyBtn;
|
||||
|
||||
/// No description provided for @passwordlessRecoveryStatusEnabled.
|
||||
|
|
@ -3881,7 +3891,7 @@ abstract class AppLocalizations {
|
|||
/// No description provided for @passwordlessRecoveryInfoHowItWorksDesc.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'twonly operates without phone numbers, which is why we rely on a decentralized recovery mechanism. Using Shamir\'s Secret Sharing, your cryptographic identity is split into independent shares and distributed among your trusted friends. To restore access, a predefined number of these friends must combine their shares.'**
|
||||
/// **'Because twonly operates without central user accounts or phone numbers to maximize privacy, we rely on a decentralized recovery mechanism. Using Shamir\'s Secret Sharing, your cryptographic identity is split into independent shares and distributed among your trusted friends. To restore access, a predefined threshold of these friends must combine their shares.'**
|
||||
String get passwordlessRecoveryInfoHowItWorksDesc;
|
||||
|
||||
/// No description provided for @passwordlessRecoveryInfoWhySecondFactor.
|
||||
|
|
@ -3893,7 +3903,7 @@ abstract class AppLocalizations {
|
|||
/// No description provided for @passwordlessRecoveryInfoWhySecondFactorDesc.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'The second factor (Email or PIN) provides important additional protection. Even if your friends were to secretly team up, they still couldn\'t access your account without this second factor.'**
|
||||
/// **'The second factor (Email or PIN) serves as a vital cryptographic safeguard against malicious collusion. If your trusted friends were to coordinate their shares behind your back, they still wouldn\'t be able to decrypt your identity without the second factor key.'**
|
||||
String get passwordlessRecoveryInfoWhySecondFactorDesc;
|
||||
|
||||
/// No description provided for @passwordlessRecoveryInfoGotIt.
|
||||
|
|
@ -3956,18 +3966,6 @@ abstract class AppLocalizations {
|
|||
/// **'Select trusted friends'**
|
||||
String get passwordlessRecoverySelectFriends;
|
||||
|
||||
/// No description provided for @passwordlessRecoveryTrustedFriends.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Account recovery'**
|
||||
String get passwordlessRecoveryTrustedFriends;
|
||||
|
||||
/// No description provided for @passwordlessRecoveryDoneBtn.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Done ({count})'**
|
||||
String passwordlessRecoveryDoneBtn(num count);
|
||||
|
||||
/// No description provided for @passwordlessRecoveryNoFriendsSelected.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -4034,16 +4032,10 @@ abstract class AppLocalizations {
|
|||
/// **'No recovery share stored for this contact.'**
|
||||
String get passwordlessRecoveryNoShareStored;
|
||||
|
||||
/// No description provided for @passwordlessRecoveryEmailSent.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Recovery email sent!'**
|
||||
String get passwordlessRecoveryEmailSent;
|
||||
|
||||
/// No description provided for @passwordlessRecoveryShareSent.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Recovery share sent!'**
|
||||
/// **'Recovery share successfully sent!'**
|
||||
String get passwordlessRecoveryShareSent;
|
||||
|
||||
/// No description provided for @passwordlessRecoveryNetworkError.
|
||||
|
|
@ -4091,19 +4083,19 @@ abstract class AppLocalizations {
|
|||
/// No description provided for @passwordlessRecoveryCantHelpHim.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'You can\'t help this person'**
|
||||
/// **'You can\'t help him'**
|
||||
String get passwordlessRecoveryCantHelpHim;
|
||||
|
||||
/// No description provided for @passwordlessRecoveryDoesAskedYou.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Has {username} asked you?'**
|
||||
/// **'Does {username} has asked you?'**
|
||||
String passwordlessRecoveryDoesAskedYou(Object username);
|
||||
|
||||
/// No description provided for @passwordlessRecoveryVerifySourceDesc.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Please make sure this request really came from this person. If in doubt, ask them via another channel!'**
|
||||
/// **'Please ensure that you actualy received the link/qr code from your friend! If you are unsure, please verify again and then click again on the link or come back.'**
|
||||
String get passwordlessRecoveryVerifySourceDesc;
|
||||
|
||||
/// No description provided for @passwordlessRecoveryNo.
|
||||
|
|
@ -4124,10 +4116,16 @@ abstract class AppLocalizations {
|
|||
/// **'Yes ({seconds}s)'**
|
||||
String passwordlessRecoveryYesWithTimer(Object seconds);
|
||||
|
||||
/// No description provided for @recoverPasswordlessBeingRecoveredLabel.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Account being recovered'**
|
||||
String get recoverPasswordlessBeingRecoveredLabel;
|
||||
|
||||
/// No description provided for @recoverPasswordlessSharesReceived.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'{received} out of {threshold} friends have helped'**
|
||||
/// **'{received} of {threshold} friends have shared'**
|
||||
String recoverPasswordlessSharesReceived(Object received, Object threshold);
|
||||
|
||||
/// No description provided for @recoverPasswordlessRecoverNowBtn.
|
||||
|
|
@ -4135,408 +4133,6 @@ abstract class AppLocalizations {
|
|||
/// In en, this message translates to:
|
||||
/// **'Recover now'**
|
||||
String get recoverPasswordlessRecoverNowBtn;
|
||||
|
||||
/// No description provided for @missingRecoveryContactsCardTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Account Recovery'**
|
||||
String get missingRecoveryContactsCardTitle;
|
||||
|
||||
/// No description provided for @missingRecoveryContactsCardDesc.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Select Recovery Contacts to help you recover your account without requiring a password.'**
|
||||
String get missingRecoveryContactsCardDesc;
|
||||
|
||||
/// No description provided for @missingRecoveryContactsCardAction.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Select Contacts'**
|
||||
String get missingRecoveryContactsCardAction;
|
||||
|
||||
/// No description provided for @memoriesBackupLimitReached.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Cloud backup limit reached! Please upgrade your plan or free up space.'**
|
||||
String get memoriesBackupLimitReached;
|
||||
|
||||
/// No description provided for @settingsStorageLocal.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Local storage'**
|
||||
String get settingsStorageLocal;
|
||||
|
||||
/// No description provided for @settingsStorageNoCloudBackupTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No Cloud Backup'**
|
||||
String get settingsStorageNoCloudBackupTitle;
|
||||
|
||||
/// No description provided for @settingsStorageNoCloudBackupCard.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Back up your memories to the cloud to free up local space and ensure you never lose your pictures. All end-to-end encrypted.'**
|
||||
String get settingsStorageNoCloudBackupCard;
|
||||
|
||||
/// No description provided for @galleryActionSave.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Save'**
|
||||
String get galleryActionSave;
|
||||
|
||||
/// No description provided for @galleryActionExport.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Export'**
|
||||
String get galleryActionExport;
|
||||
|
||||
/// No description provided for @galleryActionFavorite.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Favorite'**
|
||||
String get galleryActionFavorite;
|
||||
|
||||
/// No description provided for @galleryActionUnfavorite.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Unfavorite'**
|
||||
String get galleryActionUnfavorite;
|
||||
|
||||
/// No description provided for @galleryActionDelete.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Delete'**
|
||||
String get galleryActionDelete;
|
||||
|
||||
/// No description provided for @galleryActionShare.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Share'**
|
||||
String get galleryActionShare;
|
||||
|
||||
/// No description provided for @settingsStorageHidePromo.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Hide'**
|
||||
String get settingsStorageHidePromo;
|
||||
|
||||
/// No description provided for @memoriesBackupTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Memories Backup'**
|
||||
String get memoriesBackupTitle;
|
||||
|
||||
/// No description provided for @settingsHelpNews.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'News'**
|
||||
String get settingsHelpNews;
|
||||
|
||||
/// No description provided for @settingsStorageContents.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Free up space'**
|
||||
String get settingsStorageContents;
|
||||
|
||||
/// No description provided for @settingsStorageSortStorage.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Occupied storage'**
|
||||
String get settingsStorageSortStorage;
|
||||
|
||||
/// No description provided for @settingsStorageSortNewest.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Newest'**
|
||||
String get settingsStorageSortNewest;
|
||||
|
||||
/// No description provided for @settingsStorageSortOldest.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Oldest'**
|
||||
String get settingsStorageSortOldest;
|
||||
|
||||
/// No description provided for @settingsStorageNoContents.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No storage contents found.'**
|
||||
String get settingsStorageNoContents;
|
||||
|
||||
/// No description provided for @settingsStorageCloudOnly.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Cloud only'**
|
||||
String get settingsStorageCloudOnly;
|
||||
|
||||
/// No description provided for @settingsStorageLocalAndCloud.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Local and cloud'**
|
||||
String get settingsStorageLocalAndCloud;
|
||||
|
||||
/// No description provided for @settingsStorageLocalOnly.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Local only'**
|
||||
String get settingsStorageLocalOnly;
|
||||
|
||||
/// No description provided for @settingsStorageDisableBackupTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Disable Backup?'**
|
||||
String get settingsStorageDisableBackupTitle;
|
||||
|
||||
/// No description provided for @settingsStorageDisableBackupBody.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'This will delete all backed up memories from the cloud. There {count, plural, =1 {is *1 image*} other {are *{count} images*}} only stored in the cloud that will be *permanently lost*. Are you sure you want to proceed?'**
|
||||
String settingsStorageDisableBackupBody(num count);
|
||||
|
||||
/// No description provided for @settingsStorageDisableBackupBtn.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Disable'**
|
||||
String get settingsStorageDisableBackupBtn;
|
||||
|
||||
/// No description provided for @settingsStorageDisableBackupAction.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Disable Cloud Backup'**
|
||||
String get settingsStorageDisableBackupAction;
|
||||
|
||||
/// No description provided for @settingsStorageSyncNow.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Sync now'**
|
||||
String get settingsStorageSyncNow;
|
||||
|
||||
/// No description provided for @settingsStorageSyncUpToDate.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'All memories are up to date.'**
|
||||
String get settingsStorageSyncUpToDate;
|
||||
|
||||
/// No description provided for @restoreLostFlames.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Restore your {count} lost flames'**
|
||||
String restoreLostFlames(int count);
|
||||
|
||||
/// No description provided for @settingsShowRestoreFlameTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Show flame restore warning'**
|
||||
String get settingsShowRestoreFlameTitle;
|
||||
|
||||
/// No description provided for @contactLabelsTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Contact Labels'**
|
||||
String get contactLabelsTitle;
|
||||
|
||||
/// No description provided for @contactLabelsSubtitleEmpty.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No labels selected'**
|
||||
String get contactLabelsSubtitleEmpty;
|
||||
|
||||
/// No description provided for @contactLabelsMaxLimit.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Maximum 3 labels per contact'**
|
||||
String get contactLabelsMaxLimit;
|
||||
|
||||
/// No description provided for @createLabel.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Create new label'**
|
||||
String get createLabel;
|
||||
|
||||
/// No description provided for @editLabel.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Edit label'**
|
||||
String get editLabel;
|
||||
|
||||
/// No description provided for @deleteLabel.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Delete label'**
|
||||
String get deleteLabel;
|
||||
|
||||
/// No description provided for @deleteLabelConfirmation.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Are you sure you want to delete this label? It will be removed from all contacts.'**
|
||||
String get deleteLabelConfirmation;
|
||||
|
||||
/// No description provided for @labelNameHint.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Label name'**
|
||||
String get labelNameHint;
|
||||
|
||||
/// No description provided for @labelTextColor.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Text color'**
|
||||
String get labelTextColor;
|
||||
|
||||
/// No description provided for @labelBackgroundColor.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Background color'**
|
||||
String get labelBackgroundColor;
|
||||
|
||||
/// No description provided for @customColor.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Custom Color'**
|
||||
String get customColor;
|
||||
|
||||
/// No description provided for @hue.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Hue'**
|
||||
String get hue;
|
||||
|
||||
/// No description provided for @saturation.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Saturation'**
|
||||
String get saturation;
|
||||
|
||||
/// No description provided for @brightness.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Brightness'**
|
||||
String get brightness;
|
||||
|
||||
/// No description provided for @settingsAppearancePrimaryColor.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Primary Color'**
|
||||
String get settingsAppearancePrimaryColor;
|
||||
|
||||
/// No description provided for @themeSystemDefault.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'System default'**
|
||||
String get themeSystemDefault;
|
||||
|
||||
/// No description provided for @themeLight.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Light'**
|
||||
String get themeLight;
|
||||
|
||||
/// No description provided for @themeDark.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Dark'**
|
||||
String get themeDark;
|
||||
|
||||
/// No description provided for @backupRecoverySectionTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Secure Account Access'**
|
||||
String get backupRecoverySectionTitle;
|
||||
|
||||
/// No description provided for @backupRecoverySectionDescNone.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'You must select at least one recovery option, as otherwise no one can help you if you lose access to your device.'**
|
||||
String get backupRecoverySectionDescNone;
|
||||
|
||||
/// No description provided for @backupRecoverySectionDescSome.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'You can recover your account with the recovery options below. It is recommended to enable both options.'**
|
||||
String get backupRecoverySectionDescSome;
|
||||
|
||||
/// No description provided for @backupRecoveryOptionAFriends.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Via trusted friends'**
|
||||
String get backupRecoveryOptionAFriends;
|
||||
|
||||
/// No description provided for @backupRecoveryOptionAMicrocopy.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Select friends who can help you recover your account.'**
|
||||
String get backupRecoveryOptionAMicrocopy;
|
||||
|
||||
/// No description provided for @backupRecoveryOptionBPassword.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Classic Password'**
|
||||
String get backupRecoveryOptionBPassword;
|
||||
|
||||
/// No description provided for @backupRecoveryOptionBMicrocopy.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Set a password for your access.'**
|
||||
String get backupRecoveryOptionBMicrocopy;
|
||||
|
||||
/// No description provided for @backupCloudSectionTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Backup Your Data'**
|
||||
String get backupCloudSectionTitle;
|
||||
|
||||
/// No description provided for @backupCloudSectionDesc.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Backup your content so nothing gets lost.'**
|
||||
String get backupCloudSectionDesc;
|
||||
|
||||
/// No description provided for @backupCloudContactsMessages.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Contacts & Messages'**
|
||||
String get backupCloudContactsMessages;
|
||||
|
||||
/// No description provided for @backupCloudFreeActive.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Free Active'**
|
||||
String get backupCloudFreeActive;
|
||||
|
||||
/// No description provided for @backupCloudImagesMedia.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Images & Media'**
|
||||
String get backupCloudImagesMedia;
|
||||
|
||||
/// No description provided for @backupCloudProBadge.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Pro'**
|
||||
String get backupCloudProBadge;
|
||||
|
||||
/// No description provided for @memoriesAddingToFavorites.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Adding to favorites...'**
|
||||
String get memoriesAddingToFavorites;
|
||||
|
||||
/// No description provided for @memoriesRemovingFromFavorites.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Removing from favorites...'**
|
||||
String get memoriesRemovingFromFavorites;
|
||||
|
||||
/// No description provided for @memoriesDeletingProgress.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Deleting memories...'**
|
||||
String get memoriesDeletingProgress;
|
||||
|
||||
/// No description provided for @memoriesExportingProgress.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Exporting memories...'**
|
||||
String get memoriesExportingProgress;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate
|
||||
|
|
|
|||
|
|
@ -386,7 +386,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
String get contactUsSuccess => 'Feedback erfolgreich übermittelt!';
|
||||
|
||||
@override
|
||||
String get hideNewsIcon => 'Neuigkeiten-Symbol ausblenden';
|
||||
String get contactUsShortcut => 'Feedback-Symbol ausblenden';
|
||||
|
||||
@override
|
||||
String get settingsHelpTerms => 'Nutzungsbedingungen';
|
||||
|
|
@ -459,6 +459,10 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get verificationTypeLink => 'Per Link verifiziert.';
|
||||
|
||||
@override
|
||||
String get verificationTypeContactSharedByVerified =>
|
||||
'Von einem verifizierten Kontakt geteilt bekommen.';
|
||||
|
||||
@override
|
||||
String get verificationTypeMigratedFromOldVersion =>
|
||||
'Von alter Version migriert';
|
||||
|
|
@ -652,10 +656,10 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
String get proFeature2 => '✓ 1 zusätzlicher Plus Benutzer';
|
||||
|
||||
@override
|
||||
String get proFeature3 => '✓ 25GB Memories Speicher';
|
||||
String get proFeature3 => '✓ Flammen wiederherstellen';
|
||||
|
||||
@override
|
||||
String get proFeature4 => '✓ Flammen wiederherstellen';
|
||||
String get proFeature4 => '✓ twonly unterstützen';
|
||||
|
||||
@override
|
||||
String get familyFeature1 => '✓ Unbegrenzte Medien-Datei-Uploads';
|
||||
|
|
@ -664,10 +668,10 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
String get familyFeature2 => '✓ 4 zusätzliche Plus Benutzer';
|
||||
|
||||
@override
|
||||
String get familyFeature3 => '✓ 50GB Memories Speicher';
|
||||
String get familyFeature3 => '✓ Flammen wiederherstellen';
|
||||
|
||||
@override
|
||||
String get familyFeature4 => '✓ Flammen wiederherstellen';
|
||||
String get familyFeature4 => '✓ twonly unterstützen';
|
||||
|
||||
@override
|
||||
String get freeFeature1 => '✓ 10 Medien-Datei-Uploads pro Tag';
|
||||
|
|
@ -741,9 +745,6 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get deleteOkBtnForMe => 'Für mich löschen';
|
||||
|
||||
@override
|
||||
String get deleteOnlyForMe => 'Nur für mich löschen';
|
||||
|
||||
@override
|
||||
String get deleteImageTitle => 'Bist du dir sicher?';
|
||||
|
||||
|
|
@ -761,41 +762,6 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get deleteMemoriesLocalOnly => 'Nur lokal';
|
||||
|
||||
@override
|
||||
String get deleteMemoriesCompletely => 'Komplett';
|
||||
|
||||
@override
|
||||
String memoriesSelectedCount(num count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count Elemente',
|
||||
one: '1 Element',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get memoriesMenuSelectAll => 'Alles auswählen';
|
||||
|
||||
@override
|
||||
String get memoriesMenuDeselectAll => 'Alle abwählen';
|
||||
|
||||
@override
|
||||
String get memoriesMenuExport => 'In Gallery exportieren';
|
||||
|
||||
@override
|
||||
String get memoriesMenuFavorite => 'Favorisieren';
|
||||
|
||||
@override
|
||||
String get memoriesMenuDelete => 'Löschen';
|
||||
|
||||
@override
|
||||
String get memoriesMenuDeleteLocal => 'Lokal löschen';
|
||||
|
||||
@override
|
||||
String get settingsBackup => 'Backup';
|
||||
|
||||
|
|
@ -820,7 +786,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
String get backupIdentityHeader => 'Identität';
|
||||
|
||||
@override
|
||||
String get backupArchiveHeader => 'Kontakte & Nachrichten';
|
||||
String get backupArchiveHeader => 'Kontakte, Einstellungen und Nachrichten';
|
||||
|
||||
@override
|
||||
String get backupLastBackupDate => 'Letztes Backup';
|
||||
|
|
@ -850,7 +816,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get backupSelectStrongPassword =>
|
||||
'Wähle ein sicheres Passwort. Es wird empfohlen, dieses über deinen bevorzugten Passwort-Manager zu generieren und zu speichern.';
|
||||
'Wähle ein sicheres Passwort. Dies ist erforderlich, wenn du dein twonly Backup wiederherstellen möchtest.';
|
||||
|
||||
@override
|
||||
String get password => 'Passwort';
|
||||
|
|
@ -866,7 +832,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
'Das Passwort muss mindestens 10 Zeichen lang sein.';
|
||||
|
||||
@override
|
||||
String get backupEnableBackup => 'Passwort-Backup aktivieren';
|
||||
String get backupEnableBackup => 'Automatische Sicherung aktivieren';
|
||||
|
||||
@override
|
||||
String get backupTwonlySaveNow => 'Jetzt speichern';
|
||||
|
|
@ -881,26 +847,6 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
String get backupChangePasswordAuthFailed =>
|
||||
'Du kannst dein Passwort nur ändern, wenn du dich authentifiziert hast!';
|
||||
|
||||
@override
|
||||
String get backupFreeSpaceWithCloud =>
|
||||
'Speicherplatz mit Cloud-Backup freigeben';
|
||||
|
||||
@override
|
||||
String get backupMemoriesNotEnabled => 'Nicht aktiviert';
|
||||
|
||||
@override
|
||||
String get backupMemoriesUpgradeRequired => 'Upgrade erforderlich';
|
||||
|
||||
@override
|
||||
String todayAt(Object time) {
|
||||
return 'Heute um $time';
|
||||
}
|
||||
|
||||
@override
|
||||
String yesterdayAt(Object time) {
|
||||
return 'Gestern um $time';
|
||||
}
|
||||
|
||||
@override
|
||||
String get twonlySafeRecoverTitle => 'Backup wiederherstellen';
|
||||
|
||||
|
|
@ -937,6 +883,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get retransmissionRequested => 'Wird erneut versucht.';
|
||||
|
||||
@override
|
||||
String get openChangeLog => 'Changelog automatisch öffnen';
|
||||
|
||||
@override
|
||||
String reportUserTitle(Object username) {
|
||||
return 'Melde $username';
|
||||
|
|
@ -1197,6 +1146,10 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get notificationContactRequest => 'möchte sich mit dir vernetzen.';
|
||||
|
||||
@override
|
||||
String get notificationContactRequestUnknownUser =>
|
||||
'hast eine neue Kontaktanfrage erhalten.';
|
||||
|
||||
@override
|
||||
String get notificationAcceptRequest => 'ist jetzt mit dir vernetzt.';
|
||||
|
||||
|
|
@ -1234,6 +1187,12 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
return 'hat dir$inGroup geantwortet.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get notificationTitleUnknown => 'Du hast eine neue Nachricht.';
|
||||
|
||||
@override
|
||||
String get notificationBodyUnknown => 'Öffne twonly um mehr zu erfahren.';
|
||||
|
||||
@override
|
||||
String get notificationCategoryMessageTitle => 'Nachrichten';
|
||||
|
||||
|
|
@ -1394,6 +1353,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
String get onboardingBackupBody =>
|
||||
'Sichere deine twonly-Identität, da dies die einzige Möglichkeit ist, dein Konto wiederherzustellen, wenn du die App deinstallierst oder dein Handy verlierst.';
|
||||
|
||||
@override
|
||||
String get onboardingVerificationBadgeTitle => 'Verifizierungs-Haken';
|
||||
|
||||
@override
|
||||
String get onboardingUserDiscoveryShareFriends => 'Freunde teilen';
|
||||
|
||||
|
|
@ -1983,9 +1945,6 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get subscriptionPledgeSubtitle => 'Keine Werbung. Volle Privatsphäre.';
|
||||
|
||||
@override
|
||||
String get subscriptionManage => 'Abonnement verwalten';
|
||||
|
||||
@override
|
||||
String get dragToZoom => 'Zum Zoomen ziehen';
|
||||
|
||||
|
|
@ -2013,12 +1972,31 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
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';
|
||||
|
||||
|
|
@ -2070,8 +2048,12 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
String get importGalleryStarting => 'Import wird gestartet...';
|
||||
|
||||
@override
|
||||
String importGalleryComplete(Object imported, Object failed) {
|
||||
return 'Import abgeschlossen: $imported erfolgreich importiert und $failed fehlgeschlagen.';
|
||||
String importGalleryComplete(
|
||||
Object imported,
|
||||
Object duplicated,
|
||||
Object failed,
|
||||
) {
|
||||
return 'Import abgeschlossen: $imported erfolgreich importiert, $duplicated Duplikate und $failed fehlgeschlagen.';
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -2144,7 +2126,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
String get avatarCustomizeReset => 'Zurücksetzen';
|
||||
|
||||
@override
|
||||
String get passwordlessRecovery => 'Vertraute Freunde';
|
||||
String get passwordlessRecovery => 'Passwortloses Backup';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryNotConfigured => 'Nicht konfiguriert';
|
||||
|
|
@ -2208,26 +2190,47 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
String get passwordlessRecoveryInactiveFriendsDesc =>
|
||||
'Diese Freunde haben entweder ihren Teil noch nicht erhalten oder nutzen twonly nicht mehr aktiv, was bedeutet, dass sie möglicherweise nicht helfen können.';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryNotEnoughFriends =>
|
||||
'Nicht genügend Freunde ausgewählt. Du brauchst mindestens 3.';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryLoading => 'Wird geladen...';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryEnableSuccess =>
|
||||
'Vertraute Freunde erfolgreich eingerichtet!';
|
||||
'Passwortloses Backup erfolgreich aktiviert!';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryEnableFailed =>
|
||||
'Fehler beim Aktivieren des passwortlosen Backups.';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryNeedAtLeast3 =>
|
||||
'Du brauchst mindestens 3 Freunde.';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryInvalidPin => 'Ungültige PIN';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryEnterPin => 'Bitte gib eine PIN ein.';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryPinMinLength =>
|
||||
'Die PIN muss mindestens 4 Ziffern lang sein.';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryEnterEmail =>
|
||||
'Bitte gib eine E-Mail-Adresse ein.';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryEnableBtn => 'Vertraute Freunde aktivieren';
|
||||
String get passwordlessRecoveryEnableBtn => 'Passwortloses Backup aktivieren';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryRecoverBtn =>
|
||||
'Mithilfe von vertrauten Freunden wiederherstellen';
|
||||
String get passwordlessRecoveryRecoverBtn => 'Passwortlos wiederherstellen';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryModifyBtn => 'Vertraute Freunde bearbeiten';
|
||||
String get passwordlessRecoveryModifyBtn => 'Passwortloses Backup bearbeiten';
|
||||
|
||||
@override
|
||||
String passwordlessRecoveryStatusEnabled(num count) {
|
||||
|
|
@ -2239,7 +2242,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get passwordlessRecoveryInfoHowItWorksDesc =>
|
||||
'twonly funktioniert ohne Telefonnummern, weshalb wir auf einen dezentralen Wiederherstellungsmechanismus setzen. Mit Shamir\'s Secret Sharing wird deine kryptografische Identität in unabhängige Teile aufgeteilt und an deine Freunde verteilt. Um den Zugriff wiederherzustellen, muss eine vorher festgelegte Anzahl dieser Freunde ihre Teile kombinieren.';
|
||||
'Da twonly ohne zentrale Benutzerkonten oder Telefonnummern funktioniert, um deine Privatsphäre zu maximieren, setzen wir auf einen dezentralen Wiederherstellungsmechanismus. Mit Shamir\'s Secret Sharing wird deine kryptografische Identität in unabhängige Teile aufgeteilt und an deine Freunde verteilt. Um den Zugriff wiederherzustellen, muss ein vorher festgelegter Schwellenwert dieser Freunde ihre Teile kombinieren.';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryInfoWhySecondFactor =>
|
||||
|
|
@ -2247,7 +2250,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get passwordlessRecoveryInfoWhySecondFactorDesc =>
|
||||
'Der zweite Faktor (E-Mail oder PIN) ist ein wichtiger zusätzlicher Schutz. Selbst wenn sich deine Freunde heimlich zusammentun, können sie ohne diesen zweiten Faktor nicht auf dein Konto zugreifen.';
|
||||
'Der zweite Faktor (E-Mail oder PIN) dient als wichtiger kryptografischer Schutz vor bösartiger Absprache. Wenn sich deine Freunde hinter deinem Rücken absprechen würden, könnten sie deine Identität ohne den Schlüssel für den zweiten Faktor trotzdem nicht entschlüsseln.';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryInfoGotIt => 'Verstanden';
|
||||
|
|
@ -2287,14 +2290,6 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get passwordlessRecoverySelectFriends => 'Freunde auswählen';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryTrustedFriends => 'Kontowiederherstellung';
|
||||
|
||||
@override
|
||||
String passwordlessRecoveryDoneBtn(num count) {
|
||||
return 'Fertig ($count)';
|
||||
}
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryNoFriendsSelected =>
|
||||
'Noch keine Freunde ausgewählt';
|
||||
|
|
@ -2337,13 +2332,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
String get passwordlessRecoveryNoShareStored =>
|
||||
'Kein Wiederherstellungs-Teil für diesen Kontakt gespeichert.';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryEmailSent =>
|
||||
'Wiederherstellungs-E-Mail gesendet!';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryShareSent =>
|
||||
'Wiederherstellungs-Teil gesendet!';
|
||||
'Wiederherstellungs-Teil erfolgreich gesendet!';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryNetworkError =>
|
||||
|
|
@ -2370,8 +2361,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
String get passwordlessRecoveryNoContactsFound => 'Keine Kontakte gefunden';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryCantHelpHim =>
|
||||
'Du kannst dieser Person nicht helfen';
|
||||
String get passwordlessRecoveryCantHelpHim => 'Du kannst ihm nicht helfen';
|
||||
|
||||
@override
|
||||
String passwordlessRecoveryDoesAskedYou(Object username) {
|
||||
|
|
@ -2380,7 +2370,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get passwordlessRecoveryVerifySourceDesc =>
|
||||
'Bitte stelle sicher, dass diese Anfrage wirklich von dieser Person stammt. Im Zweifel frage kurz über einen anderen Kanal nach!';
|
||||
'Bitte stelle sicher, dass du den Link/QR-Code tatsächlich von deinem Freund erhalten hast! Wenn du dir unsicher bist, verifiziere dies bitte erneut und klicke dann noch einmal auf den Link oder kehre zurück.';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryNo => 'Nein';
|
||||
|
|
@ -2393,233 +2383,15 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
return 'Ja (${seconds}s)';
|
||||
}
|
||||
|
||||
@override
|
||||
String get recoverPasswordlessBeingRecoveredLabel =>
|
||||
'Konto wird wiederhergestellt';
|
||||
|
||||
@override
|
||||
String recoverPasswordlessSharesReceived(Object received, Object threshold) {
|
||||
return '$received von $threshold Freunden haben geholfen';
|
||||
return '$received von $threshold Freunden haben geteilt';
|
||||
}
|
||||
|
||||
@override
|
||||
String get recoverPasswordlessRecoverNowBtn => 'Jetzt wiederherstellen';
|
||||
|
||||
@override
|
||||
String get missingRecoveryContactsCardTitle => 'Kontowiederherstellung';
|
||||
|
||||
@override
|
||||
String get missingRecoveryContactsCardDesc =>
|
||||
'Wähle Kontakte für die Kontowiederherstellung aus, die dir helfen, dein Konto ohne Passwort wiederherzustellen.';
|
||||
|
||||
@override
|
||||
String get missingRecoveryContactsCardAction => 'Kontakte auswählen';
|
||||
|
||||
@override
|
||||
String get memoriesBackupLimitReached =>
|
||||
'Cloud-Backup-Limit erreicht! Bitte aktualisiere dein Abonnement oder gib Speicherplatz frei.';
|
||||
|
||||
@override
|
||||
String get settingsStorageLocal => 'Lokaler Speicherplatz';
|
||||
|
||||
@override
|
||||
String get settingsStorageNoCloudBackupTitle => 'Kein Cloud-Backup';
|
||||
|
||||
@override
|
||||
String get settingsStorageNoCloudBackupCard =>
|
||||
'Sichere deine Erinnerungen in der Cloud, um lokalen Speicherplatz freizugeben und deine Bilder nicht zu verlieren. Alles Ende-zu-Ende verschlüsselt.';
|
||||
|
||||
@override
|
||||
String get galleryActionSave => 'Sichern';
|
||||
|
||||
@override
|
||||
String get galleryActionExport => 'Exportieren';
|
||||
|
||||
@override
|
||||
String get galleryActionFavorite => 'Favorit';
|
||||
|
||||
@override
|
||||
String get galleryActionUnfavorite => 'Favorit';
|
||||
|
||||
@override
|
||||
String get galleryActionDelete => 'Löschen';
|
||||
|
||||
@override
|
||||
String get galleryActionShare => 'Teilen';
|
||||
|
||||
@override
|
||||
String get settingsStorageHidePromo => 'Ausblenden';
|
||||
|
||||
@override
|
||||
String get memoriesBackupTitle => 'Memories Backup';
|
||||
|
||||
@override
|
||||
String get settingsHelpNews => 'Neuigkeiten';
|
||||
|
||||
@override
|
||||
String get settingsStorageContents => 'Speicherplatz freigeben';
|
||||
|
||||
@override
|
||||
String get settingsStorageSortStorage => 'Belegter Speicher';
|
||||
|
||||
@override
|
||||
String get settingsStorageSortNewest => 'Neueste';
|
||||
|
||||
@override
|
||||
String get settingsStorageSortOldest => 'Älteste';
|
||||
|
||||
@override
|
||||
String get settingsStorageNoContents => 'Keine Speicherinhalte gefunden.';
|
||||
|
||||
@override
|
||||
String get settingsStorageCloudOnly => 'Nur Cloud';
|
||||
|
||||
@override
|
||||
String get settingsStorageLocalAndCloud => 'Lokal & Cloud';
|
||||
|
||||
@override
|
||||
String get settingsStorageLocalOnly => 'Nur lokal';
|
||||
|
||||
@override
|
||||
String get settingsStorageDisableBackupTitle => 'Backup deaktivieren?';
|
||||
|
||||
@override
|
||||
String settingsStorageDisableBackupBody(num count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '*$count Bilder*, die',
|
||||
one: '*1 Bild*, das',
|
||||
);
|
||||
return 'Dadurch werden alle gesicherten Erinnerungen aus der Cloud gelöscht. Es gibt $_temp0 nur in der Cloud und nicht lokal gespeichert sind. Diese gehen *dauerhaft verloren*. Fortfahren?';
|
||||
}
|
||||
|
||||
@override
|
||||
String get settingsStorageDisableBackupBtn => 'Deaktivieren';
|
||||
|
||||
@override
|
||||
String get settingsStorageDisableBackupAction => 'Cloud-Backup deaktivieren';
|
||||
|
||||
@override
|
||||
String get settingsStorageSyncNow => 'Jetzt synchronisieren';
|
||||
|
||||
@override
|
||||
String get settingsStorageSyncUpToDate =>
|
||||
'Alle Memories sind auf dem neuesten Stand.';
|
||||
|
||||
@override
|
||||
String restoreLostFlames(int count) {
|
||||
return 'Stelle deine $count verlorenen Flammen wieder her';
|
||||
}
|
||||
|
||||
@override
|
||||
String get settingsShowRestoreFlameTitle =>
|
||||
'Hinweis zur Flammen-Wiederherstellung anzeigen';
|
||||
|
||||
@override
|
||||
String get contactLabelsTitle => 'Kontaktlabels';
|
||||
|
||||
@override
|
||||
String get contactLabelsSubtitleEmpty => 'Keine Labels ausgewählt';
|
||||
|
||||
@override
|
||||
String get contactLabelsMaxLimit => 'Maximal 3 Labels pro Kontakt';
|
||||
|
||||
@override
|
||||
String get createLabel => 'Neues Label erstellen';
|
||||
|
||||
@override
|
||||
String get editLabel => 'Label bearbeiten';
|
||||
|
||||
@override
|
||||
String get deleteLabel => 'Label löschen';
|
||||
|
||||
@override
|
||||
String get deleteLabelConfirmation =>
|
||||
'Möchtest du dieses Label wirklich löschen? Es wird von allen Kontakten entfernt.';
|
||||
|
||||
@override
|
||||
String get labelNameHint => 'Label-Name';
|
||||
|
||||
@override
|
||||
String get labelTextColor => 'Textfarbe';
|
||||
|
||||
@override
|
||||
String get labelBackgroundColor => 'Hintergrundfarbe';
|
||||
|
||||
@override
|
||||
String get customColor => 'Eigene Farbe';
|
||||
|
||||
@override
|
||||
String get hue => 'Farbton';
|
||||
|
||||
@override
|
||||
String get saturation => 'Sättigung';
|
||||
|
||||
@override
|
||||
String get brightness => 'Helligkeit';
|
||||
|
||||
@override
|
||||
String get settingsAppearancePrimaryColor => 'Hauptfarbe';
|
||||
|
||||
@override
|
||||
String get themeSystemDefault => 'Systemstandard';
|
||||
|
||||
@override
|
||||
String get themeLight => 'Hell';
|
||||
|
||||
@override
|
||||
String get themeDark => 'Dunkel';
|
||||
|
||||
@override
|
||||
String get backupRecoverySectionTitle => 'Konto-Zugang sichern';
|
||||
|
||||
@override
|
||||
String get backupRecoverySectionDescNone =>
|
||||
'Du musst mindestens eine Wiederherstellungsoption auswählen, da dir sonst niemand helfen kann, falls du den Zugriff auf dein Gerät verlierst.';
|
||||
|
||||
@override
|
||||
String get backupRecoverySectionDescSome =>
|
||||
'Mit den untenstehenden Wiederherstellungsoptionen kannst du dein Konto wiederherstellen. Es wird empfohlen, beide Optionen zu aktivieren.';
|
||||
|
||||
@override
|
||||
String get backupRecoveryOptionAFriends => 'Über vertraute Freunde';
|
||||
|
||||
@override
|
||||
String get backupRecoveryOptionAMicrocopy =>
|
||||
'Wähle Freunde aus, die dir helfen, dein Konto wiederherzustellen.';
|
||||
|
||||
@override
|
||||
String get backupRecoveryOptionBPassword => 'Klassisches Passwort';
|
||||
|
||||
@override
|
||||
String get backupRecoveryOptionBMicrocopy =>
|
||||
'Lege ein Passwort für deinen Zugang fest.';
|
||||
|
||||
@override
|
||||
String get backupCloudSectionTitle => 'Deine Daten sichern';
|
||||
|
||||
@override
|
||||
String get backupCloudSectionDesc =>
|
||||
'Sichere deine Inhalte, damit nichts verloren geht.';
|
||||
|
||||
@override
|
||||
String get backupCloudContactsMessages => 'Kontakte & Nachrichten';
|
||||
|
||||
@override
|
||||
String get backupCloudFreeActive => 'Kostenlos aktiv';
|
||||
|
||||
@override
|
||||
String get backupCloudImagesMedia => 'Bilder & Medien';
|
||||
|
||||
@override
|
||||
String get backupCloudProBadge => 'Pro';
|
||||
|
||||
@override
|
||||
String get memoriesAddingToFavorites => 'Zu Favoriten hinzufügen...';
|
||||
|
||||
@override
|
||||
String get memoriesRemovingFromFavorites => 'Aus Favoriten entfernen...';
|
||||
|
||||
@override
|
||||
String get memoriesDeletingProgress => 'Erinnerungen werden gelöscht...';
|
||||
|
||||
@override
|
||||
String get memoriesExportingProgress => 'Erinnerungen werden exportiert...';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -382,7 +382,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
String get contactUsSuccess => 'Feedback submitted successfully!';
|
||||
|
||||
@override
|
||||
String get hideNewsIcon => 'Hide News Icon';
|
||||
String get contactUsShortcut => 'Hide Feedback Icon';
|
||||
|
||||
@override
|
||||
String get settingsHelpTerms => 'Terms of Service';
|
||||
|
|
@ -455,6 +455,10 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get verificationTypeLink => 'Verified via link.';
|
||||
|
||||
@override
|
||||
String get verificationTypeContactSharedByVerified =>
|
||||
'Contact received from a verified contact.';
|
||||
|
||||
@override
|
||||
String get verificationTypeMigratedFromOldVersion =>
|
||||
'Migrated from old version.';
|
||||
|
|
@ -647,10 +651,10 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
String get proFeature2 => '✓ 1 additional Plus user';
|
||||
|
||||
@override
|
||||
String get proFeature3 => '✓ 25GB Memories storage';
|
||||
String get proFeature3 => '✓ Restore flames';
|
||||
|
||||
@override
|
||||
String get proFeature4 => '✓ Restore flames';
|
||||
String get proFeature4 => '✓ Support twonly';
|
||||
|
||||
@override
|
||||
String get familyFeature1 => '✓ Unlimited media file uploads';
|
||||
|
|
@ -659,7 +663,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
String get familyFeature2 => '✓ 4 additional Plus user';
|
||||
|
||||
@override
|
||||
String get familyFeature3 => '✓ 50GB Memories storage';
|
||||
String get familyFeature3 => '✓ Restore flames';
|
||||
|
||||
@override
|
||||
String get familyFeature4 => '✓ Support twonly';
|
||||
|
|
@ -736,9 +740,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get deleteOkBtnForMe => 'Delete for me';
|
||||
|
||||
@override
|
||||
String get deleteOnlyForMe => 'Delete only for me';
|
||||
|
||||
@override
|
||||
String get deleteImageTitle => 'Are you sure?';
|
||||
|
||||
|
|
@ -756,41 +757,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get deleteMemoriesLocalOnly => 'Local Only';
|
||||
|
||||
@override
|
||||
String get deleteMemoriesCompletely => 'Completely';
|
||||
|
||||
@override
|
||||
String memoriesSelectedCount(num count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count Elements',
|
||||
one: '1 Element',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get memoriesMenuSelectAll => 'Select all';
|
||||
|
||||
@override
|
||||
String get memoriesMenuDeselectAll => 'Deselect all';
|
||||
|
||||
@override
|
||||
String get memoriesMenuExport => 'Export to gallery';
|
||||
|
||||
@override
|
||||
String get memoriesMenuFavorite => 'Favorite';
|
||||
|
||||
@override
|
||||
String get memoriesMenuDelete => 'Delete completely';
|
||||
|
||||
@override
|
||||
String get memoriesMenuDeleteLocal => 'Delete locally';
|
||||
|
||||
@override
|
||||
String get settingsBackup => 'Backup';
|
||||
|
||||
|
|
@ -815,7 +781,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
String get backupIdentityHeader => 'Identity';
|
||||
|
||||
@override
|
||||
String get backupArchiveHeader => 'Contacts & Messages';
|
||||
String get backupArchiveHeader => 'Contacts, Settings and Messages';
|
||||
|
||||
@override
|
||||
String get backupLastBackupDate => 'Last backup';
|
||||
|
|
@ -845,7 +811,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get backupSelectStrongPassword =>
|
||||
'Select a secure password. It is recommended to generate and store it via your password manager of choice.';
|
||||
'Choose a secure password. This is required if you want to restore your twonly Backup.';
|
||||
|
||||
@override
|
||||
String get password => 'Password';
|
||||
|
|
@ -861,7 +827,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
'Password must be at least 10 characters long.';
|
||||
|
||||
@override
|
||||
String get backupEnableBackup => 'Enable Password Backup';
|
||||
String get backupEnableBackup => 'Activate automatic backup';
|
||||
|
||||
@override
|
||||
String get backupTwonlySaveNow => 'Save now';
|
||||
|
|
@ -876,25 +842,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
String get backupChangePasswordAuthFailed =>
|
||||
'You can only change your password after you have authenticated!';
|
||||
|
||||
@override
|
||||
String get backupFreeSpaceWithCloud => 'Free Space with Cloud Backup';
|
||||
|
||||
@override
|
||||
String get backupMemoriesNotEnabled => 'Not enabled';
|
||||
|
||||
@override
|
||||
String get backupMemoriesUpgradeRequired => 'Upgrade required';
|
||||
|
||||
@override
|
||||
String todayAt(Object time) {
|
||||
return 'Today at $time';
|
||||
}
|
||||
|
||||
@override
|
||||
String yesterdayAt(Object time) {
|
||||
return 'Yesterday at $time';
|
||||
}
|
||||
|
||||
@override
|
||||
String get twonlySafeRecoverTitle => 'Restore backup';
|
||||
|
||||
|
|
@ -931,6 +878,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get retransmissionRequested => 'Retransmission requested';
|
||||
|
||||
@override
|
||||
String get openChangeLog => 'Open changelog automatically';
|
||||
|
||||
@override
|
||||
String reportUserTitle(Object username) {
|
||||
return 'Report $username';
|
||||
|
|
@ -1190,6 +1140,10 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get notificationContactRequest => 'wants to connect with you.';
|
||||
|
||||
@override
|
||||
String get notificationContactRequestUnknownUser =>
|
||||
'have received a new contact request.';
|
||||
|
||||
@override
|
||||
String get notificationAcceptRequest => 'is now connected with you.';
|
||||
|
||||
|
|
@ -1227,6 +1181,12 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
return 'has responded$inGroup.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get notificationTitleUnknown => 'You have a new message.';
|
||||
|
||||
@override
|
||||
String get notificationBodyUnknown => 'Open twonly to learn more.';
|
||||
|
||||
@override
|
||||
String get notificationCategoryMessageTitle => 'Messages';
|
||||
|
||||
|
|
@ -1385,6 +1345,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
String get onboardingBackupBody =>
|
||||
'Back up your twonly identity, as this is the only way to restore your account if you uninstall the app or lose your phone.';
|
||||
|
||||
@override
|
||||
String get onboardingVerificationBadgeTitle => 'Verification Badge';
|
||||
|
||||
@override
|
||||
String get onboardingUserDiscoveryShareFriends => 'Share your friends';
|
||||
|
||||
|
|
@ -1968,9 +1931,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get subscriptionPledgeSubtitle => 'Zero ads. Total privacy.';
|
||||
|
||||
@override
|
||||
String get subscriptionManage => 'Manage subscription';
|
||||
|
||||
@override
|
||||
String get dragToZoom => 'Drag to Zoom';
|
||||
|
||||
|
|
@ -1998,12 +1958,30 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
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';
|
||||
|
||||
|
|
@ -2055,8 +2033,12 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
String get importGalleryStarting => 'Starting import...';
|
||||
|
||||
@override
|
||||
String importGalleryComplete(Object imported, Object failed) {
|
||||
return 'Import complete: $imported successfully imported and $failed failed.';
|
||||
String importGalleryComplete(
|
||||
Object imported,
|
||||
Object duplicated,
|
||||
Object failed,
|
||||
) {
|
||||
return 'Import complete: $imported successfully imported, $duplicated duplicated and $failed failed.';
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -2129,7 +2111,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
String get avatarCustomizeReset => 'Reset';
|
||||
|
||||
@override
|
||||
String get passwordlessRecovery => 'Trusted Friends';
|
||||
String get passwordlessRecovery => 'Passwordless Recovery';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryNotConfigured => 'Not configured';
|
||||
|
|
@ -2193,24 +2175,46 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
String get passwordlessRecoveryInactiveFriendsDesc =>
|
||||
'These friends either have not yet received their share or do not use twonly actively anymore, which could mean that they maybe cannot help.';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryNotEnoughFriends =>
|
||||
'Not enough friends selected. You need at least 3.';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryLoading => 'Loading...';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryEnableSuccess =>
|
||||
'Trusted Friends successfully set up!';
|
||||
'Passwordless recovery successfully enabled!';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryEnableFailed =>
|
||||
'Failed to enable passwordless recovery.';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryNeedAtLeast3 =>
|
||||
'You need at least 3 trusted friends.';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryInvalidPin => 'Invalid PIN';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryEnterPin => 'Please enter a PIN.';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryPinMinLength =>
|
||||
'PIN must be at least 4 digits.';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryEnterEmail => 'Please enter an email address.';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryEnableBtn => 'Enable Trusted Friends';
|
||||
String get passwordlessRecoveryEnableBtn => 'Enable Passwordless Recovery';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryRecoverBtn => 'Recover via Trusted Friends';
|
||||
String get passwordlessRecoveryRecoverBtn => 'Recover passwordless';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryModifyBtn => 'Modify Trusted Friends';
|
||||
String get passwordlessRecoveryModifyBtn => 'Modify Passwordless Recovery';
|
||||
|
||||
@override
|
||||
String passwordlessRecoveryStatusEnabled(num count) {
|
||||
|
|
@ -2222,14 +2226,14 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get passwordlessRecoveryInfoHowItWorksDesc =>
|
||||
'twonly operates without phone numbers, which is why we rely on a decentralized recovery mechanism. Using Shamir\'s Secret Sharing, your cryptographic identity is split into independent shares and distributed among your trusted friends. To restore access, a predefined number of these friends must combine their shares.';
|
||||
'Because twonly operates without central user accounts or phone numbers to maximize privacy, we rely on a decentralized recovery mechanism. Using Shamir\'s Secret Sharing, your cryptographic identity is split into independent shares and distributed among your trusted friends. To restore access, a predefined threshold of these friends must combine their shares.';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryInfoWhySecondFactor => 'Why a Second Factor?';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryInfoWhySecondFactorDesc =>
|
||||
'The second factor (Email or PIN) provides important additional protection. Even if your friends were to secretly team up, they still couldn\'t access your account without this second factor.';
|
||||
'The second factor (Email or PIN) serves as a vital cryptographic safeguard against malicious collusion. If your trusted friends were to coordinate their shares behind your back, they still wouldn\'t be able to decrypt your identity without the second factor key.';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryInfoGotIt => 'Got it';
|
||||
|
|
@ -2269,14 +2273,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get passwordlessRecoverySelectFriends => 'Select trusted friends';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryTrustedFriends => 'Account recovery';
|
||||
|
||||
@override
|
||||
String passwordlessRecoveryDoneBtn(num count) {
|
||||
return 'Done ($count)';
|
||||
}
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryNoFriendsSelected =>
|
||||
'No trusted friends selected yet';
|
||||
|
|
@ -2318,10 +2314,8 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
'No recovery share stored for this contact.';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryEmailSent => 'Recovery email sent!';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryShareSent => 'Recovery share sent!';
|
||||
String get passwordlessRecoveryShareSent =>
|
||||
'Recovery share successfully sent!';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryNetworkError =>
|
||||
|
|
@ -2348,16 +2342,16 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
String get passwordlessRecoveryNoContactsFound => 'No contacts found';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryCantHelpHim => 'You can\'t help this person';
|
||||
String get passwordlessRecoveryCantHelpHim => 'You can\'t help him';
|
||||
|
||||
@override
|
||||
String passwordlessRecoveryDoesAskedYou(Object username) {
|
||||
return 'Has $username asked you?';
|
||||
return 'Does $username has asked you?';
|
||||
}
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryVerifySourceDesc =>
|
||||
'Please make sure this request really came from this person. If in doubt, ask them via another channel!';
|
||||
'Please ensure that you actualy received the link/qr code from your friend! If you are unsure, please verify again and then click again on the link or come back.';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryNo => 'No';
|
||||
|
|
@ -2370,231 +2364,15 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
return 'Yes (${seconds}s)';
|
||||
}
|
||||
|
||||
@override
|
||||
String get recoverPasswordlessBeingRecoveredLabel =>
|
||||
'Account being recovered';
|
||||
|
||||
@override
|
||||
String recoverPasswordlessSharesReceived(Object received, Object threshold) {
|
||||
return '$received out of $threshold friends have helped';
|
||||
return '$received of $threshold friends have shared';
|
||||
}
|
||||
|
||||
@override
|
||||
String get recoverPasswordlessRecoverNowBtn => 'Recover now';
|
||||
|
||||
@override
|
||||
String get missingRecoveryContactsCardTitle => 'Account Recovery';
|
||||
|
||||
@override
|
||||
String get missingRecoveryContactsCardDesc =>
|
||||
'Select Recovery Contacts to help you recover your account without requiring a password.';
|
||||
|
||||
@override
|
||||
String get missingRecoveryContactsCardAction => 'Select Contacts';
|
||||
|
||||
@override
|
||||
String get memoriesBackupLimitReached =>
|
||||
'Cloud backup limit reached! Please upgrade your plan or free up space.';
|
||||
|
||||
@override
|
||||
String get settingsStorageLocal => 'Local storage';
|
||||
|
||||
@override
|
||||
String get settingsStorageNoCloudBackupTitle => 'No Cloud Backup';
|
||||
|
||||
@override
|
||||
String get settingsStorageNoCloudBackupCard =>
|
||||
'Back up your memories to the cloud to free up local space and ensure you never lose your pictures. All end-to-end encrypted.';
|
||||
|
||||
@override
|
||||
String get galleryActionSave => 'Save';
|
||||
|
||||
@override
|
||||
String get galleryActionExport => 'Export';
|
||||
|
||||
@override
|
||||
String get galleryActionFavorite => 'Favorite';
|
||||
|
||||
@override
|
||||
String get galleryActionUnfavorite => 'Unfavorite';
|
||||
|
||||
@override
|
||||
String get galleryActionDelete => 'Delete';
|
||||
|
||||
@override
|
||||
String get galleryActionShare => 'Share';
|
||||
|
||||
@override
|
||||
String get settingsStorageHidePromo => 'Hide';
|
||||
|
||||
@override
|
||||
String get memoriesBackupTitle => 'Memories Backup';
|
||||
|
||||
@override
|
||||
String get settingsHelpNews => 'News';
|
||||
|
||||
@override
|
||||
String get settingsStorageContents => 'Free up space';
|
||||
|
||||
@override
|
||||
String get settingsStorageSortStorage => 'Occupied storage';
|
||||
|
||||
@override
|
||||
String get settingsStorageSortNewest => 'Newest';
|
||||
|
||||
@override
|
||||
String get settingsStorageSortOldest => 'Oldest';
|
||||
|
||||
@override
|
||||
String get settingsStorageNoContents => 'No storage contents found.';
|
||||
|
||||
@override
|
||||
String get settingsStorageCloudOnly => 'Cloud only';
|
||||
|
||||
@override
|
||||
String get settingsStorageLocalAndCloud => 'Local and cloud';
|
||||
|
||||
@override
|
||||
String get settingsStorageLocalOnly => 'Local only';
|
||||
|
||||
@override
|
||||
String get settingsStorageDisableBackupTitle => 'Disable Backup?';
|
||||
|
||||
@override
|
||||
String settingsStorageDisableBackupBody(num count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'are *$count images*',
|
||||
one: 'is *1 image*',
|
||||
);
|
||||
return 'This will delete all backed up memories from the cloud. There $_temp0 only stored in the cloud that will be *permanently lost*. Are you sure you want to proceed?';
|
||||
}
|
||||
|
||||
@override
|
||||
String get settingsStorageDisableBackupBtn => 'Disable';
|
||||
|
||||
@override
|
||||
String get settingsStorageDisableBackupAction => 'Disable Cloud Backup';
|
||||
|
||||
@override
|
||||
String get settingsStorageSyncNow => 'Sync now';
|
||||
|
||||
@override
|
||||
String get settingsStorageSyncUpToDate => 'All memories are up to date.';
|
||||
|
||||
@override
|
||||
String restoreLostFlames(int count) {
|
||||
return 'Restore your $count lost flames';
|
||||
}
|
||||
|
||||
@override
|
||||
String get settingsShowRestoreFlameTitle => 'Show flame restore warning';
|
||||
|
||||
@override
|
||||
String get contactLabelsTitle => 'Contact Labels';
|
||||
|
||||
@override
|
||||
String get contactLabelsSubtitleEmpty => 'No labels selected';
|
||||
|
||||
@override
|
||||
String get contactLabelsMaxLimit => 'Maximum 3 labels per contact';
|
||||
|
||||
@override
|
||||
String get createLabel => 'Create new label';
|
||||
|
||||
@override
|
||||
String get editLabel => 'Edit label';
|
||||
|
||||
@override
|
||||
String get deleteLabel => 'Delete label';
|
||||
|
||||
@override
|
||||
String get deleteLabelConfirmation =>
|
||||
'Are you sure you want to delete this label? It will be removed from all contacts.';
|
||||
|
||||
@override
|
||||
String get labelNameHint => 'Label name';
|
||||
|
||||
@override
|
||||
String get labelTextColor => 'Text color';
|
||||
|
||||
@override
|
||||
String get labelBackgroundColor => 'Background color';
|
||||
|
||||
@override
|
||||
String get customColor => 'Custom Color';
|
||||
|
||||
@override
|
||||
String get hue => 'Hue';
|
||||
|
||||
@override
|
||||
String get saturation => 'Saturation';
|
||||
|
||||
@override
|
||||
String get brightness => 'Brightness';
|
||||
|
||||
@override
|
||||
String get settingsAppearancePrimaryColor => 'Primary Color';
|
||||
|
||||
@override
|
||||
String get themeSystemDefault => 'System default';
|
||||
|
||||
@override
|
||||
String get themeLight => 'Light';
|
||||
|
||||
@override
|
||||
String get themeDark => 'Dark';
|
||||
|
||||
@override
|
||||
String get backupRecoverySectionTitle => 'Secure Account Access';
|
||||
|
||||
@override
|
||||
String get backupRecoverySectionDescNone =>
|
||||
'You must select at least one recovery option, as otherwise no one can help you if you lose access to your device.';
|
||||
|
||||
@override
|
||||
String get backupRecoverySectionDescSome =>
|
||||
'You can recover your account with the recovery options below. It is recommended to enable both options.';
|
||||
|
||||
@override
|
||||
String get backupRecoveryOptionAFriends => 'Via trusted friends';
|
||||
|
||||
@override
|
||||
String get backupRecoveryOptionAMicrocopy =>
|
||||
'Select friends who can help you recover your account.';
|
||||
|
||||
@override
|
||||
String get backupRecoveryOptionBPassword => 'Classic Password';
|
||||
|
||||
@override
|
||||
String get backupRecoveryOptionBMicrocopy =>
|
||||
'Set a password for your access.';
|
||||
|
||||
@override
|
||||
String get backupCloudSectionTitle => 'Backup Your Data';
|
||||
|
||||
@override
|
||||
String get backupCloudSectionDesc =>
|
||||
'Backup your content so nothing gets lost.';
|
||||
|
||||
@override
|
||||
String get backupCloudContactsMessages => 'Contacts & Messages';
|
||||
|
||||
@override
|
||||
String get backupCloudFreeActive => 'Free Active';
|
||||
|
||||
@override
|
||||
String get backupCloudImagesMedia => 'Images & Media';
|
||||
|
||||
@override
|
||||
String get backupCloudProBadge => 'Pro';
|
||||
|
||||
@override
|
||||
String get memoriesAddingToFavorites => 'Adding to favorites...';
|
||||
|
||||
@override
|
||||
String get memoriesRemovingFromFavorites => 'Removing from favorites...';
|
||||
|
||||
@override
|
||||
String get memoriesDeletingProgress => 'Deleting memories...';
|
||||
|
||||
@override
|
||||
String get memoriesExportingProgress => 'Exporting memories...';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 50cc5cb69dd91a5c626534fe4c015e50d2a0a486
|
||||
Subproject commit b7d703c82a5ac0b6776847cb4eb6d637620f6585
|
||||
|
|
@ -14,7 +14,6 @@ class ReceivedRecoveryShare {
|
|||
required this.myAvatarSvg,
|
||||
required this.threshold,
|
||||
required this.sharedSecretDataBytes,
|
||||
this.trustedFriendAvatarSvg,
|
||||
});
|
||||
|
||||
factory ReceivedRecoveryShare.fromJson(Map<String, dynamic> json) =>
|
||||
|
|
@ -23,7 +22,6 @@ class ReceivedRecoveryShare {
|
|||
final int messageId;
|
||||
|
||||
final String trustedFriendDisplayName;
|
||||
final List<int>? trustedFriendAvatarSvg;
|
||||
|
||||
final String myDisplayName;
|
||||
final int myUserId;
|
||||
|
|
@ -60,6 +58,7 @@ class OnboardingState {
|
|||
List<int>? encryptionKey;
|
||||
bool emailRecoveryRequested;
|
||||
|
||||
|
||||
List<ReceivedRecoveryShare> receivedShares = [];
|
||||
|
||||
Map<String, dynamic> toJson() => _$OnboardingStateToJson(this);
|
||||
|
|
|
|||
|
|
@ -20,9 +20,6 @@ ReceivedRecoveryShare _$ReceivedRecoveryShareFromJson(
|
|||
sharedSecretDataBytes: (json['sharedSecretDataBytes'] as List<dynamic>)
|
||||
.map((e) => (e as num).toInt())
|
||||
.toList(),
|
||||
trustedFriendAvatarSvg: (json['trustedFriendAvatarSvg'] as List<dynamic>?)
|
||||
?.map((e) => (e as num).toInt())
|
||||
.toList(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ReceivedRecoveryShareToJson(
|
||||
|
|
@ -30,7 +27,6 @@ Map<String, dynamic> _$ReceivedRecoveryShareToJson(
|
|||
) => <String, dynamic>{
|
||||
'messageId': instance.messageId,
|
||||
'trustedFriendDisplayName': instance.trustedFriendDisplayName,
|
||||
'trustedFriendAvatarSvg': instance.trustedFriendAvatarSvg,
|
||||
'myDisplayName': instance.myDisplayName,
|
||||
'myUserId': instance.myUserId,
|
||||
'myAvatarSvg': instance.myAvatarSvg,
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ class UserData {
|
|||
String username;
|
||||
String displayName;
|
||||
String? avatarSvg;
|
||||
String? avatarJson;
|
||||
|
||||
@JsonKey(defaultValue: 0)
|
||||
int appVersion = 0;
|
||||
|
|
@ -56,12 +57,6 @@ class UserData {
|
|||
@JsonKey(defaultValue: ThemeMode.system)
|
||||
ThemeMode themeMode = ThemeMode.system;
|
||||
|
||||
int? primaryColorValue;
|
||||
|
||||
Color get primaryColor => primaryColorValue != null
|
||||
? Color(primaryColorValue!)
|
||||
: const Color(0xFF57CC99);
|
||||
|
||||
int? defaultShowTime;
|
||||
|
||||
@JsonKey(defaultValue: false)
|
||||
|
|
@ -77,7 +72,7 @@ class UserData {
|
|||
bool videoStabilizationEnabled = true;
|
||||
|
||||
@JsonKey(defaultValue: true)
|
||||
bool showNewsShortcut = true;
|
||||
bool showFeedbackShortcut = true;
|
||||
|
||||
@JsonKey(defaultValue: false)
|
||||
bool showShowImagePreviewWhenSending = false;
|
||||
|
|
@ -98,24 +93,16 @@ class UserData {
|
|||
@JsonKey(defaultValue: true)
|
||||
bool typingIndicators = true;
|
||||
|
||||
@JsonKey(defaultValue: true)
|
||||
bool showRestoreFlame = true;
|
||||
|
||||
String? myBestFriendGroupId;
|
||||
|
||||
DateTime? signalLastSignedPreKeyUpdated;
|
||||
|
||||
DateTime? signalLastPqcPreKeysUploaded;
|
||||
|
||||
@JsonKey(defaultValue: false)
|
||||
bool allowErrorTrackingViaSentry = false;
|
||||
|
||||
@JsonKey(defaultValue: false)
|
||||
bool screenLockEnabled = false;
|
||||
|
||||
@JsonKey(defaultValue: false)
|
||||
bool isCloudBackupEnabled = false;
|
||||
|
||||
// > User Discovery Configurations
|
||||
|
||||
@JsonKey(defaultValue: false)
|
||||
|
|
@ -152,9 +139,6 @@ class UserData {
|
|||
@JsonKey(defaultValue: true)
|
||||
bool hideChangeLog = true;
|
||||
|
||||
@JsonKey(defaultValue: false)
|
||||
bool hideMemoriesBackupPromo = false;
|
||||
|
||||
@JsonKey(defaultValue: true)
|
||||
bool updateFCMToken = true;
|
||||
|
||||
|
|
@ -211,12 +195,8 @@ class TwonlySafeBackup {
|
|||
factory TwonlySafeBackup.fromJson(Map<String, dynamic> json) =>
|
||||
_$TwonlySafeBackupFromJson(json);
|
||||
|
||||
@JsonKey(defaultValue: 0)
|
||||
int lastBackupSize = 0;
|
||||
|
||||
@JsonKey(defaultValue: LastBackupUploadState.none)
|
||||
LastBackupUploadState backupUploadState = LastBackupUploadState.none;
|
||||
|
||||
DateTime? lastBackupDone;
|
||||
List<int> backupId;
|
||||
List<int> encryptionKey;
|
||||
|
|
@ -233,13 +213,17 @@ class PasswordLessRecovery {
|
|||
// Only stored, so the user can see his deposit email address...
|
||||
String? email;
|
||||
|
||||
// <--
|
||||
// Data shared with trusted friends
|
||||
|
||||
@JsonKey(defaultValue: 2)
|
||||
int threshold;
|
||||
// Used to derive the key from the email/pin
|
||||
List<int>? serverKeyProtection;
|
||||
// Trusted friends are able to brute-force the pin -> Server delets after X tries
|
||||
List<int>? pinSeed;
|
||||
List<int>? pinUnlockToken;
|
||||
|
||||
// Stored not on the server, so the server is unable to link a email to a user until the actuall recovery or can
|
||||
// brute-force the pin
|
||||
List<int>? encryptedServerKeyNonce;
|
||||
// --->
|
||||
|
||||
// Checking with the server that the server data is valid and not delted throug the pin protection for example.
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) =>
|
|||
appVersion: (json['appVersion'] as num?)?.toInt() ?? 0,
|
||||
)
|
||||
..avatarSvg = json['avatarSvg'] as String?
|
||||
..avatarJson = json['avatarJson'] as String?
|
||||
..avatarCounter = (json['avatarCounter'] as num?)?.toInt() ?? 0
|
||||
..isDeveloper = json['isDeveloper'] as bool? ?? false
|
||||
..deviceId = (json['deviceId'] as num?)?.toInt() ?? 0
|
||||
|
|
@ -32,7 +33,6 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) =>
|
|||
..themeMode =
|
||||
$enumDecodeNullable(_$ThemeModeEnumMap, json['themeMode']) ??
|
||||
ThemeMode.system
|
||||
..primaryColorValue = (json['primaryColorValue'] as num?)?.toInt()
|
||||
..defaultShowTime = (json['defaultShowTime'] as num?)?.toInt()
|
||||
..requestedAudioPermission =
|
||||
json['requestedAudioPermission'] as bool? ?? false
|
||||
|
|
@ -41,7 +41,7 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) =>
|
|||
json['automaticallyMarkEqualMediaFilesAsOpened'] as bool? ?? false
|
||||
..videoStabilizationEnabled =
|
||||
json['videoStabilizationEnabled'] as bool? ?? true
|
||||
..showNewsShortcut = json['showNewsShortcut'] as bool? ?? true
|
||||
..showFeedbackShortcut = json['showFeedbackShortcut'] as bool? ?? true
|
||||
..showShowImagePreviewWhenSending =
|
||||
json['showShowImagePreviewWhenSending'] as bool? ?? false
|
||||
..startWithCameraOpen = json['startWithCameraOpen'] as bool? ?? true
|
||||
|
|
@ -60,20 +60,14 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) =>
|
|||
..autoStoreAllSendUnlimitedMediaFiles =
|
||||
json['autoStoreAllSendUnlimitedMediaFiles'] as bool? ?? false
|
||||
..typingIndicators = json['typingIndicators'] as bool? ?? true
|
||||
..showRestoreFlame = json['showRestoreFlame'] as bool? ?? true
|
||||
..myBestFriendGroupId = json['myBestFriendGroupId'] as String?
|
||||
..signalLastSignedPreKeyUpdated =
|
||||
json['signalLastSignedPreKeyUpdated'] == null
|
||||
? null
|
||||
: DateTime.parse(json['signalLastSignedPreKeyUpdated'] as String)
|
||||
..signalLastPqcPreKeysUploaded =
|
||||
json['signalLastPqcPreKeysUploaded'] == null
|
||||
? null
|
||||
: DateTime.parse(json['signalLastPqcPreKeysUploaded'] as String)
|
||||
..allowErrorTrackingViaSentry =
|
||||
json['allowErrorTrackingViaSentry'] as bool? ?? false
|
||||
..screenLockEnabled = json['screenLockEnabled'] as bool? ?? false
|
||||
..isCloudBackupEnabled = json['isCloudBackupEnabled'] as bool? ?? false
|
||||
..isUserDiscoveryEnabled =
|
||||
json['isUserDiscoveryEnabled'] as bool? ?? false
|
||||
..requiredSendImages = (json['requiredSendImages'] as num?)?.toInt() ?? 4
|
||||
|
|
@ -94,8 +88,6 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) =>
|
|||
?.map((e) => (e as num).toInt())
|
||||
.toList()
|
||||
..hideChangeLog = json['hideChangeLog'] as bool? ?? true
|
||||
..hideMemoriesBackupPromo =
|
||||
json['hideMemoriesBackupPromo'] as bool? ?? false
|
||||
..updateFCMToken = json['updateFCMToken'] as bool? ?? true
|
||||
..canUseLoginTokenForAuth =
|
||||
json['canUseLoginTokenForAuth'] as bool? ?? true
|
||||
|
|
@ -128,6 +120,7 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
|||
'username': instance.username,
|
||||
'displayName': instance.displayName,
|
||||
'avatarSvg': instance.avatarSvg,
|
||||
'avatarJson': instance.avatarJson,
|
||||
'appVersion': instance.appVersion,
|
||||
'avatarCounter': instance.avatarCounter,
|
||||
'isDeveloper': instance.isDeveloper,
|
||||
|
|
@ -140,14 +133,13 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
|||
'lastPlanBallance': instance.lastPlanBallance,
|
||||
'additionalUserInvites': instance.additionalUserInvites,
|
||||
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
|
||||
'primaryColorValue': instance.primaryColorValue,
|
||||
'defaultShowTime': instance.defaultShowTime,
|
||||
'requestedAudioPermission': instance.requestedAudioPermission,
|
||||
'enableDatabaseLogging': instance.enableDatabaseLogging,
|
||||
'automaticallyMarkEqualMediaFilesAsOpened':
|
||||
instance.automaticallyMarkEqualMediaFilesAsOpened,
|
||||
'videoStabilizationEnabled': instance.videoStabilizationEnabled,
|
||||
'showNewsShortcut': instance.showNewsShortcut,
|
||||
'showFeedbackShortcut': instance.showFeedbackShortcut,
|
||||
'showShowImagePreviewWhenSending': instance.showShowImagePreviewWhenSending,
|
||||
'startWithCameraOpen': instance.startWithCameraOpen,
|
||||
'preSelectedEmojies': instance.preSelectedEmojies,
|
||||
|
|
@ -156,15 +148,11 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
|||
'autoStoreAllSendUnlimitedMediaFiles':
|
||||
instance.autoStoreAllSendUnlimitedMediaFiles,
|
||||
'typingIndicators': instance.typingIndicators,
|
||||
'showRestoreFlame': instance.showRestoreFlame,
|
||||
'myBestFriendGroupId': instance.myBestFriendGroupId,
|
||||
'signalLastSignedPreKeyUpdated': instance.signalLastSignedPreKeyUpdated
|
||||
?.toIso8601String(),
|
||||
'signalLastPqcPreKeysUploaded': instance.signalLastPqcPreKeysUploaded
|
||||
?.toIso8601String(),
|
||||
'allowErrorTrackingViaSentry': instance.allowErrorTrackingViaSentry,
|
||||
'screenLockEnabled': instance.screenLockEnabled,
|
||||
'isCloudBackupEnabled': instance.isCloudBackupEnabled,
|
||||
'isUserDiscoveryEnabled': instance.isUserDiscoveryEnabled,
|
||||
'requiredSendImages': instance.requiredSendImages,
|
||||
'userDiscoveryThreshold': instance.userDiscoveryThreshold,
|
||||
|
|
@ -177,7 +165,6 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
|||
'currentSignedPreKeyIndexStart': instance.currentSignedPreKeyIndexStart,
|
||||
'lastChangeLogHash': instance.lastChangeLogHash,
|
||||
'hideChangeLog': instance.hideChangeLog,
|
||||
'hideMemoriesBackupPromo': instance.hideMemoriesBackupPromo,
|
||||
'updateFCMToken': instance.updateFCMToken,
|
||||
'canUseLoginTokenForAuth': instance.canUseLoginTokenForAuth,
|
||||
'twonlySafeBackup': instance.twonlySafeBackup,
|
||||
|
|
@ -215,13 +202,11 @@ TwonlySafeBackup _$TwonlySafeBackupFromJson(Map<String, dynamic> json) =>
|
|||
.map((e) => (e as num).toInt())
|
||||
.toList(),
|
||||
)
|
||||
..lastBackupSize = (json['lastBackupSize'] as num?)?.toInt() ?? 0
|
||||
..backupUploadState =
|
||||
$enumDecodeNullable(
|
||||
_$LastBackupUploadStateEnumMap,
|
||||
json['backupUploadState'],
|
||||
) ??
|
||||
LastBackupUploadState.none
|
||||
..lastBackupSize = (json['lastBackupSize'] as num).toInt()
|
||||
..backupUploadState = $enumDecode(
|
||||
_$LastBackupUploadStateEnumMap,
|
||||
json['backupUploadState'],
|
||||
)
|
||||
..lastBackupDone = json['lastBackupDone'] == null
|
||||
? null
|
||||
: DateTime.parse(json['lastBackupDone'] as String);
|
||||
|
|
@ -245,14 +230,18 @@ const _$LastBackupUploadStateEnumMap = {
|
|||
|
||||
PasswordLessRecovery _$PasswordLessRecoveryFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => PasswordLessRecovery((json['threshold'] as num?)?.toInt() ?? 2)
|
||||
) => PasswordLessRecovery((json['threshold'] as num).toInt())
|
||||
..email = json['email'] as String?
|
||||
..serverKeyProtection = (json['serverKeyProtection'] as List<dynamic>?)
|
||||
..pinSeed = (json['pinSeed'] as List<dynamic>?)
|
||||
?.map((e) => (e as num).toInt())
|
||||
.toList()
|
||||
..pinUnlockToken = (json['pinUnlockToken'] as List<dynamic>?)
|
||||
?.map((e) => (e as num).toInt())
|
||||
.toList()
|
||||
..encryptedServerKeyNonce =
|
||||
(json['encryptedServerKeyNonce'] as List<dynamic>?)
|
||||
?.map((e) => (e as num).toInt())
|
||||
.toList()
|
||||
..lastServerHeartbeat = json['lastServerHeartbeat'] == null
|
||||
? null
|
||||
: DateTime.parse(json['lastServerHeartbeat'] as String)
|
||||
|
|
@ -268,8 +257,9 @@ Map<String, dynamic> _$PasswordLessRecoveryToJson(
|
|||
) => <String, dynamic>{
|
||||
'email': instance.email,
|
||||
'threshold': instance.threshold,
|
||||
'serverKeyProtection': instance.serverKeyProtection,
|
||||
'pinSeed': instance.pinSeed,
|
||||
'pinUnlockToken': instance.pinUnlockToken,
|
||||
'encryptedServerKeyNonce': instance.encryptedServerKeyNonce,
|
||||
'lastServerHeartbeat': instance.lastServerHeartbeat?.toIso8601String(),
|
||||
'lastContactHeartbeat': instance.lastContactHeartbeat?.toIso8601String(),
|
||||
'encryptedServerKey': instance.encryptedServerKey,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -339,11 +339,11 @@ const Handshake_GetServerKeyForPasswordLessRecovery$json = {
|
|||
'2': [
|
||||
{'1': 'user_id', '3': 1, '4': 1, '5': 3, '10': 'userId'},
|
||||
{
|
||||
'1': 'server_key_protection',
|
||||
'1': 'encrypted_server_key_none',
|
||||
'3': 2,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'10': 'serverKeyProtection'
|
||||
'10': 'encryptedServerKeyNone'
|
||||
},
|
||||
{
|
||||
'1': 'pin_unlock_token',
|
||||
|
|
@ -462,20 +462,20 @@ final $typed_data.Uint8List handshakeDescriptor = $convert.base64Decode(
|
|||
'VXaXRoTG9naW5Ub2tlbhIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQSLAoSc2VjcmV0X2xvZ2lu'
|
||||
'X3Rva2VuGAIgASgMUhBzZWNyZXRMb2dpblRva2VuEh8KC2FwcF92ZXJzaW9uGAMgASgJUgphcH'
|
||||
'BWZXJzaW9uEhsKCWRldmljZV9pZBgEIAEoA1IIZGV2aWNlSWQSIwoNaW5fYmFja2dyb3VuZBgF'
|
||||
'IAEoCFIMaW5CYWNrZ3JvdW5kGqUCCiNHZXRTZXJ2ZXJLZXlGb3JQYXNzd29yZExlc3NSZWNvdm'
|
||||
'VyeRIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQSMgoVc2VydmVyX2tleV9wcm90ZWN0aW9uGAIg'
|
||||
'ASgMUhNzZXJ2ZXJLZXlQcm90ZWN0aW9uEi0KEHBpbl91bmxvY2tfdG9rZW4YAyABKAxIAFIOcG'
|
||||
'luVW5sb2NrVG9rZW6IAQESMQoScGluX3Byb3RlY3Rpb25fa2V5GAQgASgMSAFSEHBpblByb3Rl'
|
||||
'Y3Rpb25LZXmIAQESGQoFZW1haWwYBSABKAlIAlIFZW1haWyIAQFCEwoRX3Bpbl91bmxvY2tfdG'
|
||||
'9rZW5CFQoTX3Bpbl9wcm90ZWN0aW9uX2tleUIICgZfZW1haWwaywEKIFJlZ2lzdGVyUGFzc3dv'
|
||||
'cmRsZXNzTm90aWZpY2F0aW9uEicKD25vdGlmaWNhdGlvbl9pZBgBIAEoCVIObm90aWZpY2F0aW'
|
||||
'9uSWQSLgoTZG93bmxvYWRfYXV0aF90b2tlbhgCIAEoDFIRZG93bmxvYWRBdXRoVG9rZW4SGwoJ'
|
||||
'bGFuZ19jb2RlGAMgASgJUghsYW5nQ29kZRIiCgpnb29nbGVfZmNtGAQgASgJSABSCWdvb2dsZU'
|
||||
'ZjbYgBAUINCgtfZ29vZ2xlX2ZjbRq8AQogQ2hlY2tGb3JQYXNzd29yZGxlc3NOb3RpZmljYXRp'
|
||||
'b24SJwoPbm90aWZpY2F0aW9uX2lkGAEgASgJUg5ub3RpZmljYXRpb25JZBIuChNkb3dubG9hZF'
|
||||
'9hdXRoX3Rva2VuGAIgASgMUhFkb3dubG9hZEF1dGhUb2tlbhI/ChxhbHJlYWR5X3JlY2VpdmVk'
|
||||
'X21lc3NhZ2VfaWRzGAMgAygDUhlhbHJlYWR5UmVjZWl2ZWRNZXNzYWdlSWRzQgsKCUhhbmRzaG'
|
||||
'FrZQ==');
|
||||
'IAEoCFIMaW5CYWNrZ3JvdW5kGqwCCiNHZXRTZXJ2ZXJLZXlGb3JQYXNzd29yZExlc3NSZWNvdm'
|
||||
'VyeRIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQSOQoZZW5jcnlwdGVkX3NlcnZlcl9rZXlfbm9u'
|
||||
'ZRgCIAEoDFIWZW5jcnlwdGVkU2VydmVyS2V5Tm9uZRItChBwaW5fdW5sb2NrX3Rva2VuGAMgAS'
|
||||
'gMSABSDnBpblVubG9ja1Rva2VuiAEBEjEKEnBpbl9wcm90ZWN0aW9uX2tleRgEIAEoDEgBUhBw'
|
||||
'aW5Qcm90ZWN0aW9uS2V5iAEBEhkKBWVtYWlsGAUgASgJSAJSBWVtYWlsiAEBQhMKEV9waW5fdW'
|
||||
'5sb2NrX3Rva2VuQhUKE19waW5fcHJvdGVjdGlvbl9rZXlCCAoGX2VtYWlsGssBCiBSZWdpc3Rl'
|
||||
'clBhc3N3b3JkbGVzc05vdGlmaWNhdGlvbhInCg9ub3RpZmljYXRpb25faWQYASABKAlSDm5vdG'
|
||||
'lmaWNhdGlvbklkEi4KE2Rvd25sb2FkX2F1dGhfdG9rZW4YAiABKAxSEWRvd25sb2FkQXV0aFRv'
|
||||
'a2VuEhsKCWxhbmdfY29kZRgDIAEoCVIIbGFuZ0NvZGUSIgoKZ29vZ2xlX2ZjbRgEIAEoCUgAUg'
|
||||
'lnb29nbGVGY22IAQFCDQoLX2dvb2dsZV9mY20avAEKIENoZWNrRm9yUGFzc3dvcmRsZXNzTm90'
|
||||
'aWZpY2F0aW9uEicKD25vdGlmaWNhdGlvbl9pZBgBIAEoCVIObm90aWZpY2F0aW9uSWQSLgoTZG'
|
||||
'93bmxvYWRfYXV0aF90b2tlbhgCIAEoDFIRZG93bmxvYWRBdXRoVG9rZW4SPwocYWxyZWFkeV9y'
|
||||
'ZWNlaXZlZF9tZXNzYWdlX2lkcxgDIAMoA1IZYWxyZWFkeVJlY2VpdmVkTWVzc2FnZUlkc0ILCg'
|
||||
'lIYW5kc2hha2U=');
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
const ApplicationData$json = {
|
||||
|
|
@ -526,6 +526,15 @@ const ApplicationData$json = {
|
|||
'9': 0,
|
||||
'10': 'updateGoogleFcmToken'
|
||||
},
|
||||
{
|
||||
'1': 'deprecated_9',
|
||||
'3': 9,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.ApplicationData.Deprecated',
|
||||
'9': 0,
|
||||
'10': 'deprecated9'
|
||||
},
|
||||
{
|
||||
'1': 'getCurrentPlanInfos',
|
||||
'3': 10,
|
||||
|
|
@ -535,6 +544,15 @@ const ApplicationData$json = {
|
|||
'9': 0,
|
||||
'10': 'getCurrentPlanInfos'
|
||||
},
|
||||
{
|
||||
'1': 'deprecated_11',
|
||||
'3': 11,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.ApplicationData.Deprecated',
|
||||
'9': 0,
|
||||
'10': 'deprecated11'
|
||||
},
|
||||
{
|
||||
'1': 'getAvailablePlans',
|
||||
'3': 12,
|
||||
|
|
@ -544,6 +562,60 @@ const ApplicationData$json = {
|
|||
'9': 0,
|
||||
'10': 'getAvailablePlans'
|
||||
},
|
||||
{
|
||||
'1': 'deprecated_13',
|
||||
'3': 13,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.ApplicationData.Deprecated',
|
||||
'9': 0,
|
||||
'10': 'deprecated13'
|
||||
},
|
||||
{
|
||||
'1': 'deprecated_14',
|
||||
'3': 14,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.ApplicationData.Deprecated',
|
||||
'9': 0,
|
||||
'10': 'deprecated14'
|
||||
},
|
||||
{
|
||||
'1': 'deprecated_15',
|
||||
'3': 15,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.ApplicationData.Deprecated',
|
||||
'9': 0,
|
||||
'10': 'deprecated15'
|
||||
},
|
||||
{
|
||||
'1': 'deprecated_16',
|
||||
'3': 16,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.ApplicationData.Deprecated',
|
||||
'9': 0,
|
||||
'10': 'deprecated16'
|
||||
},
|
||||
{
|
||||
'1': 'deprecated_17',
|
||||
'3': 17,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.ApplicationData.Deprecated',
|
||||
'9': 0,
|
||||
'10': 'deprecated17'
|
||||
},
|
||||
{
|
||||
'1': 'deprecated_19',
|
||||
'3': 19,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.ApplicationData.Deprecated',
|
||||
'9': 0,
|
||||
'10': 'deprecated19'
|
||||
},
|
||||
{
|
||||
'1': 'downloadDone',
|
||||
'3': 20,
|
||||
|
|
@ -661,78 +733,6 @@ const ApplicationData$json = {
|
|||
'9': 0,
|
||||
'10': 'passwordlessNotification'
|
||||
},
|
||||
{
|
||||
'1': 'request_memories_upload',
|
||||
'3': 33,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.ApplicationData.RequestMemoriesUpload',
|
||||
'9': 0,
|
||||
'10': 'requestMemoriesUpload'
|
||||
},
|
||||
{
|
||||
'1': 'confirm_memories_upload',
|
||||
'3': 34,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.ApplicationData.ConfirmMemoriesUpload',
|
||||
'9': 0,
|
||||
'10': 'confirmMemoriesUpload'
|
||||
},
|
||||
{
|
||||
'1': 'get_memories_list',
|
||||
'3': 35,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.ApplicationData.GetMemoriesList',
|
||||
'9': 0,
|
||||
'10': 'getMemoriesList'
|
||||
},
|
||||
{
|
||||
'1': 'get_memories_url',
|
||||
'3': 36,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.ApplicationData.GetMemoriesUrl',
|
||||
'9': 0,
|
||||
'10': 'getMemoriesUrl'
|
||||
},
|
||||
{
|
||||
'1': 'get_memories_usage',
|
||||
'3': 37,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.ApplicationData.GetMemoriesUsage',
|
||||
'9': 0,
|
||||
'10': 'getMemoriesUsage'
|
||||
},
|
||||
{
|
||||
'1': 'delete_memory',
|
||||
'3': 38,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.ApplicationData.DeleteMemory',
|
||||
'9': 0,
|
||||
'10': 'deleteMemory'
|
||||
},
|
||||
{
|
||||
'1': 'disable_memories_backup',
|
||||
'3': 39,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.ApplicationData.DisableMemoriesBackup',
|
||||
'9': 0,
|
||||
'10': 'disableMemoriesBackup'
|
||||
},
|
||||
{
|
||||
'1': 'upload_pqc_prekeys',
|
||||
'3': 40,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.ApplicationData.UploadPqcPreKeys',
|
||||
'9': 0,
|
||||
'10': 'uploadPqcPrekeys'
|
||||
},
|
||||
],
|
||||
'3': [
|
||||
ApplicationData_TextMessage$json,
|
||||
|
|
@ -741,13 +741,12 @@ const ApplicationData$json = {
|
|||
ApplicationData_UpdateGoogleFcmToken$json,
|
||||
ApplicationData_GetUserById$json,
|
||||
ApplicationData_GetAvailablePlans$json,
|
||||
ApplicationData_GetAddAccountsInvites$json,
|
||||
ApplicationData_GetCurrentPlanInfos$json,
|
||||
ApplicationData_RemoveAdditionalUser$json,
|
||||
ApplicationData_GetPrekeysByUserId$json,
|
||||
ApplicationData_GetSignedPreKeyByUserId$json,
|
||||
ApplicationData_UpdateSignedPreKey$json,
|
||||
ApplicationData_PqcPreKey$json,
|
||||
ApplicationData_UploadPqcPreKeys$json,
|
||||
ApplicationData_DownloadDone$json,
|
||||
ApplicationData_ReportUser$json,
|
||||
ApplicationData_IPAPurchase$json,
|
||||
|
|
@ -755,25 +754,13 @@ const ApplicationData$json = {
|
|||
ApplicationData_DeleteAccount$json,
|
||||
ApplicationData_AddAdditionalUser$json,
|
||||
ApplicationData_SetLoginToken$json,
|
||||
ApplicationData_Deprecated$json,
|
||||
ApplicationData_RegisterPasswordLessRecovery$json,
|
||||
ApplicationData_PasswordlessNotification$json,
|
||||
ApplicationData_RequestMemoriesUpload$json,
|
||||
ApplicationData_ConfirmMemoriesUpload$json,
|
||||
ApplicationData_GetMemoriesList$json,
|
||||
ApplicationData_GetMemoriesUrl$json,
|
||||
ApplicationData_GetMemoriesUsage$json,
|
||||
ApplicationData_DeleteMemory$json,
|
||||
ApplicationData_DisableMemoriesBackup$json
|
||||
ApplicationData_PasswordlessNotification$json
|
||||
],
|
||||
'8': [
|
||||
{'1': 'ApplicationData'},
|
||||
],
|
||||
'9': [
|
||||
{'1': 9, '2': 10},
|
||||
{'1': 11, '2': 12},
|
||||
{'1': 13, '2': 18},
|
||||
{'1': 19, '2': 20},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
|
|
@ -834,6 +821,11 @@ const ApplicationData_GetAvailablePlans$json = {
|
|||
'1': 'GetAvailablePlans',
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
const ApplicationData_GetAddAccountsInvites$json = {
|
||||
'1': 'GetAddAccountsInvites',
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
const ApplicationData_GetCurrentPlanInfos$json = {
|
||||
'1': 'GetCurrentPlanInfos',
|
||||
|
|
@ -879,81 +871,6 @@ const ApplicationData_UpdateSignedPreKey$json = {
|
|||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
const ApplicationData_PqcPreKey$json = {
|
||||
'1': 'PqcPreKey',
|
||||
'2': [
|
||||
{'1': 'ecc_pre_key_id', '3': 1, '4': 1, '5': 3, '10': 'eccPreKeyId'},
|
||||
{'1': 'ecc_pre_key', '3': 2, '4': 1, '5': 12, '10': 'eccPreKey'},
|
||||
{'1': 'kyber_pre_key_id', '3': 3, '4': 1, '5': 3, '10': 'kyberPreKeyId'},
|
||||
{'1': 'kyber_pre_key', '3': 4, '4': 1, '5': 12, '10': 'kyberPreKey'},
|
||||
{
|
||||
'1': 'kyber_pre_key_signature',
|
||||
'3': 5,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'10': 'kyberPreKeySignature'
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
const ApplicationData_UploadPqcPreKeys$json = {
|
||||
'1': 'UploadPqcPreKeys',
|
||||
'2': [
|
||||
{
|
||||
'1': 'ecc_signed_prekey_id',
|
||||
'3': 1,
|
||||
'4': 1,
|
||||
'5': 3,
|
||||
'10': 'eccSignedPrekeyId'
|
||||
},
|
||||
{
|
||||
'1': 'ecc_signed_prekey',
|
||||
'3': 2,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'10': 'eccSignedPrekey'
|
||||
},
|
||||
{
|
||||
'1': 'ecc_signed_prekey_signature',
|
||||
'3': 3,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'10': 'eccSignedPrekeySignature'
|
||||
},
|
||||
{
|
||||
'1': 'kyber_signed_prekey_id',
|
||||
'3': 4,
|
||||
'4': 1,
|
||||
'5': 3,
|
||||
'10': 'kyberSignedPrekeyId'
|
||||
},
|
||||
{
|
||||
'1': 'kyber_signed_prekey',
|
||||
'3': 5,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'10': 'kyberSignedPrekey'
|
||||
},
|
||||
{
|
||||
'1': 'kyber_signed_prekey_signature',
|
||||
'3': 6,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'10': 'kyberSignedPrekeySignature'
|
||||
},
|
||||
{
|
||||
'1': 'prekeys',
|
||||
'3': 7,
|
||||
'4': 3,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.ApplicationData.PqcPreKey',
|
||||
'10': 'prekeys'
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
const ApplicationData_DownloadDone$json = {
|
||||
'1': 'DownloadDone',
|
||||
|
|
@ -1013,6 +930,11 @@ const ApplicationData_SetLoginToken$json = {
|
|||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
const ApplicationData_Deprecated$json = {
|
||||
'1': 'Deprecated',
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
const ApplicationData_RegisterPasswordLessRecovery$json = {
|
||||
'1': 'RegisterPasswordLessRecovery',
|
||||
|
|
@ -1054,60 +976,6 @@ const ApplicationData_PasswordlessNotification$json = {
|
|||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
const ApplicationData_RequestMemoriesUpload$json = {
|
||||
'1': 'RequestMemoriesUpload',
|
||||
'2': [
|
||||
{'1': 'size', '3': 1, '4': 1, '5': 3, '10': 'size'},
|
||||
{'1': 'original_date', '3': 2, '4': 1, '5': 3, '10': 'originalDate'},
|
||||
{'1': 'media_id', '3': 3, '4': 1, '5': 9, '10': 'mediaId'},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
const ApplicationData_ConfirmMemoriesUpload$json = {
|
||||
'1': 'ConfirmMemoriesUpload',
|
||||
'2': [
|
||||
{'1': 'media_id', '3': 1, '4': 1, '5': 9, '10': 'mediaId'},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
const ApplicationData_GetMemoriesList$json = {
|
||||
'1': 'GetMemoriesList',
|
||||
'2': [
|
||||
{'1': 'offset_date', '3': 1, '4': 1, '5': 3, '10': 'offsetDate'},
|
||||
{'1': 'limit', '3': 2, '4': 1, '5': 3, '10': 'limit'},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
const ApplicationData_GetMemoriesUrl$json = {
|
||||
'1': 'GetMemoriesUrl',
|
||||
'2': [
|
||||
{'1': 'media_id', '3': 1, '4': 1, '5': 9, '10': 'mediaId'},
|
||||
{'1': 'thumbnail', '3': 2, '4': 1, '5': 8, '10': 'thumbnail'},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
const ApplicationData_GetMemoriesUsage$json = {
|
||||
'1': 'GetMemoriesUsage',
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
const ApplicationData_DeleteMemory$json = {
|
||||
'1': 'DeleteMemory',
|
||||
'2': [
|
||||
{'1': 'media_id', '3': 1, '4': 1, '5': 9, '10': 'mediaId'},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
const ApplicationData_DisableMemoriesBackup$json = {
|
||||
'1': 'DisableMemoriesBackup',
|
||||
};
|
||||
|
||||
/// Descriptor for `ApplicationData`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List applicationDataDescriptor = $convert.base64Decode(
|
||||
'Cg9BcHBsaWNhdGlvbkRhdGESUQoLdGV4dE1lc3NhZ2UYASABKAsyLS5jbGllbnRfdG9fc2Vydm'
|
||||
|
|
@ -1118,92 +986,71 @@ final $typed_data.Uint8List applicationDataDescriptor = $convert.base64Decode(
|
|||
'VzZXJJZEgAUhJnZXRQcmVrZXlzQnlVc2VySWQSUQoLZ2V0VXNlckJ5SWQYBiABKAsyLS5jbGll'
|
||||
'bnRfdG9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5HZXRVc2VyQnlJZEgAUgtnZXRVc2VyQnlJZB'
|
||||
'JsChR1cGRhdGVHb29nbGVGY21Ub2tlbhgIIAEoCzI2LmNsaWVudF90b19zZXJ2ZXIuQXBwbGlj'
|
||||
'YXRpb25EYXRhLlVwZGF0ZUdvb2dsZUZjbVRva2VuSABSFHVwZGF0ZUdvb2dsZUZjbVRva2VuEm'
|
||||
'kKE2dldEN1cnJlbnRQbGFuSW5mb3MYCiABKAsyNS5jbGllbnRfdG9fc2VydmVyLkFwcGxpY2F0'
|
||||
'aW9uRGF0YS5HZXRDdXJyZW50UGxhbkluZm9zSABSE2dldEN1cnJlbnRQbGFuSW5mb3MSYwoRZ2'
|
||||
'V0QXZhaWxhYmxlUGxhbnMYDCABKAsyMy5jbGllbnRfdG9fc2VydmVyLkFwcGxpY2F0aW9uRGF0'
|
||||
'YS5HZXRBdmFpbGFibGVQbGFuc0gAUhFnZXRBdmFpbGFibGVQbGFucxJUCgxkb3dubG9hZERvbm'
|
||||
'UYFCABKAsyLi5jbGllbnRfdG9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5Eb3dubG9hZERvbmVI'
|
||||
'AFIMZG93bmxvYWREb25lEnUKF2dldFNpZ25lZFByZWtleUJ5VXNlcmlkGBYgASgLMjkuY2xpZW'
|
||||
'50X3RvX3NlcnZlci5BcHBsaWNhdGlvbkRhdGEuR2V0U2lnbmVkUHJlS2V5QnlVc2VySWRIAFIX'
|
||||
'Z2V0U2lnbmVkUHJla2V5QnlVc2VyaWQSZgoSdXBkYXRlU2lnbmVkUHJla2V5GBcgASgLMjQuY2'
|
||||
'xpZW50X3RvX3NlcnZlci5BcHBsaWNhdGlvbkRhdGEuVXBkYXRlU2lnbmVkUHJlS2V5SABSEnVw'
|
||||
'ZGF0ZVNpZ25lZFByZWtleRJXCg1kZWxldGVBY2NvdW50GBggASgLMi8uY2xpZW50X3RvX3Nlcn'
|
||||
'Zlci5BcHBsaWNhdGlvbkRhdGEuRGVsZXRlQWNjb3VudEgAUg1kZWxldGVBY2NvdW50Ek4KCnJl'
|
||||
'cG9ydFVzZXIYGSABKAsyLC5jbGllbnRfdG9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5SZXBvcn'
|
||||
'RVc2VySABSCnJlcG9ydFVzZXISWgoOY2hhbmdlVXNlcm5hbWUYGiABKAsyMC5jbGllbnRfdG9f'
|
||||
'c2VydmVyLkFwcGxpY2F0aW9uRGF0YS5DaGFuZ2VVc2VybmFtZUgAUg5jaGFuZ2VVc2VybmFtZR'
|
||||
'JRCgtpcGFQdXJjaGFzZRgbIAEoCzItLmNsaWVudF90b19zZXJ2ZXIuQXBwbGljYXRpb25EYXRh'
|
||||
'LklQQVB1cmNoYXNlSABSC2lwYVB1cmNoYXNlElcKDWlwYUZvcmNlQ2hlY2sYHCABKAsyLy5jbG'
|
||||
'llbnRfdG9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5JUEFGb3JjZUNoZWNrSABSDWlwYUZvcmNl'
|
||||
'Q2hlY2sSbAoUcmVtb3ZlQWRkaXRpb25hbFVzZXIYEiABKAsyNi5jbGllbnRfdG9fc2VydmVyLk'
|
||||
'FwcGxpY2F0aW9uRGF0YS5SZW1vdmVBZGRpdGlvbmFsVXNlckgAUhRyZW1vdmVBZGRpdGlvbmFs'
|
||||
'VXNlchJjChFhZGRBZGRpdGlvbmFsVXNlchgdIAEoCzIzLmNsaWVudF90b19zZXJ2ZXIuQXBwbG'
|
||||
'ljYXRpb25EYXRhLkFkZEFkZGl0aW9uYWxVc2VySABSEWFkZEFkZGl0aW9uYWxVc2VyElkKD3Nl'
|
||||
'dF9sb2dpbl90b2tlbhgeIAEoCzIvLmNsaWVudF90b19zZXJ2ZXIuQXBwbGljYXRpb25EYXRhLl'
|
||||
'NldExvZ2luVG9rZW5IAFINc2V0TG9naW5Ub2tlbhKGAQoecmVnaXN0ZXJfcGFzc3dvcmRsZXNz'
|
||||
'X3JlY292ZXJ5GB8gASgLMj4uY2xpZW50X3RvX3NlcnZlci5BcHBsaWNhdGlvbkRhdGEuUmVnaX'
|
||||
'N0ZXJQYXNzd29yZExlc3NSZWNvdmVyeUgAUhxyZWdpc3RlclBhc3N3b3JkbGVzc1JlY292ZXJ5'
|
||||
'EnkKGXBhc3N3b3JkbGVzc19ub3RpZmljYXRpb24YICABKAsyOi5jbGllbnRfdG9fc2VydmVyLk'
|
||||
'FwcGxpY2F0aW9uRGF0YS5QYXNzd29yZGxlc3NOb3RpZmljYXRpb25IAFIYcGFzc3dvcmRsZXNz'
|
||||
'Tm90aWZpY2F0aW9uEnEKF3JlcXVlc3RfbWVtb3JpZXNfdXBsb2FkGCEgASgLMjcuY2xpZW50X3'
|
||||
'RvX3NlcnZlci5BcHBsaWNhdGlvbkRhdGEuUmVxdWVzdE1lbW9yaWVzVXBsb2FkSABSFXJlcXVl'
|
||||
'c3RNZW1vcmllc1VwbG9hZBJxChdjb25maXJtX21lbW9yaWVzX3VwbG9hZBgiIAEoCzI3LmNsaW'
|
||||
'VudF90b19zZXJ2ZXIuQXBwbGljYXRpb25EYXRhLkNvbmZpcm1NZW1vcmllc1VwbG9hZEgAUhVj'
|
||||
'b25maXJtTWVtb3JpZXNVcGxvYWQSXwoRZ2V0X21lbW9yaWVzX2xpc3QYIyABKAsyMS5jbGllbn'
|
||||
'RfdG9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5HZXRNZW1vcmllc0xpc3RIAFIPZ2V0TWVtb3Jp'
|
||||
'ZXNMaXN0ElwKEGdldF9tZW1vcmllc191cmwYJCABKAsyMC5jbGllbnRfdG9fc2VydmVyLkFwcG'
|
||||
'xpY2F0aW9uRGF0YS5HZXRNZW1vcmllc1VybEgAUg5nZXRNZW1vcmllc1VybBJiChJnZXRfbWVt'
|
||||
'b3JpZXNfdXNhZ2UYJSABKAsyMi5jbGllbnRfdG9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5HZX'
|
||||
'RNZW1vcmllc1VzYWdlSABSEGdldE1lbW9yaWVzVXNhZ2USVQoNZGVsZXRlX21lbW9yeRgmIAEo'
|
||||
'CzIuLmNsaWVudF90b19zZXJ2ZXIuQXBwbGljYXRpb25EYXRhLkRlbGV0ZU1lbW9yeUgAUgxkZW'
|
||||
'xldGVNZW1vcnkScQoXZGlzYWJsZV9tZW1vcmllc19iYWNrdXAYJyABKAsyNy5jbGllbnRfdG9f'
|
||||
'c2VydmVyLkFwcGxpY2F0aW9uRGF0YS5EaXNhYmxlTWVtb3JpZXNCYWNrdXBIAFIVZGlzYWJsZU'
|
||||
'1lbW9yaWVzQmFja3VwEmIKEnVwbG9hZF9wcWNfcHJla2V5cxgoIAEoCzIyLmNsaWVudF90b19z'
|
||||
'ZXJ2ZXIuQXBwbGljYXRpb25EYXRhLlVwbG9hZFBxY1ByZUtleXNIAFIQdXBsb2FkUHFjUHJla2'
|
||||
'V5cxpqCgtUZXh0TWVzc2FnZRIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQSEgoEYm9keRgDIAEo'
|
||||
'DFIEYm9keRIgCglwdXNoX2RhdGEYBCABKAxIAFIIcHVzaERhdGGIAQFCDAoKX3B1c2hfZGF0YR'
|
||||
'ovChFHZXRVc2VyQnlVc2VybmFtZRIaCgh1c2VybmFtZRgBIAEoCVIIdXNlcm5hbWUaLAoOQ2hh'
|
||||
'bmdlVXNlcm5hbWUSGgoIdXNlcm5hbWUYASABKAlSCHVzZXJuYW1lGjUKFFVwZGF0ZUdvb2dsZU'
|
||||
'ZjbVRva2VuEh0KCmdvb2dsZV9mY20YASABKAlSCWdvb2dsZUZjbRomCgtHZXRVc2VyQnlJZBIX'
|
||||
'Cgd1c2VyX2lkGAEgASgDUgZ1c2VySWQaEwoRR2V0QXZhaWxhYmxlUGxhbnMaFQoTR2V0Q3Vycm'
|
||||
'VudFBsYW5JbmZvcxovChRSZW1vdmVBZGRpdGlvbmFsVXNlchIXCgd1c2VyX2lkGAEgASgDUgZ1'
|
||||
'c2VySWQaLQoSR2V0UHJla2V5c0J5VXNlcklkEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBoyCh'
|
||||
'dHZXRTaWduZWRQcmVLZXlCeVVzZXJJZBIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQamwEKElVw'
|
||||
'ZGF0ZVNpZ25lZFByZUtleRIoChBzaWduZWRfcHJla2V5X2lkGAEgASgDUg5zaWduZWRQcmVrZX'
|
||||
'lJZBIjCg1zaWduZWRfcHJla2V5GAIgASgMUgxzaWduZWRQcmVrZXkSNgoXc2lnbmVkX3ByZWtl'
|
||||
'eV9zaWduYXR1cmUYAyABKAxSFXNpZ25lZFByZWtleVNpZ25hdHVyZRrUAQoJUHFjUHJlS2V5Ei'
|
||||
'MKDmVjY19wcmVfa2V5X2lkGAEgASgDUgtlY2NQcmVLZXlJZBIeCgtlY2NfcHJlX2tleRgCIAEo'
|
||||
'DFIJZWNjUHJlS2V5EicKEGt5YmVyX3ByZV9rZXlfaWQYAyABKANSDWt5YmVyUHJlS2V5SWQSIg'
|
||||
'oNa3liZXJfcHJlX2tleRgEIAEoDFILa3liZXJQcmVLZXkSNQoXa3liZXJfcHJlX2tleV9zaWdu'
|
||||
'YXR1cmUYBSABKAxSFGt5YmVyUHJlS2V5U2lnbmF0dXJlGp0DChBVcGxvYWRQcWNQcmVLZXlzEi'
|
||||
'8KFGVjY19zaWduZWRfcHJla2V5X2lkGAEgASgDUhFlY2NTaWduZWRQcmVrZXlJZBIqChFlY2Nf'
|
||||
'c2lnbmVkX3ByZWtleRgCIAEoDFIPZWNjU2lnbmVkUHJla2V5Ej0KG2VjY19zaWduZWRfcHJla2'
|
||||
'V5X3NpZ25hdHVyZRgDIAEoDFIYZWNjU2lnbmVkUHJla2V5U2lnbmF0dXJlEjMKFmt5YmVyX3Np'
|
||||
'Z25lZF9wcmVrZXlfaWQYBCABKANSE2t5YmVyU2lnbmVkUHJla2V5SWQSLgoTa3liZXJfc2lnbm'
|
||||
'VkX3ByZWtleRgFIAEoDFIRa3liZXJTaWduZWRQcmVrZXkSQQoda3liZXJfc2lnbmVkX3ByZWtl'
|
||||
'eV9zaWduYXR1cmUYBiABKAxSGmt5YmVyU2lnbmVkUHJla2V5U2lnbmF0dXJlEkUKB3ByZWtleX'
|
||||
'MYByADKAsyKy5jbGllbnRfdG9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5QcWNQcmVLZXlSB3By'
|
||||
'ZWtleXMaNQoMRG93bmxvYWREb25lEiUKDmRvd25sb2FkX3Rva2VuGAEgASgMUg1kb3dubG9hZF'
|
||||
'Rva2VuGk4KClJlcG9ydFVzZXISKAoQcmVwb3J0ZWRfdXNlcl9pZBgBIAEoA1IOcmVwb3J0ZWRV'
|
||||
'c2VySWQSFgoGcmVhc29uGAIgASgJUgZyZWFzb24acQoLSVBBUHVyY2hhc2USHQoKcHJvZHVjdF'
|
||||
'9pZBgBIAEoCVIJcHJvZHVjdElkEhYKBnNvdXJjZRgCIAEoCVIGc291cmNlEisKEXZlcmlmaWNh'
|
||||
'dGlvbl9kYXRhGAMgASgJUhB2ZXJpZmljYXRpb25EYXRhGg8KDUlQQUZvcmNlQ2hlY2saDwoNRG'
|
||||
'VsZXRlQWNjb3VudBosChFBZGRBZGRpdGlvbmFsVXNlchIXCgd1c2VyX2lkGAEgASgDUgZ1c2Vy'
|
||||
'SWQaMAoNU2V0TG9naW5Ub2tlbhIfCgtsb2dpbl90b2tlbhgBIAEoDFIKbG9naW5Ub2tlbhqOAQ'
|
||||
'ocUmVnaXN0ZXJQYXNzd29yZExlc3NSZWNvdmVyeRIuChJlbmNyeXB0ZWRTZXJ2ZXJLZXkYASAB'
|
||||
'KAxSEmVuY3J5cHRlZFNlcnZlcktleRIrCg5waW5VbmxvY2tUb2tlbhgCIAEoDEgAUg5waW5Vbm'
|
||||
'xvY2tUb2tlbogBAUIRCg9fcGluVW5sb2NrVG9rZW4acAoYUGFzc3dvcmRsZXNzTm90aWZpY2F0'
|
||||
'aW9uEicKD25vdGlmaWNhdGlvbl9pZBgBIAEoCVIObm90aWZpY2F0aW9uSWQSKwoRZW5jcnlwdG'
|
||||
'VkX21lc3NhZ2UYAiABKAxSEGVuY3J5cHRlZE1lc3NhZ2UaawoVUmVxdWVzdE1lbW9yaWVzVXBs'
|
||||
'b2FkEhIKBHNpemUYASABKANSBHNpemUSIwoNb3JpZ2luYWxfZGF0ZRgCIAEoA1IMb3JpZ2luYW'
|
||||
'xEYXRlEhkKCG1lZGlhX2lkGAMgASgJUgdtZWRpYUlkGjIKFUNvbmZpcm1NZW1vcmllc1VwbG9h'
|
||||
'ZBIZCghtZWRpYV9pZBgBIAEoCVIHbWVkaWFJZBpICg9HZXRNZW1vcmllc0xpc3QSHwoLb2Zmc2'
|
||||
'V0X2RhdGUYASABKANSCm9mZnNldERhdGUSFAoFbGltaXQYAiABKANSBWxpbWl0GkkKDkdldE1l'
|
||||
'bW9yaWVzVXJsEhkKCG1lZGlhX2lkGAEgASgJUgdtZWRpYUlkEhwKCXRodW1ibmFpbBgCIAEoCF'
|
||||
'IJdGh1bWJuYWlsGhIKEEdldE1lbW9yaWVzVXNhZ2UaKQoMRGVsZXRlTWVtb3J5EhkKCG1lZGlh'
|
||||
'X2lkGAEgASgJUgdtZWRpYUlkGhcKFURpc2FibGVNZW1vcmllc0JhY2t1cEIRCg9BcHBsaWNhdG'
|
||||
'lvbkRhdGFKBAgJEApKBAgLEAxKBAgNEBJKBAgTEBQ=');
|
||||
'YXRpb25EYXRhLlVwZGF0ZUdvb2dsZUZjbVRva2VuSABSFHVwZGF0ZUdvb2dsZUZjbVRva2VuEl'
|
||||
'EKDGRlcHJlY2F0ZWRfORgJIAEoCzIsLmNsaWVudF90b19zZXJ2ZXIuQXBwbGljYXRpb25EYXRh'
|
||||
'LkRlcHJlY2F0ZWRIAFILZGVwcmVjYXRlZDkSaQoTZ2V0Q3VycmVudFBsYW5JbmZvcxgKIAEoCz'
|
||||
'I1LmNsaWVudF90b19zZXJ2ZXIuQXBwbGljYXRpb25EYXRhLkdldEN1cnJlbnRQbGFuSW5mb3NI'
|
||||
'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'
|
||||
'ljYXRpb25EYXRhLlNldExvZ2luVG9rZW5IAFINc2V0TG9naW5Ub2tlbhKGAQoecmVnaXN0ZXJf'
|
||||
'cGFzc3dvcmRsZXNzX3JlY292ZXJ5GB8gASgLMj4uY2xpZW50X3RvX3NlcnZlci5BcHBsaWNhdG'
|
||||
'lvbkRhdGEuUmVnaXN0ZXJQYXNzd29yZExlc3NSZWNvdmVyeUgAUhxyZWdpc3RlclBhc3N3b3Jk'
|
||||
'bGVzc1JlY292ZXJ5EnkKGXBhc3N3b3JkbGVzc19ub3RpZmljYXRpb24YICABKAsyOi5jbGllbn'
|
||||
'RfdG9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5QYXNzd29yZGxlc3NOb3RpZmljYXRpb25IAFIY'
|
||||
'cGFzc3dvcmRsZXNzTm90aWZpY2F0aW9uGmoKC1RleHRNZXNzYWdlEhcKB3VzZXJfaWQYASABKA'
|
||||
'NSBnVzZXJJZBISCgRib2R5GAMgASgMUgRib2R5EiAKCXB1c2hfZGF0YRgEIAEoDEgAUghwdXNo'
|
||||
'RGF0YYgBAUIMCgpfcHVzaF9kYXRhGi8KEUdldFVzZXJCeVVzZXJuYW1lEhoKCHVzZXJuYW1lGA'
|
||||
'EgASgJUgh1c2VybmFtZRosCg5DaGFuZ2VVc2VybmFtZRIaCgh1c2VybmFtZRgBIAEoCVIIdXNl'
|
||||
'cm5hbWUaNQoUVXBkYXRlR29vZ2xlRmNtVG9rZW4SHQoKZ29vZ2xlX2ZjbRgBIAEoCVIJZ29vZ2'
|
||||
'xlRmNtGiYKC0dldFVzZXJCeUlkEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBoTChFHZXRBdmFp'
|
||||
'bGFibGVQbGFucxoXChVHZXRBZGRBY2NvdW50c0ludml0ZXMaFQoTR2V0Q3VycmVudFBsYW5Jbm'
|
||||
'ZvcxovChRSZW1vdmVBZGRpdGlvbmFsVXNlchIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQaLQoS'
|
||||
'R2V0UHJla2V5c0J5VXNlcklkEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBoyChdHZXRTaWduZW'
|
||||
'RQcmVLZXlCeVVzZXJJZBIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQamwEKElVwZGF0ZVNpZ25l'
|
||||
'ZFByZUtleRIoChBzaWduZWRfcHJla2V5X2lkGAEgASgDUg5zaWduZWRQcmVrZXlJZBIjCg1zaW'
|
||||
'duZWRfcHJla2V5GAIgASgMUgxzaWduZWRQcmVrZXkSNgoXc2lnbmVkX3ByZWtleV9zaWduYXR1'
|
||||
'cmUYAyABKAxSFXNpZ25lZFByZWtleVNpZ25hdHVyZRo1CgxEb3dubG9hZERvbmUSJQoOZG93bm'
|
||||
'xvYWRfdG9rZW4YASABKAxSDWRvd25sb2FkVG9rZW4aTgoKUmVwb3J0VXNlchIoChByZXBvcnRl'
|
||||
'ZF91c2VyX2lkGAEgASgDUg5yZXBvcnRlZFVzZXJJZBIWCgZyZWFzb24YAiABKAlSBnJlYXNvbh'
|
||||
'pxCgtJUEFQdXJjaGFzZRIdCgpwcm9kdWN0X2lkGAEgASgJUglwcm9kdWN0SWQSFgoGc291cmNl'
|
||||
'GAIgASgJUgZzb3VyY2USKwoRdmVyaWZpY2F0aW9uX2RhdGEYAyABKAlSEHZlcmlmaWNhdGlvbk'
|
||||
'RhdGEaDwoNSVBBRm9yY2VDaGVjaxoPCg1EZWxldGVBY2NvdW50GiwKEUFkZEFkZGl0aW9uYWxV'
|
||||
'c2VyEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBowCg1TZXRMb2dpblRva2VuEh8KC2xvZ2luX3'
|
||||
'Rva2VuGAEgASgMUgpsb2dpblRva2VuGgwKCkRlcHJlY2F0ZWQajgEKHFJlZ2lzdGVyUGFzc3dv'
|
||||
'cmRMZXNzUmVjb3ZlcnkSLgoSZW5jcnlwdGVkU2VydmVyS2V5GAEgASgMUhJlbmNyeXB0ZWRTZX'
|
||||
'J2ZXJLZXkSKwoOcGluVW5sb2NrVG9rZW4YAiABKAxIAFIOcGluVW5sb2NrVG9rZW6IAQFCEQoP'
|
||||
'X3BpblVubG9ja1Rva2VuGnAKGFBhc3N3b3JkbGVzc05vdGlmaWNhdGlvbhInCg9ub3RpZmljYX'
|
||||
'Rpb25faWQYASABKAlSDm5vdGlmaWNhdGlvbklkEisKEWVuY3J5cHRlZF9tZXNzYWdlGAIgASgM'
|
||||
'UhBlbmNyeXB0ZWRNZXNzYWdlQhEKD0FwcGxpY2F0aW9uRGF0YQ==');
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response$json = {
|
||||
|
|
@ -1228,12 +1075,7 @@ const Response$json = {
|
|||
'10': 'error'
|
||||
},
|
||||
],
|
||||
'3': [
|
||||
Response_PreKey$json,
|
||||
Response_Prekeys$json,
|
||||
Response_PqcPrekeys$json,
|
||||
Response_Ok$json
|
||||
],
|
||||
'3': [Response_PreKey$json, Response_Prekeys$json, Response_Ok$json],
|
||||
'8': [
|
||||
{'1': 'Response'},
|
||||
],
|
||||
|
|
@ -1263,21 +1105,6 @@ const Response_Prekeys$json = {
|
|||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response_PqcPrekeys$json = {
|
||||
'1': 'PqcPrekeys',
|
||||
'2': [
|
||||
{
|
||||
'1': 'prekeys',
|
||||
'3': 1,
|
||||
'4': 3,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.ApplicationData.PqcPreKey',
|
||||
'10': 'prekeys'
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response_Ok$json = {
|
||||
'1': 'Ok',
|
||||
|
|
@ -1292,15 +1119,6 @@ const Response_Ok$json = {
|
|||
'9': 0,
|
||||
'10': 'prekeys'
|
||||
},
|
||||
{
|
||||
'1': 'prekeys_pqc',
|
||||
'3': 3,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.Response.PqcPrekeys',
|
||||
'9': 0,
|
||||
'10': 'prekeysPqc'
|
||||
},
|
||||
],
|
||||
'8': [
|
||||
{'1': 'Ok'},
|
||||
|
|
@ -1313,8 +1131,5 @@ final $typed_data.Uint8List responseDescriptor = $convert.base64Decode(
|
|||
'ICb2sSKAoFZXJyb3IYAiABKA4yEC5lcnJvci5FcnJvckNvZGVIAFIFZXJyb3IaMAoGUHJlS2V5'
|
||||
'Eg4KAmlkGAEgASgDUgJpZBIWCgZwcmVrZXkYAiABKAxSBnByZWtleRpGCgdQcmVrZXlzEjsKB3'
|
||||
'ByZWtleXMYASADKAsyIS5jbGllbnRfdG9fc2VydmVyLlJlc3BvbnNlLlByZUtleVIHcHJla2V5'
|
||||
'cxpTCgpQcWNQcmVrZXlzEkUKB3ByZWtleXMYASADKAsyKy5jbGllbnRfdG9fc2VydmVyLkFwcG'
|
||||
'xpY2F0aW9uRGF0YS5QcWNQcmVLZXlSB3ByZWtleXMaqgEKAk9rEhQKBE5vbmUYASABKAhIAFIE'
|
||||
'Tm9uZRI+CgdwcmVrZXlzGAIgASgLMiIuY2xpZW50X3RvX3NlcnZlci5SZXNwb25zZS5QcmVrZX'
|
||||
'lzSABSB3ByZWtleXMSSAoLcHJla2V5c19wcWMYAyABKAsyJS5jbGllbnRfdG9fc2VydmVyLlJl'
|
||||
'c3BvbnNlLlBxY1ByZWtleXNIAFIKcHJla2V5c1BxY0IECgJPa0IKCghSZXNwb25zZQ==');
|
||||
'cxpgCgJPaxIUCgROb25lGAEgASgISABSBE5vbmUSPgoHcHJla2V5cxgCIAEoCzIiLmNsaWVudF'
|
||||
'90b19zZXJ2ZXIuUmVzcG9uc2UuUHJla2V5c0gAUgdwcmVrZXlzQgQKAk9rQgoKCFJlc3BvbnNl');
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -79,14 +79,6 @@ const V0$json = {
|
|||
'9': 0,
|
||||
'10': 'RequestNewPreKeys'
|
||||
},
|
||||
{
|
||||
'1': 'RequestNewPqcPreKeys',
|
||||
'3': 8,
|
||||
'4': 1,
|
||||
'5': 8,
|
||||
'9': 0,
|
||||
'10': 'RequestNewPqcPreKeys'
|
||||
},
|
||||
{
|
||||
'1': 'error',
|
||||
'3': 6,
|
||||
|
|
@ -108,9 +100,8 @@ final $typed_data.Uint8List v0Descriptor = $convert.base64Decode(
|
|||
'llbnQuUmVzcG9uc2VIAFIIcmVzcG9uc2USPgoKbmV3TWVzc2FnZRgDIAEoCzIcLnNlcnZlcl90'
|
||||
'b19jbGllbnQuTmV3TWVzc2FnZUgAUgpuZXdNZXNzYWdlEkEKC25ld01lc3NhZ2VzGAcgASgLMh'
|
||||
'0uc2VydmVyX3RvX2NsaWVudC5OZXdNZXNzYWdlc0gAUgtuZXdNZXNzYWdlcxIuChFSZXF1ZXN0'
|
||||
'TmV3UHJlS2V5cxgEIAEoCEgAUhFSZXF1ZXN0TmV3UHJlS2V5cxI0ChRSZXF1ZXN0TmV3UHFjUH'
|
||||
'JlS2V5cxgIIAEoCEgAUhRSZXF1ZXN0TmV3UHFjUHJlS2V5cxIoCgVlcnJvchgGIAEoDjIQLmVy'
|
||||
'cm9yLkVycm9yQ29kZUgAUgVlcnJvckIGCgRLaW5k');
|
||||
'TmV3UHJlS2V5cxgEIAEoCEgAUhFSZXF1ZXN0TmV3UHJlS2V5cxIoCgVlcnJvchgGIAEoDjIQLm'
|
||||
'Vycm9yLkVycm9yQ29kZUgAUgVlcnJvckIGCgRLaW5k');
|
||||
|
||||
@$core.Deprecated('Use newMessageDescriptor instead')
|
||||
const NewMessage$json = {
|
||||
|
|
@ -176,24 +167,17 @@ const Response$json = {
|
|||
Response_AddAccountsInvite$json,
|
||||
Response_AddAccountsInvites$json,
|
||||
Response_AdditionalAccount$json,
|
||||
Response_Deprecated$json,
|
||||
Response_Transaction$json,
|
||||
Response_PlanBallance$json,
|
||||
Response_PreKey$json,
|
||||
Response_SignedPreKey$json,
|
||||
Response_PqcPreKey$json,
|
||||
Response_PqcBundle$json,
|
||||
Response_UserData$json,
|
||||
Response_UploadToken$json,
|
||||
Response_DownloadTokens$json,
|
||||
Response_ProofOfWork$json,
|
||||
Response_PasswordlessNotificationMessage$json,
|
||||
Response_PasswordlessNotificationMessages$json,
|
||||
Response_PresignedPost$json,
|
||||
Response_MemoriesUploadUrls$json,
|
||||
Response_MediaItem$json,
|
||||
Response_MemoriesList$json,
|
||||
Response_MemoriesUrl$json,
|
||||
Response_MemoriesUsage$json,
|
||||
Response_Ok$json
|
||||
],
|
||||
'8': [
|
||||
|
|
@ -258,13 +242,6 @@ const Response_Plan$json = {
|
|||
'5': 8,
|
||||
'10': 'isAdditionalAccount'
|
||||
},
|
||||
{
|
||||
'1': 'memories_size_limit',
|
||||
'3': 11,
|
||||
'4': 1,
|
||||
'5': 3,
|
||||
'10': 'memoriesSizeLimit'
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
|
@ -316,6 +293,11 @@ const Response_AdditionalAccount$json = {
|
|||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response_Deprecated$json = {
|
||||
'1': 'Deprecated',
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response_Transaction$json = {
|
||||
'1': 'Transaction',
|
||||
|
|
@ -425,86 +407,6 @@ const Response_SignedPreKey$json = {
|
|||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response_PqcPreKey$json = {
|
||||
'1': 'PqcPreKey',
|
||||
'2': [
|
||||
{'1': 'ecc_pre_key_id', '3': 1, '4': 1, '5': 3, '10': 'eccPreKeyId'},
|
||||
{'1': 'ecc_pre_key', '3': 2, '4': 1, '5': 12, '10': 'eccPreKey'},
|
||||
{'1': 'kyber_pre_key_id', '3': 3, '4': 1, '5': 3, '10': 'kyberPreKeyId'},
|
||||
{'1': 'kyber_pre_key', '3': 4, '4': 1, '5': 12, '10': 'kyberPreKey'},
|
||||
{
|
||||
'1': 'kyber_pre_key_signature',
|
||||
'3': 5,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'10': 'kyberPreKeySignature'
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response_PqcBundle$json = {
|
||||
'1': 'PqcBundle',
|
||||
'2': [
|
||||
{
|
||||
'1': 'ecc_signed_prekey_id',
|
||||
'3': 1,
|
||||
'4': 1,
|
||||
'5': 3,
|
||||
'10': 'eccSignedPrekeyId'
|
||||
},
|
||||
{
|
||||
'1': 'ecc_signed_prekey',
|
||||
'3': 2,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'10': 'eccSignedPrekey'
|
||||
},
|
||||
{
|
||||
'1': 'ecc_signed_prekey_signature',
|
||||
'3': 3,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'10': 'eccSignedPrekeySignature'
|
||||
},
|
||||
{
|
||||
'1': 'kyber_signed_prekey_id',
|
||||
'3': 4,
|
||||
'4': 1,
|
||||
'5': 3,
|
||||
'10': 'kyberSignedPrekeyId'
|
||||
},
|
||||
{
|
||||
'1': 'kyber_signed_prekey',
|
||||
'3': 5,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'10': 'kyberSignedPrekey'
|
||||
},
|
||||
{
|
||||
'1': 'kyber_signed_prekey_signature',
|
||||
'3': 6,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'10': 'kyberSignedPrekeySignature'
|
||||
},
|
||||
{
|
||||
'1': 'prekey',
|
||||
'3': 7,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.server_to_client.Response.PqcPreKey',
|
||||
'9': 0,
|
||||
'10': 'prekey',
|
||||
'17': true
|
||||
},
|
||||
],
|
||||
'8': [
|
||||
{'1': '_prekey'},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response_UserData$json = {
|
||||
'1': 'UserData',
|
||||
|
|
@ -572,16 +474,6 @@ const Response_UserData$json = {
|
|||
'10': 'registrationId',
|
||||
'17': true
|
||||
},
|
||||
{
|
||||
'1': 'pqc_bundle',
|
||||
'3': 9,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.server_to_client.Response.PqcBundle',
|
||||
'9': 6,
|
||||
'10': 'pqcBundle',
|
||||
'17': true
|
||||
},
|
||||
],
|
||||
'8': [
|
||||
{'1': '_username'},
|
||||
|
|
@ -590,7 +482,6 @@ const Response_UserData$json = {
|
|||
{'1': '_signed_prekey_signature'},
|
||||
{'1': '_signed_prekey_id'},
|
||||
{'1': '_registration_id'},
|
||||
{'1': '_pqc_bundle'},
|
||||
],
|
||||
};
|
||||
|
||||
|
|
@ -650,106 +541,6 @@ const Response_PasswordlessNotificationMessages$json = {
|
|||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response_PresignedPost$json = {
|
||||
'1': 'PresignedPost',
|
||||
'2': [
|
||||
{'1': 'url', '3': 1, '4': 1, '5': 9, '10': 'url'},
|
||||
{
|
||||
'1': 'fields',
|
||||
'3': 2,
|
||||
'4': 3,
|
||||
'5': 11,
|
||||
'6': '.server_to_client.Response.PresignedPost.FieldsEntry',
|
||||
'10': 'fields'
|
||||
},
|
||||
],
|
||||
'3': [Response_PresignedPost_FieldsEntry$json],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response_PresignedPost_FieldsEntry$json = {
|
||||
'1': 'FieldsEntry',
|
||||
'2': [
|
||||
{'1': 'key', '3': 1, '4': 1, '5': 9, '10': 'key'},
|
||||
{'1': 'value', '3': 2, '4': 1, '5': 9, '10': 'value'},
|
||||
],
|
||||
'7': {'7': true},
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response_MemoriesUploadUrls$json = {
|
||||
'1': 'MemoriesUploadUrls',
|
||||
'2': [
|
||||
{'1': 'media_id', '3': 1, '4': 1, '5': 9, '10': 'mediaId'},
|
||||
{
|
||||
'1': 'thumbnail_upload',
|
||||
'3': 2,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.server_to_client.Response.PresignedPost',
|
||||
'10': 'thumbnailUpload'
|
||||
},
|
||||
{
|
||||
'1': 'full_upload',
|
||||
'3': 3,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.server_to_client.Response.PresignedPost',
|
||||
'10': 'fullUpload'
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response_MediaItem$json = {
|
||||
'1': 'MediaItem',
|
||||
'2': [
|
||||
{'1': 'media_id', '3': 1, '4': 1, '5': 9, '10': 'mediaId'},
|
||||
{'1': 'original_date', '3': 2, '4': 1, '5': 3, '10': 'originalDate'},
|
||||
{
|
||||
'1': 'thumbnail_download_url',
|
||||
'3': 3,
|
||||
'4': 1,
|
||||
'5': 9,
|
||||
'10': 'thumbnailDownloadUrl'
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response_MemoriesList$json = {
|
||||
'1': 'MemoriesList',
|
||||
'2': [
|
||||
{
|
||||
'1': 'items',
|
||||
'3': 1,
|
||||
'4': 3,
|
||||
'5': 11,
|
||||
'6': '.server_to_client.Response.MediaItem',
|
||||
'10': 'items'
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response_MemoriesUrl$json = {
|
||||
'1': 'MemoriesUrl',
|
||||
'2': [
|
||||
{'1': 'full_download_url', '3': 1, '4': 1, '5': 9, '10': 'fullDownloadUrl'},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response_MemoriesUsage$json = {
|
||||
'1': 'MemoriesUsage',
|
||||
'2': [
|
||||
{'1': 'max_bytes', '3': 1, '4': 1, '5': 3, '10': 'maxBytes'},
|
||||
{'1': 'current_bytes', '3': 2, '4': 1, '5': 3, '10': 'currentBytes'},
|
||||
{'1': 'count', '3': 3, '4': 1, '5': 3, '10': 'count'},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response_Ok$json = {
|
||||
'1': 'Ok',
|
||||
|
|
@ -783,6 +574,15 @@ const Response_Ok$json = {
|
|||
'10': 'userdata'
|
||||
},
|
||||
{'1': 'authtoken', '3': 6, '4': 1, '5': 12, '9': 0, '10': 'authtoken'},
|
||||
{
|
||||
'1': 'deprecated_7',
|
||||
'3': 7,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.server_to_client.Response.Deprecated',
|
||||
'9': 0,
|
||||
'10': 'deprecated7'
|
||||
},
|
||||
{
|
||||
'1': 'authenticated',
|
||||
'3': 8,
|
||||
|
|
@ -810,6 +610,15 @@ const Response_Ok$json = {
|
|||
'9': 0,
|
||||
'10': 'planballance'
|
||||
},
|
||||
{
|
||||
'1': 'deprecated_11',
|
||||
'3': 11,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.server_to_client.Response.Deprecated',
|
||||
'9': 0,
|
||||
'10': 'deprecated11'
|
||||
},
|
||||
{
|
||||
'1': 'addaccountsinvites',
|
||||
'3': 12,
|
||||
|
|
@ -863,57 +672,17 @@ const Response_Ok$json = {
|
|||
'9': 0,
|
||||
'10': 'passwordlessNotificationMessages'
|
||||
},
|
||||
{
|
||||
'1': 'memories_upload_urls',
|
||||
'3': 18,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.server_to_client.Response.MemoriesUploadUrls',
|
||||
'9': 0,
|
||||
'10': 'memoriesUploadUrls'
|
||||
},
|
||||
{
|
||||
'1': 'memories_list',
|
||||
'3': 19,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.server_to_client.Response.MemoriesList',
|
||||
'9': 0,
|
||||
'10': 'memoriesList'
|
||||
},
|
||||
{
|
||||
'1': 'memories_url',
|
||||
'3': 20,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.server_to_client.Response.MemoriesUrl',
|
||||
'9': 0,
|
||||
'10': 'memoriesUrl'
|
||||
},
|
||||
{
|
||||
'1': 'memories_usage',
|
||||
'3': 21,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.server_to_client.Response.MemoriesUsage',
|
||||
'9': 0,
|
||||
'10': 'memoriesUsage'
|
||||
},
|
||||
],
|
||||
'8': [
|
||||
{'1': 'Ok'},
|
||||
],
|
||||
'9': [
|
||||
{'1': 7, '2': 8},
|
||||
{'1': 11, '2': 12},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `Response`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List responseDescriptor = $convert.base64Decode(
|
||||
'CghSZXNwb25zZRIvCgJvaxgBIAEoCzIdLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuT2tIAF'
|
||||
'ICb2sSKAoFZXJyb3IYAiABKA4yEC5lcnJvci5FcnJvckNvZGVIAFIFZXJyb3IaIwoNQXV0aGVu'
|
||||
'dGljYXRlZBISCgRwbGFuGAEgASgJUgRwbGFuGpQECgRQbGFuEhcKB3BsYW5faWQYASABKAlSBn'
|
||||
'dGljYXRlZBISCgRwbGFuGAEgASgJUgRwbGFuGuQDCgRQbGFuEhcKB3BsYW5faWQYASABKAlSBn'
|
||||
'BsYW5JZBIqChF1cGxvYWRfc2l6ZV9saW1pdBgCIAEoA1IPdXBsb2FkU2l6ZUxpbWl0EjcKGGRh'
|
||||
'aWx5X21lZGlhX3VwbG9hZF9saW1pdBgDIAEoA1IVZGFpbHlNZWRpYVVwbG9hZExpbWl0ElQKKG'
|
||||
'1heGltYWxfdXBsb2FkX3NpemVfb2Zfc2luZ2xlX21lZGlhX3NpemUYBCABKANSIm1heGltYWxV'
|
||||
|
|
@ -922,95 +691,64 @@ final $typed_data.Uint8List responseDescriptor = $convert.base64Decode(
|
|||
'A1IQbW9udGhseUNvc3RzQ2VudBIqChF5ZWFybHlfY29zdHNfY2VudBgIIAEoA1IPeWVhcmx5Q2'
|
||||
'9zdHNDZW50EkAKHWFsbG93ZWRfdG9fc2VuZF90ZXh0X21lc3NhZ2VzGAkgASgIUhlhbGxvd2Vk'
|
||||
'VG9TZW5kVGV4dE1lc3NhZ2VzEjIKFWlzX2FkZGl0aW9uYWxfYWNjb3VudBgKIAEoCFITaXNBZG'
|
||||
'RpdGlvbmFsQWNjb3VudBIuChNtZW1vcmllc19zaXplX2xpbWl0GAsgASgDUhFtZW1vcmllc1Np'
|
||||
'emVMaW1pdBo+CgVQbGFucxI1CgVwbGFucxgBIAMoCzIfLnNlcnZlcl90b19jbGllbnQuUmVzcG'
|
||||
'9uc2UuUGxhblIFcGxhbnMaTQoRQWRkQWNjb3VudHNJbnZpdGUSFwoHcGxhbl9pZBgBIAEoCVIG'
|
||||
'cGxhbklkEh8KC2ludml0ZV9jb2RlGAIgASgJUgppbnZpdGVDb2RlGlwKEkFkZEFjY291bnRzSW'
|
||||
'52aXRlcxJGCgdpbnZpdGVzGAEgAygLMiwuc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5BZGRB'
|
||||
'Y2NvdW50c0ludml0ZVIHaW52aXRlcxpFChFBZGRpdGlvbmFsQWNjb3VudBIXCgd1c2VyX2lkGA'
|
||||
'EgASgDUgZ1c2VySWQSFwoHcGxhbl9pZBgDIAEoCVIGcGxhbklkGg0KC1RyYW5zYWN0aW9uGpcF'
|
||||
'CgxQbGFuQmFsbGFuY2USQAoddXNlZF9kYWlseV9tZWRpYV91cGxvYWRfbGltaXQYASABKANSGX'
|
||||
'VzZWREYWlseU1lZGlhVXBsb2FkTGltaXQSPgocdXNlZF91cGxvYWRfbWVkaWFfc2l6ZV9saW1p'
|
||||
'dBgCIAEoA1IYdXNlZFVwbG9hZE1lZGlhU2l6ZUxpbWl0EjMKE3BheW1lbnRfcGVyaW9kX2RheX'
|
||||
'MYAyABKANIAFIRcGF5bWVudFBlcmlvZERheXOIAQESSwogbGFzdF9wYXltZW50X2RvbmVfdW5p'
|
||||
'eF90aW1lc3RhbXAYBCABKANIAVIcbGFzdFBheW1lbnREb25lVW5peFRpbWVzdGFtcIgBARJKCg'
|
||||
'x0cmFuc2FjdGlvbnMYBSADKAsyJi5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLlRyYW5zYWN0'
|
||||
'aW9uUgx0cmFuc2FjdGlvbnMSXQoTYWRkaXRpb25hbF9hY2NvdW50cxgGIAMoCzIsLnNlcnZlcl'
|
||||
'90b19jbGllbnQuUmVzcG9uc2UuQWRkaXRpb25hbEFjY291bnRSEmFkZGl0aW9uYWxBY2NvdW50'
|
||||
'cxImCgxhdXRvX3JlbmV3YWwYByABKAhIAlILYXV0b1JlbmV3YWyIAQESQgobYWRkaXRpb25hbF'
|
||||
'9hY2NvdW50X293bmVyX2lkGAggASgDSANSGGFkZGl0aW9uYWxBY2NvdW50T3duZXJJZIgBAUIW'
|
||||
'ChRfcGF5bWVudF9wZXJpb2RfZGF5c0IjCiFfbGFzdF9wYXltZW50X2RvbmVfdW5peF90aW1lc3'
|
||||
'RhbXBCDwoNX2F1dG9fcmVuZXdhbEIeChxfYWRkaXRpb25hbF9hY2NvdW50X293bmVyX2lkGjAK'
|
||||
'BlByZUtleRIOCgJpZBgBIAEoA1ICaWQSFgoGcHJla2V5GAIgASgMUgZwcmVrZXkalQEKDFNpZ2'
|
||||
'5lZFByZUtleRIoChBzaWduZWRfcHJla2V5X2lkGAEgASgDUg5zaWduZWRQcmVrZXlJZBIjCg1z'
|
||||
'aWduZWRfcHJla2V5GAIgASgMUgxzaWduZWRQcmVrZXkSNgoXc2lnbmVkX3ByZWtleV9zaWduYX'
|
||||
'R1cmUYAyABKAxSFXNpZ25lZFByZWtleVNpZ25hdHVyZRrUAQoJUHFjUHJlS2V5EiMKDmVjY19w'
|
||||
'cmVfa2V5X2lkGAEgASgDUgtlY2NQcmVLZXlJZBIeCgtlY2NfcHJlX2tleRgCIAEoDFIJZWNjUH'
|
||||
'JlS2V5EicKEGt5YmVyX3ByZV9rZXlfaWQYAyABKANSDWt5YmVyUHJlS2V5SWQSIgoNa3liZXJf'
|
||||
'cHJlX2tleRgEIAEoDFILa3liZXJQcmVLZXkSNQoXa3liZXJfcHJlX2tleV9zaWduYXR1cmUYBS'
|
||||
'ABKAxSFGt5YmVyUHJlS2V5U2lnbmF0dXJlGp0DCglQcWNCdW5kbGUSLwoUZWNjX3NpZ25lZF9w'
|
||||
'cmVrZXlfaWQYASABKANSEWVjY1NpZ25lZFByZWtleUlkEioKEWVjY19zaWduZWRfcHJla2V5GA'
|
||||
'IgASgMUg9lY2NTaWduZWRQcmVrZXkSPQobZWNjX3NpZ25lZF9wcmVrZXlfc2lnbmF0dXJlGAMg'
|
||||
'ASgMUhhlY2NTaWduZWRQcmVrZXlTaWduYXR1cmUSMwoWa3liZXJfc2lnbmVkX3ByZWtleV9pZB'
|
||||
'gEIAEoA1ITa3liZXJTaWduZWRQcmVrZXlJZBIuChNreWJlcl9zaWduZWRfcHJla2V5GAUgASgM'
|
||||
'UhFreWJlclNpZ25lZFByZWtleRJBCh1reWJlcl9zaWduZWRfcHJla2V5X3NpZ25hdHVyZRgGIA'
|
||||
'EoDFIaa3liZXJTaWduZWRQcmVrZXlTaWduYXR1cmUSQQoGcHJla2V5GAcgASgLMiQuc2VydmVy'
|
||||
'X3RvX2NsaWVudC5SZXNwb25zZS5QcWNQcmVLZXlIAFIGcHJla2V5iAEBQgkKB19wcmVrZXkazw'
|
||||
'QKCFVzZXJEYXRhEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBI7CgdwcmVrZXlzGAIgAygLMiEu'
|
||||
'c2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5QcmVLZXlSB3ByZWtleXMSHwoIdXNlcm5hbWUYBy'
|
||||
'ABKAxIAFIIdXNlcm5hbWWIAQESMwoTcHVibGljX2lkZW50aXR5X2tleRgDIAEoDEgBUhFwdWJs'
|
||||
'aWNJZGVudGl0eUtleYgBARIoCg1zaWduZWRfcHJla2V5GAQgASgMSAJSDHNpZ25lZFByZWtleY'
|
||||
'gBARI7ChdzaWduZWRfcHJla2V5X3NpZ25hdHVyZRgFIAEoDEgDUhVzaWduZWRQcmVrZXlTaWdu'
|
||||
'YXR1cmWIAQESLQoQc2lnbmVkX3ByZWtleV9pZBgGIAEoA0gEUg5zaWduZWRQcmVrZXlJZIgBAR'
|
||||
'IsCg9yZWdpc3RyYXRpb25faWQYCCABKANIBVIOcmVnaXN0cmF0aW9uSWSIAQESSAoKcHFjX2J1'
|
||||
'bmRsZRgJIAEoCzIkLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuUHFjQnVuZGxlSAZSCXBxY0'
|
||||
'J1bmRsZYgBAUILCglfdXNlcm5hbWVCFgoUX3B1YmxpY19pZGVudGl0eV9rZXlCEAoOX3NpZ25l'
|
||||
'ZF9wcmVrZXlCGgoYX3NpZ25lZF9wcmVrZXlfc2lnbmF0dXJlQhMKEV9zaWduZWRfcHJla2V5X2'
|
||||
'lkQhIKEF9yZWdpc3RyYXRpb25faWRCDQoLX3BxY19idW5kbGUaWQoLVXBsb2FkVG9rZW4SIQoM'
|
||||
'dXBsb2FkX3Rva2VuGAEgASgMUgt1cGxvYWRUb2tlbhInCg9kb3dubG9hZF90b2tlbnMYAiADKA'
|
||||
'xSDmRvd25sb2FkVG9rZW5zGjkKDkRvd25sb2FkVG9rZW5zEicKD2Rvd25sb2FkX3Rva2VucxgB'
|
||||
'IAMoDFIOZG93bmxvYWRUb2tlbnMaRQoLUHJvb2ZPZldvcmsSFgoGcHJlZml4GAEgASgJUgZwcm'
|
||||
'VmaXgSHgoKZGlmZmljdWx0eRgCIAEoA1IKZGlmZmljdWx0eRpeCh9QYXNzd29yZGxlc3NOb3Rp'
|
||||
'ZmljYXRpb25NZXNzYWdlEg4KAmlkGAEgASgDUgJpZBIrChFlbmNyeXB0ZWRfbWVzc2FnZRgCIA'
|
||||
'EoDFIQZW5jcnlwdGVkTWVzc2FnZRp6CiBQYXNzd29yZGxlc3NOb3RpZmljYXRpb25NZXNzYWdl'
|
||||
'cxJWCghtZXNzYWdlcxgBIAMoCzI6LnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuUGFzc3dvcm'
|
||||
'RsZXNzTm90aWZpY2F0aW9uTWVzc2FnZVIIbWVzc2FnZXMaqgEKDVByZXNpZ25lZFBvc3QSEAoD'
|
||||
'dXJsGAEgASgJUgN1cmwSTAoGZmllbGRzGAIgAygLMjQuc2VydmVyX3RvX2NsaWVudC5SZXNwb2'
|
||||
'5zZS5QcmVzaWduZWRQb3N0LkZpZWxkc0VudHJ5UgZmaWVsZHMaOQoLRmllbGRzRW50cnkSEAoD'
|
||||
'a2V5GAEgASgJUgNrZXkSFAoFdmFsdWUYAiABKAlSBXZhbHVlOgI4ARrPAQoSTWVtb3JpZXNVcG'
|
||||
'xvYWRVcmxzEhkKCG1lZGlhX2lkGAEgASgJUgdtZWRpYUlkElMKEHRodW1ibmFpbF91cGxvYWQY'
|
||||
'AiABKAsyKC5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLlByZXNpZ25lZFBvc3RSD3RodW1ibm'
|
||||
'FpbFVwbG9hZBJJCgtmdWxsX3VwbG9hZBgDIAEoCzIoLnNlcnZlcl90b19jbGllbnQuUmVzcG9u'
|
||||
'c2UuUHJlc2lnbmVkUG9zdFIKZnVsbFVwbG9hZBqBAQoJTWVkaWFJdGVtEhkKCG1lZGlhX2lkGA'
|
||||
'EgASgJUgdtZWRpYUlkEiMKDW9yaWdpbmFsX2RhdGUYAiABKANSDG9yaWdpbmFsRGF0ZRI0ChZ0'
|
||||
'aHVtYm5haWxfZG93bmxvYWRfdXJsGAMgASgJUhR0aHVtYm5haWxEb3dubG9hZFVybBpKCgxNZW'
|
||||
'1vcmllc0xpc3QSOgoFaXRlbXMYASADKAsyJC5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLk1l'
|
||||
'ZGlhSXRlbVIFaXRlbXMaOQoLTWVtb3JpZXNVcmwSKgoRZnVsbF9kb3dubG9hZF91cmwYASABKA'
|
||||
'lSD2Z1bGxEb3dubG9hZFVybBpnCg1NZW1vcmllc1VzYWdlEhsKCW1heF9ieXRlcxgBIAEoA1II'
|
||||
'bWF4Qnl0ZXMSIwoNY3VycmVudF9ieXRlcxgCIAEoA1IMY3VycmVudEJ5dGVzEhQKBWNvdW50GA'
|
||||
'MgASgDUgVjb3VudBr1CgoCT2sSFAoETm9uZRgBIAEoCEgAUgROb25lEhgKBnVzZXJpZBgCIAEo'
|
||||
'A0gAUgZ1c2VyaWQSJgoNYXV0aGNoYWxsZW5nZRgDIAEoDEgAUg1hdXRoY2hhbGxlbmdlEkoKC3'
|
||||
'VwbG9hZHRva2VuGAQgASgLMiYuc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5VcGxvYWRUb2tl'
|
||||
'bkgAUgt1cGxvYWR0b2tlbhJBCgh1c2VyZGF0YRgFIAEoCzIjLnNlcnZlcl90b19jbGllbnQuUm'
|
||||
'VzcG9uc2UuVXNlckRhdGFIAFIIdXNlcmRhdGESHgoJYXV0aHRva2VuGAYgASgMSABSCWF1dGh0'
|
||||
'b2tlbhJQCg1hdXRoZW50aWNhdGVkGAggASgLMiguc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS'
|
||||
'5BdXRoZW50aWNhdGVkSABSDWF1dGhlbnRpY2F0ZWQSOAoFcGxhbnMYCSABKAsyIC5zZXJ2ZXJf'
|
||||
'dG9fY2xpZW50LlJlc3BvbnNlLlBsYW5zSABSBXBsYW5zEk0KDHBsYW5iYWxsYW5jZRgKIAEoCz'
|
||||
'InLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuUGxhbkJhbGxhbmNlSABSDHBsYW5iYWxsYW5j'
|
||||
'ZRJfChJhZGRhY2NvdW50c2ludml0ZXMYDCABKAsyLS5zZXJ2ZXJfdG9fY2xpZW50LlJlc3Bvbn'
|
||||
'NlLkFkZEFjY291bnRzSW52aXRlc0gAUhJhZGRhY2NvdW50c2ludml0ZXMSUwoOZG93bmxvYWR0'
|
||||
'b2tlbnMYDSABKAsyKS5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLkRvd25sb2FkVG9rZW5zSA'
|
||||
'BSDmRvd25sb2FkdG9rZW5zEk0KDHNpZ25lZHByZWtleRgOIAEoCzInLnNlcnZlcl90b19jbGll'
|
||||
'bnQuUmVzcG9uc2UuU2lnbmVkUHJlS2V5SABSDHNpZ25lZHByZWtleRJKCgtwcm9vZk9mV29yax'
|
||||
'gPIAEoCzImLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuUHJvb2ZPZldvcmtIAFILcHJvb2ZP'
|
||||
'ZldvcmsSSQogcGFzc3dvcmRsZXNzX3JlY292ZXJ5X3NlcnZlcl9rZXkYECABKAxIAFIdcGFzc3'
|
||||
'dvcmRsZXNzUmVjb3ZlcnlTZXJ2ZXJLZXkSiwEKInBhc3N3b3JkbGVzc19ub3RpZmljYXRpb25f'
|
||||
'bWVzc2FnZXMYESABKAsyOy5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLlBhc3N3b3JkbGVzc0'
|
||||
'5vdGlmaWNhdGlvbk1lc3NhZ2VzSABSIHBhc3N3b3JkbGVzc05vdGlmaWNhdGlvbk1lc3NhZ2Vz'
|
||||
'EmEKFG1lbW9yaWVzX3VwbG9hZF91cmxzGBIgASgLMi0uc2VydmVyX3RvX2NsaWVudC5SZXNwb2'
|
||||
'5zZS5NZW1vcmllc1VwbG9hZFVybHNIAFISbWVtb3JpZXNVcGxvYWRVcmxzEk4KDW1lbW9yaWVz'
|
||||
'X2xpc3QYEyABKAsyJy5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLk1lbW9yaWVzTGlzdEgAUg'
|
||||
'xtZW1vcmllc0xpc3QSSwoMbWVtb3JpZXNfdXJsGBQgASgLMiYuc2VydmVyX3RvX2NsaWVudC5S'
|
||||
'ZXNwb25zZS5NZW1vcmllc1VybEgAUgttZW1vcmllc1VybBJRCg5tZW1vcmllc191c2FnZRgVIA'
|
||||
'EoCzIoLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuTWVtb3JpZXNVc2FnZUgAUg1tZW1vcmll'
|
||||
'c1VzYWdlQgQKAk9rSgQIBxAISgQICxAMQgoKCFJlc3BvbnNl');
|
||||
'RpdGlvbmFsQWNjb3VudBo+CgVQbGFucxI1CgVwbGFucxgBIAMoCzIfLnNlcnZlcl90b19jbGll'
|
||||
'bnQuUmVzcG9uc2UuUGxhblIFcGxhbnMaTQoRQWRkQWNjb3VudHNJbnZpdGUSFwoHcGxhbl9pZB'
|
||||
'gBIAEoCVIGcGxhbklkEh8KC2ludml0ZV9jb2RlGAIgASgJUgppbnZpdGVDb2RlGlwKEkFkZEFj'
|
||||
'Y291bnRzSW52aXRlcxJGCgdpbnZpdGVzGAEgAygLMiwuc2VydmVyX3RvX2NsaWVudC5SZXNwb2'
|
||||
'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'
|
||||
'bHR5Gl4KH1Bhc3N3b3JkbGVzc05vdGlmaWNhdGlvbk1lc3NhZ2USDgoCaWQYASABKANSAmlkEi'
|
||||
'sKEWVuY3J5cHRlZF9tZXNzYWdlGAIgASgMUhBlbmNyeXB0ZWRNZXNzYWdlGnoKIFBhc3N3b3Jk'
|
||||
'bGVzc05vdGlmaWNhdGlvbk1lc3NhZ2VzElYKCG1lc3NhZ2VzGAEgAygLMjouc2VydmVyX3RvX2'
|
||||
'NsaWVudC5SZXNwb25zZS5QYXNzd29yZGxlc3NOb3RpZmljYXRpb25NZXNzYWdlUghtZXNzYWdl'
|
||||
'cxqwCQoCT2sSFAoETm9uZRgBIAEoCEgAUgROb25lEhgKBnVzZXJpZBgCIAEoA0gAUgZ1c2VyaW'
|
||||
'QSJgoNYXV0aGNoYWxsZW5nZRgDIAEoDEgAUg1hdXRoY2hhbGxlbmdlEkoKC3VwbG9hZHRva2Vu'
|
||||
'GAQgASgLMiYuc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5VcGxvYWRUb2tlbkgAUgt1cGxvYW'
|
||||
'R0b2tlbhJBCgh1c2VyZGF0YRgFIAEoCzIjLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuVXNl'
|
||||
'ckRhdGFIAFIIdXNlcmRhdGESHgoJYXV0aHRva2VuGAYgASgMSABSCWF1dGh0b2tlbhJKCgxkZX'
|
||||
'ByZWNhdGVkXzcYByABKAsyJS5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLkRlcHJlY2F0ZWRI'
|
||||
'AFILZGVwcmVjYXRlZDcSUAoNYXV0aGVudGljYXRlZBgIIAEoCzIoLnNlcnZlcl90b19jbGllbn'
|
||||
'QuUmVzcG9uc2UuQXV0aGVudGljYXRlZEgAUg1hdXRoZW50aWNhdGVkEjgKBXBsYW5zGAkgASgL'
|
||||
'MiAuc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5QbGFuc0gAUgVwbGFucxJNCgxwbGFuYmFsbG'
|
||||
'FuY2UYCiABKAsyJy5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLlBsYW5CYWxsYW5jZUgAUgxw'
|
||||
'bGFuYmFsbGFuY2USTAoNZGVwcmVjYXRlZF8xMRgLIAEoCzIlLnNlcnZlcl90b19jbGllbnQuUm'
|
||||
'VzcG9uc2UuRGVwcmVjYXRlZEgAUgxkZXByZWNhdGVkMTESXwoSYWRkYWNjb3VudHNpbnZpdGVz'
|
||||
'GAwgASgLMi0uc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5BZGRBY2NvdW50c0ludml0ZXNIAF'
|
||||
'ISYWRkYWNjb3VudHNpbnZpdGVzElMKDmRvd25sb2FkdG9rZW5zGA0gASgLMikuc2VydmVyX3Rv'
|
||||
'X2NsaWVudC5SZXNwb25zZS5Eb3dubG9hZFRva2Vuc0gAUg5kb3dubG9hZHRva2VucxJNCgxzaW'
|
||||
'duZWRwcmVrZXkYDiABKAsyJy5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLlNpZ25lZFByZUtl'
|
||||
'eUgAUgxzaWduZWRwcmVrZXkSSgoLcHJvb2ZPZldvcmsYDyABKAsyJi5zZXJ2ZXJfdG9fY2xpZW'
|
||||
'50LlJlc3BvbnNlLlByb29mT2ZXb3JrSABSC3Byb29mT2ZXb3JrEkkKIHBhc3N3b3JkbGVzc19y'
|
||||
'ZWNvdmVyeV9zZXJ2ZXJfa2V5GBAgASgMSABSHXBhc3N3b3JkbGVzc1JlY292ZXJ5U2VydmVyS2'
|
||||
'V5EosBCiJwYXNzd29yZGxlc3Nfbm90aWZpY2F0aW9uX21lc3NhZ2VzGBEgASgLMjsuc2VydmVy'
|
||||
'X3RvX2NsaWVudC5SZXNwb25zZS5QYXNzd29yZGxlc3NOb3RpZmljYXRpb25NZXNzYWdlc0gAUi'
|
||||
'BwYXNzd29yZGxlc3NOb3RpZmljYXRpb25NZXNzYWdlc0IECgJPa0IKCghSZXNwb25zZQ==');
|
||||
|
|
|
|||
|
|
@ -9,11 +9,4 @@ message TwonlySafeBackupEncrypted {
|
|||
bytes mac = 1;
|
||||
bytes nonce = 2;
|
||||
bytes cipher_text = 3;
|
||||
}
|
||||
|
||||
message CloudMediaBackupEncrypted {
|
||||
string addition = 1;
|
||||
bytes encrypted_media_key = 2; // Generated by Rust MainKey
|
||||
bytes media_nonce = 3; // Nonce used for ChaCha20
|
||||
bytes media_ciphertext = 4; // The actual media_ciphertext (including the Poly1305 MAC)
|
||||
}
|
||||
|
|
@ -164,99 +164,6 @@ class TwonlySafeBackupEncrypted extends $pb.GeneratedMessage {
|
|||
void clearCipherText() => $_clearField(3);
|
||||
}
|
||||
|
||||
class CloudMediaBackupEncrypted extends $pb.GeneratedMessage {
|
||||
factory CloudMediaBackupEncrypted({
|
||||
$core.String? addition,
|
||||
$core.List<$core.int>? encryptedMediaKey,
|
||||
$core.List<$core.int>? mediaNonce,
|
||||
$core.List<$core.int>? mediaCiphertext,
|
||||
}) {
|
||||
final result = create();
|
||||
if (addition != null) result.addition = addition;
|
||||
if (encryptedMediaKey != null) result.encryptedMediaKey = encryptedMediaKey;
|
||||
if (mediaNonce != null) result.mediaNonce = mediaNonce;
|
||||
if (mediaCiphertext != null) result.mediaCiphertext = mediaCiphertext;
|
||||
return result;
|
||||
}
|
||||
|
||||
CloudMediaBackupEncrypted._();
|
||||
|
||||
factory CloudMediaBackupEncrypted.fromBuffer($core.List<$core.int> data,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromBuffer(data, registry);
|
||||
factory CloudMediaBackupEncrypted.fromJson($core.String json,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromJson(json, registry);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
_omitMessageNames ? '' : 'CloudMediaBackupEncrypted',
|
||||
createEmptyInstance: create)
|
||||
..aOS(1, _omitFieldNames ? '' : 'addition')
|
||||
..a<$core.List<$core.int>>(
|
||||
2, _omitFieldNames ? '' : 'encryptedMediaKey', $pb.PbFieldType.OY)
|
||||
..a<$core.List<$core.int>>(
|
||||
3, _omitFieldNames ? '' : 'mediaNonce', $pb.PbFieldType.OY)
|
||||
..a<$core.List<$core.int>>(
|
||||
4, _omitFieldNames ? '' : 'mediaCiphertext', $pb.PbFieldType.OY)
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
CloudMediaBackupEncrypted clone() => deepCopy();
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
CloudMediaBackupEncrypted copyWith(
|
||||
void Function(CloudMediaBackupEncrypted) updates) =>
|
||||
super.copyWith((message) => updates(message as CloudMediaBackupEncrypted))
|
||||
as CloudMediaBackupEncrypted;
|
||||
|
||||
@$core.override
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static CloudMediaBackupEncrypted create() => CloudMediaBackupEncrypted._();
|
||||
@$core.override
|
||||
CloudMediaBackupEncrypted createEmptyInstance() => create();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static CloudMediaBackupEncrypted getDefault() => _defaultInstance ??=
|
||||
$pb.GeneratedMessage.$_defaultFor<CloudMediaBackupEncrypted>(create);
|
||||
static CloudMediaBackupEncrypted? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.String get addition => $_getSZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set addition($core.String value) => $_setString(0, value);
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasAddition() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearAddition() => $_clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.List<$core.int> get encryptedMediaKey => $_getN(1);
|
||||
@$pb.TagNumber(2)
|
||||
set encryptedMediaKey($core.List<$core.int> value) => $_setBytes(1, value);
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasEncryptedMediaKey() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearEncryptedMediaKey() => $_clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.List<$core.int> get mediaNonce => $_getN(2);
|
||||
@$pb.TagNumber(3)
|
||||
set mediaNonce($core.List<$core.int> value) => $_setBytes(2, value);
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasMediaNonce() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearMediaNonce() => $_clearField(3);
|
||||
|
||||
@$pb.TagNumber(4)
|
||||
$core.List<$core.int> get mediaCiphertext => $_getN(3);
|
||||
@$pb.TagNumber(4)
|
||||
set mediaCiphertext($core.List<$core.int> value) => $_setBytes(3, value);
|
||||
@$pb.TagNumber(4)
|
||||
$core.bool hasMediaCiphertext() => $_has(3);
|
||||
@$pb.TagNumber(4)
|
||||
void clearMediaCiphertext() => $_clearField(4);
|
||||
}
|
||||
|
||||
const $core.bool _omitFieldNames =
|
||||
$core.bool.fromEnvironment('protobuf.omit_field_names');
|
||||
const $core.bool _omitMessageNames =
|
||||
|
|
|
|||
|
|
@ -51,27 +51,3 @@ final $typed_data.Uint8List twonlySafeBackupEncryptedDescriptor =
|
|||
$convert.base64Decode(
|
||||
'ChlUd29ubHlTYWZlQmFja3VwRW5jcnlwdGVkEhAKA21hYxgBIAEoDFIDbWFjEhQKBW5vbmNlGA'
|
||||
'IgASgMUgVub25jZRIfCgtjaXBoZXJfdGV4dBgDIAEoDFIKY2lwaGVyVGV4dA==');
|
||||
|
||||
@$core.Deprecated('Use cloudMediaBackupEncryptedDescriptor instead')
|
||||
const CloudMediaBackupEncrypted$json = {
|
||||
'1': 'CloudMediaBackupEncrypted',
|
||||
'2': [
|
||||
{'1': 'addition', '3': 1, '4': 1, '5': 9, '10': 'addition'},
|
||||
{
|
||||
'1': 'encrypted_media_key',
|
||||
'3': 2,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'10': 'encryptedMediaKey'
|
||||
},
|
||||
{'1': 'media_nonce', '3': 3, '4': 1, '5': 12, '10': 'mediaNonce'},
|
||||
{'1': 'media_ciphertext', '3': 4, '4': 1, '5': 12, '10': 'mediaCiphertext'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `CloudMediaBackupEncrypted`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List cloudMediaBackupEncryptedDescriptor = $convert.base64Decode(
|
||||
'ChlDbG91ZE1lZGlhQmFja3VwRW5jcnlwdGVkEhoKCGFkZGl0aW9uGAEgASgJUghhZGRpdGlvbh'
|
||||
'IuChNlbmNyeXB0ZWRfbWVkaWFfa2V5GAIgASgMUhFlbmNyeXB0ZWRNZWRpYUtleRIfCgttZWRp'
|
||||
'YV9ub25jZRgDIAEoDFIKbWVkaWFOb25jZRIpChBtZWRpYV9jaXBoZXJ0ZXh0GAQgASgMUg9tZW'
|
||||
'RpYUNpcGhlcnRleHQ=');
|
||||
|
|
|
|||
|
|
@ -25,8 +25,6 @@ class Message_Type extends $pb.ProtobufEnum {
|
|||
Message_Type._(3, _omitEnumNames ? '' : 'PREKEY_BUNDLE');
|
||||
static const Message_Type TEST_NOTIFICATION =
|
||||
Message_Type._(4, _omitEnumNames ? '' : 'TEST_NOTIFICATION');
|
||||
static const Message_Type CIPHERTEXT_V2 =
|
||||
Message_Type._(5, _omitEnumNames ? '' : 'CIPHERTEXT_V2');
|
||||
|
||||
static const $core.List<Message_Type> values = <Message_Type>[
|
||||
SENDER_DELIVERY_RECEIPT,
|
||||
|
|
@ -34,11 +32,10 @@ class Message_Type extends $pb.ProtobufEnum {
|
|||
CIPHERTEXT,
|
||||
PREKEY_BUNDLE,
|
||||
TEST_NOTIFICATION,
|
||||
CIPHERTEXT_V2,
|
||||
];
|
||||
|
||||
static final $core.List<Message_Type?> _byValue =
|
||||
$pb.ProtobufEnum.$_initByValueList(values, 5);
|
||||
$pb.ProtobufEnum.$_initByValueList(values, 4);
|
||||
static Message_Type? valueOf($core.int value) =>
|
||||
value < 0 || value >= _byValue.length ? null : _byValue[value];
|
||||
|
||||
|
|
|
|||
|
|
@ -57,7 +57,6 @@ const Message_Type$json = {
|
|||
{'1': 'CIPHERTEXT', '2': 2},
|
||||
{'1': 'PREKEY_BUNDLE', '2': 3},
|
||||
{'1': 'TEST_NOTIFICATION', '2': 4},
|
||||
{'1': 'CIPHERTEXT_V2', '2': 5},
|
||||
],
|
||||
};
|
||||
|
||||
|
|
@ -66,10 +65,10 @@ final $typed_data.Uint8List messageDescriptor = $convert.base64Decode(
|
|||
'CgdNZXNzYWdlEiEKBHR5cGUYASABKA4yDS5NZXNzYWdlLlR5cGVSBHR5cGUSHQoKcmVjZWlwdF'
|
||||
'9pZBgCIAEoCVIJcmVjZWlwdElkEjAKEWVuY3J5cHRlZF9jb250ZW50GAMgASgMSABSEGVuY3J5'
|
||||
'cHRlZENvbnRlbnSIAQESQwoRcGxhaW50ZXh0X2NvbnRlbnQYBCABKAsyES5QbGFpbnRleHRDb2'
|
||||
'50ZW50SAFSEHBsYWludGV4dENvbnRlbnSIAQEihwEKBFR5cGUSGwoXU0VOREVSX0RFTElWRVJZ'
|
||||
'X1JFQ0VJUFQQABIVChFQTEFJTlRFWFRfQ09OVEVOVBABEg4KCkNJUEhFUlRFWFQQAhIRCg1QUk'
|
||||
'VLRVlfQlVORExFEAMSFQoRVEVTVF9OT1RJRklDQVRJT04QBBIRCg1DSVBIRVJURVhUX1YyEAVC'
|
||||
'FAoSX2VuY3J5cHRlZF9jb250ZW50QhQKEl9wbGFpbnRleHRfY29udGVudA==');
|
||||
'50ZW50SAFSEHBsYWludGV4dENvbnRlbnSIAQEidAoEVHlwZRIbChdTRU5ERVJfREVMSVZFUllf'
|
||||
'UkVDRUlQVBAAEhUKEVBMQUlOVEVYVF9DT05URU5UEAESDgoKQ0lQSEVSVEVYVBACEhEKDVBSRU'
|
||||
'tFWV9CVU5ETEUQAxIVChFURVNUX05PVElGSUNBVElPThAEQhQKEl9lbmNyeXB0ZWRfY29udGVu'
|
||||
'dEIUChJfcGxhaW50ZXh0X2NvbnRlbnQ=');
|
||||
|
||||
@$core.Deprecated('Use plaintextContentDescriptor instead')
|
||||
const PlaintextContent$json = {
|
||||
|
|
|
|||
|
|
@ -17,8 +17,80 @@ import 'package:protobuf/protobuf.dart' as $pb;
|
|||
|
||||
export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions;
|
||||
|
||||
/// Used as envelope for TrustedFriendShare and RecoveryData.
|
||||
/// Encrypted using XChaCha20-Poly1305.
|
||||
/// Send from the person who tries to recover their account.
|
||||
/// This can be done via a link, which will then be opend in the app of the contact.
|
||||
/// The contact than has to manualy select from which user he got the request.
|
||||
/// -> Using this phishing is harder, as the user has to manualy select the user to recovery
|
||||
/// -> The user who wants to recover his account does not need to remember her old username
|
||||
class RecoveryRequest extends $pb.GeneratedMessage {
|
||||
factory RecoveryRequest({
|
||||
$core.String? notificationId,
|
||||
$core.List<$core.int>? publicKey,
|
||||
}) {
|
||||
final result = create();
|
||||
if (notificationId != null) result.notificationId = notificationId;
|
||||
if (publicKey != null) result.publicKey = publicKey;
|
||||
return result;
|
||||
}
|
||||
|
||||
RecoveryRequest._();
|
||||
|
||||
factory RecoveryRequest.fromBuffer($core.List<$core.int> data,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromBuffer(data, registry);
|
||||
factory RecoveryRequest.fromJson($core.String json,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromJson(json, registry);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
_omitMessageNames ? '' : 'RecoveryRequest',
|
||||
package: const $pb.PackageName(
|
||||
_omitMessageNames ? '' : 'passwordless_recovery'),
|
||||
createEmptyInstance: create)
|
||||
..aOS(1, _omitFieldNames ? '' : 'notificationId')
|
||||
..a<$core.List<$core.int>>(
|
||||
2, _omitFieldNames ? '' : 'publicKey', $pb.PbFieldType.OY)
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
RecoveryRequest clone() => deepCopy();
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
RecoveryRequest copyWith(void Function(RecoveryRequest) updates) =>
|
||||
super.copyWith((message) => updates(message as RecoveryRequest))
|
||||
as RecoveryRequest;
|
||||
|
||||
@$core.override
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static RecoveryRequest create() => RecoveryRequest._();
|
||||
@$core.override
|
||||
RecoveryRequest createEmptyInstance() => create();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static RecoveryRequest getDefault() => _defaultInstance ??=
|
||||
$pb.GeneratedMessage.$_defaultFor<RecoveryRequest>(create);
|
||||
static RecoveryRequest? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.String get notificationId => $_getSZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set notificationId($core.String value) => $_setString(0, value);
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasNotificationId() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearNotificationId() => $_clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.List<$core.int> get publicKey => $_getN(1);
|
||||
@$pb.TagNumber(2)
|
||||
set publicKey($core.List<$core.int> value) => $_setBytes(1, value);
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasPublicKey() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearPublicKey() => $_clearField(2);
|
||||
}
|
||||
|
||||
/// Used as envelope for TrustedFriendShare and RecoveryData
|
||||
class EncryptedEnvelope extends $pb.GeneratedMessage {
|
||||
factory EncryptedEnvelope({
|
||||
$core.List<$core.int>? encryptedData,
|
||||
|
|
@ -182,8 +254,8 @@ class TrustedFriendShare_User extends $pb.GeneratedMessage {
|
|||
void clearAvatar() => $_clearField(3);
|
||||
}
|
||||
|
||||
/// Sent from the trusted friend to the recovering user via the server.
|
||||
/// This is symmetrically encrypted using the encryption key received from the recovery link.
|
||||
/// Send from the trusted friend to
|
||||
/// This is encrypted with the received public key.
|
||||
class TrustedFriendShare extends $pb.GeneratedMessage {
|
||||
factory TrustedFriendShare({
|
||||
TrustedFriendShare_User? trustedFriend,
|
||||
|
|
@ -241,7 +313,7 @@ class TrustedFriendShare extends $pb.GeneratedMessage {
|
|||
$pb.GeneratedMessage.$_defaultFor<TrustedFriendShare>(create);
|
||||
static TrustedFriendShare? _defaultInstance;
|
||||
|
||||
/// This allows to display to the recovering user which trusted friend has sent their recovery data.
|
||||
/// This allows to display the user which user has send him his recovery data.
|
||||
@$pb.TagNumber(1)
|
||||
TrustedFriendShare_User get trustedFriend => $_getN(0);
|
||||
@$pb.TagNumber(1)
|
||||
|
|
@ -253,7 +325,7 @@ class TrustedFriendShare extends $pb.GeneratedMessage {
|
|||
@$pb.TagNumber(1)
|
||||
TrustedFriendShare_User ensureTrustedFriend() => $_ensure(0);
|
||||
|
||||
/// This allows to display the userdata, showing that the trusted friend is recovering the correct person.
|
||||
/// This allows to display the userdata, showing that he is recovering the correct person.
|
||||
@$pb.TagNumber(2)
|
||||
TrustedFriendShare_User get shareUser => $_getN(1);
|
||||
@$pb.TagNumber(2)
|
||||
|
|
@ -265,7 +337,7 @@ class TrustedFriendShare extends $pb.GeneratedMessage {
|
|||
@$pb.TagNumber(2)
|
||||
TrustedFriendShare_User ensureShareUser() => $_ensure(1);
|
||||
|
||||
/// The minimum threshold required to reconstruct the shares.
|
||||
/// The minimum threshold required to decrypte the shares.
|
||||
@$pb.TagNumber(3)
|
||||
$core.int get threshold => $_getIZ(2);
|
||||
@$pb.TagNumber(3)
|
||||
|
|
@ -275,7 +347,7 @@ class TrustedFriendShare extends $pb.GeneratedMessage {
|
|||
@$pb.TagNumber(3)
|
||||
void clearThreshold() => $_clearField(3);
|
||||
|
||||
/// The actual share which will be used to reconstruct SharedSecretData.
|
||||
/// The actual share which will become: SharedSecretData
|
||||
@$pb.TagNumber(4)
|
||||
$core.List<$core.int> get sharedSecretData => $_getN(3);
|
||||
@$pb.TagNumber(4)
|
||||
|
|
@ -355,18 +427,20 @@ class RecoveryData extends $pb.GeneratedMessage {
|
|||
void clearKeyManager() => $_clearField(3);
|
||||
}
|
||||
|
||||
/// After receiving threshold shares, this is reconstructed by the recovering user.
|
||||
/// After received all shares this is decrypted by the user restoring its own
|
||||
class SharedSecretData extends $pb.GeneratedMessage {
|
||||
factory SharedSecretData({
|
||||
$core.List<$core.int>? recoveryData,
|
||||
$core.List<$core.int>? serverKeyProtection,
|
||||
$core.List<$core.int>? encryptedServerKeyNonce,
|
||||
$core.List<$core.int>? pinSeed,
|
||||
$core.List<$core.int>? pinUnlockToken,
|
||||
$core.String? emailHint,
|
||||
}) {
|
||||
final result = create();
|
||||
if (recoveryData != null) result.recoveryData = recoveryData;
|
||||
if (serverKeyProtection != null)
|
||||
result.serverKeyProtection = serverKeyProtection;
|
||||
if (encryptedServerKeyNonce != null)
|
||||
result.encryptedServerKeyNonce = encryptedServerKeyNonce;
|
||||
if (pinSeed != null) result.pinSeed = pinSeed;
|
||||
if (pinUnlockToken != null) result.pinUnlockToken = pinUnlockToken;
|
||||
if (emailHint != null) result.emailHint = emailHint;
|
||||
return result;
|
||||
|
|
@ -389,7 +463,9 @@ class SharedSecretData extends $pb.GeneratedMessage {
|
|||
..a<$core.List<$core.int>>(
|
||||
1, _omitFieldNames ? '' : 'recoveryData', $pb.PbFieldType.OY)
|
||||
..a<$core.List<$core.int>>(
|
||||
3, _omitFieldNames ? '' : 'serverKeyProtection', $pb.PbFieldType.OY)
|
||||
3, _omitFieldNames ? '' : 'encryptedServerKeyNonce', $pb.PbFieldType.OY)
|
||||
..a<$core.List<$core.int>>(
|
||||
4, _omitFieldNames ? '' : 'pinSeed', $pb.PbFieldType.OY)
|
||||
..a<$core.List<$core.int>>(
|
||||
5, _omitFieldNames ? '' : 'pinUnlockToken', $pb.PbFieldType.OY)
|
||||
..aOS(6, _omitFieldNames ? '' : 'emailHint')
|
||||
|
|
@ -414,7 +490,7 @@ class SharedSecretData extends $pb.GeneratedMessage {
|
|||
$pb.GeneratedMessage.$_defaultFor<SharedSecretData>(create);
|
||||
static SharedSecretData? _defaultInstance;
|
||||
|
||||
/// The recovery data (RecoveryData). It is encrypted with the server_key in case a second factor was chosen.
|
||||
/// The recovery data is encrypted in case a second factor was chosen.
|
||||
@$pb.TagNumber(1)
|
||||
$core.List<$core.int> get recoveryData => $_getN(0);
|
||||
@$pb.TagNumber(1)
|
||||
|
|
@ -424,33 +500,40 @@ class SharedSecretData extends $pb.GeneratedMessage {
|
|||
@$pb.TagNumber(1)
|
||||
void clearRecoveryData() => $_clearField(1);
|
||||
|
||||
/// Combined with the user's PIN/Email via HKDF to derive the key to decrypt the encrypted server_key.
|
||||
/// The server NEVER sees this value.
|
||||
@$pb.TagNumber(3)
|
||||
$core.List<$core.int> get serverKeyProtection => $_getN(1);
|
||||
$core.List<$core.int> get encryptedServerKeyNonce => $_getN(1);
|
||||
@$pb.TagNumber(3)
|
||||
set serverKeyProtection($core.List<$core.int> value) => $_setBytes(1, value);
|
||||
set encryptedServerKeyNonce($core.List<$core.int> value) =>
|
||||
$_setBytes(1, value);
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasServerKeyProtection() => $_has(1);
|
||||
$core.bool hasEncryptedServerKeyNonce() => $_has(1);
|
||||
@$pb.TagNumber(3)
|
||||
void clearServerKeyProtection() => $_clearField(3);
|
||||
void clearEncryptedServerKeyNonce() => $_clearField(3);
|
||||
|
||||
@$pb.TagNumber(4)
|
||||
$core.List<$core.int> get pinSeed => $_getN(2);
|
||||
@$pb.TagNumber(4)
|
||||
set pinSeed($core.List<$core.int> value) => $_setBytes(2, value);
|
||||
@$pb.TagNumber(4)
|
||||
$core.bool hasPinSeed() => $_has(2);
|
||||
@$pb.TagNumber(4)
|
||||
void clearPinSeed() => $_clearField(4);
|
||||
|
||||
/// Used to authenticate with the server to fetch the encrypted server_key.
|
||||
@$pb.TagNumber(5)
|
||||
$core.List<$core.int> get pinUnlockToken => $_getN(2);
|
||||
$core.List<$core.int> get pinUnlockToken => $_getN(3);
|
||||
@$pb.TagNumber(5)
|
||||
set pinUnlockToken($core.List<$core.int> value) => $_setBytes(2, value);
|
||||
set pinUnlockToken($core.List<$core.int> value) => $_setBytes(3, value);
|
||||
@$pb.TagNumber(5)
|
||||
$core.bool hasPinUnlockToken() => $_has(2);
|
||||
$core.bool hasPinUnlockToken() => $_has(3);
|
||||
@$pb.TagNumber(5)
|
||||
void clearPinUnlockToken() => $_clearField(5);
|
||||
|
||||
@$pb.TagNumber(6)
|
||||
$core.String get emailHint => $_getSZ(3);
|
||||
$core.String get emailHint => $_getSZ(4);
|
||||
@$pb.TagNumber(6)
|
||||
set emailHint($core.String value) => $_setString(3, value);
|
||||
set emailHint($core.String value) => $_setString(4, value);
|
||||
@$pb.TagNumber(6)
|
||||
$core.bool hasEmailHint() => $_has(3);
|
||||
$core.bool hasEmailHint() => $_has(4);
|
||||
@$pb.TagNumber(6)
|
||||
void clearEmailHint() => $_clearField(6);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,20 @@ import 'dart:convert' as $convert;
|
|||
import 'dart:core' as $core;
|
||||
import 'dart:typed_data' as $typed_data;
|
||||
|
||||
@$core.Deprecated('Use recoveryRequestDescriptor instead')
|
||||
const RecoveryRequest$json = {
|
||||
'1': 'RecoveryRequest',
|
||||
'2': [
|
||||
{'1': 'notification_id', '3': 1, '4': 1, '5': 9, '10': 'notificationId'},
|
||||
{'1': 'public_key', '3': 2, '4': 1, '5': 12, '10': 'publicKey'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `RecoveryRequest`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List recoveryRequestDescriptor = $convert.base64Decode(
|
||||
'Cg9SZWNvdmVyeVJlcXVlc3QSJwoPbm90aWZpY2F0aW9uX2lkGAEgASgJUg5ub3RpZmljYXRpb2'
|
||||
'5JZBIdCgpwdWJsaWNfa2V5GAIgASgMUglwdWJsaWNLZXk=');
|
||||
|
||||
@$core.Deprecated('Use encryptedEnvelopeDescriptor instead')
|
||||
const EncryptedEnvelope$json = {
|
||||
'1': 'EncryptedEnvelope',
|
||||
|
|
@ -102,12 +116,21 @@ const SharedSecretData$json = {
|
|||
'2': [
|
||||
{'1': 'recovery_data', '3': 1, '4': 1, '5': 12, '10': 'recoveryData'},
|
||||
{
|
||||
'1': 'server_key_protection',
|
||||
'1': 'encrypted_server_key_nonce',
|
||||
'3': 3,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'9': 0,
|
||||
'10': 'serverKeyProtection',
|
||||
'10': 'encryptedServerKeyNonce',
|
||||
'17': true
|
||||
},
|
||||
{
|
||||
'1': 'pin_seed',
|
||||
'3': 4,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'9': 1,
|
||||
'10': 'pinSeed',
|
||||
'17': true
|
||||
},
|
||||
{
|
||||
|
|
@ -115,7 +138,7 @@ const SharedSecretData$json = {
|
|||
'3': 5,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'9': 1,
|
||||
'9': 2,
|
||||
'10': 'pinUnlockToken',
|
||||
'17': true
|
||||
},
|
||||
|
|
@ -124,13 +147,14 @@ const SharedSecretData$json = {
|
|||
'3': 6,
|
||||
'4': 1,
|
||||
'5': 9,
|
||||
'9': 2,
|
||||
'9': 3,
|
||||
'10': 'emailHint',
|
||||
'17': true
|
||||
},
|
||||
],
|
||||
'8': [
|
||||
{'1': '_server_key_protection'},
|
||||
{'1': '_encrypted_server_key_nonce'},
|
||||
{'1': '_pin_seed'},
|
||||
{'1': '_pin_unlock_token'},
|
||||
{'1': '_email_hint'},
|
||||
],
|
||||
|
|
@ -139,7 +163,8 @@ const SharedSecretData$json = {
|
|||
/// Descriptor for `SharedSecretData`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List sharedSecretDataDescriptor = $convert.base64Decode(
|
||||
'ChBTaGFyZWRTZWNyZXREYXRhEiMKDXJlY292ZXJ5X2RhdGEYASABKAxSDHJlY292ZXJ5RGF0YR'
|
||||
'I3ChVzZXJ2ZXJfa2V5X3Byb3RlY3Rpb24YAyABKAxIAFITc2VydmVyS2V5UHJvdGVjdGlvbogB'
|
||||
'ARItChBwaW5fdW5sb2NrX3Rva2VuGAUgASgMSAFSDnBpblVubG9ja1Rva2VuiAEBEiIKCmVtYW'
|
||||
'lsX2hpbnQYBiABKAlIAlIJZW1haWxIaW50iAEBQhgKFl9zZXJ2ZXJfa2V5X3Byb3RlY3Rpb25C'
|
||||
'EwoRX3Bpbl91bmxvY2tfdG9rZW5CDQoLX2VtYWlsX2hpbnQ=');
|
||||
'JAChplbmNyeXB0ZWRfc2VydmVyX2tleV9ub25jZRgDIAEoDEgAUhdlbmNyeXB0ZWRTZXJ2ZXJL'
|
||||
'ZXlOb25jZYgBARIeCghwaW5fc2VlZBgEIAEoDEgBUgdwaW5TZWVkiAEBEi0KEHBpbl91bmxvY2'
|
||||
'tfdG9rZW4YBSABKAxIAlIOcGluVW5sb2NrVG9rZW6IAQESIgoKZW1haWxfaGludBgGIAEoCUgD'
|
||||
'UgllbWFpbEhpbnSIAQFCHQobX2VuY3J5cHRlZF9zZXJ2ZXJfa2V5X25vbmNlQgsKCV9waW5fc2'
|
||||
'VlZEITChFfcGluX3VubG9ja190b2tlbkINCgtfZW1haWxfaGludA==');
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ message Message {
|
|||
CIPHERTEXT = 2;
|
||||
PREKEY_BUNDLE = 3;
|
||||
TEST_NOTIFICATION = 4;
|
||||
CIPHERTEXT_V2 = 5;
|
||||
}
|
||||
Type type = 1;
|
||||
string receipt_id = 2;
|
||||
|
|
|
|||
|
|
@ -1,28 +1,42 @@
|
|||
syntax = "proto3";
|
||||
package passwordless_recovery;
|
||||
|
||||
// Used as envelope for TrustedFriendShare and RecoveryData.
|
||||
// Encrypted using XChaCha20-Poly1305.
|
||||
// Recovery Process
|
||||
// - Generating: TempID and a new assymetric key pair
|
||||
// - Uploading to the server TempID + Push Tokens so the server can notify the user that someone helped him
|
||||
|
||||
|
||||
// Send from the person who tries to recover their account.
|
||||
// This can be done via a link, which will then be opend in the app of the contact.
|
||||
// The contact than has to manualy select from which user he got the request.
|
||||
// -> Using this phishing is harder, as the user has to manualy select the user to recovery
|
||||
// -> The user who wants to recover his account does not need to remember her old username
|
||||
message RecoveryRequest {
|
||||
string notification_id = 1;
|
||||
bytes public_key = 2;
|
||||
}
|
||||
|
||||
// Used as envelope for TrustedFriendShare and RecoveryData
|
||||
message EncryptedEnvelope {
|
||||
bytes encrypted_data = 1;
|
||||
bytes iv = 2;
|
||||
bytes mac = 3;
|
||||
}
|
||||
|
||||
// Sent from the trusted friend to the recovering user via the server.
|
||||
// This is symmetrically encrypted using the encryption key received from the recovery link.
|
||||
// Send from the trusted friend to
|
||||
// This is encrypted with the received public key.
|
||||
message TrustedFriendShare {
|
||||
|
||||
// This allows to display to the recovering user which trusted friend has sent their recovery data.
|
||||
// This allows to display the user which user has send him his recovery data.
|
||||
User trusted_friend = 1;
|
||||
|
||||
// This allows to display the userdata, showing that the trusted friend is recovering the correct person.
|
||||
// This allows to display the userdata, showing that he is recovering the correct person.
|
||||
User share_user = 2;
|
||||
|
||||
// The minimum threshold required to reconstruct the shares.
|
||||
// The minimum threshold required to decrypte the shares.
|
||||
int32 threshold = 3;
|
||||
|
||||
// The actual share which will be used to reconstruct SharedSecretData.
|
||||
// The actual share which will become: SharedSecretData
|
||||
bytes shared_secret_data = 4;
|
||||
|
||||
message User {
|
||||
|
|
@ -38,18 +52,18 @@ message RecoveryData {
|
|||
bytes key_manager = 3;
|
||||
}
|
||||
|
||||
// After receiving threshold shares, this is reconstructed by the recovering user.
|
||||
// After received all shares this is decrypted by the user restoring its own
|
||||
message SharedSecretData {
|
||||
|
||||
// The recovery data (RecoveryData). It is encrypted with the server_key in case a second factor was chosen.
|
||||
// The recovery data is encrypted in case a second factor was chosen.
|
||||
bytes recovery_data = 1;
|
||||
|
||||
// Combined with the user's PIN/Email via HKDF to derive the key to decrypt the encrypted server_key.
|
||||
// The server NEVER sees this value.
|
||||
optional bytes server_key_protection = 3;
|
||||
optional bytes encrypted_server_key_nonce = 3;
|
||||
|
||||
// Used to authenticate with the server to fetch the encrypted server_key.
|
||||
optional bytes pin_seed = 4;
|
||||
optional bytes pin_unlock_token = 5;
|
||||
|
||||
optional string email_hint = 6;
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ import 'package:twonly/src/visual/views/settings/help/diagnostics.view.dart';
|
|||
import 'package:twonly/src/visual/views/settings/help/faq.view.dart';
|
||||
import 'package:twonly/src/visual/views/settings/help/faq/verification_badge_faq.view.dart';
|
||||
import 'package:twonly/src/visual/views/settings/help/help.view.dart';
|
||||
import 'package:twonly/src/visual/views/settings/help/news.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';
|
||||
|
|
@ -260,10 +259,6 @@ final routerProvider = GoRouter(
|
|||
path: 'contact_us',
|
||||
builder: (context, state) => const ContactUsView(),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'news',
|
||||
builder: (context, state) => const NewsView(),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'diagnostics',
|
||||
builder: (context, state) => const DiagnosticsView(),
|
||||
|
|
|
|||
|
|
@ -2,23 +2,18 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/services/user.service.dart';
|
||||
import 'package:twonly/src/visual/themes/light.dart';
|
||||
|
||||
class SettingsChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
||||
late ThemeMode _themeMode;
|
||||
late Color _primaryColor;
|
||||
|
||||
ThemeMode get themeMode => _themeMode;
|
||||
Color get primaryColor => _primaryColor;
|
||||
|
||||
void loadSettings() {
|
||||
if (userService.isUserCreated) {
|
||||
_themeMode = userService.currentUser.themeMode;
|
||||
_primaryColor = userService.currentUser.primaryColor;
|
||||
notifyListeners();
|
||||
} else {
|
||||
_themeMode = ThemeMode.system;
|
||||
_primaryColor = defaultPrimaryColor;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -33,16 +28,4 @@ class SettingsChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
|||
|
||||
await UserService.update((u) => u.themeMode = newThemeMode);
|
||||
}
|
||||
|
||||
Future<void> updatePrimaryColor(Color newColor) async {
|
||||
if (newColor.toARGB32() == _primaryColor.toARGB32()) return;
|
||||
|
||||
_primaryColor = newColor;
|
||||
|
||||
notifyListeners();
|
||||
|
||||
await UserService.update(
|
||||
(u) => u.primaryColorValue = newColor.toARGB32(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,6 @@ import 'package:twonly/core/bridge/wrapper/key_manager.dart';
|
|||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/model/protobuf/api/websocket/client_to_server.pb.dart'
|
||||
as client;
|
||||
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'
|
||||
|
|
@ -34,7 +32,6 @@ 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.service.dart';
|
||||
import 'package:twonly/src/services/memories/memories_cloud.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/passwordless_recovery.service.dart';
|
||||
|
|
@ -146,7 +143,6 @@ class ApiService {
|
|||
unawaited(PasswordlessRecoveryService.performHeartbeat());
|
||||
|
||||
unawaited(UserDiscoveryService.checkForNewAnnouncedUsers());
|
||||
memoriesCloudService.init();
|
||||
|
||||
if (userService.currentUser.userStudyParticipantsToken != null) {
|
||||
// In case the user participates in the user study, call the handler after authenticated, to be sure there is a internet connection
|
||||
|
|
@ -299,17 +295,9 @@ class ApiService {
|
|||
return result;
|
||||
}
|
||||
|
||||
Future<bool> sendResponse(ClientToServer response) async {
|
||||
final channel = _channel;
|
||||
if (channel == null || channel.closeCode != null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
channel.sink.add(response.writeToBuffer());
|
||||
return true;
|
||||
} catch (e) {
|
||||
Log.warn('Could not send response to server: $e');
|
||||
return false;
|
||||
Future<void> sendResponse(ClientToServer response) async {
|
||||
if (_channel != null) {
|
||||
_channel!.sink.add(response.writeToBuffer());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -635,13 +623,7 @@ class ApiService {
|
|||
|
||||
final signalStore = await getSignalStoreFromIdentity(signalIdentity);
|
||||
|
||||
final signedPreKeysList = await signalStore.loadSignedPreKeys();
|
||||
if (signedPreKeysList.isEmpty) {
|
||||
throw Exception(
|
||||
'Signal Signed PreKeys list is empty. Database insertion likely failed due to lack of storage space or a corrupted database.',
|
||||
);
|
||||
}
|
||||
final signedPreKey = signedPreKeysList[0];
|
||||
final signedPreKey = (await signalStore.loadSignedPreKeys())[0];
|
||||
|
||||
final loginToken = await RustKeyManager.getLoginToken();
|
||||
|
||||
|
|
@ -728,82 +710,6 @@ class ApiService {
|
|||
return sendRequestSync(req);
|
||||
}
|
||||
|
||||
Future<server.Response_MemoriesUploadUrls?> requestMemoriesUpload(
|
||||
int sizeBytes,
|
||||
DateTime originalDate,
|
||||
String mediaId,
|
||||
) async {
|
||||
final get = ApplicationData_RequestMemoriesUpload()
|
||||
..size = Int64(sizeBytes)
|
||||
..originalDate = Int64(originalDate.millisecondsSinceEpoch)
|
||||
..mediaId = mediaId;
|
||||
final appData = ApplicationData()..requestMemoriesUpload = get;
|
||||
final req = createClientToServerFromApplicationData(appData);
|
||||
final res = await sendRequestSync(req);
|
||||
if (res.isSuccess) {
|
||||
final ok = res.value as server.Response_Ok;
|
||||
if (ok.hasMemoriesUploadUrls()) {
|
||||
return ok.memoriesUploadUrls;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<server.Response_MemoriesUsage?> getMemoriesUsage() async {
|
||||
final appData = ApplicationData()
|
||||
..getMemoriesUsage = ApplicationData_GetMemoriesUsage();
|
||||
final req = createClientToServerFromApplicationData(appData);
|
||||
final res = await sendRequestSync(req);
|
||||
if (res.isSuccess) {
|
||||
final ok = res.value as server.Response_Ok;
|
||||
if (ok.hasMemoriesUsage()) {
|
||||
return ok.memoriesUsage;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<server.Response_MemoriesUrl?> getMemoriesUrl(
|
||||
String mediaId,
|
||||
bool thumbnail,
|
||||
) async {
|
||||
final appData = ApplicationData()
|
||||
..getMemoriesUrl = ApplicationData_GetMemoriesUrl(
|
||||
mediaId: mediaId,
|
||||
thumbnail: thumbnail,
|
||||
);
|
||||
final req = createClientToServerFromApplicationData(appData);
|
||||
final res = await sendRequestSync(req);
|
||||
if (res.isSuccess) {
|
||||
final ok = res.value as server.Response_Ok;
|
||||
if (ok.hasMemoriesUrl()) {
|
||||
return ok.memoriesUrl;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<Result> confirmMemoriesUpload(String mediaId) async {
|
||||
final get = ApplicationData_ConfirmMemoriesUpload()..mediaId = mediaId;
|
||||
final appData = ApplicationData()..confirmMemoriesUpload = get;
|
||||
final req = createClientToServerFromApplicationData(appData);
|
||||
return sendRequestSync(req);
|
||||
}
|
||||
|
||||
Future<Result> deleteMemory(String mediaId) async {
|
||||
final get = ApplicationData_DeleteMemory()..mediaId = mediaId;
|
||||
final appData = ApplicationData()..deleteMemory = get;
|
||||
final req = createClientToServerFromApplicationData(appData);
|
||||
return sendRequestSync(req);
|
||||
}
|
||||
|
||||
Future<Result> disableMemoriesBackup() async {
|
||||
final get = ApplicationData_DisableMemoriesBackup();
|
||||
final appData = ApplicationData()..disableMemoriesBackup = get;
|
||||
final req = createClientToServerFromApplicationData(appData);
|
||||
return sendRequestSync(req);
|
||||
}
|
||||
|
||||
Future<int?> getUserIdFromUsername(String username) async {
|
||||
final appData = Handshake(
|
||||
getUseridByUsername: Handshake_GetUserIdByUsername(username: username),
|
||||
|
|
@ -872,15 +778,15 @@ class ApiService {
|
|||
|
||||
Future<Result> getServerKeyForPasswordlessRecovery({
|
||||
required int userId,
|
||||
List<int>? serverKeyProtection,
|
||||
List<int>? encryptedServerKeyNone,
|
||||
List<int>? pinUnlockToken,
|
||||
List<int>? pinProtectionKey,
|
||||
String? email,
|
||||
}) async {
|
||||
final get = Handshake_GetServerKeyForPasswordLessRecovery()
|
||||
..userId = Int64(userId);
|
||||
if (serverKeyProtection != null) {
|
||||
get.serverKeyProtection = serverKeyProtection;
|
||||
if (encryptedServerKeyNone != null) {
|
||||
get.encryptedServerKeyNone = encryptedServerKeyNone;
|
||||
}
|
||||
if (pinUnlockToken != null) {
|
||||
get.pinUnlockToken = pinUnlockToken;
|
||||
|
|
@ -897,6 +803,7 @@ class ApiService {
|
|||
return sendRequestSync(req, authenticated: false);
|
||||
}
|
||||
|
||||
|
||||
Future<Result> submitRecoveryShare({
|
||||
required String notificationId,
|
||||
required List<int> encryptedMessage,
|
||||
|
|
@ -1008,28 +915,6 @@ class ApiService {
|
|||
return sendRequestSync(req);
|
||||
}
|
||||
|
||||
Future<Result> uploadPqcPreKeys(
|
||||
int eccSignedPreKeyId,
|
||||
Uint8List eccSignedPreKey,
|
||||
Uint8List eccSignedPreKeySignature,
|
||||
int kyberSignedPreKeyId,
|
||||
Uint8List kyberSignedPreKey,
|
||||
Uint8List kyberSignedPreKeySignature,
|
||||
List<client.ApplicationData_PqcPreKey> prekeys,
|
||||
) async {
|
||||
final get = ApplicationData_UploadPqcPreKeys()
|
||||
..eccSignedPrekeyId = Int64(eccSignedPreKeyId)
|
||||
..eccSignedPrekey = eccSignedPreKey
|
||||
..eccSignedPrekeySignature = eccSignedPreKeySignature
|
||||
..kyberSignedPrekeyId = Int64(kyberSignedPreKeyId)
|
||||
..kyberSignedPrekey = kyberSignedPreKey
|
||||
..kyberSignedPrekeySignature = kyberSignedPreKeySignature
|
||||
..prekeys.addAll(prekeys);
|
||||
final appData = ApplicationData()..uploadPqcPrekeys = get;
|
||||
final req = createClientToServerFromApplicationData(appData);
|
||||
return sendRequestSync(req);
|
||||
}
|
||||
|
||||
Future<Response_PlanBallance?> loadPlanBalance({bool useCache = true}) async {
|
||||
final ballance = await getPlanBallance();
|
||||
if (ballance != null) {
|
||||
|
|
@ -1069,8 +954,7 @@ class ApiService {
|
|||
|
||||
/// Polls the server for new passwordless recovery notification messages.
|
||||
/// [alreadyReceivedIds] prevents the server from sending duplicates.
|
||||
Future<server.Response_PasswordlessNotificationMessages?>
|
||||
checkForPasswordlessNotification({
|
||||
Future<server.Response_PasswordlessNotificationMessages?> checkForPasswordlessNotification({
|
||||
required String notificationId,
|
||||
required List<int> downloadAuthToken,
|
||||
List<Int64>? alreadyReceivedIds,
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:twonly/core/bridge/wrapper/signal.dart';
|
||||
import 'package:twonly/src/model/protobuf/api/websocket/client_to_server.pb.dart'
|
||||
as client;
|
||||
import 'package:twonly/src/services/signal/identity.signal.dart';
|
||||
|
|
@ -19,25 +18,3 @@ Future<client.Response> handleRequestNewPreKey() async {
|
|||
final ok = client.Response_Ok()..prekeys = prekeys;
|
||||
return client.Response()..ok = ok;
|
||||
}
|
||||
|
||||
Future<client.Response?> handleRequestNewPqcPreKey() async {
|
||||
final pqcKeys = await RustSignal.generatePqcPrekeys();
|
||||
if (pqcKeys.isEmpty) return null;
|
||||
|
||||
final prekeysList = <client.ApplicationData_PqcPreKey>[];
|
||||
for (final pqcKey in pqcKeys) {
|
||||
prekeysList.add(
|
||||
client.ApplicationData_PqcPreKey(
|
||||
eccPreKeyId: Int64(pqcKey.eccPreKeyId),
|
||||
eccPreKey: pqcKey.eccPreKey,
|
||||
kyberPreKeyId: Int64(pqcKey.kyberPreKeyId),
|
||||
kyberPreKey: pqcKey.kyberPreKey,
|
||||
kyberPreKeySignature: pqcKey.kyberPreKeySignature,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final prekeys = client.Response_PqcPrekeys(prekeys: prekeysList);
|
||||
final ok = client.Response_Ok()..prekeysPqc = prekeys;
|
||||
return client.Response()..ok = ok;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ Future<void> initFileDownloader() async {
|
|||
if (update.task.taskId.contains('download_')) {
|
||||
await handleDownloadStatusUpdate(update);
|
||||
}
|
||||
|
||||
if (update.task.taskId.contains('backup_')) {
|
||||
await BackupService.handleBackupStatusUpdate(
|
||||
update.task.taskId,
|
||||
|
|
|
|||
|
|
@ -222,27 +222,6 @@ Future<void> finishStartedPreprocessing() async {
|
|||
Log.info('Ignoring media files as it is a draft');
|
||||
continue;
|
||||
}
|
||||
|
||||
final messages = await twonlyDB.messagesDao.getMessagesByMediaId(
|
||||
mediaFile.mediaId,
|
||||
);
|
||||
if (messages.isEmpty) {
|
||||
if (mediaFile.createdAt.isBefore(
|
||||
clock.now().subtract(const Duration(hours: 1)),
|
||||
)) {
|
||||
Log.info(
|
||||
'Deleted orphaned media file ${mediaFile.mediaId} as no messages reference it.',
|
||||
);
|
||||
MediaFileService(mediaFile).fullMediaRemoval();
|
||||
await twonlyDB.mediaFilesDao.deleteMediaFile(mediaFile.mediaId);
|
||||
} else {
|
||||
Log.info(
|
||||
'Media file ${mediaFile.mediaId} has no messages, but is too new to be deleted by finishStartedPreprocessing. Skipping.',
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
final service = MediaFileService(mediaFile);
|
||||
if (!service.originalPath.existsSync() &&
|
||||
|
|
@ -400,54 +379,49 @@ Future<void> insertMediaFileInMessagesTable(
|
|||
List<String> groupIds, {
|
||||
AdditionalMessageData? additionalData,
|
||||
}) async {
|
||||
await twonlyDB.transaction(() async {
|
||||
await twonlyDB.mediaFilesDao.updateAllMediaFiles(
|
||||
const MediaFilesCompanion(
|
||||
isDraftMedia: Value(false),
|
||||
),
|
||||
);
|
||||
for (final groupId in groupIds) {
|
||||
final groupMembers = await twonlyDB.groupsDao.getGroupContact(groupId);
|
||||
if (groupMembers.length == 1) {
|
||||
if (groupMembers.first.accountDeleted) {
|
||||
Log.warn(
|
||||
'Did not send media file to $groupId because the only account has deleted his account.',
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
final message = await twonlyDB.messagesDao.insertMessage(
|
||||
MessagesCompanion(
|
||||
groupId: Value(groupId),
|
||||
mediaId: Value(mediaService.mediaFile.mediaId),
|
||||
type: Value(MessageType.media.name),
|
||||
additionalMessageData: Value.absentIfNull(
|
||||
additionalData?.writeToBuffer(),
|
||||
),
|
||||
),
|
||||
);
|
||||
await twonlyDB.groupsDao.increaseLastMessageExchange(
|
||||
groupId,
|
||||
clock.now(),
|
||||
);
|
||||
if (message != null) {
|
||||
Log.info(
|
||||
'Created message ${message.messageId} for media ${message.mediaId}',
|
||||
await twonlyDB.mediaFilesDao.updateAllMediaFiles(
|
||||
const MediaFilesCompanion(
|
||||
isDraftMedia: Value(false),
|
||||
),
|
||||
);
|
||||
for (final groupId in groupIds) {
|
||||
final groupMembers = await twonlyDB.groupsDao.getGroupContact(groupId);
|
||||
if (groupMembers.length == 1) {
|
||||
if (groupMembers.first.accountDeleted) {
|
||||
Log.warn(
|
||||
'Did not send media file to $groupId because the only account has deleted his account.',
|
||||
);
|
||||
// de-archive contact when sending a new message
|
||||
await twonlyDB.groupsDao.updateGroup(
|
||||
message.groupId,
|
||||
const GroupsCompanion(
|
||||
archived: Value(false),
|
||||
deletedContent: Value(false),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
Log.error('Error inserting media upload message in database.');
|
||||
continue;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
final message = await twonlyDB.messagesDao.insertMessage(
|
||||
MessagesCompanion(
|
||||
groupId: Value(groupId),
|
||||
mediaId: Value(mediaService.mediaFile.mediaId),
|
||||
type: Value(MessageType.media.name),
|
||||
additionalMessageData: Value.absentIfNull(
|
||||
additionalData?.writeToBuffer(),
|
||||
),
|
||||
),
|
||||
);
|
||||
await twonlyDB.groupsDao.increaseLastMessageExchange(groupId, clock.now());
|
||||
if (message != null) {
|
||||
Log.info(
|
||||
'Created message ${message.messageId} for media ${message.mediaId}',
|
||||
);
|
||||
// de-archive contact when sending a new message
|
||||
await twonlyDB.groupsDao.updateGroup(
|
||||
message.groupId,
|
||||
const GroupsCompanion(
|
||||
archived: Value(false),
|
||||
deletedContent: Value(false),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
Log.error('Error inserting media upload message in database.');
|
||||
}
|
||||
}
|
||||
|
||||
unawaited(startBackgroundMediaUpload(mediaService));
|
||||
}
|
||||
|
|
@ -492,20 +466,12 @@ Future<void> _startBackgroundMediaUploadInternal(
|
|||
mediaService.mediaFile.mediaId,
|
||||
);
|
||||
if (messages.isEmpty) {
|
||||
if (mediaService.mediaFile.createdAt.isBefore(
|
||||
clock.now().subtract(const Duration(hours: 1)),
|
||||
)) {
|
||||
Log.warn(
|
||||
'Media files ${mediaService.mediaFile.mediaId} has no original, temp, or stored path. Removing it from DB as files are not existent.',
|
||||
);
|
||||
await twonlyDB.mediaFilesDao.deleteMediaFile(
|
||||
mediaService.mediaFile.mediaId,
|
||||
);
|
||||
} else {
|
||||
Log.warn(
|
||||
'Media files ${mediaService.mediaFile.mediaId} has no paths, but is too new to be deleted. Skipping deletion.',
|
||||
);
|
||||
}
|
||||
Log.warn(
|
||||
'Media files ${mediaService.mediaFile.mediaId} has no original, temp, or stored path. Removing it from DB as files are not existent.',
|
||||
);
|
||||
await twonlyDB.mediaFilesDao.deleteMediaFile(
|
||||
mediaService.mediaFile.mediaId,
|
||||
);
|
||||
} else {
|
||||
Log.warn(
|
||||
'Media files ${mediaService.mediaFile.mediaId} has no original, temp, or stored path, but messages still reference it. Marking as uploaded to stop retries.',
|
||||
|
|
@ -526,51 +492,20 @@ Future<void> _startBackgroundMediaUploadInternal(
|
|||
}
|
||||
|
||||
if (!mediaService.encryptedPath.existsSync()) {
|
||||
Log.info(
|
||||
'Media ${mediaService.mediaFile.mediaId}: Encrypted file not found. Starting encryption.',
|
||||
);
|
||||
await _encryptMediaFiles(mediaService);
|
||||
if (!mediaService.encryptedPath.existsSync()) {
|
||||
Log.warn(
|
||||
'Media ${mediaService.mediaFile.mediaId}: Encryption failed. Encrypted file still missing.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
Log.info(
|
||||
'Media ${mediaService.mediaFile.mediaId}: Encryption completed successfully.',
|
||||
);
|
||||
} else {
|
||||
Log.info(
|
||||
'Media ${mediaService.mediaFile.mediaId}: Encrypted file already exists.',
|
||||
);
|
||||
}
|
||||
|
||||
if (!mediaService.uploadRequestPath.existsSync()) {
|
||||
Log.info(
|
||||
'Media ${mediaService.mediaFile.mediaId}: Upload request file not found. Creating it.',
|
||||
);
|
||||
await _createUploadRequest(mediaService);
|
||||
if (!mediaService.uploadRequestPath.existsSync()) {
|
||||
Log.warn(
|
||||
'Media ${mediaService.mediaFile.mediaId}: Upload request file creation returned empty (e.g. no messages).',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
Log.info(
|
||||
'Media ${mediaService.mediaFile.mediaId}: Upload request file already exists.',
|
||||
);
|
||||
}
|
||||
|
||||
if (mediaService.uploadRequestPath.existsSync()) {
|
||||
Log.info(
|
||||
'Media ${mediaService.mediaFile.mediaId}: Transitioning uploadState from ${mediaService.mediaFile.uploadState} to uploading.',
|
||||
);
|
||||
await mediaService.setUploadState(UploadState.uploading);
|
||||
// at this point the original file is not used any more, so it can be deleted
|
||||
if (mediaService.originalPath.existsSync()) {
|
||||
Log.info(
|
||||
'Media ${mediaService.mediaFile.mediaId}: Deleting original file as it is now encrypted.',
|
||||
);
|
||||
mediaService.originalPath.deleteSync();
|
||||
}
|
||||
}
|
||||
|
|
@ -618,9 +553,6 @@ Future<void> _createUploadRequest(MediaFileService media) async {
|
|||
|
||||
if (messages.isEmpty) {
|
||||
// There where no user selected who should receive the image, so waiting with this step...
|
||||
Log.info(
|
||||
'Media ${media.mediaFile.mediaId}: No recipient messages found, waiting to create upload request',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ import 'dart:io';
|
|||
import 'package:clock/clock.dart';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||
import 'package:mutex/mutex.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||
import 'package:twonly/src/database/tables/contacts.table.dart';
|
||||
import 'package:twonly/src/database/tables/messages.table.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart';
|
||||
|
|
@ -114,6 +114,22 @@ Future<(Uint8List, Uint8List?)?> _tryToSendCompleteMessageInternal({
|
|||
// ignore: parameter_assignments
|
||||
receipt = loadedReceipt;
|
||||
|
||||
if (receipt.retryCount >= 2) {
|
||||
// After two retries, change the receiptId. This addresses a bug where the receiver received the message and marked it as received,
|
||||
// but the app was closed before the message was fully processed. Because the receipt was already stored, subsequent retries were
|
||||
// detected as duplicates and rejected.
|
||||
final oldReceiptId = receipt.receiptId;
|
||||
final updatedReceipt = await twonlyDB.receiptsDao.rotateReceiptId(
|
||||
oldReceiptId,
|
||||
);
|
||||
if (updatedReceipt != null) {
|
||||
Log.info(
|
||||
'Changed receiptId $oldReceiptId to ${updatedReceipt.receiptId} as retryCount is ${receipt.retryCount}',
|
||||
);
|
||||
receipt = updatedReceipt;
|
||||
}
|
||||
}
|
||||
|
||||
final contact = await twonlyDB.contactsDao.getContactById(
|
||||
receipt.contactId,
|
||||
);
|
||||
|
|
@ -160,11 +176,11 @@ Future<(Uint8List, Uint8List?)?> _tryToSendCompleteMessageInternal({
|
|||
}
|
||||
|
||||
if (message.type == pb.Message_Type.CIPHERTEXT) {
|
||||
final encryptResult = await signalEncryptMessage(
|
||||
final cipherText = await signalEncryptMessage(
|
||||
receipt.contactId,
|
||||
Uint8List.fromList(message.encryptedContent),
|
||||
);
|
||||
if (encryptResult == null) {
|
||||
if (cipherText == null) {
|
||||
Log.error(
|
||||
'[${receipt.receiptId}] Could not encrypt the message for user ${receipt.contactId}. Aborting and trying again.',
|
||||
);
|
||||
|
|
@ -178,31 +194,16 @@ Future<(Uint8List, Uint8List?)?> _tryToSendCompleteMessageInternal({
|
|||
await twonlyDB.receiptsDao.deleteReceipt(receipt.receiptId);
|
||||
return null;
|
||||
}
|
||||
message
|
||||
..encryptedContent = encryptResult.ciphertext
|
||||
..type = encryptResult.type;
|
||||
} else if (message.type == pb.Message_Type.CIPHERTEXT_V2) {
|
||||
final encryptResult = await signalEncryptMessageV2(
|
||||
receipt.contactId,
|
||||
Uint8List.fromList(message.encryptedContent),
|
||||
);
|
||||
if (encryptResult == null) {
|
||||
Log.error(
|
||||
'[${receipt.receiptId}] Could not encrypt the message (V2) for user ${receipt.contactId}. Aborting and trying again.',
|
||||
);
|
||||
if (receipt.messageId != null) {
|
||||
await twonlyDB.messagesDao.handleMessageAckByServer(
|
||||
receipt.contactId,
|
||||
receipt.messageId!,
|
||||
clock.now(),
|
||||
);
|
||||
}
|
||||
await twonlyDB.receiptsDao.deleteReceipt(receipt.receiptId);
|
||||
return null;
|
||||
message.encryptedContent = cipherText.serialize();
|
||||
switch (cipherText.getType()) {
|
||||
case CiphertextMessage.prekeyType:
|
||||
message.type = pb.Message_Type.PREKEY_BUNDLE;
|
||||
case CiphertextMessage.whisperType:
|
||||
message.type = pb.Message_Type.CIPHERTEXT;
|
||||
default:
|
||||
Log.error('Invalid ciphertext type: ${cipherText.getType()}.');
|
||||
return null;
|
||||
}
|
||||
message
|
||||
..encryptedContent = encryptResult.ciphertext
|
||||
..type = encryptResult.type;
|
||||
}
|
||||
|
||||
if (onlyReturnEncryptedData) {
|
||||
|
|
@ -481,11 +482,8 @@ Future<(Uint8List, Uint8List?)?> sendCipherText(
|
|||
}
|
||||
}
|
||||
|
||||
final contact = await twonlyDB.contactsDao.getContactById(contactId);
|
||||
final isV2 = contact?.signalVersion == SignalVersion.v2;
|
||||
|
||||
final response = pb.Message()
|
||||
..type = isV2 ? pb.Message_Type.CIPHERTEXT_V2 : pb.Message_Type.CIPHERTEXT
|
||||
..type = pb.Message_Type.CIPHERTEXT
|
||||
..encryptedContent = encryptedContent.writeToBuffer();
|
||||
|
||||
var retryCounter = 0;
|
||||
|
|
|
|||
|
|
@ -49,8 +49,6 @@ Future<void> handleServerMessage(server.ServerToClient msg) async {
|
|||
try {
|
||||
if (msg.v0.hasRequestNewPreKeys()) {
|
||||
response = await handleRequestNewPreKey();
|
||||
} else if (msg.v0.hasRequestNewPqcPreKeys()) {
|
||||
response = (await handleRequestNewPqcPreKey()) ?? response;
|
||||
} else if (msg.v0.hasNewMessage()) {
|
||||
Log.info('Got 1 message from the server.');
|
||||
await handleClient2ClientMessage(msg.v0.newMessage);
|
||||
|
|
@ -80,16 +78,9 @@ Future<void> handleServerMessage(server.ServerToClient msg) async {
|
|||
..seq = msg.v0.seq
|
||||
..response = response;
|
||||
|
||||
final responseSent = await apiService.sendResponse(ClientToServer()..v0 = v0);
|
||||
if (responseSent) {
|
||||
Log.info(
|
||||
'Successfully queued response for server message ${msg.v0.seq}.',
|
||||
);
|
||||
} else {
|
||||
Log.warn('Could not send response for server message ${msg.v0.seq}.');
|
||||
}
|
||||
await apiService.sendResponse(ClientToServer()..v0 = v0);
|
||||
AppState.gotMessageFromServer = true;
|
||||
Log.info('All messages from the server processed.');
|
||||
Log.info('All messages from the server proccessed.');
|
||||
}
|
||||
|
||||
DateTime lastPushKeyRequest = clock.now().subtract(const Duration(hours: 1));
|
||||
|
|
@ -148,43 +139,6 @@ Future<void> _handleClient2ClientMessage(
|
|||
}
|
||||
|
||||
if (await twonlyDB.receiptsDao.isDuplicated(receiptId)) {
|
||||
if (message.type == Message_Type.SENDER_DELIVERY_RECEIPT) {
|
||||
Log.info(
|
||||
'[$receiptId] Delivery receipt is a duplicate. Skipping receipt response.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
const duplicateReceiptCooldown = Duration(days: 10);
|
||||
final shouldResend = await twonlyDB.receiptsDao.claimDuplicateReceiptResend(
|
||||
receiptId,
|
||||
duplicateReceiptCooldown,
|
||||
);
|
||||
if (!shouldResend) {
|
||||
Log.info(
|
||||
'[$receiptId] Message is a duplicate. Skipping delivery receipt during cooldown.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
Log.info(
|
||||
'[$receiptId] Message is a duplicate and cooldown elapsed. Sending delivery receipt again.',
|
||||
);
|
||||
try {
|
||||
final response = Message(type: Message_Type.SENDER_DELIVERY_RECEIPT);
|
||||
await twonlyDB.receiptsDao.insertReceipt(
|
||||
ReceiptsCompanion(
|
||||
receiptId: Value(receiptId),
|
||||
contactId: Value(fromUserId),
|
||||
message: Value(response.writeToBuffer()),
|
||||
contactWillSendsReceipt: const Value(false),
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
Log.warn('[$receiptId] Error handling duplicate receipt ACK: $e');
|
||||
}
|
||||
await tryToSendCompleteMessage(
|
||||
receiptId: receiptId,
|
||||
blocking: false,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -236,7 +190,6 @@ Future<void> _handleClient2ClientMessage(
|
|||
}
|
||||
|
||||
case Message_Type.CIPHERTEXT:
|
||||
case Message_Type.CIPHERTEXT_V2:
|
||||
case Message_Type.PREKEY_BUNDLE:
|
||||
if (message.hasEncryptedContent()) {
|
||||
Value<String>? receiptIdDB;
|
||||
|
|
@ -313,13 +266,12 @@ Future<void> _handleClient2ClientMessage(
|
|||
} catch (e) {
|
||||
Log.warn('[$receiptId] Error inserting receipt: $e');
|
||||
}
|
||||
|
||||
targetReceiptId ??= receiptIdDB?.value ?? receiptId;
|
||||
|
||||
await tryToSendCompleteMessage(
|
||||
receiptId: targetReceiptId,
|
||||
blocking: false,
|
||||
);
|
||||
if (targetReceiptId != null) {
|
||||
await tryToSendCompleteMessage(
|
||||
receiptId: targetReceiptId,
|
||||
blocking: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
case Message_Type.TEST_NOTIFICATION:
|
||||
break;
|
||||
|
|
@ -345,23 +297,12 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessageRaw(
|
|||
Set<int>? brokenSessionsInCurrentBatch,
|
||||
}) async {
|
||||
Log.info('[$receiptId] calling signalDecryptMessage');
|
||||
EncryptedContent? encryptedContent;
|
||||
PlaintextContent_DecryptionErrorMessage_Type? decryptionErrorType;
|
||||
|
||||
if (messageType == Message_Type.CIPHERTEXT_V2) {
|
||||
(encryptedContent, decryptionErrorType) = await signalDecryptMessageV2(
|
||||
fromUserId,
|
||||
encryptedContentRaw,
|
||||
brokenSessionsInCurrentBatch: brokenSessionsInCurrentBatch,
|
||||
);
|
||||
} else {
|
||||
(encryptedContent, decryptionErrorType) = await signalDecryptMessageV1(
|
||||
fromUserId,
|
||||
encryptedContentRaw,
|
||||
messageType.value,
|
||||
brokenSessionsInCurrentBatch: brokenSessionsInCurrentBatch,
|
||||
);
|
||||
}
|
||||
var (encryptedContent, decryptionErrorType) = await signalDecryptMessage(
|
||||
fromUserId,
|
||||
encryptedContentRaw,
|
||||
messageType.value,
|
||||
brokenSessionsInCurrentBatch: brokenSessionsInCurrentBatch,
|
||||
);
|
||||
|
||||
if (encryptedContent == null) {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -33,12 +33,7 @@ class Result<T, E> {
|
|||
}
|
||||
|
||||
DateTime fromTimestamp(Int64 timeStamp) {
|
||||
final date = DateTime.fromMillisecondsSinceEpoch(timeStamp.toInt());
|
||||
final now = DateTime.now();
|
||||
if (date.isAfter(now)) {
|
||||
return now;
|
||||
}
|
||||
return date;
|
||||
return DateTime.fromMillisecondsSinceEpoch(timeStamp.toInt());
|
||||
}
|
||||
|
||||
// ignore: strict_raw_type
|
||||
|
|
|
|||
|
|
@ -89,40 +89,36 @@ Future<bool> initBackgroundExecution() async {
|
|||
final Mutex _keyValueMutex = Mutex();
|
||||
|
||||
// ignore: unreachable_from_main
|
||||
Future<bool> backgroundFetch({
|
||||
int? lastExecutionInSecondsLimit = 120,
|
||||
}) async {
|
||||
if (lastExecutionInSecondsLimit != null) {
|
||||
final shouldBeExecuted = await exclusiveAccess(
|
||||
lockName: 'periodic_task',
|
||||
mutex: _keyValueMutex,
|
||||
action: () async {
|
||||
final lastExecution = await KeyValueStore.get(
|
||||
KeyValueKeys.lastPeriodicTaskExecution,
|
||||
);
|
||||
if (lastExecution != null && lastExecution.containsKey('timestamp')) {
|
||||
final lastExecutionTime = lastExecution['timestamp'] as int?;
|
||||
if (lastExecutionTime != null) {
|
||||
final lastExecutionDate = DateTime.fromMillisecondsSinceEpoch(
|
||||
lastExecutionTime,
|
||||
);
|
||||
if (DateTime.now().difference(lastExecutionDate).inSeconds <
|
||||
lastExecutionInSecondsLimit) {
|
||||
return false;
|
||||
}
|
||||
Future<void> handlePeriodicTask({int lastExecutionInSecondsLimit = 120}) async {
|
||||
final shouldBeExecuted = await exclusiveAccess(
|
||||
lockName: 'periodic_task',
|
||||
mutex: _keyValueMutex,
|
||||
action: () async {
|
||||
final lastExecution = await KeyValueStore.get(
|
||||
KeyValueKeys.lastPeriodicTaskExecution,
|
||||
);
|
||||
if (lastExecution != null && lastExecution.containsKey('timestamp')) {
|
||||
final lastExecutionTime = lastExecution['timestamp'] as int?;
|
||||
if (lastExecutionTime != null) {
|
||||
final lastExecutionDate = DateTime.fromMillisecondsSinceEpoch(
|
||||
lastExecutionTime,
|
||||
);
|
||||
if (DateTime.now().difference(lastExecutionDate).inSeconds <
|
||||
lastExecutionInSecondsLimit) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
await KeyValueStore.put(KeyValueKeys.lastPeriodicTaskExecution, {
|
||||
'timestamp': DateTime.now().millisecondsSinceEpoch,
|
||||
});
|
||||
return true;
|
||||
},
|
||||
);
|
||||
}
|
||||
await KeyValueStore.put(KeyValueKeys.lastPeriodicTaskExecution, {
|
||||
'timestamp': DateTime.now().millisecondsSinceEpoch,
|
||||
});
|
||||
return true;
|
||||
},
|
||||
);
|
||||
|
||||
if (!shouldBeExecuted) return false;
|
||||
}
|
||||
if (!shouldBeExecuted) return;
|
||||
|
||||
Log.info('Periodic task was called.');
|
||||
Log.info('eu.twonly.periodic_task was called.');
|
||||
AppState.gotMessageFromServer = false;
|
||||
|
||||
final stopwatch = Stopwatch()..start();
|
||||
|
|
@ -134,16 +130,14 @@ Future<bool> backgroundFetch({
|
|||
|
||||
if (!await apiService.connect()) {
|
||||
Log.info('Could not connect to the api. Returning early.');
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!apiService.isAuthenticated) {
|
||||
Log.info('Api is not authenticated. Returning early.');
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
var receiveMessage = false;
|
||||
|
||||
try {
|
||||
while (!AppState.gotMessageFromServer) {
|
||||
if (stopwatch.elapsed.inSeconds >= 15) {
|
||||
|
|
@ -154,22 +148,19 @@ Future<bool> backgroundFetch({
|
|||
}
|
||||
|
||||
if (AppState.gotMessageFromServer) {
|
||||
receiveMessage = true;
|
||||
Log.info('Received a server message from the server.');
|
||||
}
|
||||
|
||||
await finishStartedPreprocessing();
|
||||
|
||||
if (lastExecutionInSecondsLimit != null) {
|
||||
await Future.delayed(const Duration(milliseconds: 2000));
|
||||
}
|
||||
await Future.delayed(const Duration(milliseconds: 2000));
|
||||
} finally {
|
||||
await apiService.close(() {});
|
||||
stopwatch.stop();
|
||||
}
|
||||
|
||||
Log.info('Periodic task finished after ${stopwatch.elapsed}.');
|
||||
return receiveMessage;
|
||||
Log.info('eu.twonly.periodic_task finished after ${stopwatch.elapsed}.');
|
||||
return;
|
||||
}
|
||||
|
||||
Future<void> handleProcessingTask() async {
|
||||
|
|
|
|||
|
|
@ -247,7 +247,7 @@ class BackupService {
|
|||
}
|
||||
|
||||
if (recoveryData.state == BackupRecoveryState.archiveBackupStarted) {
|
||||
// The KeyManager was restored successfully, restore the archive now.
|
||||
// The KeyManager was restored sucessfully, restore the archive now.
|
||||
try {
|
||||
final downloadToken =
|
||||
await RustBackupArchive.getBackupDownloadToken();
|
||||
|
|
|
|||
|
|
@ -347,7 +347,7 @@ Future<(int, EncryptedGroupState)?> fetchGroupState(Group group) async {
|
|||
}
|
||||
// the state is now updated and the appended_group_state should be removed on the server, so just call this
|
||||
// function again, to sync the local database
|
||||
return await fetchGroupState(group);
|
||||
return fetchGroupState(group);
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
|
@ -10,6 +12,7 @@ import 'package:twonly/locator.dart';
|
|||
import 'package:twonly/src/constants/routes.keys.dart';
|
||||
import 'package:twonly/src/database/tables/contacts.table.dart';
|
||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||
import 'package:twonly/src/services/api/mediafiles/upload.api.dart';
|
||||
import 'package:twonly/src/services/passwordless_recovery.service.dart'
|
||||
show PasswordlessRecoveryService;
|
||||
import 'package:twonly/src/services/signal/session.signal.dart';
|
||||
|
|
@ -17,6 +20,7 @@ import 'package:twonly/src/utils/log.dart';
|
|||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/utils/qr.utils.dart';
|
||||
import 'package:twonly/src/visual/components/alert.dialog.dart';
|
||||
import 'package:twonly/src/visual/views/camera/share_image_editor.view.dart';
|
||||
import 'package:twonly/src/visual/views/contact/add_contact_via_qr_link.view.dart';
|
||||
import 'package:twonly/src/visual/views/contact/add_new_contact.view.dart';
|
||||
|
||||
|
|
@ -28,7 +32,8 @@ Future<bool> handleIntentUrl(BuildContext context, Uri uri) async {
|
|||
// Check if this is the QR code link which was
|
||||
// therefore scanned with the system camera
|
||||
|
||||
if (uri.toString().startsWith(PasswordlessRecoveryService.linkPrefix)) {
|
||||
if (kDebugMode &&
|
||||
uri.toString().startsWith(PasswordlessRecoveryService.linkPrefix)) {
|
||||
await PasswordlessRecoveryService.handleRecoveryLink(uri.toString());
|
||||
return true;
|
||||
}
|
||||
|
|
@ -145,20 +150,53 @@ Future<void> _pubKeysDoNotMatch(BuildContext context, String username) async {
|
|||
);
|
||||
}
|
||||
|
||||
Future<void> handleIntentMediaFile(
|
||||
BuildContext context,
|
||||
String filePath,
|
||||
MediaType type,
|
||||
) async {
|
||||
final file = File(filePath);
|
||||
if (!file.existsSync()) {
|
||||
Log.error('The shared intent file does not exits.');
|
||||
return;
|
||||
}
|
||||
|
||||
final newMediaService = await initializeMediaUpload(
|
||||
type,
|
||||
userService.currentUser.defaultShowTime,
|
||||
);
|
||||
if (newMediaService == null) {
|
||||
Log.error('Could not create new media file for intent shared file');
|
||||
return;
|
||||
}
|
||||
|
||||
file.copySync(newMediaService.originalPath.path);
|
||||
if (!context.mounted) return;
|
||||
|
||||
await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ShareImageEditorView(
|
||||
mediaFileService: newMediaService,
|
||||
sharedFromGallery: true,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
StreamSubscription<List<SharedFile>> initIntentStreams(
|
||||
BuildContext context,
|
||||
void Function(Uri) onUrlCallBack,
|
||||
void Function(String, MediaType) onMediaCallBack,
|
||||
) {
|
||||
FlutterSharingIntent.instance.getInitialSharing().then((f) {
|
||||
if (!context.mounted) return;
|
||||
handleIntentSharedFile(context, f, onUrlCallBack, onMediaCallBack);
|
||||
handleIntentSharedFile(context, f, onUrlCallBack);
|
||||
});
|
||||
|
||||
return FlutterSharingIntent.instance.getMediaStream().listen(
|
||||
(f) {
|
||||
if (!context.mounted) return;
|
||||
handleIntentSharedFile(context, f, onUrlCallBack, onMediaCallBack);
|
||||
handleIntentSharedFile(context, f, onUrlCallBack);
|
||||
},
|
||||
// ignore: inference_failure_on_untyped_parameter
|
||||
onError: (err) {
|
||||
|
|
@ -171,7 +209,6 @@ Future<void> handleIntentSharedFile(
|
|||
BuildContext context,
|
||||
List<SharedFile> files,
|
||||
void Function(Uri) onUrlCallBack,
|
||||
void Function(String, MediaType) onMediaCallBack,
|
||||
) async {
|
||||
for (final file in files) {
|
||||
if (file.value == null) {
|
||||
|
|
@ -195,9 +232,9 @@ Future<void> handleIntentSharedFile(
|
|||
if (file.value!.endsWith('.gif')) {
|
||||
type = MediaType.gif;
|
||||
}
|
||||
onMediaCallBack(file.value!, type);
|
||||
await handleIntentMediaFile(context, file.value!, type);
|
||||
case SharedMediaType.VIDEO:
|
||||
onMediaCallBack(file.value!, MediaType.video);
|
||||
await handleIntentMediaFile(context, file.value!, MediaType.video);
|
||||
// ignore: no_default_cases
|
||||
default:
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import 'dart:typed_data';
|
|||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:cryptography_plus/cryptography_plus.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||
import 'package:twonly/src/database/tables/contacts.table.dart';
|
||||
|
|
@ -18,24 +17,6 @@ import 'package:twonly/src/utils/misc.dart';
|
|||
import 'package:twonly/src/visual/components/verification_success_dialog.comp.dart';
|
||||
|
||||
class KeyVerificationService {
|
||||
static final StreamController<void> _verificationSuccessCloseController =
|
||||
StreamController<void>.broadcast();
|
||||
|
||||
static Stream<void> get onVerificationSuccessClose =>
|
||||
_verificationSuccessCloseController.stream;
|
||||
|
||||
static void notifyVerificationSuccessClose() {
|
||||
_verificationSuccessCloseController.add(null);
|
||||
}
|
||||
|
||||
static void closeVerificationFlows(BuildContext context) {
|
||||
Navigator.popUntil(context, (route) {
|
||||
final name = route.settings.name;
|
||||
if (name == null) return false;
|
||||
return !name.contains('qr_scanner') && !name.contains('verifybadge');
|
||||
});
|
||||
}
|
||||
|
||||
static Future<List<int>> getNewSecretVerificationToken() async {
|
||||
final token = getRandomUint8List(16);
|
||||
await twonlyDB.keyVerificationDao.insertVerificationToken(token);
|
||||
|
|
|
|||
|
|
@ -13,8 +13,6 @@ import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
|||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/services/mediafiles/compression.service.dart';
|
||||
import 'package:twonly/src/services/mediafiles/thumbnail.service.dart';
|
||||
import 'package:twonly/src/services/memories/memories_cloud.service.dart'
|
||||
show MemoriesCloudService;
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
||||
|
|
@ -222,7 +220,6 @@ class MediaFileService {
|
|||
} catch (_) {}
|
||||
}
|
||||
if (mediaFile.stored &&
|
||||
mediaFile.cloudState == CloudState.none &&
|
||||
mediaFile.createdAt.isBefore(
|
||||
clock.now().subtract(const Duration(days: 30)),
|
||||
)) {
|
||||
|
|
@ -323,12 +320,10 @@ class MediaFileService {
|
|||
name: mediaFile.mediaId,
|
||||
);
|
||||
} else {
|
||||
unawaited(
|
||||
saveImageToGallery(
|
||||
await storedPath.readAsBytes(),
|
||||
createdAt: mediaFile.createdAt,
|
||||
name: mediaFile.mediaId,
|
||||
),
|
||||
await saveImageToGallery(
|
||||
storedPath.readAsBytesSync(),
|
||||
createdAt: mediaFile.createdAt,
|
||||
name: mediaFile.mediaId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -340,7 +335,6 @@ class MediaFileService {
|
|||
unawaited(createThumbnail());
|
||||
await calculateAndSaveSize();
|
||||
await hashMediaFile();
|
||||
await MemoriesCloudService().checkUploads();
|
||||
// updateFromDb is done in hashStoredMedia()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ Future<bool> createThumbnailsForImage(
|
|||
destinationFile.absolute.path,
|
||||
minWidth: 300,
|
||||
minHeight: 300,
|
||||
quality: 50,
|
||||
quality: 100,
|
||||
format: CompressFormat.webp,
|
||||
);
|
||||
stopwatch.stop();
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:blurhash_dart/blurhash_dart.dart' as bh;
|
||||
import 'package:clock/clock.dart';
|
||||
import 'package:drift/drift.dart' show Value;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:image/image.dart' as img;
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||
|
|
@ -193,8 +190,7 @@ class MemoriesService {
|
|||
|
||||
return MemoriesState(
|
||||
filesToMigrate: filesToMigrate,
|
||||
totalFilesToMigrate:
|
||||
filesToMigrate, // Reset total when computing new state? No, keep existing total if migrating.
|
||||
totalFilesToMigrate: filesToMigrate, // Reset total when computing new state? No, keep existing total if migrating.
|
||||
galleryItems: tempGalleryItems,
|
||||
months: tempMonths,
|
||||
orderedByMonth: tempOrderedByMonth,
|
||||
|
|
@ -260,12 +256,7 @@ class MemoriesService {
|
|||
// Phase 2: Background — hash, crop analysis, size calculation.
|
||||
// Each DB write here fires the stream subscription above, keeping
|
||||
// the gallery state fresh without a separate notification step.
|
||||
_dbSubscription?.pause();
|
||||
try {
|
||||
await _backgroundProcessPendingFiles(pendingFiles);
|
||||
} finally {
|
||||
_dbSubscription?.resume();
|
||||
}
|
||||
await _backgroundProcessPendingFiles(pendingFiles);
|
||||
} catch (e) {
|
||||
Log.error('Error in background migration queue: $e');
|
||||
}
|
||||
|
|
@ -289,26 +280,6 @@ class MemoriesService {
|
|||
if (mediaService.mediaFile.sizeInBytes == null) {
|
||||
await mediaService.calculateAndSaveSize();
|
||||
}
|
||||
|
||||
if (mediaService.mediaFile.blurhash == null) {
|
||||
try {
|
||||
final imageFile = mediaService.thumbnailPath.existsSync()
|
||||
? mediaService.thumbnailPath
|
||||
: mediaService.originalPath;
|
||||
if (imageFile.existsSync()) {
|
||||
final bytes = await imageFile.readAsBytes();
|
||||
final blurhash = await compute(_calculateBlurhash, bytes);
|
||||
if (blurhash != null) {
|
||||
await twonlyDB.mediaFilesDao.updateMedia(
|
||||
mediaFile.mediaId,
|
||||
MediaFilesCompanion(blurhash: Value(blurhash)),
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
Log.error('Error generating blurhash for ${mediaFile.mediaId}: $e');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
Log.error(
|
||||
'Error in background processing of ${mediaFile.mediaId}: $e',
|
||||
|
|
@ -389,13 +360,3 @@ class MemoriesService {
|
|||
_stateController.close();
|
||||
}
|
||||
}
|
||||
|
||||
String? _calculateBlurhash(Uint8List bytes) {
|
||||
try {
|
||||
final image = img.decodeImage(bytes);
|
||||
if (image != null) {
|
||||
return bh.BlurHash.encode(image).hash;
|
||||
}
|
||||
} catch (_) {}
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,413 +0,0 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cryptography_flutter_plus/cryptography_flutter_plus.dart';
|
||||
import 'package:cryptography_plus/cryptography_plus.dart';
|
||||
import 'package:drift/drift.dart' show Value;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:mutex/mutex.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:twonly/core/bridge/wrapper/key_manager.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart'
|
||||
as server;
|
||||
import 'package:twonly/src/model/protobuf/client/generated/backup.pb.dart';
|
||||
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
||||
class MemoriesBackupProgress {
|
||||
MemoriesBackupProgress({
|
||||
required this.totalPending,
|
||||
required this.currentUploaded,
|
||||
required this.currentUploadProgress,
|
||||
this.currentMediaId,
|
||||
});
|
||||
|
||||
final int totalPending;
|
||||
final int currentUploaded;
|
||||
final double currentUploadProgress;
|
||||
final String? currentMediaId;
|
||||
}
|
||||
|
||||
class ProgressMultipartRequest extends http.MultipartRequest {
|
||||
ProgressMultipartRequest(super.method, super.url, {this.onProgress});
|
||||
|
||||
final void Function(int bytes, int totalBytes)? onProgress;
|
||||
|
||||
@override
|
||||
http.ByteStream finalize() {
|
||||
final byteStream = super.finalize();
|
||||
if (onProgress == null) return byteStream;
|
||||
|
||||
final total = contentLength;
|
||||
var bytes = 0;
|
||||
|
||||
final transformer = StreamTransformer<List<int>, List<int>>.fromHandlers(
|
||||
handleData: (data, sink) {
|
||||
bytes += data.length;
|
||||
onProgress!(bytes, total);
|
||||
sink.add(data);
|
||||
},
|
||||
);
|
||||
|
||||
return http.ByteStream(byteStream.transform(transformer));
|
||||
}
|
||||
}
|
||||
|
||||
class MemoriesCloudService {
|
||||
static final Map<String, Mutex> _fileMutexes = {};
|
||||
|
||||
Timer? _timer;
|
||||
bool _isProcessing = false;
|
||||
bool get isProcessing => _isProcessing;
|
||||
|
||||
final _progressController =
|
||||
StreamController<MemoriesBackupProgress?>.broadcast();
|
||||
Stream<MemoriesBackupProgress?> get progressStream =>
|
||||
_progressController.stream;
|
||||
|
||||
MemoriesBackupProgress? _currentProgress;
|
||||
MemoriesBackupProgress? get currentProgress => _currentProgress;
|
||||
|
||||
void init() {
|
||||
_timer = Timer.periodic(const Duration(minutes: 5), (_) {
|
||||
checkUploads();
|
||||
});
|
||||
// Run immediately
|
||||
Future.delayed(const Duration(seconds: 10), checkUploads);
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_timer?.cancel();
|
||||
_progressController.close();
|
||||
}
|
||||
|
||||
void _updateProgress({
|
||||
required int totalPending,
|
||||
required int currentUploaded,
|
||||
required double currentUploadProgress,
|
||||
String? currentMediaId,
|
||||
}) {
|
||||
_currentProgress = MemoriesBackupProgress(
|
||||
totalPending: totalPending,
|
||||
currentUploaded: currentUploaded,
|
||||
currentUploadProgress: currentUploadProgress,
|
||||
currentMediaId: currentMediaId,
|
||||
);
|
||||
_progressController.add(_currentProgress);
|
||||
}
|
||||
|
||||
Future<void> checkUploads() async {
|
||||
if (_isProcessing || !userService.currentUser.isCloudBackupEnabled) return;
|
||||
|
||||
try {
|
||||
final memories = await twonlyDB.mediaFilesDao.getMemoriesToBackup();
|
||||
if (memories.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
_isProcessing = true;
|
||||
|
||||
final total = memories.length;
|
||||
var uploaded = 0;
|
||||
|
||||
_updateProgress(
|
||||
totalPending: total,
|
||||
currentUploaded: uploaded,
|
||||
currentUploadProgress: 0,
|
||||
);
|
||||
|
||||
for (final mediaFile in memories) {
|
||||
_updateProgress(
|
||||
totalPending: total,
|
||||
currentUploaded: uploaded,
|
||||
currentUploadProgress: 0,
|
||||
currentMediaId: mediaFile.mediaId,
|
||||
);
|
||||
|
||||
final success = await _backupMemory(mediaFile, (progress) {
|
||||
_updateProgress(
|
||||
totalPending: total,
|
||||
currentUploaded: uploaded,
|
||||
currentUploadProgress: progress,
|
||||
currentMediaId: mediaFile.mediaId,
|
||||
);
|
||||
});
|
||||
|
||||
if (success) {
|
||||
uploaded++;
|
||||
}
|
||||
|
||||
_updateProgress(
|
||||
totalPending: total,
|
||||
currentUploaded: uploaded,
|
||||
currentUploadProgress: 0,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
Log.error('Error in MemoriesCloudService.checkUploads: $e');
|
||||
} finally {
|
||||
_currentProgress = null;
|
||||
_isProcessing = false;
|
||||
_progressController.add(null);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<bool> downloadFromCloud(
|
||||
MediaFileService media, {
|
||||
required bool isThumbnail,
|
||||
}) async {
|
||||
final mediaId = media.mediaFile.mediaId;
|
||||
final mutex = _fileMutexes.putIfAbsent(mediaId, Mutex.new);
|
||||
|
||||
return mutex.protect(() async {
|
||||
final targetPath = isThumbnail ? media.thumbnailPath : media.storedPath;
|
||||
|
||||
if (targetPath.existsSync() && targetPath.lengthSync() > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final urls = await apiService.getMemoriesUrl(mediaId, isThumbnail);
|
||||
if (urls == null || !urls.hasFullDownloadUrl()) return false;
|
||||
|
||||
try {
|
||||
final response = await http.get(Uri.parse(urls.fullDownloadUrl));
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
return await _decryptFile(response.bodyBytes, targetPath);
|
||||
} else {
|
||||
Log.warn(
|
||||
'Failed to download ${isThumbnail ? 'thumbnail' : 'full media'} statuscode ${response.statusCode}',
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
Log.warn(e);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
Future<bool> _backupMemory(
|
||||
MediaFile mediaFile,
|
||||
void Function(double progress) onProgress,
|
||||
) async {
|
||||
try {
|
||||
final ms = MediaFileService(mediaFile);
|
||||
|
||||
if (!ms.storedPath.existsSync()) return false;
|
||||
|
||||
if (!mediaFile.hasThumbnail) {
|
||||
await MediaFileService(mediaFile).createThumbnail();
|
||||
}
|
||||
|
||||
final sizeBytes = ms.storedPath.lengthSync();
|
||||
final urls = await apiService.requestMemoriesUpload(
|
||||
sizeBytes,
|
||||
mediaFile.createdAt,
|
||||
mediaFile.mediaId,
|
||||
);
|
||||
|
||||
if (urls == null) {
|
||||
Log.error('Could not get upload URLs for memory ${mediaFile.mediaId}');
|
||||
return false;
|
||||
}
|
||||
|
||||
await twonlyDB.mediaFilesDao.updateMedia(
|
||||
mediaFile.mediaId,
|
||||
const MediaFilesCompanion(
|
||||
cloudState: Value(CloudState.pending),
|
||||
),
|
||||
);
|
||||
|
||||
final mediaKey = getRandomUint8List(32);
|
||||
final encryptedMediaKey = await RustKeyManager.encryptCloudMediaKey(
|
||||
mediaKey: mediaKey,
|
||||
addition: 'app',
|
||||
);
|
||||
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
|
||||
// 1. Upload thumbnail if exists
|
||||
if (ms.thumbnailPath.existsSync() && urls.hasThumbnailUpload()) {
|
||||
final thumbFile = await _encryptFile(
|
||||
ms.thumbnailPath,
|
||||
mediaKey,
|
||||
encryptedMediaKey,
|
||||
tempDir,
|
||||
'thumb_${mediaFile.mediaId}',
|
||||
);
|
||||
try {
|
||||
await _uploadToS3(urls.thumbnailUpload, thumbFile, (_) {});
|
||||
} finally {
|
||||
if (thumbFile.existsSync()) {
|
||||
thumbFile.deleteSync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Upload full media if exists
|
||||
if (urls.hasFullUpload()) {
|
||||
final fullFile = await _encryptFile(
|
||||
ms.storedPath,
|
||||
mediaKey,
|
||||
encryptedMediaKey,
|
||||
tempDir,
|
||||
'full_${mediaFile.mediaId}',
|
||||
);
|
||||
try {
|
||||
await _uploadToS3(urls.fullUpload, fullFile, onProgress);
|
||||
} finally {
|
||||
if (fullFile.existsSync()) {
|
||||
fullFile.deleteSync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Confirm upload
|
||||
final confirmRes = await apiService.confirmMemoriesUpload(
|
||||
mediaFile.mediaId,
|
||||
);
|
||||
if (confirmRes.isSuccess) {
|
||||
await twonlyDB.mediaFilesDao.updateMedia(
|
||||
mediaFile.mediaId,
|
||||
const MediaFilesCompanion(
|
||||
cloudState: Value(CloudState.uploaded),
|
||||
),
|
||||
);
|
||||
Log.info(
|
||||
'Cloud backup complete and confirmed for ${mediaFile.mediaId}',
|
||||
);
|
||||
return true;
|
||||
} else {
|
||||
throw Exception('Server confirmation failed: ${confirmRes.error}');
|
||||
}
|
||||
} catch (e) {
|
||||
Log.error('Error backing up memory ${mediaFile.mediaId}: $e');
|
||||
await twonlyDB.mediaFilesDao.updateMedia(
|
||||
mediaFile.mediaId,
|
||||
const MediaFilesCompanion(
|
||||
cloudState: Value(CloudState.none),
|
||||
),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _uploadToS3(
|
||||
server.Response_PresignedPost presignedPost,
|
||||
File file,
|
||||
void Function(double progress) onProgress,
|
||||
) async {
|
||||
final request = ProgressMultipartRequest(
|
||||
'POST',
|
||||
Uri.parse(presignedPost.url),
|
||||
onProgress: (bytes, total) {
|
||||
if (total > 0) {
|
||||
onProgress(bytes / total);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
request.fields.addAll(presignedPost.fields);
|
||||
request.files.add(
|
||||
await http.MultipartFile.fromPath(
|
||||
'file',
|
||||
file.path,
|
||||
filename: 'file',
|
||||
),
|
||||
);
|
||||
|
||||
final streamedResponse = await request.send();
|
||||
final response = await http.Response.fromStream(streamedResponse);
|
||||
if (response.statusCode != 200 && response.statusCode != 204) {
|
||||
throw Exception(
|
||||
'S3 upload failed: ${response.statusCode} - ${response.body}',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<File> _encryptFile(
|
||||
File inputFile,
|
||||
Uint8List mediaKey,
|
||||
List<int> encryptedMediaKey,
|
||||
Directory tempDir,
|
||||
String prefix,
|
||||
) async {
|
||||
final dataToEncrypt = await inputFile.readAsBytes();
|
||||
final chacha20 = FlutterChacha20.poly1305Aead();
|
||||
final nonce = chacha20.newNonce();
|
||||
|
||||
final secretBox = await chacha20.encrypt(
|
||||
dataToEncrypt,
|
||||
secretKey: SecretKey(mediaKey),
|
||||
nonce: nonce,
|
||||
);
|
||||
|
||||
final cipherTextWithMac = Uint8List.fromList(
|
||||
secretBox.cipherText + secretBox.mac.bytes,
|
||||
);
|
||||
|
||||
final payload = CloudMediaBackupEncrypted()
|
||||
..addition = 'app'
|
||||
..encryptedMediaKey = encryptedMediaKey
|
||||
..mediaNonce = nonce
|
||||
..mediaCiphertext = cipherTextWithMac;
|
||||
|
||||
final outFile = File('${tempDir.path}/$prefix.encrypted');
|
||||
await outFile.writeAsBytes(payload.writeToBuffer());
|
||||
return outFile;
|
||||
}
|
||||
|
||||
static Future<bool> _decryptFile(
|
||||
Uint8List encryptedData,
|
||||
File outFile,
|
||||
) async {
|
||||
try {
|
||||
// 2. Parse the protobuf payload FIRST to access the key and addition
|
||||
final payload = CloudMediaBackupEncrypted.fromBuffer(encryptedData);
|
||||
|
||||
// 3. Get the media key using the encrypted key and addition from the payload
|
||||
final mediaKey = await RustKeyManager.decryptCloudMediaKey(
|
||||
encryptedMediaKey: payload.encryptedMediaKey,
|
||||
addition: payload.addition,
|
||||
);
|
||||
|
||||
// 4. Extract the concatenated ciphertext + MAC, and the nonce
|
||||
final nonce = payload.mediaNonce;
|
||||
final cipherTextWithMac = payload.mediaCiphertext;
|
||||
|
||||
// 5. Separate the ciphertext and the MAC (Poly1305 MAC is always 16 bytes)
|
||||
const macLength = 16;
|
||||
final cipherTextLength = cipherTextWithMac.length - macLength;
|
||||
|
||||
final cipherText = cipherTextWithMac.sublist(0, cipherTextLength);
|
||||
final macBytes = cipherTextWithMac.sublist(cipherTextLength);
|
||||
|
||||
// 6. Reconstruct the SecretBox
|
||||
final secretBox = SecretBox(
|
||||
cipherText,
|
||||
nonce: nonce,
|
||||
mac: Mac(macBytes),
|
||||
);
|
||||
|
||||
// 7. Decrypt the data using the newly retrieved media key
|
||||
final chacha20 = FlutterChacha20.poly1305Aead();
|
||||
final decryptedBytes = await chacha20.decrypt(
|
||||
secretBox,
|
||||
secretKey: SecretKey(mediaKey),
|
||||
);
|
||||
|
||||
await outFile.writeAsBytes(decryptedBytes);
|
||||
return true;
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final memoriesCloudService = MemoriesCloudService();
|
||||
|
|
@ -13,7 +13,6 @@ import 'package:twonly/src/database/tables/contacts.table.dart';
|
|||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/model/json/signal_identity.model.dart';
|
||||
import 'package:twonly/src/services/api/mediafiles/download.api.dart';
|
||||
import 'package:twonly/src/services/passwordless_recovery.service.dart';
|
||||
import 'package:twonly/src/services/user.service.dart';
|
||||
import 'package:twonly/src/services/user_discovery.service.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
|
|
@ -171,14 +170,9 @@ Future<void> runMigrations() async {
|
|||
});
|
||||
}
|
||||
|
||||
if (userService.currentUser.appVersion < 118) {
|
||||
await PasswordlessRecoveryService.migratePasswordlessRecovery();
|
||||
await UserService.update((u) => u.appVersion = 118);
|
||||
}
|
||||
|
||||
if (kDebugMode) {
|
||||
assert(
|
||||
AppState.latestAppVersionId == 118,
|
||||
AppState.latestAppVersionId == 117,
|
||||
'Forgot to update the target version in runMigrations() after incrementing AppState.latestAppVersionId.',
|
||||
);
|
||||
assert(
|
||||
|
|
|
|||
|
|
@ -1,302 +0,0 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:ui' as ui;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:path/path.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
|
||||
class BlogEntry {
|
||||
BlogEntry({
|
||||
required this.title,
|
||||
required this.link,
|
||||
required this.guid,
|
||||
required this.description,
|
||||
required this.imageUrl,
|
||||
this.pubDate,
|
||||
});
|
||||
|
||||
factory BlogEntry.fromJson(Map<String, dynamic> json) => BlogEntry(
|
||||
title: (json['title'] as String?) ?? '',
|
||||
link: (json['link'] as String?) ?? '',
|
||||
guid: (json['guid'] as String?) ?? '',
|
||||
description: (json['description'] as String?) ?? '',
|
||||
imageUrl: (json['imageUrl'] as String?) ?? '',
|
||||
pubDate: json['pubDate'] != null
|
||||
? DateTime.tryParse(json['pubDate'] as String)
|
||||
: null,
|
||||
);
|
||||
|
||||
final String title;
|
||||
final String link;
|
||||
final String guid;
|
||||
final String description;
|
||||
final String imageUrl;
|
||||
final DateTime? pubDate;
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'title': title,
|
||||
'link': link,
|
||||
'guid': guid,
|
||||
'description': description,
|
||||
'imageUrl': imageUrl,
|
||||
'pubDate': pubDate?.toIso8601String(),
|
||||
};
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'BlogEntry(title: $title, link: $link, guid: $guid, description: $description, imageUrl: $imageUrl, pubDate: $pubDate)';
|
||||
}
|
||||
}
|
||||
|
||||
class NewsService {
|
||||
List<BlogEntry> entries = [];
|
||||
Set<String> openedGuids = {};
|
||||
DateTime? lastDownloadedAt;
|
||||
bool hasLoadedBefore = false;
|
||||
|
||||
final ValueNotifier<int> unreadCountNotifier = ValueNotifier<int>(0);
|
||||
|
||||
File get _cacheFile =>
|
||||
File(join(AppEnvironment.supportDir, 'news_feed.json'));
|
||||
|
||||
Future<void> init() async {
|
||||
try {
|
||||
await loadCache();
|
||||
} catch (e) {
|
||||
Log.error('Failed to load news cache: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> loadCache() async {
|
||||
final file = _cacheFile;
|
||||
if (!file.existsSync()) {
|
||||
return;
|
||||
}
|
||||
final content = await file.readAsString();
|
||||
final json = jsonDecode(content) as Map<String, dynamic>;
|
||||
|
||||
hasLoadedBefore = json['hasLoadedBefore'] as bool? ?? false;
|
||||
|
||||
final lastDownloadedStr = json['lastDownloadedAt'] as String?;
|
||||
if (lastDownloadedStr != null) {
|
||||
lastDownloadedAt = DateTime.tryParse(lastDownloadedStr);
|
||||
}
|
||||
|
||||
final openedList = json['openedGuids'] as List<dynamic>? ?? [];
|
||||
openedGuids = openedList.map((e) => e.toString()).toSet();
|
||||
|
||||
final entriesList = json['entries'] as List<dynamic>? ?? [];
|
||||
entries = entriesList
|
||||
.map((e) => BlogEntry.fromJson(e as Map<String, dynamic>))
|
||||
.toList();
|
||||
|
||||
_updateUnreadCount();
|
||||
}
|
||||
|
||||
Future<void> saveCache() async {
|
||||
final file = _cacheFile;
|
||||
final json = {
|
||||
'hasLoadedBefore': hasLoadedBefore,
|
||||
'lastDownloadedAt': lastDownloadedAt?.toIso8601String(),
|
||||
'openedGuids': openedGuids.toList(),
|
||||
'entries': entries.map((e) => e.toJson()).toList(),
|
||||
};
|
||||
await file.writeAsString(jsonEncode(json));
|
||||
}
|
||||
|
||||
void _updateUnreadCount() {
|
||||
var count = 0;
|
||||
for (final entry in entries) {
|
||||
if (!openedGuids.contains(entry.guid)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
unreadCountNotifier.value = count;
|
||||
}
|
||||
|
||||
Future<void> markAllAsRead() async {
|
||||
for (final entry in entries) {
|
||||
openedGuids.add(entry.guid);
|
||||
}
|
||||
_updateUnreadCount();
|
||||
await saveCache();
|
||||
}
|
||||
|
||||
Future<void> fetchFeed({http.Client? client, bool force = false}) async {
|
||||
try {
|
||||
final lang = ui.PlatformDispatcher.instance.locale.languageCode;
|
||||
final url = lang == 'de'
|
||||
? 'https://twonly.eu/de/blog/rss.xml'
|
||||
: 'https://twonly.eu/en/blog/rss.xml';
|
||||
|
||||
final response = client != null
|
||||
? await client.get(Uri.parse(url))
|
||||
: await http.get(Uri.parse(url));
|
||||
if (response.statusCode != 200) {
|
||||
Log.error('Failed to download RSS feed: code ${response.statusCode}');
|
||||
return;
|
||||
}
|
||||
|
||||
final xml = utf8.decode(response.bodyBytes);
|
||||
final newEntries = _parseRss(xml);
|
||||
|
||||
if (!hasLoadedBefore) {
|
||||
// First run: mark all fetched as already opened
|
||||
for (final entry in newEntries) {
|
||||
openedGuids.add(entry.guid);
|
||||
}
|
||||
hasLoadedBefore = true;
|
||||
}
|
||||
|
||||
entries = newEntries;
|
||||
lastDownloadedAt = DateTime.now();
|
||||
|
||||
_updateUnreadCount();
|
||||
await saveCache();
|
||||
Log.info(
|
||||
'Successfully fetched ${entries.length} news entries. Unread count: ${unreadCountNotifier.value}',
|
||||
);
|
||||
} catch (e) {
|
||||
Log.error('Failed to fetch news feed: $e');
|
||||
}
|
||||
}
|
||||
|
||||
List<BlogEntry> _parseRss(String xml) {
|
||||
final entries = <BlogEntry>[];
|
||||
final itemRegex = RegExp(r'<item>([\s\S]*?)<\/item>');
|
||||
final matches = itemRegex.allMatches(xml);
|
||||
|
||||
for (final match in matches) {
|
||||
final itemContent = match.group(1) ?? '';
|
||||
|
||||
final titleMatch = RegExp(
|
||||
r'<title>([\s\S]*?)<\/title>',
|
||||
).firstMatch(itemContent);
|
||||
final title = _stripCdata(titleMatch?.group(1) ?? '');
|
||||
|
||||
final linkMatch = RegExp(
|
||||
r'<link>([\s\S]*?)<\/link>',
|
||||
).firstMatch(itemContent);
|
||||
final link = _stripCdata(linkMatch?.group(1) ?? '');
|
||||
|
||||
final guidMatch = RegExp(
|
||||
r'<guid[^>]*?>([\s\S]*?)<\/guid>',
|
||||
).firstMatch(itemContent);
|
||||
final guid = _stripCdata(guidMatch?.group(1) ?? link);
|
||||
|
||||
final descMatch = RegExp(
|
||||
r'<description>([\s\S]*?)<\/description>',
|
||||
).firstMatch(itemContent);
|
||||
final descriptionRaw = _stripCdata(descMatch?.group(1) ?? '');
|
||||
final description = descriptionRaw
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll(''', "'")
|
||||
.replaceAll(''', "'")
|
||||
.replaceAll(RegExp('<[^>]*>'), '')
|
||||
.trim();
|
||||
|
||||
var imageUrl = '';
|
||||
final enclosureMatch = RegExp(
|
||||
r'<enclosure\s+[^>]*?url="([^"]+)"',
|
||||
).firstMatch(itemContent);
|
||||
if (enclosureMatch != null) {
|
||||
imageUrl = enclosureMatch.group(1) ?? '';
|
||||
}
|
||||
if (imageUrl.isEmpty) {
|
||||
final mediaMatch = RegExp(
|
||||
r'<media:content\s+[^>]*?url="([^"]+)"',
|
||||
).firstMatch(itemContent);
|
||||
if (mediaMatch != null) {
|
||||
imageUrl = mediaMatch.group(1) ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
final pubDateMatch = RegExp(
|
||||
r'<pubDate>([\s\S]*?)<\/pubDate>',
|
||||
).firstMatch(itemContent);
|
||||
DateTime? pubDate;
|
||||
if (pubDateMatch != null) {
|
||||
final pubDateStr = pubDateMatch.group(1) ?? '';
|
||||
pubDate = DateTime.tryParse(pubDateStr);
|
||||
pubDate ??= _parseRfc2822(pubDateStr);
|
||||
}
|
||||
|
||||
entries.add(
|
||||
BlogEntry(
|
||||
title: title,
|
||||
link: link,
|
||||
guid: guid,
|
||||
description: description,
|
||||
imageUrl: imageUrl,
|
||||
pubDate: pubDate,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
String _stripCdata(String input) {
|
||||
var s = input.trim();
|
||||
if (s.startsWith('<![CDATA[')) {
|
||||
s = s.substring(9);
|
||||
}
|
||||
if (s.endsWith(']]>')) {
|
||||
s = s.substring(0, s.length - 3);
|
||||
}
|
||||
return s.trim();
|
||||
}
|
||||
|
||||
DateTime? _parseRfc2822(String dateString) {
|
||||
try {
|
||||
var cleaned = dateString.trim();
|
||||
if (cleaned.contains(',')) {
|
||||
cleaned = cleaned.split(',')[1].trim();
|
||||
}
|
||||
final parts = cleaned.split(RegExp(r'\s+'));
|
||||
if (parts.length < 4) return null;
|
||||
|
||||
final day = int.tryParse(parts[0]) ?? 1;
|
||||
final monthStr = parts[1].toLowerCase();
|
||||
final year = int.tryParse(parts[2]) ?? DateTime.now().year;
|
||||
|
||||
final timeParts = parts[3].split(':');
|
||||
final hour = timeParts.isNotEmpty ? (int.tryParse(timeParts[0]) ?? 0) : 0;
|
||||
final minute = timeParts.length > 1
|
||||
? (int.tryParse(timeParts[1]) ?? 0)
|
||||
: 0;
|
||||
final second = timeParts.length > 2
|
||||
? (int.tryParse(timeParts[2]) ?? 0)
|
||||
: 0;
|
||||
|
||||
const months = {
|
||||
'jan': 1,
|
||||
'feb': 2,
|
||||
'mar': 3,
|
||||
'apr': 4,
|
||||
'may': 5,
|
||||
'jun': 6,
|
||||
'jul': 7,
|
||||
'aug': 8,
|
||||
'sep': 9,
|
||||
'oct': 10,
|
||||
'nov': 11,
|
||||
'dec': 12,
|
||||
};
|
||||
final monthStringPart = monthStr.length > 3
|
||||
? monthStr.substring(0, 3)
|
||||
: monthStr;
|
||||
final month = months[monthStringPart] ?? 1;
|
||||
|
||||
return DateTime.utc(year, month, day, hour, minute, second);
|
||||
} catch (e) {
|
||||
Log.warn('Failed to parse date: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,8 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:cryptography_flutter_plus/cryptography_flutter_plus.dart';
|
||||
import 'package:cryptography_plus/cryptography_plus.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
|
|
@ -102,7 +100,6 @@ Future<void> showLocalPushNotification(
|
|||
PushUser pushUser,
|
||||
PushNotification pushNotification, {
|
||||
String? groupId,
|
||||
String? titleSuffix,
|
||||
}) async {
|
||||
String? title;
|
||||
String? body;
|
||||
|
|
@ -128,9 +125,7 @@ Future<void> showLocalPushNotification(
|
|||
if (targetGroupId != null) {
|
||||
try {
|
||||
final currentUri = routerProvider.routerDelegate.currentConfiguration.uri;
|
||||
final isResumed =
|
||||
WidgetsBinding.instance.lifecycleState == AppLifecycleState.resumed;
|
||||
if (isResumed && currentUri.path.contains(targetGroupId)) {
|
||||
if (currentUri.path.contains(targetGroupId)) {
|
||||
Log.info(
|
||||
'Suppressing local push notification because chat with group $targetGroupId is currently open.',
|
||||
);
|
||||
|
|
@ -142,10 +137,6 @@ Future<void> showLocalPushNotification(
|
|||
}
|
||||
|
||||
title = pushUser.displayName;
|
||||
if (titleSuffix != null && titleSuffix.isNotEmpty) {
|
||||
title = '$title $titleSuffix';
|
||||
}
|
||||
|
||||
body = getPushNotificationText(pushNotification);
|
||||
if (body == '') {
|
||||
Log.error('No push notification type defined!');
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
|||
|
||||
if (Platform.isAndroid) {
|
||||
if (isInitialized) {
|
||||
await backgroundFetch(lastExecutionInSecondsLimit: 3);
|
||||
await handlePeriodicTask(lastExecutionInSecondsLimit: 10);
|
||||
}
|
||||
} else {
|
||||
// make sure every thing run...
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
|||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/constants/secure_storage.keys.dart';
|
||||
import 'package:twonly/src/services/background/callback_dispatcher.background.dart';
|
||||
import 'package:twonly/src/services/notifications/background.notifications.dart';
|
||||
import 'package:twonly/src/services/notifications/fcm.background.dart';
|
||||
import 'package:twonly/src/services/user.service.dart';
|
||||
|
|
@ -145,7 +144,6 @@ class FcmNotificationService {
|
|||
}
|
||||
|
||||
static Future<void> handleRemoteMessage(RemoteMessage message) async {
|
||||
Log.info('handleRemoteMessage received message: ${message.messageId}');
|
||||
await _updateLastFcmMessageTimestamp();
|
||||
if (!Platform.isAndroid) {
|
||||
Log.error('Got message in Dart while on iOS');
|
||||
|
|
@ -157,24 +155,6 @@ class FcmNotificationService {
|
|||
return;
|
||||
}
|
||||
|
||||
// In scenarios like Android Doze Mode or aggressive background restrictions, the OS may kill
|
||||
// or heavily restrict network access, preventing the WebSocket from connecting in time.
|
||||
// By parsing the FCM data payload offline, we can instantly display the notification, which also
|
||||
// prevents FCM from penalizing/downgrading the app's data message priority for failing to show a notification.
|
||||
// This is just a workarround until the new Rust decryption is enrolled fully.
|
||||
final pushDataString = message.data['push_data'] as String?;
|
||||
if (pushDataString != null) {
|
||||
if (apiService.isConnected) {
|
||||
Log.info('Got FCM message, but API is connected...');
|
||||
} else {
|
||||
Log.info('Trying to connect to the API in the background.');
|
||||
|
||||
if (await backgroundFetch()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (message.notification != null || message.data['title'] != null) {
|
||||
final title =
|
||||
message.notification?.title ?? message.data['title'] as String? ?? '';
|
||||
|
|
|
|||
|
|
@ -5,13 +5,15 @@ import 'package:clock/clock.dart';
|
|||
import 'package:collection/collection.dart';
|
||||
import 'package:crypto/crypto.dart' hide Hmac;
|
||||
import 'package:cryptography_plus/cryptography_plus.dart'
|
||||
show Hkdf, Hmac, Mac, SecretBox, SecretKey, Xchacha20;
|
||||
show Hmac, Mac, SecretBox, SecretKey, Xchacha20;
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:twonly/core/bridge/wrapper.dart';
|
||||
import 'package:twonly/core/bridge/wrapper/key_manager.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/constants/keyvalue.keys.dart';
|
||||
import 'package:twonly/src/constants/routes.keys.dart';
|
||||
import 'package:twonly/src/database/daos/contacts.dao.dart'
|
||||
show getContactDisplayName;
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
|
|
@ -24,7 +26,7 @@ import 'package:twonly/src/model/protobuf/client/generated/passwordless_recovery
|
|||
import 'package:twonly/src/providers/routing.provider.dart';
|
||||
import 'package:twonly/src/services/api/messages.api.dart';
|
||||
import 'package:twonly/src/services/user.service.dart';
|
||||
|
||||
import 'package:twonly/src/utils/avatars.dart' show getAvatarSvg;
|
||||
import 'package:twonly/src/utils/keyvalue.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
|
@ -36,8 +38,6 @@ class PasswordlessRecoveryService {
|
|||
static final StreamController<String> onEmailTokenReceived =
|
||||
StreamController<String>.broadcast();
|
||||
|
||||
static String? lastEmailToken;
|
||||
|
||||
static String linkPrefix = 'https://me.twonly.eu/r/#';
|
||||
|
||||
static final Set<String> _handledNotificationIds = {};
|
||||
|
|
@ -50,8 +50,11 @@ class PasswordlessRecoveryService {
|
|||
final parts = fragment.split('/');
|
||||
if (parts.length < 2) {
|
||||
if (fragment.isNotEmpty) {
|
||||
lastEmailToken = fragment;
|
||||
onEmailTokenReceived.add(fragment);
|
||||
final context = rootNavigatorKey.currentContext;
|
||||
if (context != null && context.mounted) {
|
||||
unawaited(context.push(Routes.recoverPasswordless, extra: fragment));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -105,7 +108,9 @@ class PasswordlessRecoveryService {
|
|||
final shareUser = TrustedFriendShare_User(
|
||||
userId: Int64(contact.userId),
|
||||
displayName: getContactDisplayName(contact),
|
||||
avatar: contact.avatarSvgCompressed,
|
||||
avatar: contact.avatarSvgCompressed != null
|
||||
? utf8.encode(getAvatarSvg(contact.avatarSvgCompressed!))
|
||||
: null,
|
||||
);
|
||||
|
||||
final trustedFriendShare = TrustedFriendShare(
|
||||
|
|
@ -185,45 +190,52 @@ class PasswordlessRecoveryService {
|
|||
|
||||
switch (secondFactorType) {
|
||||
case SecondFactorType.email:
|
||||
config.email = secondFactorValue.toLowerCase();
|
||||
emailHint = createEmailHint(config.email!);
|
||||
|
||||
// The serverKey is encrypted with the email; this protects the server from seeing the user's email address, while
|
||||
// also ensuring that the server can only send the real secret to the user's configured email, as a different
|
||||
// email will result in a different secret key.
|
||||
// This is only stored so the user can see there email, and verify that he has set the valid mail...
|
||||
config.email = secondFactorValue;
|
||||
emailHint = createEmailHint(secondFactorValue);
|
||||
|
||||
// E-Mail Protection:
|
||||
// - Server can only learn the email during recovery. Ensured as the server gets the NONCE to decrypt only during recovery.
|
||||
// - Trusted-friends: Server key is only sent to the mail, they would need access to the user's mail account.
|
||||
config.serverKeyProtection = getRandomUint8List(32);
|
||||
|
||||
final hkdf = Hkdf(hmac: Hmac.sha256(), outputLength: 32);
|
||||
secondFactorEncryptedServerKeyKey = await hkdf.deriveKey(
|
||||
secretKey: SecretKey(config.serverKeyProtection!),
|
||||
nonce: utf8.encode(config.email!),
|
||||
// - Server can only learn the email during recovery. Ensured as the server gets the NONCE and MAC to decrpyt during recovery.
|
||||
// - Trusted-friends: Server key is send to the mail, they whould need access to the user's mail account.
|
||||
secondFactorEncryptedServerKeyKey = SecretKey(
|
||||
Uint8List.fromList(sha256.convert(utf8.encode(config.email!)).bytes),
|
||||
);
|
||||
|
||||
case SecondFactorType.pin:
|
||||
|
||||
// The pin seed - never shared with the server - ensures that the server is unable to brute-force real user's pin
|
||||
config
|
||||
..serverKeyProtection = getRandomUint8List(32)
|
||||
// As the pin is heavily protected against brute-forcing e.g. will be deleted by the server after 10 tries, the
|
||||
// unlock token is required to prevent a malicious user (except the trusted friends) to trigger this deletion.
|
||||
..pinUnlockToken = getRandomUint8List(32);
|
||||
// The pin seed - never shared with the server - ensures that the server is unable to brute-force real user's pin.
|
||||
config.pinSeed = getRandomUint8List(32);
|
||||
|
||||
// As the pin is heavily protected against brute-forcing e.g. will be deleted by the server after X tries, the
|
||||
// unlock token is required to prevent a malicous user (except the trusted friends) to triger this deletion.
|
||||
config.pinUnlockToken = getRandomUint8List(32);
|
||||
|
||||
// Brute-force protection for the user's pin:
|
||||
// - Server: Does not know the seed.
|
||||
// - Trusted friends: Can only check the result 10 times before the server deletes the key. As they do not have
|
||||
// the mac and the cipher text they are unable to brute-force the pin locally. And the server only allows 10 tries.
|
||||
final hkdf = Hkdf(hmac: Hmac.sha256(), outputLength: 32);
|
||||
secondFactorEncryptedServerKeyKey = await hkdf.deriveKey(
|
||||
secretKey: SecretKey(config.serverKeyProtection!),
|
||||
nonce: utf8.encode(secondFactorValue),
|
||||
// - Trusted friends:
|
||||
// Can only check the result X times before the server deletes the key. As they do not have
|
||||
// the mac and the cypher text they are unable to brute-force the pin localy. And the server only allows 10
|
||||
// tries.
|
||||
final pinProtectionKey = await Hmac.sha256().calculateMac(
|
||||
Uint8List.fromList(utf8.encode(secondFactorValue)),
|
||||
secretKey: SecretKey(config.pinSeed!),
|
||||
);
|
||||
|
||||
// To restore the user has to provide the server with this encryption key. The server then can verify the
|
||||
// correct pin was entered, when decrypting the server key as he also receives the mac and nonce from the user
|
||||
// during recovery. Only when the mac is correct the server provides the user with the serverKey.
|
||||
secondFactorEncryptedServerKeyKey = SecretKey(pinProtectionKey.bytes);
|
||||
|
||||
case SecondFactorType.none:
|
||||
}
|
||||
|
||||
if (secondFactorEncryptedServerKeyKey != null) {
|
||||
// The server key is used to encrypt the RecoveryData of the users. This ensures that when the trusted friends
|
||||
// collaborate, they additionally need the serverKey to decrypt the user's key.
|
||||
// colaberate, they additional need the serverKey to decrypt the user's key.
|
||||
serverKey = getRandomUint8List(32);
|
||||
|
||||
final secretBox = await xchacha20.encrypt(
|
||||
|
|
@ -232,16 +244,18 @@ class PasswordlessRecoveryService {
|
|||
nonce: xchacha20.newNonce(),
|
||||
);
|
||||
|
||||
// The server only gets the encrypted server key, the mac, and the nonce.
|
||||
// This ensures that the server never learns the user's original pin, as he is missing the serverKeyProtection and also unable to
|
||||
// brute-force the email of the user.
|
||||
// The server only gets the encrypted server key and the mac. Because the server does not know the nonce (192-bit
|
||||
// because of XChaCha), he is unable to decrypt the server key without the help of the trusted friends. This
|
||||
// ensures that the server never learns the users orginal pin, as he is missing the pin_seed and also unable to
|
||||
// brute-force the email of the user as he does not have the nonce.
|
||||
encryptedServerKey = Uint8List.fromList([
|
||||
...secretBox.cipherText,
|
||||
...secretBox.mac.bytes,
|
||||
...secretBox.nonce,
|
||||
]);
|
||||
|
||||
config.encryptedServerKey = encryptedServerKey;
|
||||
config
|
||||
..encryptedServerKeyNonce = secretBox.nonce
|
||||
..encryptedServerKey = encryptedServerKey;
|
||||
}
|
||||
|
||||
// 3. Using shamir's secret to generate the shares for the users.
|
||||
|
|
@ -271,9 +285,10 @@ class PasswordlessRecoveryService {
|
|||
|
||||
final sharedSecretData = SharedSecretData(
|
||||
recoveryData: recoveryData,
|
||||
serverKeyProtection: config.serverKeyProtection,
|
||||
pinSeed: config.pinSeed,
|
||||
pinUnlockToken: config.pinUnlockToken,
|
||||
emailHint: emailHint,
|
||||
encryptedServerKeyNonce: config.encryptedServerKeyNonce,
|
||||
).writeToBuffer();
|
||||
|
||||
// 3.2. Use the amount of trusted friends to generate the shares
|
||||
|
|
@ -311,37 +326,35 @@ class PasswordlessRecoveryService {
|
|||
|
||||
unawaited(performHeartbeat());
|
||||
|
||||
// The passwordless is configured successfully.
|
||||
// The passwordless is configured sucessfully.
|
||||
return true;
|
||||
}
|
||||
|
||||
static Future<bool> testPin(String pin) async {
|
||||
final config = userService.currentUser.passwordLessRecovery;
|
||||
if (config?.serverKeyProtection == null ||
|
||||
config?.encryptedServerKey == null) {
|
||||
if (config?.pinSeed == null || config?.encryptedServerKey == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
final hkdf = Hkdf(hmac: Hmac.sha256(), outputLength: 32);
|
||||
final secondFactorEncryptedServerKeyKey = await hkdf.deriveKey(
|
||||
secretKey: SecretKey(config!.serverKeyProtection!),
|
||||
nonce: utf8.encode(pin),
|
||||
final pinProtectionKey = await Hmac.sha256().calculateMac(
|
||||
Uint8List.fromList(utf8.encode(pin)),
|
||||
secretKey: SecretKey(config!.pinSeed!),
|
||||
);
|
||||
|
||||
final secondFactorEncryptedServerKeyKey = SecretKey(
|
||||
pinProtectionKey.bytes,
|
||||
);
|
||||
|
||||
final xchacha20 = Xchacha20.poly1305Aead();
|
||||
|
||||
final combined = config.encryptedServerKey!;
|
||||
final nonceBytes = combined.sublist(combined.length - 24);
|
||||
final macBytes = combined.sublist(
|
||||
combined.length - 40,
|
||||
combined.length - 24,
|
||||
);
|
||||
final cipherText = combined.sublist(0, combined.length - 40);
|
||||
final cipherText = combined.sublist(0, combined.length - 16);
|
||||
final macBytes = combined.sublist(combined.length - 16);
|
||||
|
||||
final secretBox = SecretBox(
|
||||
cipherText,
|
||||
nonce: nonceBytes,
|
||||
nonce: config.encryptedServerKeyNonce!,
|
||||
mac: Mac(macBytes),
|
||||
);
|
||||
|
||||
|
|
@ -386,7 +399,7 @@ class PasswordlessRecoveryService {
|
|||
clock.now().difference(lastContactHeartbeat).inHours >= 24;
|
||||
|
||||
if (isContactHeartbeatOlderThan24h) {
|
||||
// Get all contacts where recoveryLastHeartbeat is NULL. Then for each contact send.
|
||||
// Get all contacts where recoveryLastHeartbeat is NULL. Then for each contacts send.
|
||||
// recoveryLastHeartbeat is ONLY updated in case the contact has responded.
|
||||
final pendingShares =
|
||||
await (twonlyDB.select(twonlyDB.contacts)..where(
|
||||
|
|
@ -532,7 +545,7 @@ class PasswordlessRecoveryService {
|
|||
final recoveryLastHeartbeat =
|
||||
const ListEquality().equals(computedHash, msg.hash)
|
||||
? clock.now()
|
||||
: null; // The stored share not valid (maybe an old backup was restored). This will cause the performHeartbeat to resend him his share
|
||||
: null; // The stored share not valid (maybe a old backup was restored). This will cause the performHeartbeat to resend him his share
|
||||
Log.info(
|
||||
'[$receiptId] Got heartbeat: ($recoveryLastHeartbeat)',
|
||||
);
|
||||
|
|
@ -592,9 +605,6 @@ class PasswordlessRecoveryService {
|
|||
final receivedShare = ReceivedRecoveryShare(
|
||||
messageId: msgId,
|
||||
trustedFriendDisplayName: share.trustedFriend.displayName,
|
||||
trustedFriendAvatarSvg: share.trustedFriend.hasAvatar()
|
||||
? share.trustedFriend.avatar
|
||||
: null,
|
||||
myDisplayName: share.shareUser.displayName,
|
||||
myUserId: share.shareUser.userId.toInt(),
|
||||
myAvatarSvg: share.shareUser.hasAvatar()
|
||||
|
|
@ -627,53 +637,4 @@ class PasswordlessRecoveryService {
|
|||
|
||||
return didUpdate;
|
||||
}
|
||||
|
||||
static Future<void> migratePasswordlessRecovery() async {
|
||||
final config = userService.currentUser.passwordLessRecovery;
|
||||
if (config == null) return;
|
||||
|
||||
final oldTrustedFriends = await (twonlyDB.select(
|
||||
twonlyDB.contacts,
|
||||
)..where((t) => t.recoveryIsTrustedFriend.equals(true))).get();
|
||||
final trustedFriendIds = oldTrustedFriends.map((e) => e.userId).toList();
|
||||
|
||||
if (trustedFriendIds.isEmpty) return;
|
||||
|
||||
if (config.email != null) {
|
||||
await enablePasswordlessRecovery(
|
||||
trustedFriendIds: trustedFriendIds,
|
||||
secondFactorType: SecondFactorType.email,
|
||||
secondFactorValue: config.email!,
|
||||
threshold: config.threshold,
|
||||
);
|
||||
} else if (config.pinUnlockToken == null) {
|
||||
await enablePasswordlessRecovery(
|
||||
trustedFriendIds: trustedFriendIds,
|
||||
secondFactorType: SecondFactorType.none,
|
||||
secondFactorValue: '',
|
||||
threshold: config.threshold,
|
||||
);
|
||||
} else {
|
||||
// It's PIN, we can't migrate it because we don't have the PIN. We delete it so the user has to do it again.
|
||||
for (final contact in oldTrustedFriends) {
|
||||
try {
|
||||
await sendCipherText(
|
||||
contact.userId,
|
||||
pb.EncryptedContent(
|
||||
passwordlessRecovery: pb.EncryptedContent_PasswordLessRecovery(
|
||||
delete: true,
|
||||
),
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
Log.error(
|
||||
'Failed to send delete PasswordLessRecovery message to contact ${contact.userId}: $e',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await twonlyDB.contactsDao.resetRecoveryDataForAllContacts();
|
||||
await UserService.update((u) => u.passwordLessRecovery = null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,48 +3,23 @@ import 'dart:typed_data';
|
|||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||
// ignore: implementation_imports
|
||||
import 'package:libsignal_protocol_dart/src/invalid_message_exception.dart';
|
||||
import 'package:twonly/core/bridge/wrapper/signal.dart';
|
||||
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'
|
||||
as pb;
|
||||
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
|
||||
import 'package:twonly/src/services/api/messages.api.dart';
|
||||
import 'package:twonly/src/services/signal/protocol_state.signal.dart';
|
||||
import 'package:twonly/src/services/signal/session.signal.dart';
|
||||
import 'package:twonly/src/services/signal/utils.signal.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
|
||||
class SignalEncryptResult {
|
||||
SignalEncryptResult(this.ciphertext, this.type);
|
||||
final Uint8List ciphertext;
|
||||
final pb.Message_Type type;
|
||||
}
|
||||
|
||||
Future<SignalEncryptResult?> signalEncryptMessage(
|
||||
Future<CiphertextMessage?> signalEncryptMessage(
|
||||
int target,
|
||||
Uint8List plaintextContent,
|
||||
) async {
|
||||
return lockingSignalProtocol.protect<SignalEncryptResult?>(() async {
|
||||
return _signalEncryptMessageV1(target, plaintextContent);
|
||||
return lockingSignalProtocol.protect<CiphertextMessage?>(() async {
|
||||
return _signalEncryptMessage(target, plaintextContent);
|
||||
});
|
||||
}
|
||||
|
||||
Future<SignalEncryptResult?> signalEncryptMessageV2(
|
||||
int target,
|
||||
Uint8List plaintextContent,
|
||||
) async {
|
||||
try {
|
||||
final res = await RustSignal.encrypt(
|
||||
name: target.toString(),
|
||||
deviceId: 1,
|
||||
plaintext: plaintextContent,
|
||||
);
|
||||
return SignalEncryptResult(res, pb.Message_Type.CIPHERTEXT_V2);
|
||||
} catch (e) {
|
||||
Log.error('Could not encrypt message (V2) for target $target: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<SignalEncryptResult?> _signalEncryptMessageV1(
|
||||
Future<CiphertextMessage?> _signalEncryptMessage(
|
||||
int target,
|
||||
Uint8List plaintextContent,
|
||||
) async {
|
||||
|
|
@ -52,97 +27,29 @@ Future<SignalEncryptResult?> _signalEncryptMessageV1(
|
|||
final signalStore = (await getSignalStore())!;
|
||||
final address = getSignalAddress(target);
|
||||
final session = SessionCipher.fromStore(signalStore, address);
|
||||
final cipherText = await session.encrypt(plaintextContent);
|
||||
|
||||
pb.Message_Type type;
|
||||
switch (cipherText.getType()) {
|
||||
case CiphertextMessage.prekeyType:
|
||||
type = pb.Message_Type.PREKEY_BUNDLE;
|
||||
case CiphertextMessage.whisperType:
|
||||
type = pb.Message_Type.CIPHERTEXT;
|
||||
default:
|
||||
Log.error('Invalid ciphertext type: ${cipherText.getType()}.');
|
||||
return null;
|
||||
}
|
||||
|
||||
return SignalEncryptResult(cipherText.serialize(), type);
|
||||
return await session.encrypt(plaintextContent);
|
||||
} catch (e) {
|
||||
Log.error('Could not encrypt message (V1) for target $target: $e');
|
||||
Log.error('Could not encrypt message for target $target: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<(pb.EncryptedContent?, pb.PlaintextContent_DecryptionErrorMessage_Type?)>
|
||||
signalDecryptMessageV2(
|
||||
int fromUserId,
|
||||
Uint8List encryptedContentRaw, {
|
||||
Set<int>? brokenSessionsInCurrentBatch,
|
||||
}) async {
|
||||
Log.info('Acquiring lockingSignalProtocol for $fromUserId (V2)');
|
||||
final (
|
||||
decryptedContent,
|
||||
errorType,
|
||||
needsResync,
|
||||
) = await lockingSignalProtocol.protect(() async {
|
||||
Log.info('Lock acquired for $fromUserId (V2)');
|
||||
try {
|
||||
final plaintext = await RustSignal.decrypt(
|
||||
name: fromUserId.toString(),
|
||||
deviceId: 1,
|
||||
ciphertext: encryptedContentRaw,
|
||||
);
|
||||
recordResyncAttempt(fromUserId, success: true);
|
||||
return (pb.EncryptedContent.fromBuffer(plaintext), null, false);
|
||||
} catch (e) {
|
||||
Log.error('Could not decrypt message (V2) from $fromUserId: $e');
|
||||
return (
|
||||
null,
|
||||
pb.PlaintextContent_DecryptionErrorMessage_Type.UNKNOWN,
|
||||
true, // Needs resync on failure
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
Log.info('Released lockingSignalProtocol for $fromUserId (V2)');
|
||||
|
||||
if (needsResync) {
|
||||
brokenSessionsInCurrentBatch?.add(fromUserId);
|
||||
if (shouldAttemptResync(fromUserId)) {
|
||||
if (await handleSessionResync(fromUserId)) {
|
||||
recordResyncAttempt(fromUserId, success: false);
|
||||
await sendCipherText(
|
||||
fromUserId,
|
||||
pb.EncryptedContent(
|
||||
errorMessages: pb.EncryptedContent_ErrorMessages(
|
||||
type: pb.EncryptedContent_ErrorMessages_Type.SESSION_OUT_OF_SYNC,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (decryptedContent, errorType);
|
||||
}
|
||||
|
||||
Future<(pb.EncryptedContent?, pb.PlaintextContent_DecryptionErrorMessage_Type?)>
|
||||
signalDecryptMessageV1(
|
||||
Future<(EncryptedContent?, PlaintextContent_DecryptionErrorMessage_Type?)>
|
||||
signalDecryptMessage(
|
||||
int fromUserId,
|
||||
Uint8List encryptedContentRaw,
|
||||
int type, {
|
||||
Set<int>? brokenSessionsInCurrentBatch,
|
||||
}) async {
|
||||
Log.info('Acquiring lockingSignalProtocol for $fromUserId (V1)');
|
||||
// Hold the lock only for the cryptographic operation, not for network I/O
|
||||
Log.info('Acquiring lockingSignalProtocol for $fromUserId');
|
||||
final (
|
||||
decryptedContent,
|
||||
errorType,
|
||||
needsResync,
|
||||
) = await lockingSignalProtocol.protect(() async {
|
||||
Log.info('Lock acquired for $fromUserId (V1)');
|
||||
Log.info('Lock acquired for $fromUserId');
|
||||
try {
|
||||
// Yield execution to the event loop to prevent UI freezing during bulk decryption
|
||||
await Future.delayed(Duration.zero);
|
||||
|
||||
final session = SessionCipher.fromStore(
|
||||
(await getSignalStore())!,
|
||||
getSignalAddress(fromUserId),
|
||||
|
|
@ -163,18 +70,18 @@ signalDecryptMessageV1(
|
|||
Log.error('Unknown Message Decryption Type: $type');
|
||||
return (
|
||||
null,
|
||||
pb.PlaintextContent_DecryptionErrorMessage_Type.UNKNOWN,
|
||||
PlaintextContent_DecryptionErrorMessage_Type.UNKNOWN,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
recordResyncAttempt(fromUserId, success: true);
|
||||
return (pb.EncryptedContent.fromBuffer(plaintext), null, false);
|
||||
return (EncryptedContent.fromBuffer(plaintext), null, false);
|
||||
} on InvalidKeyIdException catch (e) {
|
||||
Log.warn(e);
|
||||
return (
|
||||
null,
|
||||
pb.PlaintextContent_DecryptionErrorMessage_Type.PREKEY_UNKNOWN,
|
||||
PlaintextContent_DecryptionErrorMessage_Type.PREKEY_UNKNOWN,
|
||||
false,
|
||||
);
|
||||
} on DuplicateMessageException catch (e) {
|
||||
|
|
@ -184,27 +91,27 @@ signalDecryptMessageV1(
|
|||
Log.info(e);
|
||||
return (
|
||||
null,
|
||||
pb.PlaintextContent_DecryptionErrorMessage_Type.UNKNOWN,
|
||||
PlaintextContent_DecryptionErrorMessage_Type.UNKNOWN,
|
||||
false,
|
||||
);
|
||||
} on InvalidMessageException catch (e) {
|
||||
Log.warn(e);
|
||||
return (
|
||||
null,
|
||||
pb.PlaintextContent_DecryptionErrorMessage_Type.UNKNOWN,
|
||||
PlaintextContent_DecryptionErrorMessage_Type.UNKNOWN,
|
||||
true,
|
||||
);
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
return (
|
||||
null,
|
||||
pb.PlaintextContent_DecryptionErrorMessage_Type.UNKNOWN,
|
||||
PlaintextContent_DecryptionErrorMessage_Type.UNKNOWN,
|
||||
false,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
Log.info('Released lockingSignalProtocol for $fromUserId (V1)');
|
||||
Log.info('Released lockingSignalProtocol for $fromUserId');
|
||||
|
||||
// Handle session resync OUTSIDE the lock to avoid holding it during
|
||||
// network round-trips (which can block for up to 60 seconds)
|
||||
|
|
@ -220,9 +127,9 @@ signalDecryptMessageV1(
|
|||
// session
|
||||
await sendCipherText(
|
||||
fromUserId,
|
||||
pb.EncryptedContent(
|
||||
errorMessages: pb.EncryptedContent_ErrorMessages(
|
||||
type: pb.EncryptedContent_ErrorMessages_Type.SESSION_OUT_OF_SYNC,
|
||||
EncryptedContent(
|
||||
errorMessages: EncryptedContent_ErrorMessages(
|
||||
type: EncryptedContent_ErrorMessages_Type.SESSION_OUT_OF_SYNC,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:clock/clock.dart';
|
||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||
import 'package:twonly/core/bridge/wrapper/key_manager.dart';
|
||||
import 'package:twonly/core/bridge/wrapper/signal.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/database/signal/signal_signed_pre_key_store.dart';
|
||||
import 'package:twonly/src/model/json/signal_identity.model.dart';
|
||||
|
|
@ -15,61 +13,39 @@ import 'package:twonly/src/utils/log.dart';
|
|||
|
||||
class SignalIdentityService {
|
||||
static Future<void> onAuthenticated() async {
|
||||
final now = clock.now();
|
||||
final fortyEightHoursAgo = now.subtract(const Duration(hours: 48));
|
||||
final oneWeekAgo = now.subtract(const Duration(days: 7));
|
||||
|
||||
if (userService.currentUser.signalLastSignedPreKeyUpdated == null ||
|
||||
!userService.currentUser.signalLastSignedPreKeyUpdated!.isAfter(
|
||||
fortyEightHoursAgo,
|
||||
)) {
|
||||
final signedPreKey = await _getNewSignalSignedPreKey();
|
||||
if (signedPreKey == null) {
|
||||
Log.error('could not generate a new signed pre key!');
|
||||
} else {
|
||||
await UserService.update((user) {
|
||||
user.signalLastSignedPreKeyUpdated = now;
|
||||
});
|
||||
final res = await apiService.updateSignedPreKey(
|
||||
signedPreKey.id,
|
||||
signedPreKey.getKeyPair().publicKey.serialize(),
|
||||
signedPreKey.signature,
|
||||
);
|
||||
if (res.isError) {
|
||||
Log.error('could not update the signed pre key: ${res.error}');
|
||||
await UserService.update((user) {
|
||||
user.signalLastSignedPreKeyUpdated = null;
|
||||
});
|
||||
} else {
|
||||
Log.info('updated signed pre key');
|
||||
}
|
||||
if (userService.currentUser.signalLastSignedPreKeyUpdated != null) {
|
||||
final fortyEightHoursAgo = clock.now().subtract(
|
||||
const Duration(hours: 48),
|
||||
);
|
||||
final isYoungerThan48Hours =
|
||||
(userService.currentUser.signalLastSignedPreKeyUpdated!).isAfter(
|
||||
fortyEightHoursAgo,
|
||||
);
|
||||
if (isYoungerThan48Hours) {
|
||||
// The key does live for 48 hours then it expires and a new key is generated.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (userService.currentUser.signalLastPqcPreKeysUploaded == null ||
|
||||
!userService.currentUser.signalLastPqcPreKeysUploaded!.isAfter(
|
||||
oneWeekAgo,
|
||||
)) {
|
||||
final bundle = await RustSignal.generateBundle();
|
||||
|
||||
final pqcRes = await apiService.uploadPqcPreKeys(
|
||||
bundle.signedPreKeyId,
|
||||
bundle.signedPreKeyPublic,
|
||||
bundle.signedPreKeySignature,
|
||||
bundle.kyberPreKeyId,
|
||||
bundle.kyberPreKeyPublic,
|
||||
bundle.kyberPreKeySignature,
|
||||
[],
|
||||
);
|
||||
|
||||
if (pqcRes.isError) {
|
||||
Log.warn('could not update the pqc signed pre key: ${pqcRes.error}');
|
||||
} else {
|
||||
Log.info('updated pqc signed pre key');
|
||||
await UserService.update((user) {
|
||||
user.signalLastPqcPreKeysUploaded = now;
|
||||
});
|
||||
}
|
||||
final signedPreKey = await _getNewSignalSignedPreKey();
|
||||
if (signedPreKey == null) {
|
||||
Log.error('could not generate a new signed pre key!');
|
||||
return;
|
||||
}
|
||||
await UserService.update((user) {
|
||||
user.signalLastSignedPreKeyUpdated = clock.now();
|
||||
});
|
||||
final res = await apiService.updateSignedPreKey(
|
||||
signedPreKey.id,
|
||||
signedPreKey.getKeyPair().publicKey.serialize(),
|
||||
signedPreKey.signature,
|
||||
);
|
||||
if (res.isError) {
|
||||
Log.error('could not update the signed pre key: ${res.error}');
|
||||
await UserService.update((user) {
|
||||
user.signalLastSignedPreKeyUpdated = null;
|
||||
});
|
||||
} else {
|
||||
Log.info('updated signed pre key');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,7 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:drift/drift.dart' as drift;
|
||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||
import 'package:twonly/core/bridge/wrapper/signal.dart';
|
||||
import 'package:twonly/core/signal/engine.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/database/tables/contacts.table.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart';
|
||||
import 'package:twonly/src/services/signal/consts.signal.dart';
|
||||
import 'package:twonly/src/services/signal/protocol_state.signal.dart';
|
||||
|
|
@ -20,106 +15,6 @@ Future<bool> processSignalUserData(Response_UserData userData) async {
|
|||
}
|
||||
|
||||
Future<bool> _processSignalUserData(Response_UserData userData) async {
|
||||
if (userData.hasPqcBundle()) {
|
||||
return _processSignalUserDataV2(userData);
|
||||
}
|
||||
return _processSignalUserDataV1(userData);
|
||||
}
|
||||
|
||||
Future<bool> _processSignalUserDataV2(Response_UserData userData) async {
|
||||
try {
|
||||
final tempIdentityKey = IdentityKey(
|
||||
Curve.decodePoint(
|
||||
DjbECPublicKey(
|
||||
Uint8List.fromList(userData.publicIdentityKey),
|
||||
).serialize(),
|
||||
1,
|
||||
),
|
||||
);
|
||||
|
||||
final signalStore = await getSignalStore();
|
||||
if (signalStore != null) {
|
||||
final existingIdentity = await signalStore.getIdentity(
|
||||
SignalProtocolAddress(userData.userId.toString(), defaultDeviceId),
|
||||
);
|
||||
|
||||
if (existingIdentity != null && existingIdentity != tempIdentityKey) {
|
||||
Log.error(
|
||||
'Identity key mismatch for contact ${userData.userId}! Existing V1 key does not match the incoming V2 identity key.',
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int? tempEccPreKeyId;
|
||||
Uint8List? tempEccPreKeyPublic;
|
||||
if (userData.pqcBundle.hasPrekey()) {
|
||||
tempEccPreKeyId = userData.pqcBundle.prekey.eccPreKeyId.toInt();
|
||||
tempEccPreKeyPublic = Uint8List.fromList(
|
||||
userData.pqcBundle.prekey.eccPreKey,
|
||||
);
|
||||
} else if (userData.prekeys.isNotEmpty) {
|
||||
tempEccPreKeyId = userData.prekeys.first.id.toInt();
|
||||
tempEccPreKeyPublic = Curve.decodePoint(
|
||||
DjbECPublicKey(
|
||||
Uint8List.fromList(userData.prekeys.first.prekey),
|
||||
).serialize(),
|
||||
1,
|
||||
).serialize();
|
||||
}
|
||||
|
||||
final tempKyberPreKeyId = userData.pqcBundle.hasPrekey()
|
||||
? userData.pqcBundle.prekey.kyberPreKeyId.toInt()
|
||||
: userData.pqcBundle.kyberSignedPrekeyId.toInt();
|
||||
final tempKyberPreKeyPublic = userData.pqcBundle.hasPrekey()
|
||||
? Uint8List.fromList(userData.pqcBundle.prekey.kyberPreKey)
|
||||
: Uint8List.fromList(userData.pqcBundle.kyberSignedPrekey);
|
||||
final tempKyberPreKeySignature = userData.pqcBundle.hasPrekey()
|
||||
? Uint8List.fromList(userData.pqcBundle.prekey.kyberPreKeySignature)
|
||||
: Uint8List.fromList(userData.pqcBundle.kyberSignedPrekeySignature);
|
||||
|
||||
final rustBundle = FrbPreKeyBundle(
|
||||
registrationId: userData.registrationId.toInt(),
|
||||
deviceId: 1,
|
||||
preKeyId: tempEccPreKeyId,
|
||||
preKeyPublic: tempEccPreKeyPublic,
|
||||
signedPreKeyId: userData.pqcBundle.eccSignedPrekeyId.toInt(),
|
||||
signedPreKeyPublic: Uint8List.fromList(
|
||||
userData.pqcBundle.eccSignedPrekey,
|
||||
),
|
||||
signedPreKeySignature: Uint8List.fromList(
|
||||
userData.pqcBundle.eccSignedPrekeySignature,
|
||||
),
|
||||
identityKey: tempIdentityKey.publicKey.serialize(),
|
||||
kyberPreKeyId: tempKyberPreKeyId,
|
||||
kyberPreKeyPublic: tempKyberPreKeyPublic,
|
||||
kyberPreKeySignature: tempKyberPreKeySignature,
|
||||
);
|
||||
|
||||
await RustSignal.processPrekeyBundle(
|
||||
name: userData.userId.toString(),
|
||||
deviceId: 1,
|
||||
bundle: rustBundle,
|
||||
);
|
||||
|
||||
final contact = await twonlyDB.contactsDao.getContactById(
|
||||
userData.userId.toInt(),
|
||||
);
|
||||
if (contact != null && contact.signalVersion != SignalVersion.v2) {
|
||||
await twonlyDB.contactsDao.updateContact(
|
||||
contact.userId,
|
||||
const ContactsCompanion(signalVersion: drift.Value(SignalVersion.v2)),
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
Log.error('could not process pqc bundle: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _processSignalUserDataV1(Response_UserData userData) async {
|
||||
final SignalProtocolStore? signalStore = await getSignalStore();
|
||||
|
||||
if (signalStore == null) {
|
||||
|
|
|
|||
|
|
@ -5,56 +5,12 @@ import 'dart:ui' as ui;
|
|||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:mutex/mutex.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
|
||||
const defaultAvatarSvg = '''
|
||||
<!-- Taken from: https://getavataaars.com/ -->
|
||||
<svg width="264px" height="280px" viewBox="0 0 264 280" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<path
|
||||
d="M124,144.610951 L124,163 L128,163 L128,163 C167.764502,163 200,195.235498 200,235 L200,244 L0,244 L0,235 C-4.86974701e-15,195.235498 32.235498,163 72,163 L72,163 L76,163 L76,144.610951 C58.7626345,136.422372 46.3722246,119.687011 44.3051388,99.8812385 C38.4803105,99.0577866 34,94.0521096 34,88 L34,74 C34,68.0540074 38.3245733,63.1180731 44,62.1659169 L44,56 L44,56 C44,25.072054 69.072054,5.68137151e-15 100,0 L100,0 L100,0 C130.927946,-5.68137151e-15 156,25.072054 156,56 L156,62.1659169 C161.675427,63.1180731 166,68.0540074 166,74 L166,88 C166,94.0521096 161.51969,99.0577866 155.694861,99.8812385 C153.627775,119.687011 141.237365,136.422372 124,144.610951 Z"
|
||||
id="react-path-3"></path>
|
||||
</defs>
|
||||
<g id="Avataaar" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g transform="translate(-825.000000, -1100.000000)">
|
||||
<g transform="translate(825.000000, 1100.000000)">
|
||||
<g id="Avataaar" stroke-width="1" fill-rule="evenodd">
|
||||
<g id="Body" transform="translate(32.000000, 36.000000)">
|
||||
<mask id="react-mask-6" fill="white">
|
||||
<use xlink:href="#react-path-3"></use>
|
||||
</mask>
|
||||
<g id="Skin/👶🏽-03-Brown" mask="url(#react-mask-6)" fill="#57CC99">
|
||||
<g transform="translate(0.000000, 0.000000)" id="Color">
|
||||
<rect x="0" y="0" width="264" height="280"></rect>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>''';
|
||||
|
||||
String getAvatarSvg(Uint8List avatarSvgCompressed) {
|
||||
try {
|
||||
// Check for GZIP magic bytes (0x1F 0x8B)
|
||||
final isGzip =
|
||||
avatarSvgCompressed.length >= 2 &&
|
||||
avatarSvgCompressed[0] == 0x1F &&
|
||||
avatarSvgCompressed[1] == 0x8B;
|
||||
|
||||
final decodedBytes = isGzip
|
||||
? gzip.decode(avatarSvgCompressed)
|
||||
: avatarSvgCompressed;
|
||||
return utf8.decode(decodedBytes);
|
||||
} catch (e) {
|
||||
Log.error('Failed to decode avatar SVG: $e');
|
||||
return defaultAvatarSvg;
|
||||
}
|
||||
return utf8.decode(gzip.decode(avatarSvgCompressed));
|
||||
}
|
||||
|
||||
Future<void> createPushAvatars({int? forceForUserId}) async {
|
||||
|
|
@ -115,33 +71,29 @@ File currentUserAvatarFile(int avatarCounter) {
|
|||
return File('${avatarsDirectory.path}/user_$avatarCounter.png');
|
||||
}
|
||||
|
||||
final _avatarMutex = Mutex();
|
||||
|
||||
Future<String?> getUserAvatar() async {
|
||||
if (userService.currentUser.avatarSvg == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return _avatarMutex.protect(() async {
|
||||
final avatarCounter = userService.currentUser.avatarCounter;
|
||||
final file = currentUserAvatarFile(avatarCounter);
|
||||
if (file.existsSync()) {
|
||||
return file.path;
|
||||
}
|
||||
|
||||
final pictureInfo = await vg.loadPicture(
|
||||
SvgStringLoader(userService.currentUser.avatarSvg!),
|
||||
null,
|
||||
);
|
||||
|
||||
final image = await pictureInfo.picture.toImage(270, 300);
|
||||
|
||||
final byteData = await image.toByteData(format: ui.ImageByteFormat.png);
|
||||
final pngBytes = byteData!.buffer.asUint8List();
|
||||
|
||||
await file.writeAsBytes(pngBytes, flush: true);
|
||||
pictureInfo.picture.dispose();
|
||||
|
||||
final avatarCounter = userService.currentUser.avatarCounter;
|
||||
final file = currentUserAvatarFile(avatarCounter);
|
||||
if (file.existsSync()) {
|
||||
return file.path;
|
||||
});
|
||||
}
|
||||
|
||||
final pictureInfo = await vg.loadPicture(
|
||||
SvgStringLoader(userService.currentUser.avatarSvg!),
|
||||
null,
|
||||
);
|
||||
|
||||
final image = await pictureInfo.picture.toImage(270, 300);
|
||||
|
||||
final byteData = await image.toByteData(format: ui.ImageByteFormat.png);
|
||||
final pngBytes = byteData!.buffer.asUint8List();
|
||||
|
||||
await file.writeAsBytes(pngBytes);
|
||||
pictureInfo.picture.dispose();
|
||||
|
||||
return file.path;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:clock/clock.dart';
|
||||
|
|
@ -7,6 +6,7 @@ import 'package:convert/convert.dart';
|
|||
import 'package:crypto/crypto.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
||||
import 'package:gal/gal.dart';
|
||||
import 'package:image/image.dart' as img;
|
||||
import 'package:intl/intl.dart';
|
||||
|
|
@ -44,41 +44,41 @@ Future<String?> saveImageToGallery(
|
|||
|
||||
if (createdAt != null) {
|
||||
try {
|
||||
bytesToProcess = await Isolate.run(() {
|
||||
final image = img.decodeImage(imageBytes);
|
||||
if (image != null) {
|
||||
final formattedDate = DateFormat(
|
||||
'yyyy:MM:dd HH:mm:ss',
|
||||
).format(createdAt);
|
||||
image.exif.imageIfd[0x0132] = img.IfdValueAscii(
|
||||
formattedDate,
|
||||
); // DateTime
|
||||
image.exif.exifIfd[0x9003] = img.IfdValueAscii(
|
||||
formattedDate,
|
||||
); // DateTimeOriginal
|
||||
image.exif.exifIfd[0x9004] = img.IfdValueAscii(
|
||||
formattedDate,
|
||||
); // DateTimeDigitized
|
||||
final image = img.decodeImage(imageBytes);
|
||||
if (image != null) {
|
||||
final formattedDate = DateFormat(
|
||||
'yyyy:MM:dd HH:mm:ss',
|
||||
).format(createdAt);
|
||||
image.exif.imageIfd[0x0132] = img.IfdValueAscii(
|
||||
formattedDate,
|
||||
); // DateTime
|
||||
image.exif.exifIfd[0x9003] = img.IfdValueAscii(
|
||||
formattedDate,
|
||||
); // DateTimeOriginal
|
||||
image.exif.exifIfd[0x9004] = img.IfdValueAscii(
|
||||
formattedDate,
|
||||
); // DateTimeDigitized
|
||||
|
||||
return img.encodeJpg(image);
|
||||
}
|
||||
return imageBytes;
|
||||
});
|
||||
bytesToProcess = img.encodeJpg(image);
|
||||
}
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
final jpgImages = await FlutterImageCompress.compressWithList(
|
||||
// ignore: avoid_redundant_argument_values
|
||||
format: CompressFormat.jpeg,
|
||||
bytesToProcess,
|
||||
quality: 100,
|
||||
keepExif: true,
|
||||
);
|
||||
final hasAccess = await Gal.hasAccess(toAlbum: true);
|
||||
if (!hasAccess) {
|
||||
await Gal.requestAccess(toAlbum: true);
|
||||
}
|
||||
try {
|
||||
await Gal.putImageBytes(
|
||||
bytesToProcess,
|
||||
album: 'twonly',
|
||||
name: name ?? 'image',
|
||||
);
|
||||
await Gal.putImageBytes(jpgImages, album: 'twonly', name: name ?? 'image');
|
||||
return null;
|
||||
} on GalException catch (e) {
|
||||
Log.error(e);
|
||||
|
|
@ -210,10 +210,6 @@ Future<bool> authenticateUser(
|
|||
return true;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Catch unexpected platform exceptions (e.g. PlatformException) that
|
||||
// would otherwise propagate and leave callers in a broken state.
|
||||
Log.error('Unexpected authentication error: $e');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
@ -264,28 +260,6 @@ String formatDateTime(BuildContext context, DateTime? dateTime) {
|
|||
}
|
||||
}
|
||||
|
||||
String formatRelativeDateTime(BuildContext context, DateTime? dateTime) {
|
||||
if (dateTime == null) {
|
||||
return '-';
|
||||
}
|
||||
final now = clock.now();
|
||||
final today = DateTime(now.year, now.month, now.day);
|
||||
final yesterday = today.subtract(const Duration(days: 1));
|
||||
final dateDay = DateTime(dateTime.year, dateTime.month, dateTime.day);
|
||||
|
||||
final time = DateFormat.Hm(
|
||||
Localizations.localeOf(context).toLanguageTag(),
|
||||
).format(dateTime);
|
||||
|
||||
if (dateDay == today) {
|
||||
return context.lang.todayAt(time);
|
||||
} else if (dateDay == yesterday) {
|
||||
return context.lang.yesterdayAt(time);
|
||||
} else {
|
||||
return formatDateTime(context, dateTime);
|
||||
}
|
||||
}
|
||||
|
||||
String formatBytes(int bytes) {
|
||||
if (bytes <= 0) return '0 Bytes';
|
||||
const units = <String>['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
||||
|
|
|
|||
|
|
@ -1,27 +1,11 @@
|
|||
import 'dart:async';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/utils/secure_storage.dart';
|
||||
|
||||
Future<bool> deleteLocalUserData() async {
|
||||
await twonlyDB.close();
|
||||
// Wait for the background drift isolate to potentially shut down
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
|
||||
// Remove port mappings to prevent connecting to an old, detached isolate that holds a deleted DB file.
|
||||
IsolateNameServer.removePortNameMapping('drift-db/twonly');
|
||||
IsolateNameServer.removePortNameMapping('drift-db/twonly/control');
|
||||
|
||||
final appDir = await getApplicationSupportDirectory();
|
||||
if (appDir.existsSync()) {
|
||||
appDir.deleteSync(recursive: true);
|
||||
}
|
||||
await SecureStorage.instance.deleteAll();
|
||||
locator
|
||||
..unregister<TwonlyDB>()
|
||||
..registerLazySingleton<TwonlyDB>(TwonlyDB.new);
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1,148 +0,0 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
class CachedNetworkImage extends StatefulWidget {
|
||||
const CachedNetworkImage({
|
||||
required this.imageUrl,
|
||||
super.key,
|
||||
this.width,
|
||||
this.height,
|
||||
this.fit,
|
||||
this.placeholder,
|
||||
this.errorWidget,
|
||||
});
|
||||
final String imageUrl;
|
||||
final double? width;
|
||||
final double? height;
|
||||
final BoxFit? fit;
|
||||
final Widget Function(BuildContext, String)? placeholder;
|
||||
final Widget Function(BuildContext, String, dynamic)? errorWidget;
|
||||
|
||||
@override
|
||||
State<CachedNetworkImage> createState() => _CachedNetworkImageState();
|
||||
}
|
||||
|
||||
class _CachedNetworkImageState extends State<CachedNetworkImage> {
|
||||
File? _imageFile;
|
||||
bool _isLoading = true;
|
||||
dynamic _error;
|
||||
static bool _hasCleanedUp = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadImage();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(CachedNetworkImage oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.imageUrl != widget.imageUrl) {
|
||||
_loadImage();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadImage() async {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
_error = null;
|
||||
});
|
||||
|
||||
try {
|
||||
final cacheDir = await getTemporaryDirectory();
|
||||
final urlHash = md5.convert(utf8.encode(widget.imageUrl)).toString();
|
||||
final file = File('${cacheDir.path}/custom_cached_image_$urlHash');
|
||||
|
||||
if (!_hasCleanedUp) {
|
||||
_hasCleanedUp = true;
|
||||
unawaited(_cleanupOldFiles(cacheDir)); // Run asynchronously
|
||||
}
|
||||
|
||||
if (file.existsSync()) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_imageFile = file;
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final client = HttpClient();
|
||||
final request = await client.getUrl(Uri.parse(widget.imageUrl));
|
||||
final response = await request.close();
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
await response.pipe(file.openWrite());
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_imageFile = file;
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
throw Exception('Failed to load image: ${response.statusCode}');
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_error = e;
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _cleanupOldFiles(Directory cacheDir) async {
|
||||
try {
|
||||
final files = cacheDir.listSync();
|
||||
final now = DateTime.now();
|
||||
for (final f in files) {
|
||||
if (f is File && f.path.contains('custom_cached_image_')) {
|
||||
final stat = f.statSync();
|
||||
if (now.difference(stat.modified).inDays >= 7) {
|
||||
await f.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (_) {
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_isLoading) {
|
||||
if (widget.placeholder != null) {
|
||||
return widget.placeholder!(context, widget.imageUrl);
|
||||
}
|
||||
return SizedBox(width: widget.width, height: widget.height);
|
||||
}
|
||||
|
||||
if (_error != null || _imageFile == null) {
|
||||
if (widget.errorWidget != null) {
|
||||
return widget.errorWidget!(context, widget.imageUrl, _error);
|
||||
}
|
||||
return SizedBox(width: widget.width, height: widget.height);
|
||||
}
|
||||
|
||||
return Image.file(
|
||||
_imageFile!,
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
fit: widget.fit,
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
if (widget.errorWidget != null) {
|
||||
return widget.errorWidget!(context, widget.imageUrl, error);
|
||||
}
|
||||
return SizedBox(width: widget.width, height: widget.height);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,144 +0,0 @@
|
|||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
||||
class ContactLabels extends StatefulWidget {
|
||||
const ContactLabels({
|
||||
required this.contactId,
|
||||
this.fontSize = 8,
|
||||
this.padding = const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
this.emptyText,
|
||||
this.showEmptyText = false,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final int contactId;
|
||||
final double fontSize;
|
||||
final EdgeInsetsGeometry padding;
|
||||
final String? emptyText;
|
||||
final bool showEmptyText;
|
||||
|
||||
@override
|
||||
State<ContactLabels> createState() => _ContactLabelsState();
|
||||
}
|
||||
|
||||
class _ContactLabelsState extends State<ContactLabels> {
|
||||
List<Label> _labels = [];
|
||||
late StreamSubscription<List<Label>> _sub;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_sub = twonlyDB.labelsDao.watchContactLabels(widget.contactId).listen((
|
||||
labels,
|
||||
) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_labels = labels;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_sub.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_labels.isEmpty) {
|
||||
if (!widget.showEmptyText && widget.emptyText == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return Text(
|
||||
widget.emptyText ?? context.lang.contactLabelsSubtitleEmpty,
|
||||
style: TextStyle(
|
||||
fontSize: widget.fontSize,
|
||||
color: Theme.of(context).disabledColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Wrap(
|
||||
spacing: 4,
|
||||
runSpacing: 2,
|
||||
children: _labels.map((label) {
|
||||
final bgColor = Color(label.backgroundColor);
|
||||
final textColor = Color(label.textColor);
|
||||
|
||||
return Container(
|
||||
padding: widget.padding,
|
||||
decoration: BoxDecoration(
|
||||
color: bgColor,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
label.name,
|
||||
style: TextStyle(
|
||||
fontSize: widget.fontSize,
|
||||
color: textColor,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget? buildContactLabelsSubtitle({
|
||||
required int contactId,
|
||||
required List<Label> labels,
|
||||
Widget? additionalSubtitle,
|
||||
}) {
|
||||
if (additionalSubtitle != null) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
additionalSubtitle,
|
||||
if (labels.isNotEmpty)
|
||||
ContactLabels(contactId: contactId),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
if (labels.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return ContactLabels(contactId: contactId);
|
||||
}
|
||||
|
||||
class ContactLabelsSubtitleBuilder extends StatelessWidget {
|
||||
const ContactLabelsSubtitleBuilder({
|
||||
required this.contactId,
|
||||
required this.builder,
|
||||
this.additionalSubtitle,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final int contactId;
|
||||
final Widget? additionalSubtitle;
|
||||
final Widget Function(BuildContext context, Widget? subtitleWidget) builder;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return StreamBuilder<List<Label>>(
|
||||
stream: twonlyDB.labelsDao.watchContactLabels(contactId),
|
||||
builder: (context, snapshot) {
|
||||
final labels = snapshot.data ?? [];
|
||||
final subtitle = buildContactLabelsSubtitle(
|
||||
contactId: contactId,
|
||||
labels: labels,
|
||||
additionalSubtitle: additionalSubtitle,
|
||||
);
|
||||
return builder(context, subtitle);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import 'package:twonly/locator.dart';
|
|||
import 'package:twonly/src/constants/routes.keys.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/components/notification_badge.comp.dart';
|
||||
import 'package:twonly/src/visual/themes/light.dart';
|
||||
|
||||
class ContactRequestBadgeComp extends StatelessWidget {
|
||||
const ContactRequestBadgeComp({super.key});
|
||||
|
|
@ -25,8 +26,8 @@ class ContactRequestBadgeComp extends StatelessWidget {
|
|||
child: Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: context.color.primary,
|
||||
decoration: const BoxDecoration(
|
||||
color: primaryColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,114 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/elements/my_button.element.dart';
|
||||
|
||||
class CustomColorPickerDialog extends StatefulWidget {
|
||||
const CustomColorPickerDialog({
|
||||
required this.initialColor,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final Color initialColor;
|
||||
|
||||
@override
|
||||
State<CustomColorPickerDialog> createState() => _CustomColorPickerDialogState();
|
||||
}
|
||||
|
||||
class _CustomColorPickerDialogState extends State<CustomColorPickerDialog> {
|
||||
late HSVColor _hsvColor;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_hsvColor = HSVColor.fromColor(widget.initialColor);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final currentColor = _hsvColor.toColor();
|
||||
|
||||
return AlertDialog(
|
||||
title: Text(context.lang.customColor),
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Color preview box
|
||||
Center(
|
||||
child: Container(
|
||||
width: 80,
|
||||
height: 80,
|
||||
decoration: BoxDecoration(
|
||||
color: currentColor,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.15),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
// Hue Slider
|
||||
Text(context.lang.hue, style: const TextStyle(fontWeight: FontWeight.w600)),
|
||||
Slider(
|
||||
value: _hsvColor.hue,
|
||||
max: 360,
|
||||
activeColor: HSVColor.fromAHSV(1, _hsvColor.hue, 1, 1).toColor(),
|
||||
onChanged: (val) {
|
||||
setState(() {
|
||||
_hsvColor = _hsvColor.withHue(val);
|
||||
});
|
||||
},
|
||||
),
|
||||
// Saturation Slider
|
||||
Text(context.lang.saturation, style: const TextStyle(fontWeight: FontWeight.w600)),
|
||||
Slider(
|
||||
value: _hsvColor.saturation,
|
||||
onChanged: (val) {
|
||||
setState(() {
|
||||
_hsvColor = _hsvColor.withSaturation(val);
|
||||
});
|
||||
},
|
||||
),
|
||||
// Brightness / Value Slider
|
||||
Text(context.lang.brightness, style: const TextStyle(fontWeight: FontWeight.w600)),
|
||||
Slider(
|
||||
value: _hsvColor.value,
|
||||
onChanged: (val) {
|
||||
setState(() {
|
||||
_hsvColor = _hsvColor.withValue(val);
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: MyButton(
|
||||
variant: MyButtonVariant.text,
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text(context.lang.cancel),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: MyButton(
|
||||
variant: MyButtonVariant.primaryMiddle,
|
||||
onPressed: () => Navigator.of(context).pop(currentColor.toARGB32()),
|
||||
child: Text(context.lang.ok),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,117 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/elements/my_button.element.dart';
|
||||
|
||||
Future<bool?> showDeleteMemoriesDialog({
|
||||
required BuildContext context,
|
||||
required int count,
|
||||
required bool hasCloudBackup,
|
||||
}) {
|
||||
return showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => DeleteMemoriesDialog(
|
||||
count: count,
|
||||
hasCloudBackup: hasCloudBackup,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class DeleteMemoriesDialog extends StatelessWidget {
|
||||
const DeleteMemoriesDialog({
|
||||
required this.count,
|
||||
required this.hasCloudBackup,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final int count;
|
||||
final bool hasCloudBackup;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Dialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
),
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 24,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
context.lang.deleteImageTitle,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
context.lang.deleteMemoriesBody(count),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.onSurface.withValues(alpha: 0.8),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
if (hasCloudBackup) ...[
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: MyButton(
|
||||
variant: MyButtonVariant.secondaryMiddle,
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
child: Text(context.lang.deleteMemoriesLocalOnly),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: MyButton(
|
||||
variant: MyButtonVariant.errorMiddle,
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: Text(context.lang.deleteMemoriesCompletely),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
MyButton(
|
||||
variant: MyButtonVariant.text,
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(context.lang.galleryCancel),
|
||||
),
|
||||
] else ...[
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: MyButton(
|
||||
variant: MyButtonVariant.secondaryMiddle,
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(context.lang.galleryCancel),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: MyButton(
|
||||
variant: MyButtonVariant.errorMiddle,
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: Text(context.lang.delete),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -98,7 +98,6 @@ class _FlameCounterWidgetState extends State<FlameCounterWidget> {
|
|||
height: 11,
|
||||
child: EmojiAnimationComp(
|
||||
emoji: '⌛',
|
||||
repeat: false,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue