mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-07-17 22:14:08 +00:00
working server integration
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run
This commit is contained in:
parent
ca77cc4902
commit
7ea311236d
34 changed files with 2529 additions and 179 deletions
31
README.md
31
README.md
|
|
@ -61,36 +61,9 @@ If you discover a security issue in twonly, please adhere to the coordinated vul
|
|||
us your report to security@twonly.eu. We also offer for critical security issues a small bug bounties, but we can not
|
||||
guarantee a bounty currently :/
|
||||
|
||||
<!-- ## Contribution
|
||||
|
||||
If you have any questions or feature requests, feel free to start a new discussion. Issues are limited to bugs, and for maintainers only. -->
|
||||
|
||||
## Development
|
||||
|
||||
<details>
|
||||
<summary>Setup Instructions (macOS)</summary>
|
||||
|
||||
## Building
|
||||
|
||||
Some dependencies are downloaded directly from the source as there are some new changes which are not yet published on
|
||||
pub.dev or because they require some special installation.
|
||||
|
||||
```bash
|
||||
git submodule update --init --recursive
|
||||
|
||||
cd dependencies/flutter_zxing
|
||||
git submodule update --init --recursive
|
||||
./scripts/update_ios_macos_src.s
|
||||
```
|
||||
|
||||
## Debugging files
|
||||
|
||||
```bash
|
||||
run-as eu.twonly.testing ls /data/user/0/eu.twonly.testing/
|
||||
```
|
||||
|
||||
</details>
|
||||
## Contribution
|
||||
|
||||
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
|
||||
|
||||
|
|
|
|||
32
lib/app.dart
32
lib/app.dart
|
|
@ -5,9 +5,15 @@ import 'package:flutter_localizations/flutter_localizations.dart';
|
|||
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/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/passwordless_recovery.service.dart'
|
||||
show PasswordlessRecoveryService;
|
||||
import 'package:twonly/src/utils/keyvalue.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:twonly/src/utils/pow.dart';
|
||||
import 'package:twonly/src/visual/components/app_outdated.comp.dart';
|
||||
|
|
@ -18,7 +24,7 @@ import 'package:twonly/src/visual/views/home.view.dart';
|
|||
import 'package:twonly/src/visual/views/onboarding/onboarding.view.dart';
|
||||
import 'package:twonly/src/visual/views/onboarding/register.view.dart';
|
||||
import 'package:twonly/src/visual/views/onboarding/setup.view.dart';
|
||||
import 'package:twonly/src/visual/views/recovery.view.dart';
|
||||
import 'package:twonly/src/visual/views/recovery_from_secure_storage.view.dart';
|
||||
import 'package:twonly/src/visual/views/unlock_twonly.view.dart';
|
||||
|
||||
class App extends StatefulWidget {
|
||||
|
|
@ -151,6 +157,10 @@ class _AppMainWidgetState extends State<AppMainWidget> {
|
|||
|
||||
Future<void> initAsync() async {
|
||||
Log.info('AppWidgetState: initAsync started');
|
||||
final onboardingState = await KeyValueStore.getModel<OnboardingState>(
|
||||
KeyValueKeys.onboardingState,
|
||||
);
|
||||
_showOnboarding = !onboardingState.hasOnboardingFinished;
|
||||
if (userService.isUserCreated) {
|
||||
if (_initialPage != 0) {
|
||||
final count = await twonlyDB.contactsDao.getContactsCount();
|
||||
|
|
@ -180,8 +190,18 @@ class _AppMainWidgetState extends State<AppMainWidget> {
|
|||
} else {
|
||||
_proofOfWork = (null, disabled);
|
||||
}
|
||||
|
||||
if (onboardingState.hasStartedPasswordlessRecovery) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
routerProvider.push(Routes.recoverPasswordless);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await PasswordlessRecoveryService.handleRecoveryLink(
|
||||
'https://me.twonly.eu/r/#d0e204fa-3d1e-4e53-a6a5-53aadf1e2ee5/oHeAEPp_CivU3QO67L0AkSlM94E5klbP8XDLbNgsGmA',
|
||||
);
|
||||
|
||||
setState(() {
|
||||
_isLoaded = true;
|
||||
});
|
||||
|
|
@ -223,9 +243,13 @@ class _AppMainWidgetState extends State<AppMainWidget> {
|
|||
}
|
||||
} else if (_showOnboarding) {
|
||||
child = OnboardingView(
|
||||
callbackOnSuccess: () => setState(() {
|
||||
_showOnboarding = false;
|
||||
}),
|
||||
callbackOnSuccess: () async {
|
||||
await KeyValueStore.update<OnboardingState>(
|
||||
key: KeyValueKeys.onboardingState,
|
||||
update: (state) => state.hasOnboardingFinished = true,
|
||||
);
|
||||
if (mounted) setState(() => _showOnboarding = false);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
child = RegisterView(
|
||||
|
|
|
|||
|
|
@ -3,4 +3,5 @@ class KeyValueKeys {
|
|||
'last_periodic_task_execution';
|
||||
static const String currentBackupState = 'current_backup_state';
|
||||
static const String backupRecoveryState = 'backup_recovery_state';
|
||||
static const String onboardingState = 'onboarding_state';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ class Routes {
|
|||
static String profileContact(int contactId) => '/profile/contact/$contactId';
|
||||
|
||||
static const String cameraQRScanner = '/camera/qr_scanner';
|
||||
static const String recoverPasswordless = '/recover/passwordless';
|
||||
|
||||
static const String settings = '/settings';
|
||||
static const String settingsProfile = '/settings/profile';
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ abstract class AppLocalizations {
|
|||
/// No description provided for @onboardingNotProductBody.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'twonly is financed by donations and an optional subscription. Your data will never be sold.'**
|
||||
/// **'twonly is financed by an optional subscription. Your data will never be sold.'**
|
||||
String get onboardingNotProductBody;
|
||||
|
||||
/// No description provided for @registerUsernameSlogan.
|
||||
|
|
@ -3852,6 +3852,12 @@ abstract class AppLocalizations {
|
|||
/// **'Enable Passwordless Recovery'**
|
||||
String get passwordlessRecoveryEnableBtn;
|
||||
|
||||
/// No description provided for @passwordlessRecoveryRecoverBtn.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Recover passwordless'**
|
||||
String get passwordlessRecoveryRecoverBtn;
|
||||
|
||||
/// No description provided for @passwordlessRecoveryModifyBtn.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -3959,6 +3965,150 @@ abstract class AppLocalizations {
|
|||
/// In en, this message translates to:
|
||||
/// **'Recover your identity without a password.'**
|
||||
String get passwordlessRecoverySubtitle;
|
||||
|
||||
/// No description provided for @recoverPasswordlessExplanation.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'If enabled, you can recover your account by asking your selected friends to help you.'**
|
||||
String get recoverPasswordlessExplanation;
|
||||
|
||||
/// No description provided for @recoverPasswordlessNotificationCardTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Enable notifications'**
|
||||
String get recoverPasswordlessNotificationCardTitle;
|
||||
|
||||
/// No description provided for @recoverPasswordlessNotificationCardSubtitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Get notified when a friend has helped you recover your account.'**
|
||||
String get recoverPasswordlessNotificationCardSubtitle;
|
||||
|
||||
/// No description provided for @recoverPasswordlessNotificationCardBtn.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Enable push notifications'**
|
||||
String get recoverPasswordlessNotificationCardBtn;
|
||||
|
||||
/// No description provided for @recoverPasswordlessQrInstructions.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Let friends scan the QR code or share the link.'**
|
||||
String get recoverPasswordlessQrInstructions;
|
||||
|
||||
/// No description provided for @recoverPasswordlessShareBtn.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Share Recovery Link'**
|
||||
String get recoverPasswordlessShareBtn;
|
||||
|
||||
/// No description provided for @recoverPasswordlessCopyBtn.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Copy'**
|
||||
String get recoverPasswordlessCopyBtn;
|
||||
|
||||
/// No description provided for @recoverPasswordlessCopiedSnackbar.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Link copied to clipboard!'**
|
||||
String get recoverPasswordlessCopiedSnackbar;
|
||||
|
||||
/// No description provided for @passwordlessRecoveryNoShareStored.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No recovery share stored for this contact.'**
|
||||
String get passwordlessRecoveryNoShareStored;
|
||||
|
||||
/// No description provided for @passwordlessRecoveryShareSent.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Recovery share successfully sent!'**
|
||||
String get passwordlessRecoveryShareSent;
|
||||
|
||||
/// No description provided for @passwordlessRecoveryNetworkError.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Network error, please ensure you have internet'**
|
||||
String get passwordlessRecoveryNetworkError;
|
||||
|
||||
/// No description provided for @passwordlessRecoveryHelpAFriend.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Help a Friend'**
|
||||
String get passwordlessRecoveryHelpAFriend;
|
||||
|
||||
/// No description provided for @passwordlessRecoverySelectContactDesc.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Please select the contact who requested recovery below to send them their recovery key.'**
|
||||
String get passwordlessRecoverySelectContactDesc;
|
||||
|
||||
/// No description provided for @passwordlessRecoverySearchContacts.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Search contacts...'**
|
||||
String get passwordlessRecoverySearchContacts;
|
||||
|
||||
/// No description provided for @passwordlessRecoveryNoContactsFound.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No contacts found'**
|
||||
String get passwordlessRecoveryNoContactsFound;
|
||||
|
||||
/// No description provided for @passwordlessRecoveryCantHelpHim.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'You can\'t help him'**
|
||||
String get passwordlessRecoveryCantHelpHim;
|
||||
|
||||
/// No description provided for @passwordlessRecoveryDoesAskedYou.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Does {username} has asked you?'**
|
||||
String passwordlessRecoveryDoesAskedYou(Object username);
|
||||
|
||||
/// No description provided for @passwordlessRecoveryVerifySourceDesc.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'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.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No'**
|
||||
String get passwordlessRecoveryNo;
|
||||
|
||||
/// No description provided for @passwordlessRecoveryYes.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Yes'**
|
||||
String get passwordlessRecoveryYes;
|
||||
|
||||
/// No description provided for @passwordlessRecoveryYesWithTimer.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'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} of {threshold} friends have shared'**
|
||||
String recoverPasswordlessSharesReceived(Object received, Object threshold);
|
||||
|
||||
/// No description provided for @recoverPasswordlessRecoverNowBtn.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Recover now'**
|
||||
String get recoverPasswordlessRecoverNowBtn;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get onboardingNotProductBody =>
|
||||
'twonly wird durch Spenden und ein optionales Abonnement finanziert. Deine Daten werden niemals verkauft.';
|
||||
'twonly wird durch ein optionales Abonnement finanziert. Deine Daten werden niemals verkauft.';
|
||||
|
||||
@override
|
||||
String get registerUsernameSlogan => 'Konto erstellen';
|
||||
|
|
@ -404,7 +404,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get contactUsLastWarning =>
|
||||
'Dies sind die Informationen, die an uns gesendet werden. Bitte prüfen Sie sie und klicke dann auf „Abschicken“.';
|
||||
'Dies sind die Informationen, die an uns gesendet werden. Bitte prüfe sie und klicke dann auf „Abschicken“.';
|
||||
|
||||
@override
|
||||
String get contactUsSuccess => 'Feedback erfolgreich übermittelt!';
|
||||
|
|
@ -2211,6 +2211,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get passwordlessRecoveryEnableBtn => 'Passwortloses Backup aktivieren';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryRecoverBtn => 'Passwortlos wiederherstellen';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryModifyBtn => 'Passwortloses Backup bearbeiten';
|
||||
|
||||
|
|
@ -2279,4 +2282,94 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get passwordlessRecoverySubtitle =>
|
||||
'Stelle deine Identität ohne Passwort wieder her.';
|
||||
|
||||
@override
|
||||
String get recoverPasswordlessExplanation =>
|
||||
'Wenn du die passwortlose Wiederherstellung aktiviert hast, kannst du dein Konto mithilfe deiner Freunde wiederherstellen.';
|
||||
|
||||
@override
|
||||
String get recoverPasswordlessNotificationCardTitle =>
|
||||
'Benachrichtigungen aktivieren';
|
||||
|
||||
@override
|
||||
String get recoverPasswordlessNotificationCardSubtitle =>
|
||||
'Lass dich benachrichtigen, wenn ein Freund dir geholfen hat, dein Konto wiederherzustellen.';
|
||||
|
||||
@override
|
||||
String get recoverPasswordlessNotificationCardBtn =>
|
||||
'Push-Benachrichtigungen aktivieren';
|
||||
|
||||
@override
|
||||
String get recoverPasswordlessQrInstructions =>
|
||||
'Lass deine Freunde den QR-Code scannen oder teile den Link.';
|
||||
|
||||
@override
|
||||
String get recoverPasswordlessShareBtn => 'Wiederherstellungs-Link teilen';
|
||||
|
||||
@override
|
||||
String get recoverPasswordlessCopyBtn => 'Kopieren';
|
||||
|
||||
@override
|
||||
String get recoverPasswordlessCopiedSnackbar =>
|
||||
'Link in die Zwischenablage kopiert!';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryNoShareStored =>
|
||||
'Kein Wiederherstellungs-Teil für diesen Kontakt gespeichert.';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryShareSent =>
|
||||
'Wiederherstellungs-Teil erfolgreich gesendet!';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryNetworkError =>
|
||||
'Netzwerkfehler, bitte stelle sicher, dass du Internet hast';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryHelpAFriend => 'Einem Freund helfen';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoverySelectContactDesc =>
|
||||
'Wähle den Kontakt aus, dem du helfen möchtest.';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoverySearchContacts => 'Kontakte suchen...';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryNoContactsFound => 'Keine Kontakte gefunden';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryCantHelpHim => 'Du kannst ihm nicht helfen';
|
||||
|
||||
@override
|
||||
String passwordlessRecoveryDoesAskedYou(Object username) {
|
||||
return 'Hat $username dich gefragt?';
|
||||
}
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryVerifySourceDesc =>
|
||||
'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';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryYes => 'Ja';
|
||||
|
||||
@override
|
||||
String passwordlessRecoveryYesWithTimer(Object seconds) {
|
||||
return 'Ja (${seconds}s)';
|
||||
}
|
||||
|
||||
@override
|
||||
String get recoverPasswordlessBeingRecoveredLabel =>
|
||||
'Konto wird wiederhergestellt';
|
||||
|
||||
@override
|
||||
String recoverPasswordlessSharesReceived(Object received, Object threshold) {
|
||||
return '$received von $threshold Freunden haben geteilt';
|
||||
}
|
||||
|
||||
@override
|
||||
String get recoverPasswordlessRecoverNowBtn => 'Jetzt wiederherstellen';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get onboardingNotProductBody =>
|
||||
'twonly is financed by donations and an optional subscription. Your data will never be sold.';
|
||||
'twonly is financed by an optional subscription. Your data will never be sold.';
|
||||
|
||||
@override
|
||||
String get registerUsernameSlogan => 'Create your account';
|
||||
|
|
@ -2195,6 +2195,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get passwordlessRecoveryEnableBtn => 'Enable Passwordless Recovery';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryRecoverBtn => 'Recover passwordless';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryModifyBtn => 'Modify Passwordless Recovery';
|
||||
|
||||
|
|
@ -2262,4 +2265,92 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get passwordlessRecoverySubtitle =>
|
||||
'Recover your identity without a password.';
|
||||
|
||||
@override
|
||||
String get recoverPasswordlessExplanation =>
|
||||
'If enabled, you can recover your account by asking your selected friends to help you.';
|
||||
|
||||
@override
|
||||
String get recoverPasswordlessNotificationCardTitle => 'Enable notifications';
|
||||
|
||||
@override
|
||||
String get recoverPasswordlessNotificationCardSubtitle =>
|
||||
'Get notified when a friend has helped you recover your account.';
|
||||
|
||||
@override
|
||||
String get recoverPasswordlessNotificationCardBtn =>
|
||||
'Enable push notifications';
|
||||
|
||||
@override
|
||||
String get recoverPasswordlessQrInstructions =>
|
||||
'Let friends scan the QR code or share the link.';
|
||||
|
||||
@override
|
||||
String get recoverPasswordlessShareBtn => 'Share Recovery Link';
|
||||
|
||||
@override
|
||||
String get recoverPasswordlessCopyBtn => 'Copy';
|
||||
|
||||
@override
|
||||
String get recoverPasswordlessCopiedSnackbar => 'Link copied to clipboard!';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryNoShareStored =>
|
||||
'No recovery share stored for this contact.';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryShareSent =>
|
||||
'Recovery share successfully sent!';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryNetworkError =>
|
||||
'Network error, please ensure you have internet';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryHelpAFriend => 'Help a Friend';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoverySelectContactDesc =>
|
||||
'Please select the contact who requested recovery below to send them their recovery key.';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoverySearchContacts => 'Search contacts...';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryNoContactsFound => 'No contacts found';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryCantHelpHim => 'You can\'t help him';
|
||||
|
||||
@override
|
||||
String passwordlessRecoveryDoesAskedYou(Object username) {
|
||||
return 'Does $username has asked you?';
|
||||
}
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryVerifySourceDesc =>
|
||||
'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';
|
||||
|
||||
@override
|
||||
String get passwordlessRecoveryYes => 'Yes';
|
||||
|
||||
@override
|
||||
String passwordlessRecoveryYesWithTimer(Object seconds) {
|
||||
return 'Yes (${seconds}s)';
|
||||
}
|
||||
|
||||
@override
|
||||
String get recoverPasswordlessBeingRecoveredLabel =>
|
||||
'Account being recovered';
|
||||
|
||||
@override
|
||||
String recoverPasswordlessSharesReceived(Object received, Object threshold) {
|
||||
return '$received of $threshold friends have shared';
|
||||
}
|
||||
|
||||
@override
|
||||
String get recoverPasswordlessRecoverNowBtn => 'Recover now';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 4d8e1373bf3fea617157a86791be7c65edd879d3
|
||||
Subproject commit bd641ce3a3f3b9c709dc97a279d9fe8b9b6f07e1
|
||||
62
lib/src/model/json/onboarding_state.model.dart
Normal file
62
lib/src/model/json/onboarding_state.model.dart
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'onboarding_state.model.g.dart';
|
||||
|
||||
/// Holds information about a single trusted friend who has already shared their
|
||||
/// recovery part.
|
||||
@JsonSerializable()
|
||||
class ReceivedRecoveryShare {
|
||||
ReceivedRecoveryShare({
|
||||
required this.messageId,
|
||||
required this.trustedFriendDisplayName,
|
||||
required this.myDisplayName,
|
||||
required this.myUserId,
|
||||
required this.myAvatarSvg,
|
||||
required this.threshold,
|
||||
required this.sharedSecretDataBytes,
|
||||
});
|
||||
|
||||
factory ReceivedRecoveryShare.fromJson(Map<String, dynamic> json) =>
|
||||
_$ReceivedRecoveryShareFromJson(json);
|
||||
|
||||
final int messageId;
|
||||
|
||||
final String trustedFriendDisplayName;
|
||||
|
||||
final String myDisplayName;
|
||||
final int myUserId;
|
||||
final List<int>? myAvatarSvg;
|
||||
|
||||
final int threshold;
|
||||
|
||||
final List<int> sharedSecretDataBytes;
|
||||
|
||||
Map<String, dynamic> toJson() => _$ReceivedRecoveryShareToJson(this);
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class OnboardingState {
|
||||
OnboardingState({
|
||||
this.hasOnboardingFinished = false,
|
||||
this.hasStartedPasswordlessRecovery = false,
|
||||
this.notificationId,
|
||||
this.downloadAuthToken,
|
||||
this.serverRegistered = false,
|
||||
this.encryptionKey,
|
||||
});
|
||||
|
||||
factory OnboardingState.fromJson(Map<String, dynamic> json) =>
|
||||
_$OnboardingStateFromJson(json);
|
||||
|
||||
bool hasOnboardingFinished;
|
||||
bool hasStartedPasswordlessRecovery;
|
||||
|
||||
String? notificationId;
|
||||
List<int>? downloadAuthToken;
|
||||
bool serverRegistered;
|
||||
List<int>? encryptionKey;
|
||||
|
||||
List<ReceivedRecoveryShare> receivedShares = [];
|
||||
|
||||
Map<String, dynamic> toJson() => _$OnboardingStateToJson(this);
|
||||
}
|
||||
64
lib/src/model/json/onboarding_state.model.g.dart
Normal file
64
lib/src/model/json/onboarding_state.model.g.dart
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'onboarding_state.model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
ReceivedRecoveryShare _$ReceivedRecoveryShareFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => ReceivedRecoveryShare(
|
||||
messageId: (json['messageId'] as num).toInt(),
|
||||
trustedFriendDisplayName: json['trustedFriendDisplayName'] as String,
|
||||
myDisplayName: json['myDisplayName'] as String,
|
||||
myUserId: (json['myUserId'] as num).toInt(),
|
||||
myAvatarSvg: (json['myAvatarSvg'] as List<dynamic>?)
|
||||
?.map((e) => (e as num).toInt())
|
||||
.toList(),
|
||||
threshold: (json['threshold'] as num).toInt(),
|
||||
sharedSecretDataBytes: (json['sharedSecretDataBytes'] as List<dynamic>)
|
||||
.map((e) => (e as num).toInt())
|
||||
.toList(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ReceivedRecoveryShareToJson(
|
||||
ReceivedRecoveryShare instance,
|
||||
) => <String, dynamic>{
|
||||
'messageId': instance.messageId,
|
||||
'trustedFriendDisplayName': instance.trustedFriendDisplayName,
|
||||
'myDisplayName': instance.myDisplayName,
|
||||
'myUserId': instance.myUserId,
|
||||
'myAvatarSvg': instance.myAvatarSvg,
|
||||
'threshold': instance.threshold,
|
||||
'sharedSecretDataBytes': instance.sharedSecretDataBytes,
|
||||
};
|
||||
|
||||
OnboardingState _$OnboardingStateFromJson(Map<String, dynamic> json) =>
|
||||
OnboardingState(
|
||||
hasOnboardingFinished: json['hasOnboardingFinished'] as bool? ?? false,
|
||||
hasStartedPasswordlessRecovery:
|
||||
json['hasStartedPasswordlessRecovery'] as bool? ?? false,
|
||||
notificationId: json['notificationId'] as String?,
|
||||
downloadAuthToken: (json['downloadAuthToken'] as List<dynamic>?)
|
||||
?.map((e) => (e as num).toInt())
|
||||
.toList(),
|
||||
serverRegistered: json['serverRegistered'] as bool? ?? false,
|
||||
encryptionKey: (json['encryptionKey'] as List<dynamic>?)
|
||||
?.map((e) => (e as num).toInt())
|
||||
.toList(),
|
||||
)
|
||||
..receivedShares = (json['receivedShares'] as List<dynamic>)
|
||||
.map((e) => ReceivedRecoveryShare.fromJson(e as Map<String, dynamic>))
|
||||
.toList();
|
||||
|
||||
Map<String, dynamic> _$OnboardingStateToJson(OnboardingState instance) =>
|
||||
<String, dynamic>{
|
||||
'hasOnboardingFinished': instance.hasOnboardingFinished,
|
||||
'hasStartedPasswordlessRecovery': instance.hasStartedPasswordlessRecovery,
|
||||
'notificationId': instance.notificationId,
|
||||
'downloadAuthToken': instance.downloadAuthToken,
|
||||
'serverRegistered': instance.serverRegistered,
|
||||
'encryptionKey': instance.encryptionKey,
|
||||
'receivedShares': instance.receivedShares,
|
||||
};
|
||||
|
|
@ -813,7 +813,6 @@ class Handshake_GetServerKeyForPasswordLessRecovery
|
|||
extends $pb.GeneratedMessage {
|
||||
factory Handshake_GetServerKeyForPasswordLessRecovery({
|
||||
$fixnum.Int64? userId,
|
||||
$core.List<$core.int>? encryptedServerKeyMac,
|
||||
$core.List<$core.int>? encryptedServerKeyNone,
|
||||
$core.List<$core.int>? pinUnlockToken,
|
||||
$core.List<$core.int>? pinProtectionKey,
|
||||
|
|
@ -821,8 +820,6 @@ class Handshake_GetServerKeyForPasswordLessRecovery
|
|||
}) {
|
||||
final result = create();
|
||||
if (userId != null) result.userId = userId;
|
||||
if (encryptedServerKeyMac != null)
|
||||
result.encryptedServerKeyMac = encryptedServerKeyMac;
|
||||
if (encryptedServerKeyNone != null)
|
||||
result.encryptedServerKeyNone = encryptedServerKeyNone;
|
||||
if (pinUnlockToken != null) result.pinUnlockToken = pinUnlockToken;
|
||||
|
|
@ -849,14 +846,12 @@ class Handshake_GetServerKeyForPasswordLessRecovery
|
|||
createEmptyInstance: create)
|
||||
..aInt64(1, _omitFieldNames ? '' : 'userId')
|
||||
..a<$core.List<$core.int>>(
|
||||
2, _omitFieldNames ? '' : 'encryptedServerKeyMac', $pb.PbFieldType.OY)
|
||||
2, _omitFieldNames ? '' : 'encryptedServerKeyNone', $pb.PbFieldType.OY)
|
||||
..a<$core.List<$core.int>>(
|
||||
3, _omitFieldNames ? '' : 'encryptedServerKeyNone', $pb.PbFieldType.OY)
|
||||
3, _omitFieldNames ? '' : 'pinUnlockToken', $pb.PbFieldType.OY)
|
||||
..a<$core.List<$core.int>>(
|
||||
4, _omitFieldNames ? '' : 'pinUnlockToken', $pb.PbFieldType.OY)
|
||||
..a<$core.List<$core.int>>(
|
||||
5, _omitFieldNames ? '' : 'pinProtectionKey', $pb.PbFieldType.OY)
|
||||
..aOS(6, _omitFieldNames ? '' : 'email')
|
||||
4, _omitFieldNames ? '' : 'pinProtectionKey', $pb.PbFieldType.OY)
|
||||
..aOS(5, _omitFieldNames ? '' : 'email')
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
|
|
@ -894,51 +889,219 @@ class Handshake_GetServerKeyForPasswordLessRecovery
|
|||
void clearUserId() => $_clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.List<$core.int> get encryptedServerKeyMac => $_getN(1);
|
||||
$core.List<$core.int> get encryptedServerKeyNone => $_getN(1);
|
||||
@$pb.TagNumber(2)
|
||||
set encryptedServerKeyMac($core.List<$core.int> value) =>
|
||||
set encryptedServerKeyNone($core.List<$core.int> value) =>
|
||||
$_setBytes(1, value);
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasEncryptedServerKeyMac() => $_has(1);
|
||||
$core.bool hasEncryptedServerKeyNone() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearEncryptedServerKeyMac() => $_clearField(2);
|
||||
void clearEncryptedServerKeyNone() => $_clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.List<$core.int> get encryptedServerKeyNone => $_getN(2);
|
||||
$core.List<$core.int> get pinUnlockToken => $_getN(2);
|
||||
@$pb.TagNumber(3)
|
||||
set encryptedServerKeyNone($core.List<$core.int> value) =>
|
||||
$_setBytes(2, value);
|
||||
set pinUnlockToken($core.List<$core.int> value) => $_setBytes(2, value);
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasEncryptedServerKeyNone() => $_has(2);
|
||||
$core.bool hasPinUnlockToken() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearEncryptedServerKeyNone() => $_clearField(3);
|
||||
void clearPinUnlockToken() => $_clearField(3);
|
||||
|
||||
@$pb.TagNumber(4)
|
||||
$core.List<$core.int> get pinUnlockToken => $_getN(3);
|
||||
$core.List<$core.int> get pinProtectionKey => $_getN(3);
|
||||
@$pb.TagNumber(4)
|
||||
set pinUnlockToken($core.List<$core.int> value) => $_setBytes(3, value);
|
||||
set pinProtectionKey($core.List<$core.int> value) => $_setBytes(3, value);
|
||||
@$pb.TagNumber(4)
|
||||
$core.bool hasPinUnlockToken() => $_has(3);
|
||||
$core.bool hasPinProtectionKey() => $_has(3);
|
||||
@$pb.TagNumber(4)
|
||||
void clearPinUnlockToken() => $_clearField(4);
|
||||
void clearPinProtectionKey() => $_clearField(4);
|
||||
|
||||
@$pb.TagNumber(5)
|
||||
$core.List<$core.int> get pinProtectionKey => $_getN(4);
|
||||
$core.String get email => $_getSZ(4);
|
||||
@$pb.TagNumber(5)
|
||||
set pinProtectionKey($core.List<$core.int> value) => $_setBytes(4, value);
|
||||
set email($core.String value) => $_setString(4, value);
|
||||
@$pb.TagNumber(5)
|
||||
$core.bool hasPinProtectionKey() => $_has(4);
|
||||
$core.bool hasEmail() => $_has(4);
|
||||
@$pb.TagNumber(5)
|
||||
void clearPinProtectionKey() => $_clearField(5);
|
||||
void clearEmail() => $_clearField(5);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(6)
|
||||
$core.String get email => $_getSZ(5);
|
||||
@$pb.TagNumber(6)
|
||||
set email($core.String value) => $_setString(5, value);
|
||||
@$pb.TagNumber(6)
|
||||
$core.bool hasEmail() => $_has(5);
|
||||
@$pb.TagNumber(6)
|
||||
void clearEmail() => $_clearField(6);
|
||||
class Handshake_RegisterPasswordlessNotification extends $pb.GeneratedMessage {
|
||||
factory Handshake_RegisterPasswordlessNotification({
|
||||
$core.String? notificationId,
|
||||
$core.List<$core.int>? downloadAuthToken,
|
||||
$core.String? langCode,
|
||||
$core.String? googleFcm,
|
||||
}) {
|
||||
final result = create();
|
||||
if (notificationId != null) result.notificationId = notificationId;
|
||||
if (downloadAuthToken != null) result.downloadAuthToken = downloadAuthToken;
|
||||
if (langCode != null) result.langCode = langCode;
|
||||
if (googleFcm != null) result.googleFcm = googleFcm;
|
||||
return result;
|
||||
}
|
||||
|
||||
Handshake_RegisterPasswordlessNotification._();
|
||||
|
||||
factory Handshake_RegisterPasswordlessNotification.fromBuffer(
|
||||
$core.List<$core.int> data,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromBuffer(data, registry);
|
||||
factory Handshake_RegisterPasswordlessNotification.fromJson($core.String json,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromJson(json, registry);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
_omitMessageNames ? '' : 'Handshake.RegisterPasswordlessNotification',
|
||||
package:
|
||||
const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'),
|
||||
createEmptyInstance: create)
|
||||
..aOS(1, _omitFieldNames ? '' : 'notificationId')
|
||||
..a<$core.List<$core.int>>(
|
||||
2, _omitFieldNames ? '' : 'downloadAuthToken', $pb.PbFieldType.OY)
|
||||
..aOS(3, _omitFieldNames ? '' : 'langCode')
|
||||
..aOS(4, _omitFieldNames ? '' : 'googleFcm')
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
Handshake_RegisterPasswordlessNotification clone() => deepCopy();
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
Handshake_RegisterPasswordlessNotification copyWith(
|
||||
void Function(Handshake_RegisterPasswordlessNotification) updates) =>
|
||||
super.copyWith((message) =>
|
||||
updates(message as Handshake_RegisterPasswordlessNotification))
|
||||
as Handshake_RegisterPasswordlessNotification;
|
||||
|
||||
@$core.override
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Handshake_RegisterPasswordlessNotification create() =>
|
||||
Handshake_RegisterPasswordlessNotification._();
|
||||
@$core.override
|
||||
Handshake_RegisterPasswordlessNotification createEmptyInstance() => create();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Handshake_RegisterPasswordlessNotification getDefault() =>
|
||||
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<
|
||||
Handshake_RegisterPasswordlessNotification>(create);
|
||||
static Handshake_RegisterPasswordlessNotification? _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 downloadAuthToken => $_getN(1);
|
||||
@$pb.TagNumber(2)
|
||||
set downloadAuthToken($core.List<$core.int> value) => $_setBytes(1, value);
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasDownloadAuthToken() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearDownloadAuthToken() => $_clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.String get langCode => $_getSZ(2);
|
||||
@$pb.TagNumber(3)
|
||||
set langCode($core.String value) => $_setString(2, value);
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasLangCode() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearLangCode() => $_clearField(3);
|
||||
|
||||
@$pb.TagNumber(4)
|
||||
$core.String get googleFcm => $_getSZ(3);
|
||||
@$pb.TagNumber(4)
|
||||
set googleFcm($core.String value) => $_setString(3, value);
|
||||
@$pb.TagNumber(4)
|
||||
$core.bool hasGoogleFcm() => $_has(3);
|
||||
@$pb.TagNumber(4)
|
||||
void clearGoogleFcm() => $_clearField(4);
|
||||
}
|
||||
|
||||
class Handshake_CheckForPasswordlessNotification extends $pb.GeneratedMessage {
|
||||
factory Handshake_CheckForPasswordlessNotification({
|
||||
$core.String? notificationId,
|
||||
$core.List<$core.int>? downloadAuthToken,
|
||||
$core.Iterable<$fixnum.Int64>? alreadyReceivedMessageIds,
|
||||
}) {
|
||||
final result = create();
|
||||
if (notificationId != null) result.notificationId = notificationId;
|
||||
if (downloadAuthToken != null) result.downloadAuthToken = downloadAuthToken;
|
||||
if (alreadyReceivedMessageIds != null)
|
||||
result.alreadyReceivedMessageIds.addAll(alreadyReceivedMessageIds);
|
||||
return result;
|
||||
}
|
||||
|
||||
Handshake_CheckForPasswordlessNotification._();
|
||||
|
||||
factory Handshake_CheckForPasswordlessNotification.fromBuffer(
|
||||
$core.List<$core.int> data,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromBuffer(data, registry);
|
||||
factory Handshake_CheckForPasswordlessNotification.fromJson($core.String json,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromJson(json, registry);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
_omitMessageNames ? '' : 'Handshake.CheckForPasswordlessNotification',
|
||||
package:
|
||||
const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'),
|
||||
createEmptyInstance: create)
|
||||
..aOS(1, _omitFieldNames ? '' : 'notificationId')
|
||||
..a<$core.List<$core.int>>(
|
||||
2, _omitFieldNames ? '' : 'downloadAuthToken', $pb.PbFieldType.OY)
|
||||
..p<$fixnum.Int64>(3, _omitFieldNames ? '' : 'alreadyReceivedMessageIds',
|
||||
$pb.PbFieldType.K6)
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
Handshake_CheckForPasswordlessNotification clone() => deepCopy();
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
Handshake_CheckForPasswordlessNotification copyWith(
|
||||
void Function(Handshake_CheckForPasswordlessNotification) updates) =>
|
||||
super.copyWith((message) =>
|
||||
updates(message as Handshake_CheckForPasswordlessNotification))
|
||||
as Handshake_CheckForPasswordlessNotification;
|
||||
|
||||
@$core.override
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Handshake_CheckForPasswordlessNotification create() =>
|
||||
Handshake_CheckForPasswordlessNotification._();
|
||||
@$core.override
|
||||
Handshake_CheckForPasswordlessNotification createEmptyInstance() => create();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Handshake_CheckForPasswordlessNotification getDefault() =>
|
||||
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<
|
||||
Handshake_CheckForPasswordlessNotification>(create);
|
||||
static Handshake_CheckForPasswordlessNotification? _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 downloadAuthToken => $_getN(1);
|
||||
@$pb.TagNumber(2)
|
||||
set downloadAuthToken($core.List<$core.int> value) => $_setBytes(1, value);
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasDownloadAuthToken() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearDownloadAuthToken() => $_clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$pb.PbList<$fixnum.Int64> get alreadyReceivedMessageIds => $_getList(2);
|
||||
}
|
||||
|
||||
enum Handshake_Handshake {
|
||||
|
|
@ -950,6 +1113,8 @@ enum Handshake_Handshake {
|
|||
authenticateWithLoginToken,
|
||||
getUseridByUsername,
|
||||
getServerKeyForPasswordlessRecovery,
|
||||
registerPasswordlessNotification,
|
||||
checkForPasswordlessNotification,
|
||||
notSet
|
||||
}
|
||||
|
||||
|
|
@ -964,6 +1129,10 @@ class Handshake extends $pb.GeneratedMessage {
|
|||
Handshake_GetUserIdByUsername? getUseridByUsername,
|
||||
Handshake_GetServerKeyForPasswordLessRecovery?
|
||||
getServerKeyForPasswordlessRecovery,
|
||||
Handshake_RegisterPasswordlessNotification?
|
||||
registerPasswordlessNotification,
|
||||
Handshake_CheckForPasswordlessNotification?
|
||||
checkForPasswordlessNotification,
|
||||
}) {
|
||||
final result = create();
|
||||
if (register != null) result.register = register;
|
||||
|
|
@ -978,6 +1147,12 @@ class Handshake extends $pb.GeneratedMessage {
|
|||
if (getServerKeyForPasswordlessRecovery != null)
|
||||
result.getServerKeyForPasswordlessRecovery =
|
||||
getServerKeyForPasswordlessRecovery;
|
||||
if (registerPasswordlessNotification != null)
|
||||
result.registerPasswordlessNotification =
|
||||
registerPasswordlessNotification;
|
||||
if (checkForPasswordlessNotification != null)
|
||||
result.checkForPasswordlessNotification =
|
||||
checkForPasswordlessNotification;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -1000,6 +1175,8 @@ class Handshake extends $pb.GeneratedMessage {
|
|||
6: Handshake_Handshake.authenticateWithLoginToken,
|
||||
7: Handshake_Handshake.getUseridByUsername,
|
||||
8: Handshake_Handshake.getServerKeyForPasswordlessRecovery,
|
||||
9: Handshake_Handshake.registerPasswordlessNotification,
|
||||
10: Handshake_Handshake.checkForPasswordlessNotification,
|
||||
0: Handshake_Handshake.notSet
|
||||
};
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
|
|
@ -1007,7 +1184,7 @@ class Handshake extends $pb.GeneratedMessage {
|
|||
package:
|
||||
const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'),
|
||||
createEmptyInstance: create)
|
||||
..oo(0, [1, 2, 3, 4, 5, 6, 7, 8])
|
||||
..oo(0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
|
||||
..aOM<Handshake_Register>(1, _omitFieldNames ? '' : 'register',
|
||||
subBuilder: Handshake_Register.create)
|
||||
..aOM<Handshake_GetAuthChallenge>(
|
||||
|
|
@ -1029,6 +1206,12 @@ class Handshake extends $pb.GeneratedMessage {
|
|||
..aOM<Handshake_GetServerKeyForPasswordLessRecovery>(
|
||||
8, _omitFieldNames ? '' : 'getServerKeyForPasswordlessRecovery',
|
||||
subBuilder: Handshake_GetServerKeyForPasswordLessRecovery.create)
|
||||
..aOM<Handshake_RegisterPasswordlessNotification>(
|
||||
9, _omitFieldNames ? '' : 'registerPasswordlessNotification',
|
||||
subBuilder: Handshake_RegisterPasswordlessNotification.create)
|
||||
..aOM<Handshake_CheckForPasswordlessNotification>(
|
||||
10, _omitFieldNames ? '' : 'checkForPasswordlessNotification',
|
||||
subBuilder: Handshake_CheckForPasswordlessNotification.create)
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
|
|
@ -1057,6 +1240,8 @@ class Handshake extends $pb.GeneratedMessage {
|
|||
@$pb.TagNumber(6)
|
||||
@$pb.TagNumber(7)
|
||||
@$pb.TagNumber(8)
|
||||
@$pb.TagNumber(9)
|
||||
@$pb.TagNumber(10)
|
||||
Handshake_Handshake whichHandshake() =>
|
||||
_Handshake_HandshakeByTag[$_whichOneof(0)]!;
|
||||
@$pb.TagNumber(1)
|
||||
|
|
@ -1067,6 +1252,8 @@ class Handshake extends $pb.GeneratedMessage {
|
|||
@$pb.TagNumber(6)
|
||||
@$pb.TagNumber(7)
|
||||
@$pb.TagNumber(8)
|
||||
@$pb.TagNumber(9)
|
||||
@$pb.TagNumber(10)
|
||||
void clearHandshake() => $_clearField($_whichOneof(0));
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
|
|
@ -1165,6 +1352,36 @@ class Handshake extends $pb.GeneratedMessage {
|
|||
@$pb.TagNumber(8)
|
||||
Handshake_GetServerKeyForPasswordLessRecovery
|
||||
ensureGetServerKeyForPasswordlessRecovery() => $_ensure(7);
|
||||
|
||||
@$pb.TagNumber(9)
|
||||
Handshake_RegisterPasswordlessNotification
|
||||
get registerPasswordlessNotification => $_getN(8);
|
||||
@$pb.TagNumber(9)
|
||||
set registerPasswordlessNotification(
|
||||
Handshake_RegisterPasswordlessNotification value) =>
|
||||
$_setField(9, value);
|
||||
@$pb.TagNumber(9)
|
||||
$core.bool hasRegisterPasswordlessNotification() => $_has(8);
|
||||
@$pb.TagNumber(9)
|
||||
void clearRegisterPasswordlessNotification() => $_clearField(9);
|
||||
@$pb.TagNumber(9)
|
||||
Handshake_RegisterPasswordlessNotification
|
||||
ensureRegisterPasswordlessNotification() => $_ensure(8);
|
||||
|
||||
@$pb.TagNumber(10)
|
||||
Handshake_CheckForPasswordlessNotification
|
||||
get checkForPasswordlessNotification => $_getN(9);
|
||||
@$pb.TagNumber(10)
|
||||
set checkForPasswordlessNotification(
|
||||
Handshake_CheckForPasswordlessNotification value) =>
|
||||
$_setField(10, value);
|
||||
@$pb.TagNumber(10)
|
||||
$core.bool hasCheckForPasswordlessNotification() => $_has(9);
|
||||
@$pb.TagNumber(10)
|
||||
void clearCheckForPasswordlessNotification() => $_clearField(10);
|
||||
@$pb.TagNumber(10)
|
||||
Handshake_CheckForPasswordlessNotification
|
||||
ensureCheckForPasswordlessNotification() => $_ensure(9);
|
||||
}
|
||||
|
||||
class ApplicationData_TextMessage extends $pb.GeneratedMessage {
|
||||
|
|
@ -2422,6 +2639,79 @@ class ApplicationData_RegisterPasswordLessRecovery
|
|||
void clearPinUnlockToken() => $_clearField(2);
|
||||
}
|
||||
|
||||
class ApplicationData_PasswordlessNotification extends $pb.GeneratedMessage {
|
||||
factory ApplicationData_PasswordlessNotification({
|
||||
$core.String? notificationId,
|
||||
$core.List<$core.int>? encryptedMessage,
|
||||
}) {
|
||||
final result = create();
|
||||
if (notificationId != null) result.notificationId = notificationId;
|
||||
if (encryptedMessage != null) result.encryptedMessage = encryptedMessage;
|
||||
return result;
|
||||
}
|
||||
|
||||
ApplicationData_PasswordlessNotification._();
|
||||
|
||||
factory ApplicationData_PasswordlessNotification.fromBuffer(
|
||||
$core.List<$core.int> data,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromBuffer(data, registry);
|
||||
factory ApplicationData_PasswordlessNotification.fromJson($core.String json,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromJson(json, registry);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
_omitMessageNames ? '' : 'ApplicationData.PasswordlessNotification',
|
||||
package:
|
||||
const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'),
|
||||
createEmptyInstance: create)
|
||||
..aOS(1, _omitFieldNames ? '' : 'notificationId')
|
||||
..a<$core.List<$core.int>>(
|
||||
2, _omitFieldNames ? '' : 'encryptedMessage', $pb.PbFieldType.OY)
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
ApplicationData_PasswordlessNotification clone() => deepCopy();
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
ApplicationData_PasswordlessNotification copyWith(
|
||||
void Function(ApplicationData_PasswordlessNotification) updates) =>
|
||||
super.copyWith((message) =>
|
||||
updates(message as ApplicationData_PasswordlessNotification))
|
||||
as ApplicationData_PasswordlessNotification;
|
||||
|
||||
@$core.override
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static ApplicationData_PasswordlessNotification create() =>
|
||||
ApplicationData_PasswordlessNotification._();
|
||||
@$core.override
|
||||
ApplicationData_PasswordlessNotification createEmptyInstance() => create();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static ApplicationData_PasswordlessNotification getDefault() =>
|
||||
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<
|
||||
ApplicationData_PasswordlessNotification>(create);
|
||||
static ApplicationData_PasswordlessNotification? _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 encryptedMessage => $_getN(1);
|
||||
@$pb.TagNumber(2)
|
||||
set encryptedMessage($core.List<$core.int> value) => $_setBytes(1, value);
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasEncryptedMessage() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearEncryptedMessage() => $_clearField(2);
|
||||
}
|
||||
|
||||
enum ApplicationData_ApplicationData {
|
||||
textMessage,
|
||||
getUserByUsername,
|
||||
|
|
@ -2450,6 +2740,7 @@ enum ApplicationData_ApplicationData {
|
|||
addAdditionalUser,
|
||||
setLoginToken,
|
||||
registerPasswordlessRecovery,
|
||||
passwordlessNotification,
|
||||
notSet
|
||||
}
|
||||
|
||||
|
|
@ -2482,6 +2773,7 @@ class ApplicationData extends $pb.GeneratedMessage {
|
|||
ApplicationData_AddAdditionalUser? addAdditionalUser,
|
||||
ApplicationData_SetLoginToken? setLoginToken,
|
||||
ApplicationData_RegisterPasswordLessRecovery? registerPasswordlessRecovery,
|
||||
ApplicationData_PasswordlessNotification? passwordlessNotification,
|
||||
}) {
|
||||
final result = create();
|
||||
if (textMessage != null) result.textMessage = textMessage;
|
||||
|
|
@ -2518,6 +2810,8 @@ class ApplicationData extends $pb.GeneratedMessage {
|
|||
if (setLoginToken != null) result.setLoginToken = setLoginToken;
|
||||
if (registerPasswordlessRecovery != null)
|
||||
result.registerPasswordlessRecovery = registerPasswordlessRecovery;
|
||||
if (passwordlessNotification != null)
|
||||
result.passwordlessNotification = passwordlessNotification;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -2559,6 +2853,7 @@ class ApplicationData extends $pb.GeneratedMessage {
|
|||
29: ApplicationData_ApplicationData.addAdditionalUser,
|
||||
30: ApplicationData_ApplicationData.setLoginToken,
|
||||
31: ApplicationData_ApplicationData.registerPasswordlessRecovery,
|
||||
32: ApplicationData_ApplicationData.passwordlessNotification,
|
||||
0: ApplicationData_ApplicationData.notSet
|
||||
};
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
|
|
@ -2593,7 +2888,8 @@ class ApplicationData extends $pb.GeneratedMessage {
|
|||
28,
|
||||
29,
|
||||
30,
|
||||
31
|
||||
31,
|
||||
32
|
||||
])
|
||||
..aOM<ApplicationData_TextMessage>(1, _omitFieldNames ? '' : 'textMessage',
|
||||
protoName: 'textMessage',
|
||||
|
|
@ -2688,6 +2984,9 @@ class ApplicationData extends $pb.GeneratedMessage {
|
|||
..aOM<ApplicationData_RegisterPasswordLessRecovery>(
|
||||
31, _omitFieldNames ? '' : 'registerPasswordlessRecovery',
|
||||
subBuilder: ApplicationData_RegisterPasswordLessRecovery.create)
|
||||
..aOM<ApplicationData_PasswordlessNotification>(
|
||||
32, _omitFieldNames ? '' : 'passwordlessNotification',
|
||||
subBuilder: ApplicationData_PasswordlessNotification.create)
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
|
|
@ -2736,6 +3035,7 @@ class ApplicationData extends $pb.GeneratedMessage {
|
|||
@$pb.TagNumber(29)
|
||||
@$pb.TagNumber(30)
|
||||
@$pb.TagNumber(31)
|
||||
@$pb.TagNumber(32)
|
||||
ApplicationData_ApplicationData whichApplicationData() =>
|
||||
_ApplicationData_ApplicationDataByTag[$_whichOneof(0)]!;
|
||||
@$pb.TagNumber(1)
|
||||
|
|
@ -2765,6 +3065,7 @@ class ApplicationData extends $pb.GeneratedMessage {
|
|||
@$pb.TagNumber(29)
|
||||
@$pb.TagNumber(30)
|
||||
@$pb.TagNumber(31)
|
||||
@$pb.TagNumber(32)
|
||||
void clearApplicationData() => $_clearField($_whichOneof(0));
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
|
|
@ -3085,6 +3386,21 @@ class ApplicationData extends $pb.GeneratedMessage {
|
|||
@$pb.TagNumber(31)
|
||||
ApplicationData_RegisterPasswordLessRecovery
|
||||
ensureRegisterPasswordlessRecovery() => $_ensure(26);
|
||||
|
||||
@$pb.TagNumber(32)
|
||||
ApplicationData_PasswordlessNotification get passwordlessNotification =>
|
||||
$_getN(27);
|
||||
@$pb.TagNumber(32)
|
||||
set passwordlessNotification(
|
||||
ApplicationData_PasswordlessNotification value) =>
|
||||
$_setField(32, value);
|
||||
@$pb.TagNumber(32)
|
||||
$core.bool hasPasswordlessNotification() => $_has(27);
|
||||
@$pb.TagNumber(32)
|
||||
void clearPasswordlessNotification() => $_clearField(32);
|
||||
@$pb.TagNumber(32)
|
||||
ApplicationData_PasswordlessNotification ensurePasswordlessNotification() =>
|
||||
$_ensure(27);
|
||||
}
|
||||
|
||||
class Response_PreKey extends $pb.GeneratedMessage {
|
||||
|
|
|
|||
|
|
@ -161,6 +161,24 @@ const Handshake$json = {
|
|||
'9': 0,
|
||||
'10': 'getServerKeyForPasswordlessRecovery'
|
||||
},
|
||||
{
|
||||
'1': 'register_passwordless_notification',
|
||||
'3': 9,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.Handshake.RegisterPasswordlessNotification',
|
||||
'9': 0,
|
||||
'10': 'registerPasswordlessNotification'
|
||||
},
|
||||
{
|
||||
'1': 'check_for_passwordless_notification',
|
||||
'3': 10,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.Handshake.CheckForPasswordlessNotification',
|
||||
'9': 0,
|
||||
'10': 'checkForPasswordlessNotification'
|
||||
},
|
||||
],
|
||||
'3': [
|
||||
Handshake_RequestPOW$json,
|
||||
|
|
@ -170,7 +188,9 @@ const Handshake$json = {
|
|||
Handshake_GetAuthToken$json,
|
||||
Handshake_Authenticate$json,
|
||||
Handshake_AuthenticateWithLoginToken$json,
|
||||
Handshake_GetServerKeyForPasswordLessRecovery$json
|
||||
Handshake_GetServerKeyForPasswordLessRecovery$json,
|
||||
Handshake_RegisterPasswordlessNotification$json,
|
||||
Handshake_CheckForPasswordlessNotification$json
|
||||
],
|
||||
'8': [
|
||||
{'1': 'Handshake'},
|
||||
|
|
@ -318,23 +338,16 @@ const Handshake_GetServerKeyForPasswordLessRecovery$json = {
|
|||
'1': 'GetServerKeyForPasswordLessRecovery',
|
||||
'2': [
|
||||
{'1': 'user_id', '3': 1, '4': 1, '5': 3, '10': 'userId'},
|
||||
{
|
||||
'1': 'encrypted_server_key_mac',
|
||||
'3': 2,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'10': 'encryptedServerKeyMac'
|
||||
},
|
||||
{
|
||||
'1': 'encrypted_server_key_none',
|
||||
'3': 3,
|
||||
'3': 2,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'10': 'encryptedServerKeyNone'
|
||||
},
|
||||
{
|
||||
'1': 'pin_unlock_token',
|
||||
'3': 4,
|
||||
'3': 3,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'9': 0,
|
||||
|
|
@ -343,14 +356,14 @@ const Handshake_GetServerKeyForPasswordLessRecovery$json = {
|
|||
},
|
||||
{
|
||||
'1': 'pin_protection_key',
|
||||
'3': 5,
|
||||
'3': 4,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'9': 1,
|
||||
'10': 'pinProtectionKey',
|
||||
'17': true
|
||||
},
|
||||
{'1': 'email', '3': 6, '4': 1, '5': 9, '9': 2, '10': 'email', '17': true},
|
||||
{'1': 'email', '3': 5, '4': 1, '5': 9, '9': 2, '10': 'email', '17': true},
|
||||
],
|
||||
'8': [
|
||||
{'1': '_pin_unlock_token'},
|
||||
|
|
@ -359,6 +372,56 @@ const Handshake_GetServerKeyForPasswordLessRecovery$json = {
|
|||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use handshakeDescriptor instead')
|
||||
const Handshake_RegisterPasswordlessNotification$json = {
|
||||
'1': 'RegisterPasswordlessNotification',
|
||||
'2': [
|
||||
{'1': 'notification_id', '3': 1, '4': 1, '5': 9, '10': 'notificationId'},
|
||||
{
|
||||
'1': 'download_auth_token',
|
||||
'3': 2,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'10': 'downloadAuthToken'
|
||||
},
|
||||
{'1': 'lang_code', '3': 3, '4': 1, '5': 9, '10': 'langCode'},
|
||||
{
|
||||
'1': 'google_fcm',
|
||||
'3': 4,
|
||||
'4': 1,
|
||||
'5': 9,
|
||||
'9': 0,
|
||||
'10': 'googleFcm',
|
||||
'17': true
|
||||
},
|
||||
],
|
||||
'8': [
|
||||
{'1': '_google_fcm'},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use handshakeDescriptor instead')
|
||||
const Handshake_CheckForPasswordlessNotification$json = {
|
||||
'1': 'CheckForPasswordlessNotification',
|
||||
'2': [
|
||||
{'1': 'notification_id', '3': 1, '4': 1, '5': 9, '10': 'notificationId'},
|
||||
{
|
||||
'1': 'download_auth_token',
|
||||
'3': 2,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'10': 'downloadAuthToken'
|
||||
},
|
||||
{
|
||||
'1': 'already_received_message_ids',
|
||||
'3': 3,
|
||||
'4': 3,
|
||||
'5': 3,
|
||||
'10': 'alreadyReceivedMessageIds'
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `Handshake`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List handshakeDescriptor = $convert.base64Decode(
|
||||
'CglIYW5kc2hha2USQgoIcmVnaXN0ZXIYASABKAsyJC5jbGllbnRfdG9fc2VydmVyLkhhbmRzaG'
|
||||
|
|
@ -375,32 +438,44 @@ final $typed_data.Uint8List handshakeDescriptor = $convert.base64Decode(
|
|||
'VIAFITZ2V0VXNlcmlkQnlVc2VybmFtZRKYAQooZ2V0X3NlcnZlcl9rZXlfZm9yX3Bhc3N3b3Jk'
|
||||
'bGVzc19yZWNvdmVyeRgIIAEoCzI/LmNsaWVudF90b19zZXJ2ZXIuSGFuZHNoYWtlLkdldFNlcn'
|
||||
'ZlcktleUZvclBhc3N3b3JkTGVzc1JlY292ZXJ5SABSI2dldFNlcnZlcktleUZvclBhc3N3b3Jk'
|
||||
'bGVzc1JlY292ZXJ5GgwKClJlcXVlc3RQT1caygMKCFJlZ2lzdGVyEhoKCHVzZXJuYW1lGAEgAS'
|
||||
'gJUgh1c2VybmFtZRIkCgtpbnZpdGVfY29kZRgCIAEoCUgAUgppbnZpdGVDb2RliAEBEi4KE3B1'
|
||||
'YmxpY19pZGVudGl0eV9rZXkYAyABKAxSEXB1YmxpY0lkZW50aXR5S2V5EiMKDXNpZ25lZF9wcm'
|
||||
'VrZXkYBCABKAxSDHNpZ25lZFByZWtleRI2ChdzaWduZWRfcHJla2V5X3NpZ25hdHVyZRgFIAEo'
|
||||
'DFIVc2lnbmVkUHJla2V5U2lnbmF0dXJlEigKEHNpZ25lZF9wcmVrZXlfaWQYBiABKANSDnNpZ2'
|
||||
'5lZFByZWtleUlkEicKD3JlZ2lzdHJhdGlvbl9pZBgHIAEoA1IOcmVnaXN0cmF0aW9uSWQSFQoG'
|
||||
'aXNfaW9zGAggASgIUgVpc0lvcxIbCglsYW5nX2NvZGUYCSABKAlSCGxhbmdDb2RlEiIKDXByb2'
|
||||
'9mX29mX3dvcmsYCiABKANSC3Byb29mT2ZXb3JrEiQKC2xvZ2luX3Rva2VuGAsgASgMSAFSCmxv'
|
||||
'Z2luVG9rZW6IAQFCDgoMX2ludml0ZV9jb2RlQg4KDF9sb2dpbl90b2tlbhoSChBHZXRBdXRoQ2'
|
||||
'hhbGxlbmdlGjEKE0dldFVzZXJJZEJ5VXNlcm5hbWUSGgoIdXNlcm5hbWUYASABKAlSCHVzZXJu'
|
||||
'YW1lGkMKDEdldEF1dGhUb2tlbhIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQSGgoIcmVzcG9uc2'
|
||||
'UYAiABKAxSCHJlc3BvbnNlGugBCgxBdXRoZW50aWNhdGUSFwoHdXNlcl9pZBgBIAEoA1IGdXNl'
|
||||
'cklkEh0KCmF1dGhfdG9rZW4YAiABKAxSCWF1dGhUb2tlbhIkCgthcHBfdmVyc2lvbhgDIAEoCU'
|
||||
'gAUgphcHBWZXJzaW9uiAEBEiAKCWRldmljZV9pZBgEIAEoA0gBUghkZXZpY2VJZIgBARIoCg1p'
|
||||
'bl9iYWNrZ3JvdW5kGAUgASgISAJSDGluQmFja2dyb3VuZIgBAUIOCgxfYXBwX3ZlcnNpb25CDA'
|
||||
'oKX2RldmljZV9pZEIQCg5faW5fYmFja2dyb3VuZBrGAQoaQXV0aGVudGljYXRlV2l0aExvZ2lu'
|
||||
'VG9rZW4SFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkEiwKEnNlY3JldF9sb2dpbl90b2tlbhgCIA'
|
||||
'EoDFIQc2VjcmV0TG9naW5Ub2tlbhIfCgthcHBfdmVyc2lvbhgDIAEoCVIKYXBwVmVyc2lvbhIb'
|
||||
'CglkZXZpY2VfaWQYBCABKANSCGRldmljZUlkEiMKDWluX2JhY2tncm91bmQYBSABKAhSDGluQm'
|
||||
'Fja2dyb3VuZBrlAgojR2V0U2VydmVyS2V5Rm9yUGFzc3dvcmRMZXNzUmVjb3ZlcnkSFwoHdXNl'
|
||||
'cl9pZBgBIAEoA1IGdXNlcklkEjcKGGVuY3J5cHRlZF9zZXJ2ZXJfa2V5X21hYxgCIAEoDFIVZW'
|
||||
'5jcnlwdGVkU2VydmVyS2V5TWFjEjkKGWVuY3J5cHRlZF9zZXJ2ZXJfa2V5X25vbmUYAyABKAxS'
|
||||
'FmVuY3J5cHRlZFNlcnZlcktleU5vbmUSLQoQcGluX3VubG9ja190b2tlbhgEIAEoDEgAUg5waW'
|
||||
'5VbmxvY2tUb2tlbogBARIxChJwaW5fcHJvdGVjdGlvbl9rZXkYBSABKAxIAVIQcGluUHJvdGVj'
|
||||
'dGlvbktleYgBARIZCgVlbWFpbBgGIAEoCUgCUgVlbWFpbIgBAUITChFfcGluX3VubG9ja190b2'
|
||||
'tlbkIVChNfcGluX3Byb3RlY3Rpb25fa2V5QggKBl9lbWFpbEILCglIYW5kc2hha2U=');
|
||||
'bGVzc1JlY292ZXJ5EowBCiJyZWdpc3Rlcl9wYXNzd29yZGxlc3Nfbm90aWZpY2F0aW9uGAkgAS'
|
||||
'gLMjwuY2xpZW50X3RvX3NlcnZlci5IYW5kc2hha2UuUmVnaXN0ZXJQYXNzd29yZGxlc3NOb3Rp'
|
||||
'ZmljYXRpb25IAFIgcmVnaXN0ZXJQYXNzd29yZGxlc3NOb3RpZmljYXRpb24SjQEKI2NoZWNrX2'
|
||||
'Zvcl9wYXNzd29yZGxlc3Nfbm90aWZpY2F0aW9uGAogASgLMjwuY2xpZW50X3RvX3NlcnZlci5I'
|
||||
'YW5kc2hha2UuQ2hlY2tGb3JQYXNzd29yZGxlc3NOb3RpZmljYXRpb25IAFIgY2hlY2tGb3JQYX'
|
||||
'Nzd29yZGxlc3NOb3RpZmljYXRpb24aDAoKUmVxdWVzdFBPVxrKAwoIUmVnaXN0ZXISGgoIdXNl'
|
||||
'cm5hbWUYASABKAlSCHVzZXJuYW1lEiQKC2ludml0ZV9jb2RlGAIgASgJSABSCmludml0ZUNvZG'
|
||||
'WIAQESLgoTcHVibGljX2lkZW50aXR5X2tleRgDIAEoDFIRcHVibGljSWRlbnRpdHlLZXkSIwoN'
|
||||
'c2lnbmVkX3ByZWtleRgEIAEoDFIMc2lnbmVkUHJla2V5EjYKF3NpZ25lZF9wcmVrZXlfc2lnbm'
|
||||
'F0dXJlGAUgASgMUhVzaWduZWRQcmVrZXlTaWduYXR1cmUSKAoQc2lnbmVkX3ByZWtleV9pZBgG'
|
||||
'IAEoA1IOc2lnbmVkUHJla2V5SWQSJwoPcmVnaXN0cmF0aW9uX2lkGAcgASgDUg5yZWdpc3RyYX'
|
||||
'Rpb25JZBIVCgZpc19pb3MYCCABKAhSBWlzSW9zEhsKCWxhbmdfY29kZRgJIAEoCVIIbGFuZ0Nv'
|
||||
'ZGUSIgoNcHJvb2Zfb2Zfd29yaxgKIAEoA1ILcHJvb2ZPZldvcmsSJAoLbG9naW5fdG9rZW4YCy'
|
||||
'ABKAxIAVIKbG9naW5Ub2tlbogBAUIOCgxfaW52aXRlX2NvZGVCDgoMX2xvZ2luX3Rva2VuGhIK'
|
||||
'EEdldEF1dGhDaGFsbGVuZ2UaMQoTR2V0VXNlcklkQnlVc2VybmFtZRIaCgh1c2VybmFtZRgBIA'
|
||||
'EoCVIIdXNlcm5hbWUaQwoMR2V0QXV0aFRva2VuEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBIa'
|
||||
'CghyZXNwb25zZRgCIAEoDFIIcmVzcG9uc2Ua6AEKDEF1dGhlbnRpY2F0ZRIXCgd1c2VyX2lkGA'
|
||||
'EgASgDUgZ1c2VySWQSHQoKYXV0aF90b2tlbhgCIAEoDFIJYXV0aFRva2VuEiQKC2FwcF92ZXJz'
|
||||
'aW9uGAMgASgJSABSCmFwcFZlcnNpb26IAQESIAoJZGV2aWNlX2lkGAQgASgDSAFSCGRldmljZU'
|
||||
'lkiAEBEigKDWluX2JhY2tncm91bmQYBSABKAhIAlIMaW5CYWNrZ3JvdW5kiAEBQg4KDF9hcHBf'
|
||||
'dmVyc2lvbkIMCgpfZGV2aWNlX2lkQhAKDl9pbl9iYWNrZ3JvdW5kGsYBChpBdXRoZW50aWNhdG'
|
||||
'VXaXRoTG9naW5Ub2tlbhIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQSLAoSc2VjcmV0X2xvZ2lu'
|
||||
'X3Rva2VuGAIgASgMUhBzZWNyZXRMb2dpblRva2VuEh8KC2FwcF92ZXJzaW9uGAMgASgJUgphcH'
|
||||
'BWZXJzaW9uEhsKCWRldmljZV9pZBgEIAEoA1IIZGV2aWNlSWQSIwoNaW5fYmFja2dyb3VuZBgF'
|
||||
'IAEoCFIMaW5CYWNrZ3JvdW5kGqwCCiNHZXRTZXJ2ZXJLZXlGb3JQYXNzd29yZExlc3NSZWNvdm'
|
||||
'VyeRIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQSOQoZZW5jcnlwdGVkX3NlcnZlcl9rZXlfbm9u'
|
||||
'ZRgCIAEoDFIWZW5jcnlwdGVkU2VydmVyS2V5Tm9uZRItChBwaW5fdW5sb2NrX3Rva2VuGAMgAS'
|
||||
'gMSABSDnBpblVubG9ja1Rva2VuiAEBEjEKEnBpbl9wcm90ZWN0aW9uX2tleRgEIAEoDEgBUhBw'
|
||||
'aW5Qcm90ZWN0aW9uS2V5iAEBEhkKBWVtYWlsGAUgASgJSAJSBWVtYWlsiAEBQhMKEV9waW5fdW'
|
||||
'5sb2NrX3Rva2VuQhUKE19waW5fcHJvdGVjdGlvbl9rZXlCCAoGX2VtYWlsGssBCiBSZWdpc3Rl'
|
||||
'clBhc3N3b3JkbGVzc05vdGlmaWNhdGlvbhInCg9ub3RpZmljYXRpb25faWQYASABKAlSDm5vdG'
|
||||
'lmaWNhdGlvbklkEi4KE2Rvd25sb2FkX2F1dGhfdG9rZW4YAiABKAxSEWRvd25sb2FkQXV0aFRv'
|
||||
'a2VuEhsKCWxhbmdfY29kZRgDIAEoCVIIbGFuZ0NvZGUSIgoKZ29vZ2xlX2ZjbRgEIAEoCUgAUg'
|
||||
'lnb29nbGVGY22IAQFCDQoLX2dvb2dsZV9mY20avAEKIENoZWNrRm9yUGFzc3dvcmRsZXNzTm90'
|
||||
'aWZpY2F0aW9uEicKD25vdGlmaWNhdGlvbl9pZBgBIAEoCVIObm90aWZpY2F0aW9uSWQSLgoTZG'
|
||||
'93bmxvYWRfYXV0aF90b2tlbhgCIAEoDFIRZG93bmxvYWRBdXRoVG9rZW4SPwocYWxyZWFkeV9y'
|
||||
'ZWNlaXZlZF9tZXNzYWdlX2lkcxgDIAMoA1IZYWxyZWFkeVJlY2VpdmVkTWVzc2FnZUlkc0ILCg'
|
||||
'lIYW5kc2hha2U=');
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
const ApplicationData$json = {
|
||||
|
|
@ -649,6 +724,15 @@ const ApplicationData$json = {
|
|||
'9': 0,
|
||||
'10': 'registerPasswordlessRecovery'
|
||||
},
|
||||
{
|
||||
'1': 'passwordless_notification',
|
||||
'3': 32,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.ApplicationData.PasswordlessNotification',
|
||||
'9': 0,
|
||||
'10': 'passwordlessNotification'
|
||||
},
|
||||
],
|
||||
'3': [
|
||||
ApplicationData_TextMessage$json,
|
||||
|
|
@ -671,7 +755,8 @@ const ApplicationData$json = {
|
|||
ApplicationData_AddAdditionalUser$json,
|
||||
ApplicationData_SetLoginToken$json,
|
||||
ApplicationData_Deprecated$json,
|
||||
ApplicationData_RegisterPasswordLessRecovery$json
|
||||
ApplicationData_RegisterPasswordLessRecovery$json,
|
||||
ApplicationData_PasswordlessNotification$json
|
||||
],
|
||||
'8': [
|
||||
{'1': 'ApplicationData'},
|
||||
|
|
@ -876,6 +961,21 @@ const ApplicationData_RegisterPasswordLessRecovery$json = {
|
|||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
const ApplicationData_PasswordlessNotification$json = {
|
||||
'1': 'PasswordlessNotification',
|
||||
'2': [
|
||||
{'1': 'notification_id', '3': 1, '4': 1, '5': 9, '10': 'notificationId'},
|
||||
{
|
||||
'1': 'encrypted_message',
|
||||
'3': 2,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'10': 'encryptedMessage'
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `ApplicationData`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List applicationDataDescriptor = $convert.base64Decode(
|
||||
'Cg9BcHBsaWNhdGlvbkRhdGESUQoLdGV4dE1lc3NhZ2UYASABKAsyLS5jbGllbnRfdG9fc2Vydm'
|
||||
|
|
@ -924,29 +1024,33 @@ final $typed_data.Uint8List applicationDataDescriptor = $convert.base64Decode(
|
|||
'ljYXRpb25EYXRhLlNldExvZ2luVG9rZW5IAFINc2V0TG9naW5Ub2tlbhKGAQoecmVnaXN0ZXJf'
|
||||
'cGFzc3dvcmRsZXNzX3JlY292ZXJ5GB8gASgLMj4uY2xpZW50X3RvX3NlcnZlci5BcHBsaWNhdG'
|
||||
'lvbkRhdGEuUmVnaXN0ZXJQYXNzd29yZExlc3NSZWNvdmVyeUgAUhxyZWdpc3RlclBhc3N3b3Jk'
|
||||
'bGVzc1JlY292ZXJ5GmoKC1RleHRNZXNzYWdlEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBISCg'
|
||||
'Rib2R5GAMgASgMUgRib2R5EiAKCXB1c2hfZGF0YRgEIAEoDEgAUghwdXNoRGF0YYgBAUIMCgpf'
|
||||
'cHVzaF9kYXRhGi8KEUdldFVzZXJCeVVzZXJuYW1lEhoKCHVzZXJuYW1lGAEgASgJUgh1c2Vybm'
|
||||
'FtZRosCg5DaGFuZ2VVc2VybmFtZRIaCgh1c2VybmFtZRgBIAEoCVIIdXNlcm5hbWUaNQoUVXBk'
|
||||
'YXRlR29vZ2xlRmNtVG9rZW4SHQoKZ29vZ2xlX2ZjbRgBIAEoCVIJZ29vZ2xlRmNtGiYKC0dldF'
|
||||
'VzZXJCeUlkEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBoTChFHZXRBdmFpbGFibGVQbGFucxoX'
|
||||
'ChVHZXRBZGRBY2NvdW50c0ludml0ZXMaFQoTR2V0Q3VycmVudFBsYW5JbmZvcxovChRSZW1vdm'
|
||||
'VBZGRpdGlvbmFsVXNlchIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQaLQoSR2V0UHJla2V5c0J5'
|
||||
'VXNlcklkEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBoyChdHZXRTaWduZWRQcmVLZXlCeVVzZX'
|
||||
'JJZBIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQamwEKElVwZGF0ZVNpZ25lZFByZUtleRIoChBz'
|
||||
'aWduZWRfcHJla2V5X2lkGAEgASgDUg5zaWduZWRQcmVrZXlJZBIjCg1zaWduZWRfcHJla2V5GA'
|
||||
'IgASgMUgxzaWduZWRQcmVrZXkSNgoXc2lnbmVkX3ByZWtleV9zaWduYXR1cmUYAyABKAxSFXNp'
|
||||
'Z25lZFByZWtleVNpZ25hdHVyZRo1CgxEb3dubG9hZERvbmUSJQoOZG93bmxvYWRfdG9rZW4YAS'
|
||||
'ABKAxSDWRvd25sb2FkVG9rZW4aTgoKUmVwb3J0VXNlchIoChByZXBvcnRlZF91c2VyX2lkGAEg'
|
||||
'ASgDUg5yZXBvcnRlZFVzZXJJZBIWCgZyZWFzb24YAiABKAlSBnJlYXNvbhpxCgtJUEFQdXJjaG'
|
||||
'FzZRIdCgpwcm9kdWN0X2lkGAEgASgJUglwcm9kdWN0SWQSFgoGc291cmNlGAIgASgJUgZzb3Vy'
|
||||
'Y2USKwoRdmVyaWZpY2F0aW9uX2RhdGEYAyABKAlSEHZlcmlmaWNhdGlvbkRhdGEaDwoNSVBBRm'
|
||||
'9yY2VDaGVjaxoPCg1EZWxldGVBY2NvdW50GiwKEUFkZEFkZGl0aW9uYWxVc2VyEhcKB3VzZXJf'
|
||||
'aWQYASABKANSBnVzZXJJZBowCg1TZXRMb2dpblRva2VuEh8KC2xvZ2luX3Rva2VuGAEgASgMUg'
|
||||
'psb2dpblRva2VuGgwKCkRlcHJlY2F0ZWQajgEKHFJlZ2lzdGVyUGFzc3dvcmRMZXNzUmVjb3Zl'
|
||||
'cnkSLgoSZW5jcnlwdGVkU2VydmVyS2V5GAEgASgMUhJlbmNyeXB0ZWRTZXJ2ZXJLZXkSKwoOcG'
|
||||
'luVW5sb2NrVG9rZW4YAiABKAxIAFIOcGluVW5sb2NrVG9rZW6IAQFCEQoPX3BpblVubG9ja1Rv'
|
||||
'a2VuQhEKD0FwcGxpY2F0aW9uRGF0YQ==');
|
||||
'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 = {
|
||||
|
|
|
|||
|
|
@ -1505,6 +1505,136 @@ class Response_ProofOfWork extends $pb.GeneratedMessage {
|
|||
void clearDifficulty() => $_clearField(2);
|
||||
}
|
||||
|
||||
class Response_PasswordlessNotificationMessage extends $pb.GeneratedMessage {
|
||||
factory Response_PasswordlessNotificationMessage({
|
||||
$fixnum.Int64? id,
|
||||
$core.List<$core.int>? encryptedMessage,
|
||||
}) {
|
||||
final result = create();
|
||||
if (id != null) result.id = id;
|
||||
if (encryptedMessage != null) result.encryptedMessage = encryptedMessage;
|
||||
return result;
|
||||
}
|
||||
|
||||
Response_PasswordlessNotificationMessage._();
|
||||
|
||||
factory Response_PasswordlessNotificationMessage.fromBuffer(
|
||||
$core.List<$core.int> data,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromBuffer(data, registry);
|
||||
factory Response_PasswordlessNotificationMessage.fromJson($core.String json,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromJson(json, registry);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
_omitMessageNames ? '' : 'Response.PasswordlessNotificationMessage',
|
||||
package:
|
||||
const $pb.PackageName(_omitMessageNames ? '' : 'server_to_client'),
|
||||
createEmptyInstance: create)
|
||||
..aInt64(1, _omitFieldNames ? '' : 'id')
|
||||
..a<$core.List<$core.int>>(
|
||||
2, _omitFieldNames ? '' : 'encryptedMessage', $pb.PbFieldType.OY)
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
Response_PasswordlessNotificationMessage clone() => deepCopy();
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
Response_PasswordlessNotificationMessage copyWith(
|
||||
void Function(Response_PasswordlessNotificationMessage) updates) =>
|
||||
super.copyWith((message) =>
|
||||
updates(message as Response_PasswordlessNotificationMessage))
|
||||
as Response_PasswordlessNotificationMessage;
|
||||
|
||||
@$core.override
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Response_PasswordlessNotificationMessage create() =>
|
||||
Response_PasswordlessNotificationMessage._();
|
||||
@$core.override
|
||||
Response_PasswordlessNotificationMessage createEmptyInstance() => create();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Response_PasswordlessNotificationMessage getDefault() =>
|
||||
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<
|
||||
Response_PasswordlessNotificationMessage>(create);
|
||||
static Response_PasswordlessNotificationMessage? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$fixnum.Int64 get id => $_getI64(0);
|
||||
@$pb.TagNumber(1)
|
||||
set id($fixnum.Int64 value) => $_setInt64(0, value);
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasId() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearId() => $_clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.List<$core.int> get encryptedMessage => $_getN(1);
|
||||
@$pb.TagNumber(2)
|
||||
set encryptedMessage($core.List<$core.int> value) => $_setBytes(1, value);
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasEncryptedMessage() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearEncryptedMessage() => $_clearField(2);
|
||||
}
|
||||
|
||||
class Response_PasswordlessNotificationMessages extends $pb.GeneratedMessage {
|
||||
factory Response_PasswordlessNotificationMessages({
|
||||
$core.Iterable<Response_PasswordlessNotificationMessage>? messages,
|
||||
}) {
|
||||
final result = create();
|
||||
if (messages != null) result.messages.addAll(messages);
|
||||
return result;
|
||||
}
|
||||
|
||||
Response_PasswordlessNotificationMessages._();
|
||||
|
||||
factory Response_PasswordlessNotificationMessages.fromBuffer(
|
||||
$core.List<$core.int> data,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromBuffer(data, registry);
|
||||
factory Response_PasswordlessNotificationMessages.fromJson($core.String json,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromJson(json, registry);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
_omitMessageNames ? '' : 'Response.PasswordlessNotificationMessages',
|
||||
package:
|
||||
const $pb.PackageName(_omitMessageNames ? '' : 'server_to_client'),
|
||||
createEmptyInstance: create)
|
||||
..pPM<Response_PasswordlessNotificationMessage>(
|
||||
1, _omitFieldNames ? '' : 'messages',
|
||||
subBuilder: Response_PasswordlessNotificationMessage.create)
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
Response_PasswordlessNotificationMessages clone() => deepCopy();
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
Response_PasswordlessNotificationMessages copyWith(
|
||||
void Function(Response_PasswordlessNotificationMessages) updates) =>
|
||||
super.copyWith((message) =>
|
||||
updates(message as Response_PasswordlessNotificationMessages))
|
||||
as Response_PasswordlessNotificationMessages;
|
||||
|
||||
@$core.override
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Response_PasswordlessNotificationMessages create() =>
|
||||
Response_PasswordlessNotificationMessages._();
|
||||
@$core.override
|
||||
Response_PasswordlessNotificationMessages createEmptyInstance() => create();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Response_PasswordlessNotificationMessages getDefault() =>
|
||||
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<
|
||||
Response_PasswordlessNotificationMessages>(create);
|
||||
static Response_PasswordlessNotificationMessages? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$pb.PbList<Response_PasswordlessNotificationMessage> get messages =>
|
||||
$_getList(0);
|
||||
}
|
||||
|
||||
enum Response_Ok_Ok {
|
||||
none,
|
||||
userid,
|
||||
|
|
@ -1522,6 +1652,7 @@ enum Response_Ok_Ok {
|
|||
signedprekey,
|
||||
proofOfWork,
|
||||
passwordlessRecoveryServerKey,
|
||||
passwordlessNotificationMessages,
|
||||
notSet
|
||||
}
|
||||
|
||||
|
|
@ -1543,6 +1674,7 @@ class Response_Ok extends $pb.GeneratedMessage {
|
|||
Response_SignedPreKey? signedprekey,
|
||||
Response_ProofOfWork? proofOfWork,
|
||||
$core.List<$core.int>? passwordlessRecoveryServerKey,
|
||||
Response_PasswordlessNotificationMessages? passwordlessNotificationMessages,
|
||||
}) {
|
||||
final result = create();
|
||||
if (none != null) result.none = none;
|
||||
|
|
@ -1563,6 +1695,9 @@ class Response_Ok extends $pb.GeneratedMessage {
|
|||
if (proofOfWork != null) result.proofOfWork = proofOfWork;
|
||||
if (passwordlessRecoveryServerKey != null)
|
||||
result.passwordlessRecoveryServerKey = passwordlessRecoveryServerKey;
|
||||
if (passwordlessNotificationMessages != null)
|
||||
result.passwordlessNotificationMessages =
|
||||
passwordlessNotificationMessages;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -1592,6 +1727,7 @@ class Response_Ok extends $pb.GeneratedMessage {
|
|||
14: Response_Ok_Ok.signedprekey,
|
||||
15: Response_Ok_Ok.proofOfWork,
|
||||
16: Response_Ok_Ok.passwordlessRecoveryServerKey,
|
||||
17: Response_Ok_Ok.passwordlessNotificationMessages,
|
||||
0: Response_Ok_Ok.notSet
|
||||
};
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
|
|
@ -1599,7 +1735,7 @@ class Response_Ok extends $pb.GeneratedMessage {
|
|||
package:
|
||||
const $pb.PackageName(_omitMessageNames ? '' : 'server_to_client'),
|
||||
createEmptyInstance: create)
|
||||
..oo(0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16])
|
||||
..oo(0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17])
|
||||
..aOB(1, _omitFieldNames ? '' : 'None', protoName: 'None')
|
||||
..aInt64(2, _omitFieldNames ? '' : 'userid')
|
||||
..a<$core.List<$core.int>>(
|
||||
|
|
@ -1633,6 +1769,9 @@ class Response_Ok extends $pb.GeneratedMessage {
|
|||
16,
|
||||
_omitFieldNames ? '' : 'passwordlessRecoveryServerKey',
|
||||
$pb.PbFieldType.OY)
|
||||
..aOM<Response_PasswordlessNotificationMessages>(
|
||||
17, _omitFieldNames ? '' : 'passwordlessNotificationMessages',
|
||||
subBuilder: Response_PasswordlessNotificationMessages.create)
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
|
|
@ -1670,6 +1809,7 @@ class Response_Ok extends $pb.GeneratedMessage {
|
|||
@$pb.TagNumber(14)
|
||||
@$pb.TagNumber(15)
|
||||
@$pb.TagNumber(16)
|
||||
@$pb.TagNumber(17)
|
||||
Response_Ok_Ok whichOk() => _Response_Ok_OkByTag[$_whichOneof(0)]!;
|
||||
@$pb.TagNumber(1)
|
||||
@$pb.TagNumber(2)
|
||||
|
|
@ -1687,6 +1827,7 @@ class Response_Ok extends $pb.GeneratedMessage {
|
|||
@$pb.TagNumber(14)
|
||||
@$pb.TagNumber(15)
|
||||
@$pb.TagNumber(16)
|
||||
@$pb.TagNumber(17)
|
||||
void clearOk() => $_clearField($_whichOneof(0));
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
|
|
@ -1856,6 +1997,21 @@ class Response_Ok extends $pb.GeneratedMessage {
|
|||
$core.bool hasPasswordlessRecoveryServerKey() => $_has(15);
|
||||
@$pb.TagNumber(16)
|
||||
void clearPasswordlessRecoveryServerKey() => $_clearField(16);
|
||||
|
||||
@$pb.TagNumber(17)
|
||||
Response_PasswordlessNotificationMessages
|
||||
get passwordlessNotificationMessages => $_getN(16);
|
||||
@$pb.TagNumber(17)
|
||||
set passwordlessNotificationMessages(
|
||||
Response_PasswordlessNotificationMessages value) =>
|
||||
$_setField(17, value);
|
||||
@$pb.TagNumber(17)
|
||||
$core.bool hasPasswordlessNotificationMessages() => $_has(16);
|
||||
@$pb.TagNumber(17)
|
||||
void clearPasswordlessNotificationMessages() => $_clearField(17);
|
||||
@$pb.TagNumber(17)
|
||||
Response_PasswordlessNotificationMessages
|
||||
ensurePasswordlessNotificationMessages() => $_ensure(16);
|
||||
}
|
||||
|
||||
enum Response_Response { ok, error, notSet }
|
||||
|
|
|
|||
|
|
@ -176,6 +176,8 @@ const Response$json = {
|
|||
Response_UploadToken$json,
|
||||
Response_DownloadTokens$json,
|
||||
Response_ProofOfWork$json,
|
||||
Response_PasswordlessNotificationMessage$json,
|
||||
Response_PasswordlessNotificationMessages$json,
|
||||
Response_Ok$json
|
||||
],
|
||||
'8': [
|
||||
|
|
@ -509,6 +511,36 @@ const Response_ProofOfWork$json = {
|
|||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response_PasswordlessNotificationMessage$json = {
|
||||
'1': 'PasswordlessNotificationMessage',
|
||||
'2': [
|
||||
{'1': 'id', '3': 1, '4': 1, '5': 3, '10': 'id'},
|
||||
{
|
||||
'1': 'encrypted_message',
|
||||
'3': 2,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'10': 'encryptedMessage'
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response_PasswordlessNotificationMessages$json = {
|
||||
'1': 'PasswordlessNotificationMessages',
|
||||
'2': [
|
||||
{
|
||||
'1': 'messages',
|
||||
'3': 1,
|
||||
'4': 3,
|
||||
'5': 11,
|
||||
'6': '.server_to_client.Response.PasswordlessNotificationMessage',
|
||||
'10': 'messages'
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response_Ok$json = {
|
||||
'1': 'Ok',
|
||||
|
|
@ -631,6 +663,15 @@ const Response_Ok$json = {
|
|||
'9': 0,
|
||||
'10': 'passwordlessRecoveryServerKey'
|
||||
},
|
||||
{
|
||||
'1': 'passwordless_notification_messages',
|
||||
'3': 17,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.server_to_client.Response.PasswordlessNotificationMessages',
|
||||
'9': 0,
|
||||
'10': 'passwordlessNotificationMessages'
|
||||
},
|
||||
],
|
||||
'8': [
|
||||
{'1': 'Ok'},
|
||||
|
|
@ -685,23 +726,29 @@ final $typed_data.Uint8List responseDescriptor = $convert.base64Decode(
|
|||
'cKD2Rvd25sb2FkX3Rva2VucxgCIAMoDFIOZG93bmxvYWRUb2tlbnMaOQoORG93bmxvYWRUb2tl'
|
||||
'bnMSJwoPZG93bmxvYWRfdG9rZW5zGAEgAygMUg5kb3dubG9hZFRva2VucxpFCgtQcm9vZk9mV2'
|
||||
'9yaxIWCgZwcmVmaXgYASABKAlSBnByZWZpeBIeCgpkaWZmaWN1bHR5GAIgASgDUgpkaWZmaWN1'
|
||||
'bHR5GqIICgJPaxIUCgROb25lGAEgASgISABSBE5vbmUSGAoGdXNlcmlkGAIgASgDSABSBnVzZX'
|
||||
'JpZBImCg1hdXRoY2hhbGxlbmdlGAMgASgMSABSDWF1dGhjaGFsbGVuZ2USSgoLdXBsb2FkdG9r'
|
||||
'ZW4YBCABKAsyJi5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLlVwbG9hZFRva2VuSABSC3VwbG'
|
||||
'9hZHRva2VuEkEKCHVzZXJkYXRhGAUgASgLMiMuc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5V'
|
||||
'c2VyRGF0YUgAUgh1c2VyZGF0YRIeCglhdXRodG9rZW4YBiABKAxIAFIJYXV0aHRva2VuEkoKDG'
|
||||
'RlcHJlY2F0ZWRfNxgHIAEoCzIlLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuRGVwcmVjYXRl'
|
||||
'ZEgAUgtkZXByZWNhdGVkNxJQCg1hdXRoZW50aWNhdGVkGAggASgLMiguc2VydmVyX3RvX2NsaW'
|
||||
'VudC5SZXNwb25zZS5BdXRoZW50aWNhdGVkSABSDWF1dGhlbnRpY2F0ZWQSOAoFcGxhbnMYCSAB'
|
||||
'KAsyIC5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLlBsYW5zSABSBXBsYW5zEk0KDHBsYW5iYW'
|
||||
'xsYW5jZRgKIAEoCzInLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuUGxhbkJhbGxhbmNlSABS'
|
||||
'DHBsYW5iYWxsYW5jZRJMCg1kZXByZWNhdGVkXzExGAsgASgLMiUuc2VydmVyX3RvX2NsaWVudC'
|
||||
'5SZXNwb25zZS5EZXByZWNhdGVkSABSDGRlcHJlY2F0ZWQxMRJfChJhZGRhY2NvdW50c2ludml0'
|
||||
'ZXMYDCABKAsyLS5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLkFkZEFjY291bnRzSW52aXRlc0'
|
||||
'gAUhJhZGRhY2NvdW50c2ludml0ZXMSUwoOZG93bmxvYWR0b2tlbnMYDSABKAsyKS5zZXJ2ZXJf'
|
||||
'dG9fY2xpZW50LlJlc3BvbnNlLkRvd25sb2FkVG9rZW5zSABSDmRvd25sb2FkdG9rZW5zEk0KDH'
|
||||
'NpZ25lZHByZWtleRgOIAEoCzInLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuU2lnbmVkUHJl'
|
||||
'S2V5SABSDHNpZ25lZHByZWtleRJKCgtwcm9vZk9mV29yaxgPIAEoCzImLnNlcnZlcl90b19jbG'
|
||||
'llbnQuUmVzcG9uc2UuUHJvb2ZPZldvcmtIAFILcHJvb2ZPZldvcmsSSQogcGFzc3dvcmRsZXNz'
|
||||
'X3JlY292ZXJ5X3NlcnZlcl9rZXkYECABKAxIAFIdcGFzc3dvcmRsZXNzUmVjb3ZlcnlTZXJ2ZX'
|
||||
'JLZXlCBAoCT2tCCgoIUmVzcG9uc2U=');
|
||||
'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==');
|
||||
|
|
|
|||
|
|
@ -24,11 +24,11 @@ export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions;
|
|||
/// -> The user who wants to recover his account does not need to remember her old username
|
||||
class RecoveryRequest extends $pb.GeneratedMessage {
|
||||
factory RecoveryRequest({
|
||||
$fixnum.Int64? tempId,
|
||||
$core.String? notificationId,
|
||||
$core.List<$core.int>? publicKey,
|
||||
}) {
|
||||
final result = create();
|
||||
if (tempId != null) result.tempId = tempId;
|
||||
if (notificationId != null) result.notificationId = notificationId;
|
||||
if (publicKey != null) result.publicKey = publicKey;
|
||||
return result;
|
||||
}
|
||||
|
|
@ -47,7 +47,7 @@ class RecoveryRequest extends $pb.GeneratedMessage {
|
|||
package: const $pb.PackageName(
|
||||
_omitMessageNames ? '' : 'passwordless_recovery'),
|
||||
createEmptyInstance: create)
|
||||
..aInt64(1, _omitFieldNames ? '' : 'tempId')
|
||||
..aOS(1, _omitFieldNames ? '' : 'notificationId')
|
||||
..a<$core.List<$core.int>>(
|
||||
2, _omitFieldNames ? '' : 'publicKey', $pb.PbFieldType.OY)
|
||||
..hasRequiredFields = false;
|
||||
|
|
@ -72,13 +72,13 @@ class RecoveryRequest extends $pb.GeneratedMessage {
|
|||
static RecoveryRequest? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$fixnum.Int64 get tempId => $_getI64(0);
|
||||
$core.String get notificationId => $_getSZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set tempId($fixnum.Int64 value) => $_setInt64(0, value);
|
||||
set notificationId($core.String value) => $_setString(0, value);
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasTempId() => $_has(0);
|
||||
$core.bool hasNotificationId() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearTempId() => $_clearField(1);
|
||||
void clearNotificationId() => $_clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.List<$core.int> get publicKey => $_getN(1);
|
||||
|
|
|
|||
|
|
@ -19,15 +19,15 @@ import 'dart:typed_data' as $typed_data;
|
|||
const RecoveryRequest$json = {
|
||||
'1': 'RecoveryRequest',
|
||||
'2': [
|
||||
{'1': 'temp_id', '3': 1, '4': 1, '5': 3, '10': 'tempId'},
|
||||
{'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(
|
||||
'Cg9SZWNvdmVyeVJlcXVlc3QSFwoHdGVtcF9pZBgBIAEoA1IGdGVtcElkEh0KCnB1YmxpY19rZX'
|
||||
'kYAiABKAxSCXB1YmxpY0tleQ==');
|
||||
'Cg9SZWNvdmVyeVJlcXVlc3QSJwoPbm90aWZpY2F0aW9uX2lkGAEgASgJUg5ub3RpZmljYXRpb2'
|
||||
'5JZBIdCgpwdWJsaWNfa2V5GAIgASgMUglwdWJsaWNLZXk=');
|
||||
|
||||
@$core.Deprecated('Use encryptedEnvelopeDescriptor instead')
|
||||
const EncryptedEnvelope$json = {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ package passwordless_recovery;
|
|||
// -> 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 {
|
||||
int64 temp_id = 1;
|
||||
string notification_id = 1;
|
||||
bytes public_key = 2;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ import 'package:twonly/src/visual/views/contact/add_new_contact.view.dart';
|
|||
import 'package:twonly/src/visual/views/contact/contact.view.dart';
|
||||
import 'package:twonly/src/visual/views/groups/group.view.dart';
|
||||
import 'package:twonly/src/visual/views/groups/group_create_select_members.view.dart';
|
||||
import 'package:twonly/src/visual/views/onboarding/recover.view.dart';
|
||||
import 'package:twonly/src/visual/views/onboarding/recover_password.view.dart';
|
||||
import 'package:twonly/src/visual/views/onboarding/recover_passwordless.view.dart';
|
||||
import 'package:twonly/src/visual/views/public_profile.view.dart';
|
||||
import 'package:twonly/src/visual/views/settings/account.view.dart';
|
||||
import 'package:twonly/src/visual/views/settings/appearance.view.dart';
|
||||
|
|
@ -58,6 +59,10 @@ final routerProvider = GoRouter(
|
|||
path: Routes.home,
|
||||
builder: (context, state) => const AppMainWidget(initialPage: 1),
|
||||
),
|
||||
GoRoute(
|
||||
path: Routes.recoverPasswordless,
|
||||
builder: (context, state) => const RecoverPasswordless(),
|
||||
),
|
||||
|
||||
// Chats
|
||||
GoRoute(
|
||||
|
|
|
|||
|
|
@ -776,6 +776,41 @@ class ApiService {
|
|||
return sendRequestSync(req);
|
||||
}
|
||||
|
||||
Future<Result> submitRecoveryShare({
|
||||
required String notificationId,
|
||||
required List<int> encryptedMessage,
|
||||
}) async {
|
||||
final req = createClientToServerFromApplicationData(
|
||||
ApplicationData(
|
||||
passwordlessNotification: ApplicationData_PasswordlessNotification(
|
||||
notificationId: notificationId,
|
||||
encryptedMessage: encryptedMessage,
|
||||
),
|
||||
),
|
||||
);
|
||||
return sendRequestSync(req);
|
||||
}
|
||||
|
||||
Future<Result> registerPasswordlessNotification({
|
||||
required String notificationId,
|
||||
required List<int> downloadAuthToken,
|
||||
required String langCode,
|
||||
required String? googleFcm,
|
||||
}) async {
|
||||
final registerNotif = Handshake_RegisterPasswordlessNotification()
|
||||
..notificationId = notificationId
|
||||
..downloadAuthToken = downloadAuthToken
|
||||
..langCode = langCode;
|
||||
if (googleFcm != null) {
|
||||
registerNotif.googleFcm = googleFcm;
|
||||
}
|
||||
|
||||
final handshake = Handshake()
|
||||
..registerPasswordlessNotification = registerNotif;
|
||||
final req = createClientToServerFromHandshake(handshake);
|
||||
return sendRequestSync(req, authenticated: false);
|
||||
}
|
||||
|
||||
Future<Result> addAdditionalUser(Int64 userId) async {
|
||||
final get = ApplicationData_AddAdditionalUser()..userId = userId;
|
||||
final appData = ApplicationData()..addAdditionalUser = get;
|
||||
|
|
@ -888,4 +923,30 @@ class ApiService {
|
|||
final req = createClientToServerFromApplicationData(appData);
|
||||
return sendRequestSync(req, contactId: target);
|
||||
}
|
||||
|
||||
/// Polls the server for new passwordless recovery notification messages.
|
||||
/// [alreadyReceivedIds] prevents the server from sending duplicates.
|
||||
Future<server.Response_PasswordlessNotificationMessages?> checkForPasswordlessNotification({
|
||||
required String notificationId,
|
||||
required List<int> downloadAuthToken,
|
||||
List<Int64>? alreadyReceivedIds,
|
||||
}) async {
|
||||
final check = Handshake_CheckForPasswordlessNotification()
|
||||
..notificationId = notificationId
|
||||
..downloadAuthToken = downloadAuthToken;
|
||||
if (alreadyReceivedIds != null && alreadyReceivedIds.isNotEmpty) {
|
||||
check.alreadyReceivedMessageIds.addAll(alreadyReceivedIds);
|
||||
}
|
||||
|
||||
final handshake = Handshake()..checkForPasswordlessNotification = check;
|
||||
final req = createClientToServerFromHandshake(handshake);
|
||||
final res = await sendRequestSync(req, authenticated: false);
|
||||
if (res.isSuccess) {
|
||||
final ok = res.value as server.Response_Ok;
|
||||
if (ok.hasPasswordlessNotificationMessages()) {
|
||||
return ok.passwordlessNotificationMessages;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ 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';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
|
@ -30,6 +32,11 @@ 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)) {
|
||||
await PasswordlessRecoveryService.handleRecoveryLink(uri.toString());
|
||||
return true;
|
||||
}
|
||||
|
||||
if (uri.toString().startsWith(QrCodeUtils.linkPrefix)) {
|
||||
final result = await QrCodeUtils.handleQrCodeLink(uri.toString());
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert' show utf8;
|
||||
import 'dart:convert' show base64Url, utf8;
|
||||
|
||||
import 'package:clock/clock.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
|
|
@ -11,20 +11,124 @@ import 'package:fixnum/fixnum.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/database/daos/contacts.dao.dart'
|
||||
show getContactDisplayName;
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/model/json/onboarding_state.model.dart';
|
||||
import 'package:twonly/src/model/json/userdata.model.dart'
|
||||
show PasswordLessRecovery;
|
||||
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'
|
||||
as pb;
|
||||
import 'package:twonly/src/model/protobuf/client/generated/passwordless_recovery.pb.dart';
|
||||
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';
|
||||
import 'package:twonly/src/visual/views/settings/backup/passwordless_recovery/help_a_friend.passwordless_recovery.view.dart';
|
||||
|
||||
enum SecondFactorType { none, pin, email }
|
||||
enum SecondFactorType { email, pin, none }
|
||||
|
||||
class PasswordlessRecoveryService {
|
||||
static String linkPrefix = 'https://me.twonly.eu/r/#';
|
||||
|
||||
static final Set<String> _handledNotificationIds = {};
|
||||
|
||||
static Future<void> handleRecoveryLink(String link) async {
|
||||
final hashIndex = link.indexOf('#');
|
||||
if (hashIndex == -1) return;
|
||||
|
||||
final fragment = link.substring(hashIndex + 1);
|
||||
final parts = fragment.split('/');
|
||||
if (parts.length < 2) return;
|
||||
|
||||
final notificationId = parts[0];
|
||||
final base64Key = parts[1];
|
||||
|
||||
if (_handledNotificationIds.contains(notificationId)) {
|
||||
Log.info(
|
||||
'Notification ID $notificationId was already handled, skipping.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
_handledNotificationIds.add(notificationId);
|
||||
|
||||
final encryptionKey = base64Url.decode(base64Url.normalize(base64Key));
|
||||
|
||||
final context = rootNavigatorKey.currentContext;
|
||||
if (context != null && context.mounted) {
|
||||
unawaited(
|
||||
context.navPush(
|
||||
HelpAFriendPasswordlessRecoveryView(
|
||||
notificationId: notificationId,
|
||||
encryptionKey: encryptionKey,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<bool> submitRecoveryShare(
|
||||
String notificationId,
|
||||
List<int> encryptionKey,
|
||||
Contact contact,
|
||||
) async {
|
||||
try {
|
||||
final share = contact.recoveryContactsSecretShare;
|
||||
if (share == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final trustedFriend = TrustedFriendShare_User(
|
||||
userId: Int64(userService.currentUser.userId),
|
||||
displayName: userService.currentUser.displayName,
|
||||
avatar: userService.currentUser.avatarSvg != null
|
||||
? utf8.encode(userService.currentUser.avatarSvg!)
|
||||
: null,
|
||||
);
|
||||
|
||||
final shareUser = TrustedFriendShare_User(
|
||||
userId: Int64(contact.userId),
|
||||
displayName: getContactDisplayName(contact),
|
||||
avatar: contact.avatarSvgCompressed != null
|
||||
? utf8.encode(getAvatarSvg(contact.avatarSvgCompressed!))
|
||||
: null,
|
||||
);
|
||||
|
||||
final trustedFriendShare = TrustedFriendShare(
|
||||
trustedFriend: trustedFriend,
|
||||
shareUser: shareUser,
|
||||
threshold: 0,
|
||||
sharedSecretData: share,
|
||||
);
|
||||
|
||||
final xchacha20 = Xchacha20.poly1305Aead();
|
||||
final secretBox = await xchacha20.encrypt(
|
||||
trustedFriendShare.writeToBuffer(),
|
||||
secretKey: SecretKey(encryptionKey),
|
||||
nonce: xchacha20.newNonce(),
|
||||
);
|
||||
|
||||
final envelope = EncryptedEnvelope(
|
||||
encryptedData: secretBox.cipherText,
|
||||
iv: secretBox.nonce,
|
||||
mac: secretBox.mac.bytes,
|
||||
);
|
||||
|
||||
final res = await apiService.submitRecoveryShare(
|
||||
notificationId: notificationId,
|
||||
encryptedMessage: envelope.writeToBuffer(),
|
||||
);
|
||||
return res.isSuccess;
|
||||
} catch (e) {
|
||||
Log.error('Failed to submit recovery share', error: e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<bool> enablePasswordlessRecovery({
|
||||
required List<int> trustedFriendIds,
|
||||
required SecondFactorType secondFactorType,
|
||||
|
|
@ -434,4 +538,85 @@ class PasswordlessRecoveryService {
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
static Future<bool> checkAndStorePasswordlessMessages(
|
||||
OnboardingState state,
|
||||
) async {
|
||||
if (!state.serverRegistered ||
|
||||
state.notificationId == null ||
|
||||
state.downloadAuthToken == null ||
|
||||
state.encryptionKey == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final alreadyReceivedIds = state.receivedShares
|
||||
.map((s) => Int64(s.messageId))
|
||||
.toList();
|
||||
|
||||
final response = await apiService.checkForPasswordlessNotification(
|
||||
notificationId: state.notificationId!,
|
||||
downloadAuthToken: state.downloadAuthToken!,
|
||||
alreadyReceivedIds: alreadyReceivedIds,
|
||||
);
|
||||
|
||||
if (response == null || response.messages.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final xchacha20 = Xchacha20.poly1305Aead();
|
||||
final secretKey = SecretKey(state.encryptionKey!);
|
||||
var didUpdate = false;
|
||||
|
||||
for (final msg in response.messages) {
|
||||
final msgId = msg.id.toInt();
|
||||
|
||||
try {
|
||||
final envelope = EncryptedEnvelope.fromBuffer(msg.encryptedMessage);
|
||||
final secretBox = SecretBox(
|
||||
envelope.encryptedData,
|
||||
nonce: envelope.iv,
|
||||
mac: Mac(envelope.mac),
|
||||
);
|
||||
final plaintext = await xchacha20.decrypt(
|
||||
secretBox,
|
||||
secretKey: secretKey,
|
||||
);
|
||||
|
||||
final share = TrustedFriendShare.fromBuffer(plaintext);
|
||||
|
||||
final receivedShare = ReceivedRecoveryShare(
|
||||
messageId: msgId,
|
||||
trustedFriendDisplayName: share.trustedFriend.displayName,
|
||||
myDisplayName: share.shareUser.displayName,
|
||||
myUserId: share.shareUser.userId.toInt(),
|
||||
myAvatarSvg: share.shareUser.hasAvatar()
|
||||
? share.shareUser.avatar
|
||||
: null,
|
||||
threshold: share.threshold,
|
||||
sharedSecretDataBytes: share.sharedSecretData,
|
||||
);
|
||||
|
||||
state.receivedShares.add(receivedShare);
|
||||
didUpdate = true;
|
||||
|
||||
Log.info(
|
||||
'Received recovery share from ${share.trustedFriend.displayName} '
|
||||
'for user ${share.shareUser.displayName}',
|
||||
);
|
||||
} catch (e) {
|
||||
Log.error(
|
||||
'Failed to decrypt/parse passwordless notification message $msgId: $e',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (didUpdate) {
|
||||
await KeyValueStore.update<OnboardingState>(
|
||||
key: KeyValueKeys.onboardingState,
|
||||
update: (s) => s.receivedShares = state.receivedShares,
|
||||
);
|
||||
}
|
||||
|
||||
return didUpdate;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,39 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:mutex/mutex.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/model/json/backup.model.dart';
|
||||
import 'package:twonly/src/model/json/onboarding_state.model.dart';
|
||||
import 'package:twonly/src/utils/exclusive_access.utils.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
|
||||
typedef ModelFactory = ({
|
||||
Object Function(Map<String, dynamic> json) fromJson,
|
||||
Map<String, dynamic> Function(Object value) toJson,
|
||||
Object Function() defaultValue,
|
||||
});
|
||||
|
||||
class KeyValueStore {
|
||||
static final Map<Type, ModelFactory> _registry = {
|
||||
OnboardingState: (
|
||||
fromJson: OnboardingState.fromJson,
|
||||
toJson: (val) => (val as OnboardingState).toJson(),
|
||||
defaultValue: OnboardingState.new,
|
||||
),
|
||||
CurrentBackupStatus: (
|
||||
fromJson: CurrentBackupStatus.fromJson,
|
||||
toJson: (val) => (val as CurrentBackupStatus).toJson(),
|
||||
defaultValue: CurrentBackupStatus.new,
|
||||
),
|
||||
BackupRecovery: (
|
||||
fromJson: BackupRecovery.fromJson,
|
||||
toJson: (val) => (val as BackupRecovery).toJson(),
|
||||
defaultValue: () => BackupRecovery(username: '', password: '', userId: 0),
|
||||
),
|
||||
};
|
||||
|
||||
static final Map<String, Mutex> _mutexes = {};
|
||||
|
||||
static Mutex _getMutex(String key) {
|
||||
|
|
@ -65,4 +92,77 @@ class KeyValueStore {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
static Future<T> update<T>({
|
||||
required String key,
|
||||
required FutureOr<void> Function(T value) update,
|
||||
}) async {
|
||||
final factory = _registry[T];
|
||||
if (factory == null) {
|
||||
throw ArgumentError('Type $T is not registered in KeyValueStore.');
|
||||
}
|
||||
return _exclusive(key, () async {
|
||||
T val;
|
||||
final file = await _getFilePath(key);
|
||||
try {
|
||||
if (file.existsSync()) {
|
||||
final contents = await file.readAsString();
|
||||
val = factory.fromJson(jsonDecode(contents) as Map<String, dynamic>) as T;
|
||||
} else {
|
||||
val = factory.defaultValue() as T;
|
||||
}
|
||||
} catch (e) {
|
||||
Log.warn('Error reading file. Resetting to default.: $e');
|
||||
val = factory.defaultValue() as T;
|
||||
}
|
||||
|
||||
await update(val);
|
||||
|
||||
try {
|
||||
await file.parent.create(recursive: true);
|
||||
await file.writeAsString(jsonEncode(factory.toJson(val as Object)));
|
||||
} catch (e) {
|
||||
Log.error('Error writing file: $e');
|
||||
}
|
||||
return val;
|
||||
});
|
||||
}
|
||||
|
||||
static Future<T> getModel<T>(String key) async {
|
||||
final factory = _registry[T];
|
||||
if (factory == null) {
|
||||
throw ArgumentError('Type $T is not registered in KeyValueStore.');
|
||||
}
|
||||
return _exclusive(key, () async {
|
||||
final file = await _getFilePath(key);
|
||||
try {
|
||||
if (file.existsSync()) {
|
||||
final contents = await file.readAsString();
|
||||
return factory.fromJson(jsonDecode(contents) as Map<String, dynamic>) as T;
|
||||
}
|
||||
} catch (e) {
|
||||
Log.warn('Error reading file. Returning default.: $e');
|
||||
}
|
||||
return factory.defaultValue() as T;
|
||||
});
|
||||
}
|
||||
|
||||
static Future<T?> getModelOrNull<T>(String key) async {
|
||||
final factory = _registry[T];
|
||||
if (factory == null) {
|
||||
throw ArgumentError('Type $T is not registered in KeyValueStore.');
|
||||
}
|
||||
return _exclusive(key, () async {
|
||||
final file = await _getFilePath(key);
|
||||
try {
|
||||
if (file.existsSync()) {
|
||||
final contents = await file.readAsString();
|
||||
return factory.fromJson(jsonDecode(contents) as Map<String, dynamic>) as T;
|
||||
}
|
||||
} catch (e) {
|
||||
Log.warn('Error reading file.: $e');
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,12 +16,14 @@ class AvatarIcon extends StatefulWidget {
|
|||
this.contactId,
|
||||
this.myAvatar = false,
|
||||
this.fontSize = 20,
|
||||
this.svg,
|
||||
this.color,
|
||||
});
|
||||
final Group? group;
|
||||
final int? contactId;
|
||||
final bool myAvatar;
|
||||
final double? fontSize;
|
||||
final String? svg;
|
||||
final Color? color;
|
||||
|
||||
@override
|
||||
|
|
@ -145,7 +147,12 @@ class _AvatarIconState extends State<AvatarIcon> {
|
|||
|
||||
Widget avatars = Container();
|
||||
|
||||
if (widget.myAvatar) {
|
||||
if (widget.svg != null) {
|
||||
avatars = SvgPicture.string(
|
||||
widget.svg!,
|
||||
errorBuilder: errorBuilder,
|
||||
);
|
||||
} else if (widget.myAvatar) {
|
||||
if (_myAvatarPath != null) {
|
||||
avatars = Image.file(
|
||||
File(_myAvatarPath!),
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ class MyInput extends StatefulWidget {
|
|||
this.errorText,
|
||||
this.obscureText = false,
|
||||
this.dense = false,
|
||||
this.readOnly = false,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
|
@ -33,6 +34,7 @@ class MyInput extends StatefulWidget {
|
|||
final String? errorText;
|
||||
final bool obscureText;
|
||||
final bool dense;
|
||||
final bool readOnly;
|
||||
|
||||
@override
|
||||
State<MyInput> createState() => _MyInputState();
|
||||
|
|
@ -64,6 +66,7 @@ class _MyInputState extends State<MyInput> {
|
|||
alwaysAnimate: true,
|
||||
child: TextField(
|
||||
controller: widget.controller,
|
||||
readOnly: widget.readOnly,
|
||||
onChanged: widget.onChanged,
|
||||
onSubmitted: widget.onSubmitted,
|
||||
onTapOutside: (event) {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ import 'package:twonly/globals.dart';
|
|||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/model/protobuf/client/generated/qr.pb.dart';
|
||||
import 'package:twonly/src/services/passwordless_recovery.service.dart'
|
||||
show PasswordlessRecoveryService;
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/utils/qr.utils.dart';
|
||||
|
|
@ -504,6 +506,11 @@ class MainCameraController {
|
|||
if (barcode.displayValue == null) continue;
|
||||
final link = barcode.displayValue!;
|
||||
|
||||
if (link.startsWith(PasswordlessRecoveryService.linkPrefix)) {
|
||||
await PasswordlessRecoveryService.handleRecoveryLink(link);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (link.startsWith(QrCodeUtils.linkPrefix)) {
|
||||
if (_handledProfileLinks.contains(link)) continue;
|
||||
_handledProfileLinks.add(link);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:restart_app/restart_app.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/views/onboarding/recover.view.dart';
|
||||
import 'package:twonly/src/visual/views/onboarding/recover_password.view.dart';
|
||||
import 'package:twonly/src/visual/views/settings/help/contact_us.view.dart';
|
||||
|
||||
class CriticalErrorView extends StatelessWidget {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
||||
class AnimatedBellIcon extends StatefulWidget {
|
||||
const AnimatedBellIcon({super.key, this.color});
|
||||
final Color? color;
|
||||
|
||||
@override
|
||||
State<AnimatedBellIcon> createState() => _AnimatedBellIconState();
|
||||
}
|
||||
|
||||
class _AnimatedBellIconState extends State<AnimatedBellIcon>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late final AnimationController _controller;
|
||||
late final Animation<double> _animation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
duration: const Duration(milliseconds: 1000),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_animation = Tween<double>(
|
||||
begin: -0.02,
|
||||
end: 0.02,
|
||||
).animate(_controller);
|
||||
|
||||
_controller.repeat(reverse: true);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RotationTransition(
|
||||
turns: _animation,
|
||||
alignment: Alignment.topCenter,
|
||||
child: Icon(
|
||||
Icons.notifications_active_rounded,
|
||||
size: 32,
|
||||
color: widget.color ?? context.color.primary,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:introduction_screen/introduction_screen.dart';
|
||||
import 'package:lottie/lottie.dart';
|
||||
import 'package:twonly/src/constants/routes.keys.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/elements/my_button.element.dart';
|
||||
|
||||
|
|
@ -56,6 +58,7 @@ class OnboardingView extends StatelessWidget {
|
|||
),
|
||||
PageViewModel(
|
||||
title: context.lang.onboardingNotProductTitle,
|
||||
useScrollView: false,
|
||||
bodyWidget: Column(
|
||||
children: [
|
||||
Text(
|
||||
|
|
@ -67,20 +70,41 @@ class OnboardingView extends StatelessWidget {
|
|||
padding: const EdgeInsets.only(
|
||||
left: 50,
|
||||
right: 50,
|
||||
top: 20,
|
||||
top: 10,
|
||||
),
|
||||
child: MyButton(
|
||||
variant: MyButtonVariant.primaryMiddle,
|
||||
onPressed: callbackOnSuccess,
|
||||
child: Text(context.lang.registerSubmitButton),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 50,
|
||||
right: 50,
|
||||
top: 10,
|
||||
),
|
||||
child: MyButton(
|
||||
onPressed: () {
|
||||
callbackOnSuccess();
|
||||
context.push(Routes.settingsBackupRecovery);
|
||||
},
|
||||
variant: MyButtonVariant.secondaryDense,
|
||||
child: Text(
|
||||
context.lang.twonlySafeRecoverBtn,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
image: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 60),
|
||||
child: Lottie.asset(
|
||||
'assets/animations/donation.lottie',
|
||||
padding: const EdgeInsets.only(top: 40),
|
||||
child: SizedBox(
|
||||
height: 200,
|
||||
child: Lottie.asset(
|
||||
'assets/animations/donation.lottie',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:restart_app/restart_app.dart';
|
||||
import 'package:twonly/src/constants/keyvalue.keys.dart';
|
||||
import 'package:twonly/src/constants/routes.keys.dart';
|
||||
import 'package:twonly/src/model/json/onboarding_state.model.dart';
|
||||
import 'package:twonly/src/services/backup.service.dart';
|
||||
import 'package:twonly/src/utils/keyvalue.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/components/snackbar.dart';
|
||||
import 'package:twonly/src/visual/elements/my_button.element.dart';
|
||||
|
|
@ -64,7 +69,6 @@ class _BackupRecoveryViewState extends State<BackupRecoveryView> {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isDark = isDarkMode(context);
|
||||
|
|
@ -175,6 +179,23 @@ class _BackupRecoveryViewState extends State<BackupRecoveryView> {
|
|||
)
|
||||
: Text(context.lang.twonlySafeRecoverBtn),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
MyButton(
|
||||
variant: MyButtonVariant.secondary,
|
||||
onPressed: () async {
|
||||
await KeyValueStore.update<OnboardingState>(
|
||||
key: KeyValueKeys.onboardingState,
|
||||
update: (state) =>
|
||||
state.hasStartedPasswordlessRecovery = true,
|
||||
);
|
||||
if (context.mounted) {
|
||||
await context.push(Routes.recoverPasswordless);
|
||||
}
|
||||
},
|
||||
child: Text(
|
||||
context.lang.passwordlessRecoveryRecoverBtn,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
const SizedBox(height: 40),
|
||||
],
|
||||
473
lib/src/visual/views/onboarding/recover_passwordless.view.dart
Normal file
473
lib/src/visual/views/onboarding/recover_passwordless.view.dart
Normal file
|
|
@ -0,0 +1,473 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:hashlib/random.dart';
|
||||
import 'package:qr_flutter/qr_flutter.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/constants/keyvalue.keys.dart';
|
||||
import 'package:twonly/src/model/json/onboarding_state.model.dart';
|
||||
import 'package:twonly/src/services/passwordless_recovery.service.dart';
|
||||
import 'package:twonly/src/utils/keyvalue.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
|
||||
import 'package:twonly/src/visual/components/snackbar.dart';
|
||||
import 'package:twonly/src/visual/elements/my_button.element.dart';
|
||||
import 'package:twonly/src/visual/elements/my_input.element.dart';
|
||||
import 'package:twonly/src/visual/views/onboarding/components/animated_bell_icon.comp.dart';
|
||||
|
||||
class RecoverPasswordless extends StatefulWidget {
|
||||
const RecoverPasswordless({super.key});
|
||||
|
||||
@override
|
||||
State<RecoverPasswordless> createState() => _RecoverPasswordlessState();
|
||||
}
|
||||
|
||||
class _RecoverPasswordlessState extends State<RecoverPasswordless> {
|
||||
bool _isLoading = true;
|
||||
bool _notificationsEnabled = false;
|
||||
String _shareUrl = '';
|
||||
|
||||
OnboardingState? _onboardingState;
|
||||
Timer? _pollTimer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initAsync();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_pollTimer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _initAsync() async {
|
||||
try {
|
||||
// 1. Load OnboardingState
|
||||
final state = await KeyValueStore.getModel<OnboardingState>(
|
||||
KeyValueKeys.onboardingState,
|
||||
);
|
||||
|
||||
// 2. Generate fields if they are missing
|
||||
if (state.notificationId == null) {
|
||||
state
|
||||
..notificationId = uuid.v4()
|
||||
..downloadAuthToken = getRandomUint8List(32)
|
||||
..encryptionKey = getRandomUint8List(32);
|
||||
await KeyValueStore.update<OnboardingState>(
|
||||
key: KeyValueKeys.onboardingState,
|
||||
update: (s) {
|
||||
s
|
||||
..notificationId = state.notificationId
|
||||
..downloadAuthToken = state.downloadAuthToken
|
||||
..encryptionKey = state.encryptionKey;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// 3. Construct URL
|
||||
final keyBytes = state.encryptionKey!;
|
||||
final base64Key = base64Url.encode(keyBytes).replaceAll('=', '');
|
||||
_shareUrl = 'https://me.twonly.eu/r/#${state.notificationId}/$base64Key';
|
||||
if (!kReleaseMode) Log.info(_shareUrl);
|
||||
|
||||
// 4. Check FCM Notification Settings
|
||||
final settings = await FirebaseMessaging.instance
|
||||
.getNotificationSettings();
|
||||
_notificationsEnabled =
|
||||
settings.authorizationStatus == AuthorizationStatus.authorized ||
|
||||
settings.authorizationStatus == AuthorizationStatus.provisional;
|
||||
|
||||
// 5. Register with the server if not registered yet
|
||||
if (!state.serverRegistered) {
|
||||
await _registerPasswordlessNotification(state);
|
||||
}
|
||||
|
||||
_onboardingState = state;
|
||||
|
||||
// 6. If already registered, do an immediate check and start the poll timer
|
||||
if (state.serverRegistered) _startPollTimer();
|
||||
} catch (e) {
|
||||
Log.warn('Error during passwordless recovery initialization: $e');
|
||||
} finally {
|
||||
if (mounted) setState(() => _isLoading = false);
|
||||
}
|
||||
}
|
||||
|
||||
void _startPollTimer() {
|
||||
if (_pollTimer != null) return;
|
||||
_pollTimer = Timer.periodic(const Duration(seconds: 10), (_) {
|
||||
_pollForMessages();
|
||||
});
|
||||
unawaited(_pollForMessages());
|
||||
}
|
||||
|
||||
Future<void> _pollForMessages() async {
|
||||
if (_onboardingState == null) return;
|
||||
final didUpdate =
|
||||
await PasswordlessRecoveryService.checkAndStorePasswordlessMessages(
|
||||
_onboardingState!,
|
||||
);
|
||||
// _onboardingState was changed in checkAndStorePasswordlessMessages
|
||||
if (didUpdate && mounted) setState(() => ());
|
||||
}
|
||||
|
||||
Future<void> _registerPasswordlessNotification(OnboardingState state) async {
|
||||
final fcmToken = await FirebaseMessaging.instance.getToken();
|
||||
if (!mounted) return;
|
||||
final res = await apiService.registerPasswordlessNotification(
|
||||
notificationId: state.notificationId!,
|
||||
downloadAuthToken: state.downloadAuthToken!,
|
||||
langCode: Localizations.localeOf(context).languageCode,
|
||||
googleFcm: fcmToken,
|
||||
);
|
||||
if (res.isSuccess) {
|
||||
state.serverRegistered = true;
|
||||
await KeyValueStore.update<OnboardingState>(
|
||||
key: KeyValueKeys.onboardingState,
|
||||
update: (s) => s.serverRegistered = true,
|
||||
);
|
||||
_startPollTimer();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _requestNotificationPermission() async {
|
||||
try {
|
||||
final settings = await FirebaseMessaging.instance.requestPermission();
|
||||
if (settings.authorizationStatus == AuthorizationStatus.authorized ||
|
||||
settings.authorizationStatus == AuthorizationStatus.provisional) {
|
||||
if (mounted) setState(() => _notificationsEnabled = true);
|
||||
final state = _onboardingState;
|
||||
if (state != null) {
|
||||
await _registerPasswordlessNotification(state);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
Log.error('Error requesting notification permission: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildProgressSection(
|
||||
BuildContext context,
|
||||
bool isDark,
|
||||
List<ReceivedRecoveryShare> shares,
|
||||
) {
|
||||
final first = shares.first;
|
||||
final threshold = first.threshold;
|
||||
final thresholdReached = shares.length >= threshold;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Center(
|
||||
child: Column(
|
||||
children: [
|
||||
AvatarIcon(
|
||||
svg: utf8.decode(first.myAvatarSvg ?? []),
|
||||
fontSize: 60,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
first.myDisplayName,
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
context.lang.recoverPasswordlessSharesReceived(
|
||||
shares.length,
|
||||
threshold,
|
||||
),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
const Spacer(),
|
||||
if (thresholdReached)
|
||||
const Icon(
|
||||
Icons.check_circle_rounded,
|
||||
color: Colors.green,
|
||||
size: 20,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: LinearProgressIndicator(
|
||||
value: shares.length / threshold,
|
||||
minHeight: 8,
|
||||
backgroundColor: isDark
|
||||
? Colors.white.withValues(alpha: 0.1)
|
||||
: Colors.black.withValues(alpha: 0.08),
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
thresholdReached ? Colors.green : context.color.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
...shares.map(
|
||||
(share) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Text(
|
||||
share.trustedFriendDisplayName,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
if (thresholdReached) ...[
|
||||
MyButton(
|
||||
onPressed: () {
|
||||
// TODO(recovery): Navigate to the actual recovery flow with the collected shares.
|
||||
Log.info('Recover now pressed with ${shares.length} shares');
|
||||
},
|
||||
child: Text(context.lang.recoverPasswordlessRecoverNowBtn),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isDark = isDarkMode(context);
|
||||
final shares = _onboardingState?.receivedShares ?? [];
|
||||
final hasShares = shares.isNotEmpty;
|
||||
final threshold = hasShares ? shares.first.threshold : 0;
|
||||
final thresholdReached = hasShares && shares.length >= threshold;
|
||||
|
||||
if (_isLoading) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(context.lang.passwordlessRecoveryRecoverBtn),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios_new_rounded),
|
||||
color: isDark ? Colors.white70 : Colors.black54,
|
||||
iconSize: 20,
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
),
|
||||
body: const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(context.lang.passwordlessRecoveryRecoverBtn),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios_new_rounded),
|
||||
color: isDark ? Colors.white70 : Colors.black54,
|
||||
iconSize: 20,
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
if (!hasShares) ...[
|
||||
Text(
|
||||
context.lang.recoverPasswordlessExplanation,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(fontSize: 16),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
],
|
||||
|
||||
if (hasShares) _buildProgressSection(context, isDark, shares),
|
||||
|
||||
if (!_notificationsEnabled && !hasShares) ...[
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: isDark
|
||||
? Colors.white.withValues(alpha: 0.05)
|
||||
: Colors.black.withValues(alpha: 0.04),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: isDark
|
||||
? Colors.white.withValues(alpha: 0.08)
|
||||
: Colors.black.withValues(alpha: 0.06),
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const AnimatedBellIcon(),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
context
|
||||
.lang
|
||||
.recoverPasswordlessNotificationCardTitle,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
context
|
||||
.lang
|
||||
.recoverPasswordlessNotificationCardSubtitle,
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
MyButton(
|
||||
variant: MyButtonVariant.secondaryDense,
|
||||
onPressed: _requestNotificationPermission,
|
||||
child: Text(
|
||||
context
|
||||
.lang
|
||||
.recoverPasswordlessNotificationCardBtn,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
],
|
||||
|
||||
if (!thresholdReached) ...[
|
||||
if (!hasShares)
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: isDark
|
||||
? Colors.white.withValues(alpha: 0.05)
|
||||
: Colors.black.withValues(alpha: 0.04),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: isDark
|
||||
? Colors.white.withValues(alpha: 0.08)
|
||||
: Colors.black.withValues(alpha: 0.06),
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.info_outline_rounded,
|
||||
color: isDark ? Colors.white70 : Colors.black54,
|
||||
size: 22,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
context.lang.recoverPasswordlessQrInstructions,
|
||||
style: const TextStyle(fontSize: 14, height: 1.4),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
if (!hasShares) const SizedBox(height: 20),
|
||||
|
||||
Center(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(
|
||||
alpha: isDark ? 0.3 : 0.08,
|
||||
),
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, 10),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: QrImageView.withQr(
|
||||
qr: QrCode.fromData(
|
||||
data: _shareUrl,
|
||||
errorCorrectLevel: QrErrorCorrectLevel.M,
|
||||
),
|
||||
eyeStyle: const QrEyeStyle(
|
||||
color: Colors.black,
|
||||
borderRadius: 4,
|
||||
),
|
||||
dataModuleStyle: const QrDataModuleStyle(
|
||||
color: Colors.black,
|
||||
borderRadius: 4,
|
||||
),
|
||||
gapless: false,
|
||||
size: 200,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
|
||||
MyInput(
|
||||
controller: TextEditingController(text: _shareUrl),
|
||||
readOnly: true,
|
||||
hintText: '',
|
||||
prefixIcon: const Icon(Icons.link_rounded),
|
||||
suffixIcon: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.copy_rounded),
|
||||
tooltip: context.lang.recoverPasswordlessCopyBtn,
|
||||
onPressed: () {
|
||||
Clipboard.setData(
|
||||
ClipboardData(text: _shareUrl),
|
||||
);
|
||||
showSnackbar(
|
||||
context,
|
||||
context.lang.recoverPasswordlessCopiedSnackbar,
|
||||
level: SnackbarLevel.success,
|
||||
);
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.share_rounded),
|
||||
tooltip: context.lang.recoverPasswordlessShareBtn,
|
||||
onPressed: () {
|
||||
final params = ShareParams(
|
||||
text: _shareUrl,
|
||||
);
|
||||
SharePlus.instance.share(params);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,324 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/database/daos/contacts.dao.dart'
|
||||
show getContactDisplayName;
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/services/passwordless_recovery.service.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
|
||||
import 'package:twonly/src/visual/components/snackbar.dart';
|
||||
import 'package:twonly/src/visual/components/verification_badge.comp.dart';
|
||||
import 'package:twonly/src/visual/elements/my_button.element.dart';
|
||||
import 'package:twonly/src/visual/elements/my_input.element.dart';
|
||||
|
||||
class HelpAFriendPasswordlessRecoveryView extends StatefulWidget {
|
||||
const HelpAFriendPasswordlessRecoveryView({
|
||||
required this.notificationId,
|
||||
required this.encryptionKey,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final String notificationId;
|
||||
final List<int> encryptionKey;
|
||||
|
||||
@override
|
||||
State<HelpAFriendPasswordlessRecoveryView> createState() =>
|
||||
_HelpAFriendPasswordlessRecoveryViewState();
|
||||
}
|
||||
|
||||
class _HelpAFriendPasswordlessRecoveryViewState
|
||||
extends State<HelpAFriendPasswordlessRecoveryView> {
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
List<Contact> _contacts = [];
|
||||
bool _isLoading = false;
|
||||
late StreamSubscription<List<Contact>> _contactSub;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_contactSub = twonlyDB.contactsDao.watchAllAcceptedContacts().listen((
|
||||
update,
|
||||
) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_contacts = update;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_searchController.dispose();
|
||||
_contactSub.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _onContactSelected(Contact contact) async {
|
||||
if (contact.recoveryContactsSecretShare == null) {
|
||||
showSnackbar(
|
||||
context,
|
||||
context.lang.passwordlessRecoveryNoShareStored,
|
||||
level: SnackbarLevel.warning,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return ConfirmRecoveryDialog(
|
||||
contact: contact,
|
||||
onConfirm: () => _submitShare(contact),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _submitShare(Contact contact) async {
|
||||
setState(() => _isLoading = true);
|
||||
final res = await PasswordlessRecoveryService.submitRecoveryShare(
|
||||
widget.notificationId,
|
||||
widget.encryptionKey,
|
||||
contact,
|
||||
);
|
||||
if (!mounted) return;
|
||||
setState(() => _isLoading = false);
|
||||
|
||||
if (res) {
|
||||
showSnackbar(
|
||||
context,
|
||||
context.lang.passwordlessRecoveryShareSent,
|
||||
level: SnackbarLevel.success,
|
||||
);
|
||||
Navigator.pop(context);
|
||||
} else {
|
||||
showSnackbar(
|
||||
context,
|
||||
context.lang.passwordlessRecoveryNetworkError,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isDark = isDarkMode(context);
|
||||
final query = _searchController.text.trim().toLowerCase();
|
||||
|
||||
final filteredContacts =
|
||||
_contacts.where((c) {
|
||||
final name = getContactDisplayName(c).toLowerCase();
|
||||
final username = c.username.toLowerCase();
|
||||
return name.contains(query) || username.contains(query);
|
||||
}).toList()..sort((a, b) {
|
||||
final aHasShare = a.recoveryContactsSecretShare != null;
|
||||
final bHasShare = b.recoveryContactsSecretShare != null;
|
||||
if (aHasShare && !bHasShare) return -1;
|
||||
if (!aHasShare && bHasShare) return 1;
|
||||
return getContactDisplayName(a).compareTo(getContactDisplayName(b));
|
||||
});
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(context.lang.passwordlessRecoveryHelpAFriend)),
|
||||
body: SafeArea(
|
||||
child: _isLoading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: isDark
|
||||
? Colors.white.withValues(alpha: 0.05)
|
||||
: Colors.black.withValues(alpha: 0.04),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: isDark
|
||||
? Colors.white.withValues(alpha: 0.08)
|
||||
: Colors.black.withValues(alpha: 0.06),
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.info_outline_rounded,
|
||||
size: 22,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
context.lang.passwordlessRecoverySelectContactDesc,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
height: 1.4,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
MyInput(
|
||||
controller: _searchController,
|
||||
hintText: context.lang.passwordlessRecoverySearchContacts,
|
||||
prefixIcon: const Icon(Icons.search),
|
||||
onChanged: (val) => setState(() {}),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Expanded(
|
||||
child: filteredContacts.isEmpty
|
||||
? Center(
|
||||
child: Text(
|
||||
context.lang.passwordlessRecoveryNoContactsFound,
|
||||
),
|
||||
)
|
||||
: ListView.builder(
|
||||
itemCount: filteredContacts.length,
|
||||
itemBuilder: (context, index) {
|
||||
final contact = filteredContacts[index];
|
||||
final hasShare =
|
||||
contact.recoveryContactsSecretShare != null;
|
||||
return Opacity(
|
||||
opacity: hasShare ? 1.0 : 0.5,
|
||||
child: ListTile(
|
||||
leading: AvatarIcon(
|
||||
contactId: contact.userId,
|
||||
),
|
||||
title: Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
getContactDisplayName(contact),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
VerificationBadgeComp(
|
||||
contact: contact,
|
||||
size: 14,
|
||||
clickable: false,
|
||||
),
|
||||
],
|
||||
),
|
||||
subtitle: hasShare
|
||||
? null
|
||||
: Text(
|
||||
context.lang.passwordlessRecoveryCantHelpHim,
|
||||
style: const TextStyle(
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
onTap: () => _onContactSelected(contact),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ConfirmRecoveryDialog extends StatefulWidget {
|
||||
const ConfirmRecoveryDialog({
|
||||
required this.contact,
|
||||
required this.onConfirm,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final Contact contact;
|
||||
final VoidCallback onConfirm;
|
||||
|
||||
@override
|
||||
State<ConfirmRecoveryDialog> createState() => _ConfirmRecoveryDialogState();
|
||||
}
|
||||
|
||||
class _ConfirmRecoveryDialogState extends State<ConfirmRecoveryDialog> {
|
||||
int _secondsRemaining = 10;
|
||||
Timer? _timer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
if (_secondsRemaining > 0) {
|
||||
_secondsRemaining--;
|
||||
} else {
|
||||
_timer?.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_timer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final username = widget.contact.username;
|
||||
final isEnabled = _secondsRemaining == 0;
|
||||
|
||||
return AlertDialog(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||
title: Text(
|
||||
context.lang.passwordlessRecoveryDoesAskedYou(username),
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
context.lang.passwordlessRecoveryVerifySourceDesc,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
MyButton(
|
||||
variant: MyButtonVariant.text,
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(context.lang.passwordlessRecoveryNo),
|
||||
),
|
||||
MyButton(
|
||||
variant: MyButtonVariant.primaryMiddle,
|
||||
onPressed: isEnabled
|
||||
? () {
|
||||
Navigator.pop(context);
|
||||
widget.onConfirm();
|
||||
}
|
||||
: null,
|
||||
child: Text(
|
||||
isEnabled
|
||||
? context.lang.passwordlessRecoveryYes
|
||||
: context.lang.passwordlessRecoveryYesWithTimer(
|
||||
_secondsRemaining.toString(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -26,7 +26,7 @@ class PasswordLessRecoverySetup extends StatefulWidget {
|
|||
|
||||
class _PasswordLessRecoverySetupState extends State<PasswordLessRecoverySetup> {
|
||||
List<Contact> _selectedContacts = [];
|
||||
SecondFactorType _secondFactor = SecondFactorType.pin;
|
||||
SecondFactorType _secondFactor = SecondFactorType.email;
|
||||
final _pinController = TextEditingController();
|
||||
final _emailController = TextEditingController();
|
||||
bool _isLoading = false;
|
||||
|
|
|
|||
Loading…
Reference in a new issue