From e520612f82087abf0332283a1736ff583a270294 Mon Sep 17 00:00:00 2001 From: Damian Molinski Date: Fri, 11 Oct 2024 11:55:47 +0200 Subject: [PATCH 1/4] feat: unlock password instructions --- .../unlock_password_instructions_panel.dart | 5 +-- .../recover/recover_seed_phrase_panel.dart | 6 ++-- .../unlock_password_instructions_panel.dart | 34 +++++++++++++++++++ .../seed_phrase/unlock_password_panel.dart | 10 ++++++ .../catalyst_voices_localizations.dart | 12 +++++++ .../catalyst_voices_localizations_en.dart | 6 ++++ .../catalyst_voices_localizations_es.dart | 6 ++++ .../lib/l10n/intl_en.arb | 4 ++- 8 files changed, 78 insertions(+), 5 deletions(-) create mode 100644 catalyst_voices/lib/pages/registration/recover/seed_phrase/unlock_password_instructions_panel.dart create mode 100644 catalyst_voices/lib/pages/registration/recover/seed_phrase/unlock_password_panel.dart diff --git a/catalyst_voices/lib/pages/registration/create_keychain/stage/unlock_password_instructions_panel.dart b/catalyst_voices/lib/pages/registration/create_keychain/stage/unlock_password_instructions_panel.dart index 2be1748cb65..9c0e2f39203 100644 --- a/catalyst_voices/lib/pages/registration/create_keychain/stage/unlock_password_instructions_panel.dart +++ b/catalyst_voices/lib/pages/registration/create_keychain/stage/unlock_password_instructions_panel.dart @@ -20,8 +20,9 @@ class UnlockPasswordInstructionsPanel extends StatelessWidget { child: SingleChildScrollView( child: RegistrationStageMessage( title: Text(l10n.createKeychainUnlockPasswordInstructionsTitle), - subtitle: - Text(l10n.createKeychainUnlockPasswordInstructionsSubtitle), + subtitle: Text( + l10n.createKeychainUnlockPasswordInstructionsSubtitle, + ), ), ), ), diff --git a/catalyst_voices/lib/pages/registration/recover/recover_seed_phrase_panel.dart b/catalyst_voices/lib/pages/registration/recover/recover_seed_phrase_panel.dart index 58cec36a83d..406e21b8877 100644 --- a/catalyst_voices/lib/pages/registration/recover/recover_seed_phrase_panel.dart +++ b/catalyst_voices/lib/pages/registration/recover/recover_seed_phrase_panel.dart @@ -1,6 +1,8 @@ import 'package:catalyst_voices/pages/registration/recover/seed_phrase/account_details_panel.dart'; import 'package:catalyst_voices/pages/registration/recover/seed_phrase/seed_phrase_input_panel.dart'; import 'package:catalyst_voices/pages/registration/recover/seed_phrase/seed_phrase_instructions_panel.dart'; +import 'package:catalyst_voices/pages/registration/recover/seed_phrase/unlock_password_instructions_panel.dart'; +import 'package:catalyst_voices/pages/registration/recover/seed_phrase/unlock_password_panel.dart'; import 'package:catalyst_voices/pages/registration/widgets/placeholder_panel.dart'; import 'package:catalyst_voices_models/catalyst_voices_models.dart'; import 'package:flutter/material.dart'; @@ -21,8 +23,8 @@ class RecoverSeedPhrasePanel extends StatelessWidget { RecoverSeedPhraseStage.seedPhrase => const SeedPhraseInputPanel(), RecoverSeedPhraseStage.accountDetails => const AccountDetailsPanel(), RecoverSeedPhraseStage.unlockPasswordInstructions => - const PlaceholderPanel(), - RecoverSeedPhraseStage.unlockPassword => const PlaceholderPanel(), + const UnlockPasswordInstructionsPanel(), + RecoverSeedPhraseStage.unlockPassword => const UnlockPasswordPanel(), RecoverSeedPhraseStage.success => const PlaceholderPanel(), }; } diff --git a/catalyst_voices/lib/pages/registration/recover/seed_phrase/unlock_password_instructions_panel.dart b/catalyst_voices/lib/pages/registration/recover/seed_phrase/unlock_password_instructions_panel.dart new file mode 100644 index 00000000000..3c06ec888ea --- /dev/null +++ b/catalyst_voices/lib/pages/registration/recover/seed_phrase/unlock_password_instructions_panel.dart @@ -0,0 +1,34 @@ +import 'package:catalyst_voices/pages/registration/widgets/registration_stage_message.dart'; +import 'package:catalyst_voices/pages/registration/widgets/registration_stage_navigation.dart'; +import 'package:catalyst_voices_localization/catalyst_voices_localization.dart'; +import 'package:flutter/material.dart'; + +class UnlockPasswordInstructionsPanel extends StatelessWidget { + const UnlockPasswordInstructionsPanel({ + super.key, + }); + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox(height: 24), + Expanded( + child: SingleChildScrollView( + child: RegistrationStageMessage( + title: Text(l10n.recoveryUnlockPasswordInstructionsTitle), + subtitle: Text( + l10n.recoveryUnlockPasswordInstructionsSubtitle, + ), + ), + ), + ), + const SizedBox(height: 10), + const RegistrationBackNextNavigation(), + ], + ); + } +} diff --git a/catalyst_voices/lib/pages/registration/recover/seed_phrase/unlock_password_panel.dart b/catalyst_voices/lib/pages/registration/recover/seed_phrase/unlock_password_panel.dart new file mode 100644 index 00000000000..4f48b235ca8 --- /dev/null +++ b/catalyst_voices/lib/pages/registration/recover/seed_phrase/unlock_password_panel.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; + +class UnlockPasswordPanel extends StatelessWidget { + const UnlockPasswordPanel({super.key}); + + @override + Widget build(BuildContext context) { + return const Placeholder(); + } +} diff --git a/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations.dart b/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations.dart index 7728be8a563..8e690344176 100644 --- a/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations.dart +++ b/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations.dart @@ -1641,6 +1641,18 @@ abstract class VoicesLocalizations { /// In en, this message translates to: /// **'Set unlock password for this device'** String get recoveryAccountDetailsAction; + + /// No description provided for @recoveryUnlockPasswordInstructionsTitle. + /// + /// In en, this message translates to: + /// **'Set your Catalyst unlock password f
or this device'** + String get recoveryUnlockPasswordInstructionsTitle; + + /// No description provided for @recoveryUnlockPasswordInstructionsSubtitle. + /// + /// In en, this message translates to: + /// **'With over 300 trillion possible combinations, your 12 word seed phrase is great for keeping your account safe. 

But it can be a bit tedious to enter every single time you want to use the app. 

In this next step, you\'ll set your Unlock Password for your current device. It\'s like a shortcut for proving ownership of your Keychain. 

Whenever you recover your account for the first time on a new device, you\'ll need to use your Catalyst Keychain to get started. Every time after that, you can use your Unlock Password to quickly regain access.'** + String get recoveryUnlockPasswordInstructionsSubtitle; } class _VoicesLocalizationsDelegate extends LocalizationsDelegate { diff --git a/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_en.dart b/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_en.dart index 34fdb490486..2592efd13ad 100644 --- a/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_en.dart +++ b/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_en.dart @@ -861,4 +861,10 @@ class VoicesLocalizationsEn extends VoicesLocalizations { @override String get recoveryAccountDetailsAction => 'Set unlock password for this device'; + + @override + String get recoveryUnlockPasswordInstructionsTitle => 'Set your Catalyst unlock password f
or this device'; + + @override + String get recoveryUnlockPasswordInstructionsSubtitle => 'With over 300 trillion possible combinations, your 12 word seed phrase is great for keeping your account safe. 

But it can be a bit tedious to enter every single time you want to use the app. 

In this next step, you\'ll set your Unlock Password for your current device. It\'s like a shortcut for proving ownership of your Keychain. 

Whenever you recover your account for the first time on a new device, you\'ll need to use your Catalyst Keychain to get started. Every time after that, you can use your Unlock Password to quickly regain access.'; } diff --git a/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_es.dart b/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_es.dart index 28abab7e5f6..f0255cf1ff8 100644 --- a/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_es.dart +++ b/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_es.dart @@ -861,4 +861,10 @@ class VoicesLocalizationsEs extends VoicesLocalizations { @override String get recoveryAccountDetailsAction => 'Set unlock password for this device'; + + @override + String get recoveryUnlockPasswordInstructionsTitle => 'Set your Catalyst unlock password f
or this device'; + + @override + String get recoveryUnlockPasswordInstructionsSubtitle => 'With over 300 trillion possible combinations, your 12 word seed phrase is great for keeping your account safe. 

But it can be a bit tedious to enter every single time you want to use the app. 

In this next step, you\'ll set your Unlock Password for your current device. It\'s like a shortcut for proving ownership of your Keychain. 

Whenever you recover your account for the first time on a new device, you\'ll need to use your Catalyst Keychain to get started. Every time after that, you can use your Unlock Password to quickly regain access.'; } diff --git a/catalyst_voices/packages/catalyst_voices_localization/lib/l10n/intl_en.arb b/catalyst_voices/packages/catalyst_voices_localization/lib/l10n/intl_en.arb index c9a89779204..47a365b5995 100644 --- a/catalyst_voices/packages/catalyst_voices_localization/lib/l10n/intl_en.arb +++ b/catalyst_voices/packages/catalyst_voices_localization/lib/l10n/intl_en.arb @@ -875,5 +875,7 @@ "recoverySeedPhraseInputSubtitle": "Enter each word of your Catalyst Key in the right order \u2028to bring your Catalyst account to this device.", "recoveryAccountTitle": "Catalyst account recovery", "recoveryAccountSuccessTitle": "Keychain recovered successfully!", - "recoveryAccountDetailsAction": "Set unlock password for this device" + "recoveryAccountDetailsAction": "Set unlock password for this device", + "recoveryUnlockPasswordInstructionsTitle": "Set your Catalyst unlock password f\u2028or this device", + "recoveryUnlockPasswordInstructionsSubtitle": "With over 300 trillion possible combinations, your 12 word seed phrase is great for keeping your account safe. \u2028\u2028But it can be a bit tedious to enter every single time you want to use the app. \u2028\u2028In this next step, you'll set your Unlock Password for your current device. It's like a shortcut for proving ownership of your Keychain. \u2028\u2028Whenever you recover your account for the first time on a new device, you'll need to use your Catalyst Keychain to get started. Every time after that, you can use your Unlock Password to quickly regain access." } \ No newline at end of file From 93318467b5dd2b1844aa8973091bb742cb1485c1 Mon Sep 17 00:00:00 2001 From: Damian Molinski Date: Fri, 11 Oct 2024 14:58:25 +0200 Subject: [PATCH 2/4] feat: remover unlock password --- .../stage/unlock_password_panel.dart | 122 ++++----------- .../seed_phrase/unlock_password_panel.dart | 147 +++++++++++++++++- .../widgets/unlock_password_form.dart | 109 +++++++++++++ .../cubits/keychain_creation_cubit.dart | 53 +------ .../registration/cubits/recover_cubit.dart | 9 +- .../cubits/unlock_password_manager.dart | 64 ++++++++ .../state_data/recover_state_data.dart | 7 + .../state_data/unlock_password_state.dart | 13 +- .../src/authentication/authentication.dart | 1 + .../src/authentication/unlock_password.dart | 23 +++ 10 files changed, 401 insertions(+), 147 deletions(-) create mode 100644 catalyst_voices/lib/pages/registration/widgets/unlock_password_form.dart create mode 100644 catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/cubits/unlock_password_manager.dart create mode 100644 catalyst_voices/packages/catalyst_voices_view_models/lib/src/authentication/unlock_password.dart diff --git a/catalyst_voices/lib/pages/registration/create_keychain/stage/unlock_password_panel.dart b/catalyst_voices/lib/pages/registration/create_keychain/stage/unlock_password_panel.dart index 77ef11775e8..c6f96172d9b 100644 --- a/catalyst_voices/lib/pages/registration/create_keychain/stage/unlock_password_panel.dart +++ b/catalyst_voices/lib/pages/registration/create_keychain/stage/unlock_password_panel.dart @@ -1,10 +1,7 @@ import 'package:catalyst_voices/pages/registration/create_keychain/bloc_unlock_password_builder.dart'; import 'package:catalyst_voices/pages/registration/widgets/registration_stage_navigation.dart'; -import 'package:catalyst_voices/widgets/indicators/voices_password_strength_indicator.dart'; -import 'package:catalyst_voices/widgets/text_field/voices_password_text_field.dart'; -import 'package:catalyst_voices/widgets/text_field/voices_text_field.dart'; +import 'package:catalyst_voices/pages/registration/widgets/unlock_password_form.dart'; import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart'; -import 'package:catalyst_voices_localization/catalyst_voices_localization.dart'; import 'package:catalyst_voices_models/catalyst_voices_models.dart'; import 'package:flutter/material.dart'; @@ -30,8 +27,8 @@ class _UnlockPasswordPanelState extends State { .keychainStateData .unlockPasswordState; - final password = unlockPasswordState.password; - final confirmPassword = unlockPasswordState.confirmPassword; + final password = unlockPasswordState.password.value; + final confirmPassword = unlockPasswordState.confirmPassword.value; _passwordController = TextEditingController(text: password) ..addListener(_onPasswordChanged); @@ -53,16 +50,12 @@ class _UnlockPasswordPanelState extends State { children: [ const SizedBox(height: 24), const SizedBox(height: 12), - _EnterPasswordTextField( - controller: _passwordController, - ), - const SizedBox(height: 12), - _BlocConfirmPasswordTextField( - controller: _confirmPasswordController, + Expanded( + child: _BlocUnlockPasswordForm( + passwordController: _passwordController, + confirmPasswordController: _confirmPasswordController, + ), ), - const Spacer(), - const SizedBox(height: 22), - const _BlocPasswordStrength(), const SizedBox(height: 22), _BlocNavigation( onNextTap: _createKeychain, @@ -105,90 +98,37 @@ class _UnlockPasswordPanelState extends State { } } -class _EnterPasswordTextField extends StatelessWidget { - final TextEditingController controller; +class _BlocUnlockPasswordForm extends StatelessWidget { + final TextEditingController passwordController; + final TextEditingController confirmPasswordController; - const _EnterPasswordTextField({ - required this.controller, + const _BlocUnlockPasswordForm({ + required this.passwordController, + required this.confirmPasswordController, }); @override Widget build(BuildContext context) { - return VoicesPasswordTextField( - controller: controller, - textInputAction: TextInputAction.next, - decoration: VoicesTextFieldDecoration( - labelText: context.l10n.enterPassword, - ), - ); - } -} - -class _BlocConfirmPasswordTextField extends StatelessWidget { - final TextEditingController controller; - - const _BlocConfirmPasswordTextField({ - required this.controller, - }); - - @override - Widget build(BuildContext context) { - return BlocUnlockPasswordBuilder<({bool showError, int minimumLength})>( - selector: (state) => ( - showError: state.showPasswordMisMatch, - minimumLength: state.minPasswordLength, - ), - builder: (context, state) { - return _ConfirmPasswordTextField( - controller: controller, - showError: state.showError, - minimumLength: state.minimumLength, + return BlocUnlockPasswordBuilder< + ({ + bool showError, + PasswordStrength passwordStrength, + bool showPasswordStrength, + })>( + selector: (state) { + return ( + showError: state.showPasswordMisMatch, + passwordStrength: state.passwordStrength, + showPasswordStrength: state.showPasswordStrength, ); }, - ); - } -} - -class _ConfirmPasswordTextField extends StatelessWidget { - final TextEditingController controller; - final bool showError; - final int minimumLength; - - const _ConfirmPasswordTextField({ - required this.controller, - this.showError = false, - required this.minimumLength, - }); - - @override - Widget build(BuildContext context) { - return VoicesPasswordTextField( - controller: controller, - decoration: VoicesTextFieldDecoration( - labelText: context.l10n.confirmPassword, - helperText: context.l10n.xCharactersMinimum(minimumLength), - errorText: showError ? context.l10n.passwordDoNotMatch : null, - ), - ); - } -} - -class _BlocPasswordStrength extends StatelessWidget { - const _BlocPasswordStrength(); - - @override - Widget build(BuildContext context) { - return BlocUnlockPasswordBuilder<({bool show, PasswordStrength strength})>( - selector: (state) => ( - show: state.showPasswordStrength, - strength: state.passwordStrength, - ), builder: (context, state) { - return Offstage( - offstage: !state.show, - child: VoicesPasswordStrengthIndicator( - passwordStrength: state.strength, - ), + return UnlockPasswordForm( + passwordController: passwordController, + confirmPasswordController: confirmPasswordController, + showError: state.showError, + passwordStrength: state.passwordStrength, + showPasswordStrength: state.showPasswordStrength, ); }, ); diff --git a/catalyst_voices/lib/pages/registration/recover/seed_phrase/unlock_password_panel.dart b/catalyst_voices/lib/pages/registration/recover/seed_phrase/unlock_password_panel.dart index 4f48b235ca8..bde07741fac 100644 --- a/catalyst_voices/lib/pages/registration/recover/seed_phrase/unlock_password_panel.dart +++ b/catalyst_voices/lib/pages/registration/recover/seed_phrase/unlock_password_panel.dart @@ -1,10 +1,153 @@ +import 'package:catalyst_voices/pages/registration/recover/bloc_recover_builder.dart'; +import 'package:catalyst_voices/pages/registration/widgets/registration_stage_navigation.dart'; +import 'package:catalyst_voices/pages/registration/widgets/unlock_password_form.dart'; +import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart'; +import 'package:catalyst_voices_models/catalyst_voices_models.dart'; import 'package:flutter/material.dart'; -class UnlockPasswordPanel extends StatelessWidget { +class UnlockPasswordPanel extends StatefulWidget { const UnlockPasswordPanel({super.key}); + @override + State createState() => _UnlockPasswordPanelState(); +} + +class _UnlockPasswordPanelState extends State { + late final TextEditingController _passwordController; + late final TextEditingController _confirmPasswordController; + + @override + void initState() { + super.initState(); + + final unlockPasswordState = RegistrationCubit.of(context) + .state + .recoverStateData + .unlockPasswordState; + + final password = unlockPasswordState.password.value; + final confirmPassword = unlockPasswordState.confirmPassword.value; + + _passwordController = TextEditingController(text: password) + ..addListener(_onPasswordChanged); + _confirmPasswordController = TextEditingController(text: confirmPassword) + ..addListener(_onConfirmPasswordChanged); + } + + @override + void dispose() { + _passwordController.dispose(); + _confirmPasswordController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox(height: 24), + const SizedBox(height: 12), + Expanded( + child: _BlocUnlockPasswordForm( + passwordController: _passwordController, + confirmPasswordController: _confirmPasswordController, + ), + ), + const SizedBox(height: 22), + _BlocNavigation( + onNextTap: _createKeychain, + onBackTap: _clearPasswordAndGoBack, + ), + ], + ); + } + + void _onPasswordChanged() { + final password = _passwordController.text; + + RegistrationCubit.of(context).recover.setPassword(password); + } + + void _onConfirmPasswordChanged() { + final confirmPassword = _confirmPasswordController.text; + + RegistrationCubit.of(context).recover.setConfirmPassword(confirmPassword); + } + + void _clearPasswordAndGoBack() { + final registration = RegistrationCubit.of(context); + + registration.recover + ..setPassword('') + ..setConfirmPassword(''); + + registration.previousStep(); + } + + Future _createKeychain() async { + // TODO(damian-molinski): to be implemented + } +} + +class _BlocUnlockPasswordForm extends StatelessWidget { + final TextEditingController passwordController; + final TextEditingController confirmPasswordController; + + const _BlocUnlockPasswordForm({ + required this.passwordController, + required this.confirmPasswordController, + }); + + @override + Widget build(BuildContext context) { + return BlocRecoverBuilder< + ({ + bool showError, + PasswordStrength passwordStrength, + bool showPasswordStrength, + })>( + selector: (state) { + final unlockPasswordState = state.unlockPasswordState; + return ( + showError: unlockPasswordState.showPasswordMisMatch, + passwordStrength: unlockPasswordState.passwordStrength, + showPasswordStrength: unlockPasswordState.showPasswordStrength, + ); + }, + builder: (context, state) { + return UnlockPasswordForm( + passwordController: passwordController, + confirmPasswordController: confirmPasswordController, + showError: state.showError, + passwordStrength: state.passwordStrength, + showPasswordStrength: state.showPasswordStrength, + ); + }, + ); + } +} + +class _BlocNavigation extends StatelessWidget { + final VoidCallback onNextTap; + final VoidCallback onBackTap; + + const _BlocNavigation({ + required this.onNextTap, + required this.onBackTap, + }); + @override Widget build(BuildContext context) { - return const Placeholder(); + return BlocRecoverBuilder( + selector: (state) => state.unlockPasswordState.isNextEnabled, + builder: (context, state) { + return RegistrationBackNextNavigation( + isNextEnabled: state, + onNextTap: onNextTap, + onBackTap: onBackTap, + ); + }, + ); } } diff --git a/catalyst_voices/lib/pages/registration/widgets/unlock_password_form.dart b/catalyst_voices/lib/pages/registration/widgets/unlock_password_form.dart new file mode 100644 index 00000000000..54bbf18a171 --- /dev/null +++ b/catalyst_voices/lib/pages/registration/widgets/unlock_password_form.dart @@ -0,0 +1,109 @@ +import 'package:catalyst_voices/widgets/widgets.dart'; +import 'package:catalyst_voices_localization/catalyst_voices_localization.dart'; +import 'package:catalyst_voices_models/catalyst_voices_models.dart'; +import 'package:flutter/material.dart'; + +class UnlockPasswordForm extends StatelessWidget { + final TextEditingController passwordController; + final TextEditingController confirmPasswordController; + final bool showError; + final PasswordStrength passwordStrength; + final bool showPasswordStrength; + + const UnlockPasswordForm({ + super.key, + required this.passwordController, + required this.confirmPasswordController, + this.showError = false, + this.passwordStrength = PasswordStrength.weak, + this.showPasswordStrength = false, + }); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _UnlockPasswordTextField( + controller: passwordController, + ), + const SizedBox(height: 12), + _ConfirmUnlockPasswordTextField( + controller: confirmPasswordController, + showError: showError, + minimumLength: PasswordStrength.minimumLength, + ), + const Spacer(), + const SizedBox(height: 22), + _PasswordStrength( + strength: passwordStrength, + visible: showPasswordStrength, + ), + ], + ); + } +} + +class _UnlockPasswordTextField extends StatelessWidget { + final TextEditingController controller; + + const _UnlockPasswordTextField({ + required this.controller, + }); + + @override + Widget build(BuildContext context) { + return VoicesPasswordTextField( + controller: controller, + textInputAction: TextInputAction.next, + decoration: VoicesTextFieldDecoration( + labelText: context.l10n.enterPassword, + ), + ); + } +} + +class _ConfirmUnlockPasswordTextField extends StatelessWidget { + final TextEditingController controller; + final bool showError; + final int minimumLength; + + const _ConfirmUnlockPasswordTextField({ + required this.controller, + this.showError = false, + required this.minimumLength, + }); + + @override + Widget build(BuildContext context) { + return VoicesPasswordTextField( + controller: controller, + decoration: VoicesTextFieldDecoration( + labelText: context.l10n.confirmPassword, + helperText: context.l10n.xCharactersMinimum(minimumLength), + errorText: showError ? context.l10n.passwordDoNotMatch : null, + ), + ); + } +} + +class _PasswordStrength extends StatelessWidget { + final PasswordStrength strength; + final bool visible; + + const _PasswordStrength({ + this.strength = PasswordStrength.weak, + this.visible = false, + }); + + @override + Widget build(BuildContext context) { + return Offstage( + offstage: !visible, + child: VoicesPasswordStrengthIndicator( + passwordStrength: strength, + ), + ); + } +} diff --git a/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/cubits/keychain_creation_cubit.dart b/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/cubits/keychain_creation_cubit.dart index a1003dbb87c..a7a3be2fbfe 100644 --- a/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/cubits/keychain_creation_cubit.dart +++ b/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/cubits/keychain_creation_cubit.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'dart:convert' show utf8; import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart'; +import 'package:catalyst_voices_blocs/src/registration/cubits/unlock_password_manager.dart'; import 'package:catalyst_voices_blocs/src/registration/state_data/keychain_state_data.dart'; import 'package:catalyst_voices_models/catalyst_voices_models.dart'; import 'package:catalyst_voices_services/catalyst_voices_services.dart'; @@ -12,7 +13,8 @@ import 'package:flutter_bloc/flutter_bloc.dart'; final _logger = Logger('KeychainCreationCubit'); -abstract interface class KeychainCreationManager { +abstract interface class KeychainCreationManager + implements UnlockPasswordManager { void buildSeedPhrase({ bool forceRefresh = false, }); @@ -23,14 +25,11 @@ abstract interface class KeychainCreationManager { Future downloadSeedPhrase(); - void setPassword(String value); - - void setConfirmPassword(String value); - Future createKeychain(); } final class KeychainCreationCubit extends Cubit + with UnlockPasswordMixin implements KeychainCreationManager { final Downloader _downloader; @@ -49,16 +48,6 @@ final class KeychainCreationCubit extends Cubit } } - UnlockPasswordState get _unlockPasswordState { - return state.unlockPasswordState; - } - - set _unlockPasswordState(UnlockPasswordState newValue) { - if (state.unlockPasswordState != newValue) { - emit(state.copyWith(unlockPasswordState: newValue)); - } - } - @override void buildSeedPhrase({ bool forceRefresh = false, @@ -125,13 +114,8 @@ final class KeychainCreationCubit extends Cubit } @override - void setPassword(String value) { - _updateUnlockPasswordState(password: value); - } - - @override - void setConfirmPassword(String value) { - _updateUnlockPasswordState(confirmPassword: value); + void onUnlockPasswordStateChanged(UnlockPasswordState data) { + emit(state.copyWith(unlockPasswordState: data)); } @override @@ -139,31 +123,6 @@ final class KeychainCreationCubit extends Cubit // TODO(damian-molinski): implement } - void _updateUnlockPasswordState({ - String? password, - String? confirmPassword, - }) { - password ??= _unlockPasswordState.password; - confirmPassword ??= _unlockPasswordState.confirmPassword; - - const minimumLength = PasswordStrength.minimumLength; - - final passwordStrength = PasswordStrength.calculate(password); - final correctLength = password.length >= minimumLength; - final matching = password == confirmPassword; - final hasConfirmPassword = confirmPassword.isNotEmpty; - - _unlockPasswordState = _unlockPasswordState.copyWith( - password: password, - confirmPassword: confirmPassword, - passwordStrength: passwordStrength, - showPasswordStrength: password.isNotEmpty, - minPasswordLength: minimumLength, - showPasswordMisMatch: correctLength && hasConfirmPassword && !matching, - isNextEnabled: correctLength && matching, - ); - } - void _buildSeedPhrase() { final seedPhrase = SeedPhrase(); diff --git a/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/cubits/recover_cubit.dart b/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/cubits/recover_cubit.dart index ea39698db63..a5aabb1d08d 100644 --- a/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/cubits/recover_cubit.dart +++ b/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/cubits/recover_cubit.dart @@ -1,6 +1,7 @@ // ignore_for_file: one_member_abstracts import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart'; +import 'package:catalyst_voices_blocs/src/registration/cubits/unlock_password_manager.dart'; import 'package:catalyst_voices_models/catalyst_voices_models.dart'; import 'package:catalyst_voices_services/catalyst_voices_services.dart'; import 'package:catalyst_voices_shared/catalyst_voices_shared.dart'; @@ -11,7 +12,7 @@ import 'package:result_type/result_type.dart'; final _logger = Logger('RecoverCubit'); -abstract interface class RecoverManager { +abstract interface class RecoverManager implements UnlockPasswordManager { Future checkLocalKeychains(); void setSeedPhraseWords(List words); @@ -20,6 +21,7 @@ abstract interface class RecoverManager { } final class RecoverCubit extends Cubit + with UnlockPasswordMixin implements RecoverManager { final RegistrationService _registrationService; @@ -98,6 +100,11 @@ final class RecoverCubit extends Cubit emit(state.copyWith(accountDetails: Optional(Failure(exception)))); } } + + @override + void onUnlockPasswordStateChanged(UnlockPasswordState data) { + emit(state.copyWith(unlockPasswordState: data)); + } } const _testWords = [ diff --git a/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/cubits/unlock_password_manager.dart b/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/cubits/unlock_password_manager.dart new file mode 100644 index 00000000000..9d1f507db4f --- /dev/null +++ b/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/cubits/unlock_password_manager.dart @@ -0,0 +1,64 @@ +import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart'; +import 'package:catalyst_voices_models/catalyst_voices_models.dart'; +import 'package:catalyst_voices_view_models/catalyst_voices_view_models.dart'; + +abstract interface class UnlockPasswordManager { + void setPassword(String value); + + void setConfirmPassword(String value); +} + +mixin UnlockPasswordMixin implements UnlockPasswordManager { + UnlockPasswordState _state = const UnlockPasswordState(); + + void onUnlockPasswordStateChanged(UnlockPasswordState data); + + @override + void setPassword(String value) { + final password = UnlockPassword.dirty(value); + + if (_state.password != password) { + _updateState(password: password); + } + } + + @override + void setConfirmPassword(String value) { + final confirmPassword = UnlockPassword.dirty(value); + + if (_state.confirmPassword != confirmPassword) { + _updateState(confirmPassword: confirmPassword); + } + } + + void _updateState({ + UnlockPassword? password, + UnlockPassword? confirmPassword, + }) { + password ??= _state.password; + confirmPassword ??= _state.confirmPassword; + + const minimumLength = PasswordStrength.minimumLength; + + final passwordStrength = password.strength(); + final isPasswordValid = password.isValid; + + final matching = password.value == confirmPassword.value; + final hasConfirmPassword = confirmPassword.value.isNotEmpty; + + final state = UnlockPasswordState( + password: password, + confirmPassword: confirmPassword, + passwordStrength: passwordStrength, + showPasswordStrength: password.value.isNotEmpty, + minPasswordLength: minimumLength, + showPasswordMisMatch: isPasswordValid && hasConfirmPassword && !matching, + isNextEnabled: isPasswordValid && matching, + ); + + if (_state != state) { + _state = state; + onUnlockPasswordStateChanged(state); + } + } +} diff --git a/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/state_data/recover_state_data.dart b/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/state_data/recover_state_data.dart index 0a8cf6dabd7..da897021ad8 100644 --- a/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/state_data/recover_state_data.dart +++ b/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/state_data/recover_state_data.dart @@ -1,3 +1,4 @@ +import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart'; import 'package:catalyst_voices_models/catalyst_voices_models.dart'; import 'package:catalyst_voices_view_models/catalyst_voices_view_models.dart'; import 'package:equatable/equatable.dart'; @@ -9,6 +10,7 @@ final class RecoverStateData extends Equatable { final List seedPhraseWords; final bool isSeedPhraseValid; final Result? accountDetails; + final UnlockPasswordState unlockPasswordState; bool get isAccountSummaryNextEnabled => accountDetails?.isSuccess ?? false; @@ -18,6 +20,7 @@ final class RecoverStateData extends Equatable { this.seedPhraseWords = const [], this.isSeedPhraseValid = false, this.accountDetails, + this.unlockPasswordState = const UnlockPasswordState(), }); RecoverStateData copyWith({ @@ -26,6 +29,7 @@ final class RecoverStateData extends Equatable { List? seedPhraseWords, bool? isSeedPhraseValid, Optional>? accountDetails, + UnlockPasswordState? unlockPasswordState, }) { return RecoverStateData( foundKeychain: foundKeychain ?? this.foundKeychain, @@ -33,6 +37,7 @@ final class RecoverStateData extends Equatable { seedPhraseWords: seedPhraseWords ?? this.seedPhraseWords, isSeedPhraseValid: isSeedPhraseValid ?? this.isSeedPhraseValid, accountDetails: accountDetails.dataOr(this.accountDetails), + unlockPasswordState: unlockPasswordState ?? this.unlockPasswordState, ); } @@ -45,6 +50,7 @@ final class RecoverStateData extends Equatable { '${seedPhraseWords.length}, ' '$isSeedPhraseValid, ' '$accountDetails, ' + '$unlockPasswordState, ' ')'; } @@ -55,6 +61,7 @@ final class RecoverStateData extends Equatable { seedPhraseWords, isSeedPhraseValid, accountDetails, + unlockPasswordState, ]; } diff --git a/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/state_data/unlock_password_state.dart b/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/state_data/unlock_password_state.dart index 54641222a55..57090e6d1d7 100644 --- a/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/state_data/unlock_password_state.dart +++ b/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/state_data/unlock_password_state.dart @@ -1,9 +1,10 @@ import 'package:catalyst_voices_models/catalyst_voices_models.dart'; +import 'package:catalyst_voices_view_models/catalyst_voices_view_models.dart'; import 'package:equatable/equatable.dart'; final class UnlockPasswordState extends Equatable { - final String password; - final String confirmPassword; + final UnlockPassword password; + final UnlockPassword confirmPassword; final PasswordStrength passwordStrength; final bool showPasswordStrength; final int minPasswordLength; @@ -11,8 +12,8 @@ final class UnlockPasswordState extends Equatable { final bool isNextEnabled; const UnlockPasswordState({ - this.password = '', - this.confirmPassword = '', + this.password = const UnlockPassword.pure(), + this.confirmPassword = const UnlockPassword.pure(), this.passwordStrength = PasswordStrength.weak, this.showPasswordStrength = false, this.minPasswordLength = PasswordStrength.minimumLength, @@ -21,8 +22,8 @@ final class UnlockPasswordState extends Equatable { }); UnlockPasswordState copyWith({ - String? password, - String? confirmPassword, + UnlockPassword? password, + UnlockPassword? confirmPassword, PasswordStrength? passwordStrength, bool? showPasswordStrength, int? minPasswordLength, diff --git a/catalyst_voices/packages/catalyst_voices_view_models/lib/src/authentication/authentication.dart b/catalyst_voices/packages/catalyst_voices_view_models/lib/src/authentication/authentication.dart index 31cc892b7de..6206c2555dc 100644 --- a/catalyst_voices/packages/catalyst_voices_view_models/lib/src/authentication/authentication.dart +++ b/catalyst_voices/packages/catalyst_voices_view_models/lib/src/authentication/authentication.dart @@ -1,2 +1,3 @@ export 'email.dart'; export 'password.dart'; +export 'unlock_password.dart'; diff --git a/catalyst_voices/packages/catalyst_voices_view_models/lib/src/authentication/unlock_password.dart b/catalyst_voices/packages/catalyst_voices_view_models/lib/src/authentication/unlock_password.dart new file mode 100644 index 00000000000..9e6a8c816cf --- /dev/null +++ b/catalyst_voices/packages/catalyst_voices_view_models/lib/src/authentication/unlock_password.dart @@ -0,0 +1,23 @@ +import 'package:catalyst_voices_models/catalyst_voices_models.dart'; +import 'package:formz/formz.dart'; + +final class UnlockPassword extends FormzInput { + const UnlockPassword.dirty([super.value = '']) : super.dirty(); + + const UnlockPassword.pure([super.value = '']) : super.pure(); + + PasswordStrength strength() { + return PasswordStrength.calculate(value); + } + + @override + UnlockPasswordError? validator(String value) { + if (value.length < PasswordStrength.minimumLength) { + return UnlockPasswordError.tooShort; + } + + return null; + } +} + +enum UnlockPasswordError { tooShort } From e7eb5e74036d97a55836affedc32d1cb455a1ca7 Mon Sep 17 00:00:00 2001 From: Damian Molinski Date: Fri, 11 Oct 2024 15:05:39 +0200 Subject: [PATCH 3/4] refactor: unify bloc unlock builder --- .../bloc_unlock_password_builder.dart | 4 +++- .../stage/unlock_password_panel.dart | 4 +++- .../seed_phrase/unlock_password_panel.dart | 17 ++++++------- .../registration/registration_info_panel.dart | 24 +++++++++++++++---- 4 files changed, 34 insertions(+), 15 deletions(-) rename catalyst_voices/lib/pages/registration/{create_keychain => }/bloc_unlock_password_builder.dart (75%) diff --git a/catalyst_voices/lib/pages/registration/create_keychain/bloc_unlock_password_builder.dart b/catalyst_voices/lib/pages/registration/bloc_unlock_password_builder.dart similarity index 75% rename from catalyst_voices/lib/pages/registration/create_keychain/bloc_unlock_password_builder.dart rename to catalyst_voices/lib/pages/registration/bloc_unlock_password_builder.dart index 19d8d57f06a..45d6e4cbc8b 100644 --- a/catalyst_voices/lib/pages/registration/create_keychain/bloc_unlock_password_builder.dart +++ b/catalyst_voices/lib/pages/registration/bloc_unlock_password_builder.dart @@ -5,12 +5,14 @@ class BlocUnlockPasswordBuilder extends BlocSelector { BlocUnlockPasswordBuilder({ super.key, + required BlocWidgetSelector + stateSelector, required BlocWidgetSelector selector, required super.builder, super.bloc, }) : super( selector: (state) { - return selector(state.keychainStateData.unlockPasswordState); + return selector(stateSelector(state)); }, ); } diff --git a/catalyst_voices/lib/pages/registration/create_keychain/stage/unlock_password_panel.dart b/catalyst_voices/lib/pages/registration/create_keychain/stage/unlock_password_panel.dart index c6f96172d9b..3edb2dcd8dd 100644 --- a/catalyst_voices/lib/pages/registration/create_keychain/stage/unlock_password_panel.dart +++ b/catalyst_voices/lib/pages/registration/create_keychain/stage/unlock_password_panel.dart @@ -1,4 +1,4 @@ -import 'package:catalyst_voices/pages/registration/create_keychain/bloc_unlock_password_builder.dart'; +import 'package:catalyst_voices/pages/registration/bloc_unlock_password_builder.dart'; import 'package:catalyst_voices/pages/registration/widgets/registration_stage_navigation.dart'; import 'package:catalyst_voices/pages/registration/widgets/unlock_password_form.dart'; import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart'; @@ -115,6 +115,7 @@ class _BlocUnlockPasswordForm extends StatelessWidget { PasswordStrength passwordStrength, bool showPasswordStrength, })>( + stateSelector: (state) => state.keychainStateData.unlockPasswordState, selector: (state) { return ( showError: state.showPasswordMisMatch, @@ -147,6 +148,7 @@ class _BlocNavigation extends StatelessWidget { @override Widget build(BuildContext context) { return BlocUnlockPasswordBuilder( + stateSelector: (state) => state.keychainStateData.unlockPasswordState, selector: (state) => state.isNextEnabled, builder: (context, state) { return RegistrationBackNextNavigation( diff --git a/catalyst_voices/lib/pages/registration/recover/seed_phrase/unlock_password_panel.dart b/catalyst_voices/lib/pages/registration/recover/seed_phrase/unlock_password_panel.dart index bde07741fac..22de61399f1 100644 --- a/catalyst_voices/lib/pages/registration/recover/seed_phrase/unlock_password_panel.dart +++ b/catalyst_voices/lib/pages/registration/recover/seed_phrase/unlock_password_panel.dart @@ -1,4 +1,4 @@ -import 'package:catalyst_voices/pages/registration/recover/bloc_recover_builder.dart'; +import 'package:catalyst_voices/pages/registration/bloc_unlock_password_builder.dart'; import 'package:catalyst_voices/pages/registration/widgets/registration_stage_navigation.dart'; import 'package:catalyst_voices/pages/registration/widgets/unlock_password_form.dart'; import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart'; @@ -101,18 +101,18 @@ class _BlocUnlockPasswordForm extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocRecoverBuilder< + return BlocUnlockPasswordBuilder< ({ bool showError, PasswordStrength passwordStrength, bool showPasswordStrength, })>( + stateSelector: (state) => state.recoverStateData.unlockPasswordState, selector: (state) { - final unlockPasswordState = state.unlockPasswordState; return ( - showError: unlockPasswordState.showPasswordMisMatch, - passwordStrength: unlockPasswordState.passwordStrength, - showPasswordStrength: unlockPasswordState.showPasswordStrength, + showError: state.showPasswordMisMatch, + passwordStrength: state.passwordStrength, + showPasswordStrength: state.showPasswordStrength, ); }, builder: (context, state) { @@ -139,8 +139,9 @@ class _BlocNavigation extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocRecoverBuilder( - selector: (state) => state.unlockPasswordState.isNextEnabled, + return BlocUnlockPasswordBuilder( + stateSelector: (state) => state.recoverStateData.unlockPasswordState, + selector: (state) => state.isNextEnabled, builder: (context, state) { return RegistrationBackNextNavigation( isNextEnabled: state, diff --git a/catalyst_voices/lib/pages/registration/registration_info_panel.dart b/catalyst_voices/lib/pages/registration/registration_info_panel.dart index 6c3da3a95b0..a0fe5bf14c1 100644 --- a/catalyst_voices/lib/pages/registration/registration_info_panel.dart +++ b/catalyst_voices/lib/pages/registration/registration_info_panel.dart @@ -1,5 +1,5 @@ +import 'package:catalyst_voices/pages/registration/bloc_unlock_password_builder.dart'; import 'package:catalyst_voices/pages/registration/create_keychain/bloc_seed_phrase_builder.dart'; -import 'package:catalyst_voices/pages/registration/create_keychain/bloc_unlock_password_builder.dart'; import 'package:catalyst_voices/pages/registration/pictures/keychain_picture.dart'; import 'package:catalyst_voices/pages/registration/pictures/keychain_with_password_picture.dart'; import 'package:catalyst_voices/pages/registration/pictures/password_picture.dart'; @@ -158,7 +158,7 @@ class _RegistrationPicture extends StatelessWidget { const _BlocSeedPhraseResultPicture(), CreateKeychainStage.unlockPasswordInstructions || CreateKeychainStage.unlockPasswordCreate => - const _BlocPasswordPicture(), + const _BlocCreationPasswordPicture(), }; } @@ -182,7 +182,7 @@ class _RegistrationPicture extends StatelessWidget { const KeychainPicture(), RecoverSeedPhraseStage.unlockPasswordInstructions || RecoverSeedPhraseStage.unlockPassword => - const PasswordPicture(), + const _BlocRecoveryPasswordPicture(), RecoverSeedPhraseStage.success => const KeychainWithPasswordPicture(), }; } @@ -216,12 +216,26 @@ class _BlocSeedPhraseResultPicture extends StatelessWidget { } } -class _BlocPasswordPicture extends StatelessWidget { - const _BlocPasswordPicture(); +class _BlocCreationPasswordPicture extends StatelessWidget { + const _BlocCreationPasswordPicture(); @override Widget build(BuildContext context) { return BlocUnlockPasswordBuilder( + stateSelector: (state) => state.keychainStateData.unlockPasswordState, + selector: (state) => state.pictureType, + builder: (context, state) => PasswordPicture(type: state), + ); + } +} + +class _BlocRecoveryPasswordPicture extends StatelessWidget { + const _BlocRecoveryPasswordPicture(); + + @override + Widget build(BuildContext context) { + return BlocUnlockPasswordBuilder( + stateSelector: (state) => state.recoverStateData.unlockPasswordState, selector: (state) => state.pictureType, builder: (context, state) => PasswordPicture(type: state), ); From c229cc2547726dca07a6db7dd95f210e8a6b3657 Mon Sep 17 00:00:00 2001 From: Damian Molinski Date: Fri, 11 Oct 2024 15:20:33 +0200 Subject: [PATCH 4/4] feat: recovery keychain creation --- .../seed_phrase/unlock_password_panel.dart | 8 ++++- .../registration/cubits/recover_cubit.dart | 30 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/catalyst_voices/lib/pages/registration/recover/seed_phrase/unlock_password_panel.dart b/catalyst_voices/lib/pages/registration/recover/seed_phrase/unlock_password_panel.dart index 22de61399f1..da2aa94c11d 100644 --- a/catalyst_voices/lib/pages/registration/recover/seed_phrase/unlock_password_panel.dart +++ b/catalyst_voices/lib/pages/registration/recover/seed_phrase/unlock_password_panel.dart @@ -86,7 +86,13 @@ class _UnlockPasswordPanelState extends State { } Future _createKeychain() async { - // TODO(damian-molinski): to be implemented + final registrationCubit = RegistrationCubit.of(context); + + final isSuccess = await registrationCubit.recover.createKeychain(); + + if (isSuccess) { + registrationCubit.nextStep(); + } } } diff --git a/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/cubits/recover_cubit.dart b/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/cubits/recover_cubit.dart index c848c8c07b7..2e7ffb80e8f 100644 --- a/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/cubits/recover_cubit.dart +++ b/catalyst_voices/packages/catalyst_voices_blocs/lib/src/registration/cubits/recover_cubit.dart @@ -18,6 +18,8 @@ abstract interface class RecoverManager implements UnlockPasswordManager { void setSeedPhraseWords(List words); Future recoverAccount(); + + Future createKeychain(); } final class RecoverCubit extends Cubit @@ -101,6 +103,34 @@ final class RecoverCubit extends Cubit } } + @override + Future createKeychain() async { + try { + final seedPhrase = _seedPhrase; + final password = this.password; + + if (seedPhrase == null) { + throw const LocalizedRegistrationSeedPhraseNotFoundException(); + } + if (password.isNotValid) { + throw const LocalizedRegistrationUnlockPasswordNotFoundException(); + } + + await _registrationService.createKeychain( + seedPhrase: seedPhrase, + unlockPassword: password.value, + ); + + return true; + } catch (error, stack) { + _logger.severe('Create keychain', error, stack); + + emitError(error); + + return false; + } + } + @override void onUnlockPasswordStateChanged(UnlockPasswordState data) { emit(state.copyWith(unlockPasswordState: data));