From 0a94d9281a652ab3fe3f6dcea51c4f1da6019b9e Mon Sep 17 00:00:00 2001 From: Andrey Molochko <36672245+AndreyMolochko@users.noreply.github.com> Date: Fri, 13 Sep 2024 19:45:10 +0300 Subject: [PATCH] fix: add biometry for modal, autofocus on sercure text field (#498) Co-authored-by: Andrey Malochka --- .../confirm_action/confirm_action_dialog.dart | 33 +++++++++++++++++++ .../confirm_action/confirm_action_model.dart | 29 ++++++++++++++++ .../confirm_action/confirm_action_wm.dart | 29 ++++++++++++++++ 3 files changed, 91 insertions(+) diff --git a/lib/feature/wallet/wallet_backup/confirm_action/confirm_action_dialog.dart b/lib/feature/wallet/wallet_backup/confirm_action/confirm_action_dialog.dart index 3d01ba802..4d0bee208 100644 --- a/lib/feature/wallet/wallet_backup/confirm_action/confirm_action_dialog.dart +++ b/lib/feature/wallet/wallet_backup/confirm_action/confirm_action_dialog.dart @@ -5,6 +5,8 @@ import 'package:app/generated/generated.dart'; import 'package:elementary/elementary.dart'; import 'package:elementary_helper/elementary_helper.dart'; import 'package:flutter/cupertino.dart'; +import 'package:local_auth/local_auth.dart'; +import 'package:lucide_icons_flutter/lucide_icons.dart'; import 'package:nekoton_repository/nekoton_repository.dart'; import 'package:ui_components_lib/v2/ui_components_lib_v2.dart'; import 'package:ui_components_lib/v2/widgets/modals/primary_bottom_sheet.dart'; @@ -61,6 +63,7 @@ class ContentConfirmAction extends ElementaryWidget { SecureTextField( textEditingController: wm.passwordController, validator: (_) => data?.error, + isAutofocus: true, hintText: LocaleKeys.enterYourPassword.tr(), ), const SizedBox(height: DimensSizeV2.d28), @@ -70,6 +73,36 @@ class ContentConfirmAction extends ElementaryWidget { isLoading: data?.isLoading ?? false, onPressed: wm.onClickConfirm, ), + StateNotifierBuilder( + listenableState: wm.availableBiometry, + builder: (_, value) { + if (value?.contains(BiometricType.face) ?? false) { + return Padding( + padding: const EdgeInsets.only(top: DimensSizeV2.d8), + child: PrimaryButton( + buttonShape: ButtonShape.pill, + title: LocaleKeys.useFaceID.tr(), + icon: LucideIcons.scanFace, + onPressed: wm.onUseBiometry, + ), + ); + } + + if (value?.contains(BiometricType.fingerprint) ?? false) { + return Padding( + padding: const EdgeInsets.only(top: DimensSizeV2.d8), + child: PrimaryButton( + buttonShape: ButtonShape.pill, + title: LocaleKeys.useFingerprint.tr(), + icon: LucideIcons.fingerprint, + onPressed: wm.onUseBiometry, + ), + ); + } + + return const SizedBox.shrink(); + }, + ), ], ); }, diff --git a/lib/feature/wallet/wallet_backup/confirm_action/confirm_action_model.dart b/lib/feature/wallet/wallet_backup/confirm_action/confirm_action_model.dart index 358d01dee..c96aa262a 100644 --- a/lib/feature/wallet/wallet_backup/confirm_action/confirm_action_model.dart +++ b/lib/feature/wallet/wallet_backup/confirm_action/confirm_action_model.dart @@ -1,7 +1,10 @@ +import 'package:app/app/service/biometry_service.dart'; import 'package:app/app/service/current_seed_service.dart'; import 'package:app/app/service/messenger/message.dart'; import 'package:app/app/service/messenger/service/messenger_service.dart'; +import 'package:app/generated/generated.dart'; import 'package:elementary/elementary.dart'; +import 'package:local_auth/local_auth.dart'; import 'package:nekoton_repository/nekoton_repository.dart' hide Message; import 'package:ui_components_lib/ui_components_lib.dart'; @@ -9,12 +12,14 @@ class ConfirmActionModel extends ElementaryModel { ConfirmActionModel( ErrorHandler errorHandler, this.account, + this.biometryService, this.nekotonRepository, this.currentSeedService, this.messengerService, ) : super(errorHandler: errorHandler); final NekotonRepository nekotonRepository; + final BiometryService biometryService; final CurrentSeedService currentSeedService; final MessengerService messengerService; final KeyAccount? account; @@ -24,6 +29,30 @@ class ConfirmActionModel extends ElementaryModel { Seed? findSeed(PublicKey publicKey) => nekotonRepository.seedList.findSeed(publicKey); + Future> getAvailableBiometry(PublicKey publicKey) async { + final isBiometryEnabled = biometryService.enabled; + final hasKeyPassword = await biometryService.hasKeyPassword(publicKey); + + if (isBiometryEnabled && hasKeyPassword) { + return biometryService.getAvailableBiometry(); + } + + return []; + } + + Future requestBiometry(PublicKey publicKey) async { + try { + final password = await biometryService.getKeyPassword( + publicKey: publicKey, + localizedReason: LocaleKeys.biometryAuthReason.tr(), + ); + + return password; + } catch (_) { + return null; + } + } + void showValidateError(String message) { messengerService.show( Message.error( diff --git a/lib/feature/wallet/wallet_backup/confirm_action/confirm_action_wm.dart b/lib/feature/wallet/wallet_backup/confirm_action/confirm_action_wm.dart index 4a4659ca0..3b1e69680 100644 --- a/lib/feature/wallet/wallet_backup/confirm_action/confirm_action_wm.dart +++ b/lib/feature/wallet/wallet_backup/confirm_action/confirm_action_wm.dart @@ -10,7 +10,9 @@ import 'package:app/feature/wallet/wallet_backup/confirm_action/confirm_action_d import 'package:app/feature/wallet/wallet_backup/confirm_action/confirm_action_model.dart'; import 'package:app/feature/wallet/wallet_backup/manual_backup/manual_back_up_dialog.dart'; import 'package:app/generated/generated.dart'; +import 'package:elementary_helper/elementary_helper.dart'; import 'package:flutter/widgets.dart'; +import 'package:local_auth/local_auth.dart'; import 'package:nekoton_repository/nekoton_repository.dart'; import 'package:ui_components_lib/v2/ui_components_lib_v2.dart'; @@ -27,6 +29,7 @@ ConfirmActionWidgetModel defaultConfirmActionWidgetModelFactory( inject(), inject(), inject(), + inject(), ), ); } @@ -44,6 +47,11 @@ class ConfirmActionWidgetModel KeyAccount? get account => model.account; + ListenableState> get availableBiometry => + _availableBiometry; + + late final _availableBiometry = createNotifier>(); + late final screenState = createEntityNotifier() ..loading(ConfirmActionData()); @@ -52,6 +60,7 @@ class ConfirmActionWidgetModel @override void initWidgetModel() { passwordController.addListener(_resetError); + _getAvailableBiometry(); super.initWidgetModel(); } @@ -63,6 +72,26 @@ class ConfirmActionWidgetModel } } + Future onUseBiometry() async { + //final publicKey = _currentAccount.value?.publicKey; + final publicKey = model.currentSeed?.publicKey; + if (publicKey != null) { + final password = await model.requestBiometry(publicKey); + + if (password != null) { + await _export(publicKey, password); + } + } + } + + Future _getAvailableBiometry() async { + final publicKey = model.currentSeed?.publicKey; + if (publicKey != null) { + final available = await model.getAvailableBiometry(publicKey); + _availableBiometry.accept(available); + } + } + void _resetError() { screenState.content(ConfirmActionData()); }