From 3e17d314355fea48c5dc7d1ad489e6afb0a22797 Mon Sep 17 00:00:00 2001 From: Matthew Fosse Date: Fri, 20 Sep 2024 20:36:39 -0700 Subject: [PATCH] add pin randomization --- assets/images/shuffle.png | Bin 0 -> 10926 bytes lib/entities/preferences_key.dart | 1 + lib/src/screens/auth/auth_page.dart | 69 +++++++++--------- lib/src/screens/pin_code/pin_code.dart | 15 ++-- lib/src/screens/pin_code/pin_code_widget.dart | 69 +++++++++++++++--- .../setup_pin_code/setup_pin_code.dart | 2 + lib/store/settings_store.dart | 13 ++++ lib/view_model/auth_view_model.dart | 9 ++- lib/view_model/setup_pin_code_view_model.dart | 6 ++ 9 files changed, 133 insertions(+), 51 deletions(-) create mode 100644 assets/images/shuffle.png diff --git a/assets/images/shuffle.png b/assets/images/shuffle.png new file mode 100644 index 0000000000000000000000000000000000000000..878938c06ec1bf6f9201b20b8469bad1655e682c GIT binary patch literal 10926 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 extends State { } class AuthPage extends StatefulWidget { - AuthPage(this.authViewModel, - {required this.onAuthenticationFinished, - this.closable = true}); + AuthPage(this.authViewModel, {required this.onAuthenticationFinished, this.closable = true}); final AuthViewModel authViewModel; final OnAuthenticationFinished onAuthenticationFinished; @@ -34,16 +32,14 @@ class AuthPage extends StatefulWidget { class AuthPagePinCodeStateImpl extends AuthPageState { final _key = GlobalKey(); final _pinCodeKey = GlobalKey(); - final _backArrowImageDarkTheme = - Image.asset('assets/images/close_button.png'); + final _backArrowImageDarkTheme = Image.asset('assets/images/close_button.png'); ReactionDisposer? _reaction; Flushbar? _authBar; Flushbar? _progressBar; @override void initState() { - _reaction ??= - reaction((_) => widget.authViewModel.state, (ExecutionState state) { + _reaction ??= reaction((_) => widget.authViewModel.state, (ExecutionState state) { if (state is ExecutedSuccessfullyState) { WidgetsBinding.instance.addPostFrameCallback((_) { widget.onAuthenticationFinished(true, this); @@ -54,9 +50,7 @@ class AuthPagePinCodeStateImpl extends AuthPageState { if (state is IsExecutingState) { WidgetsBinding.instance.addPostFrameCallback((_) { // null duration to make it indefinite until its disposed - _authBar = - createBar(S.of(context).authentication, duration: null) - ..show(context); + _authBar = createBar(S.of(context).authentication, duration: null)..show(context); }); } @@ -64,8 +58,7 @@ class AuthPagePinCodeStateImpl extends AuthPageState { WidgetsBinding.instance.addPostFrameCallback((_) async { _pinCodeKey.currentState?.clear(); dismissFlushBar(_authBar); - showBar( - context, S.of(context).failed_authentication(state.error)); + showBar(context, S.of(context).failed_authentication(state.error)); widget.onAuthenticationFinished(false, this); }); @@ -75,8 +68,7 @@ class AuthPagePinCodeStateImpl extends AuthPageState { WidgetsBinding.instance.addPostFrameCallback((_) async { _pinCodeKey.currentState?.clear(); dismissFlushBar(_authBar); - showBar( - context, S.of(context).failed_authentication(state.error)); + showBar(context, S.of(context).failed_authentication(state.error)); widget.onAuthenticationFinished(false, this); }); @@ -102,8 +94,7 @@ class AuthPagePinCodeStateImpl extends AuthPageState { @override void changeProcessText(String text) { dismissFlushBar(_authBar); - _progressBar = createBar(text, duration: null) - ..show(_key.currentContext!); + _progressBar = createBar(text, duration: null)..show(_key.currentContext!); } @override @@ -134,25 +125,33 @@ class AuthPagePinCodeStateImpl extends AuthPageState { @override Widget build(BuildContext context) { return Scaffold( - key: _key, - appBar: CupertinoNavigationBar( - leading: widget.closable - ? Container( - padding: EdgeInsets.only(top: 10), - child: SizedBox( - height: 37, - width: 37, - child: InkWell( - onTap: () => Navigator.of(context).pop(), - child: _backArrowImageDarkTheme, - ), - )) - : Container(), - backgroundColor: Theme.of(context).colorScheme.background, - border: null), - resizeToAvoidBottomInset: false, - body: PinCode((pin, _) => widget.authViewModel.auth(password: pin), - (_) => null, widget.authViewModel.pinLength, false, _pinCodeKey)); + key: _key, + appBar: CupertinoNavigationBar( + leading: widget.closable + ? Container( + padding: EdgeInsets.only(top: 10), + child: SizedBox( + height: 37, + width: 37, + child: InkWell( + onTap: () => Navigator.of(context).pop(), + child: _backArrowImageDarkTheme, + ), + )) + : Container(), + backgroundColor: Theme.of(context).colorScheme.background, + border: null), + resizeToAvoidBottomInset: false, + body: PinCode( + (pin, _) => widget.authViewModel.auth(password: pin), + (_) => null, + widget.authViewModel.setPinRandomized, + widget.authViewModel.pinLength, + widget.authViewModel.pinRandomized, + false, + _pinCodeKey, + ), + ); } void dismissFlushBar(Flushbar? bar) { diff --git a/lib/src/screens/pin_code/pin_code.dart b/lib/src/screens/pin_code/pin_code.dart index 02a917531..35e2a2fb9 100644 --- a/lib/src/screens/pin_code/pin_code.dart +++ b/lib/src/screens/pin_code/pin_code.dart @@ -5,15 +5,20 @@ class PinCode extends PinCodeWidget { PinCode( void Function(String pin, PinCodeState state) onFullPin, void Function(String pin) onChangedPin, + void Function(bool) setPinRandomized, int initialPinLength, + bool initialPinRandomized, bool hasLengthSwitcher, Key key) : super( - key: key, - onFullPin: onFullPin, - onChangedPin: onChangedPin, - hasLengthSwitcher: hasLengthSwitcher, - initialPinLength: initialPinLength); + key: key, + onFullPin: onFullPin, + onChangedPin: onChangedPin, + hasLengthSwitcher: hasLengthSwitcher, + initialPinLength: initialPinLength, + initialPinRandomized: initialPinRandomized, + setPinRandomized: setPinRandomized, + ); @override PinCodeState createState() => PinCodeState(); diff --git a/lib/src/screens/pin_code/pin_code_widget.dart b/lib/src/screens/pin_code/pin_code_widget.dart index 36328aee2..937ed7e70 100644 --- a/lib/src/screens/pin_code/pin_code_widget.dart +++ b/lib/src/screens/pin_code/pin_code_widget.dart @@ -14,14 +14,18 @@ class PinCodeWidget extends StatefulWidget { required this.initialPinLength, required this.onChangedPin, required this.hasLengthSwitcher, + required this.setPinRandomized, + required this.initialPinRandomized, this.onChangedPinLength, }) : super(key: key); final void Function(String pin, PinCodeState state) onFullPin; final void Function(String pin) onChangedPin; final void Function(int length)? onChangedPinLength; + final void Function(bool) setPinRandomized; final bool hasLengthSwitcher; final int initialPinLength; + final bool initialPinRandomized; @override State createState() => PinCodeState(); @@ -44,6 +48,8 @@ class PinCodeState extends State { String title; double _aspectRatio; Flushbar? _progressBar; + late List numbers = []; + bool randomizePin = false; int currentPinLength() => pin.length; @@ -54,6 +60,12 @@ class PinCodeState extends State { pin = ''; title = S.current.enter_your_pin; _aspectRatio = 0; + + randomizePin = widget.initialPinRandomized; + numbers = List.generate(10, (index) => index); + if (randomizePin) { + numbers.shuffle(); + } WidgetsBinding.instance.addPostFrameCallback(_afterLayout); } @@ -118,6 +130,10 @@ class PinCodeState extends State { 'assets/images/face.png', color: Theme.of(context).extension()!.titleColor, ); + final shuffleImage = Image.asset( + 'assets/images/shuffle.png', + color: Theme.of(context).extension()!.titleColor, + ); return RawKeyboardListener( focusNode: FocusNode(), @@ -144,8 +160,7 @@ class PinCodeState extends State { style: TextStyle( fontSize: 20, fontWeight: FontWeight.w500, - color: - Theme.of(context).extension()!.titleColor)), + color: Theme.of(context).extension()!.titleColor)), Spacer(flex: 3), Container( width: 180, @@ -162,7 +177,9 @@ class PinCodeState extends State { shape: BoxShape.circle, color: isFilled ? Theme.of(context).extension()!.titleColor - : Theme.of(context).extension()!.indicatorsColor + : Theme.of(context) + .extension()! + .indicatorsColor .withOpacity(0.25), )); }), @@ -208,9 +225,26 @@ class PinCodeState extends State { const double marginLeft = 15; if (index == 9) { - // Empty container - return Container( - margin: EdgeInsets.only(left: marginLeft, right: marginRight), + // randomize button + return MergeSemantics( + child: Container( + margin: EdgeInsets.only(left: marginLeft, right: marginRight), + child: Semantics( + label: S.current.delete, + button: true, + onTap: () => _toggleRandomize(), + child: TextButton( + onPressed: () => _toggleRandomize(), + style: TextButton.styleFrom( + backgroundColor: randomizePin + ? Theme.of(context).colorScheme.onBackground + : Theme.of(context).colorScheme.background, + shape: CircleBorder(), + ), + child: shuffleImage, + ), + ), + ), ); } else if (index == 10) { index = 0; @@ -225,7 +259,8 @@ class PinCodeState extends State { child: TextButton( onPressed: () => _pop(), style: TextButton.styleFrom( - backgroundColor: Theme.of(context).colorScheme.background, + backgroundColor: + Theme.of(context).colorScheme.background, shape: CircleBorder(), ), child: deleteIconImage, @@ -240,16 +275,18 @@ class PinCodeState extends State { return Container( margin: EdgeInsets.only(left: marginLeft, right: marginRight), child: TextButton( - onPressed: () => _push(index), + onPressed: () => _push(numbers[index]), style: TextButton.styleFrom( backgroundColor: Theme.of(context).colorScheme.background, shape: CircleBorder(), ), - child: Text('$index', + child: Text('${numbers[index]}', style: TextStyle( fontSize: 30.0, fontWeight: FontWeight.w600, - color: Theme.of(context).extension()!.titleColor)), + color: Theme.of(context) + .extension()! + .titleColor)), ), ); }), @@ -290,6 +327,18 @@ class PinCodeState extends State { setState(() => pin = pin.substring(0, pin.length - 1)); } + void _toggleRandomize() { + setState(() { + randomizePin = !randomizePin; + widget.setPinRandomized(randomizePin); + if (!randomizePin) { + numbers = List.generate(10, (index) => index); + } else { + numbers.shuffle(); + } + }); + } + String _changePinLengthText() { return S.current.use + (pinLength == PinCodeState.fourPinLength diff --git a/lib/src/screens/setup_pin_code/setup_pin_code.dart b/lib/src/screens/setup_pin_code/setup_pin_code.dart index 833fd9b60..e8638e822 100644 --- a/lib/src/screens/setup_pin_code/setup_pin_code.dart +++ b/lib/src/screens/setup_pin_code/setup_pin_code.dart @@ -22,6 +22,8 @@ class SetupPinCodePage extends BasePage { Widget body(BuildContext context) => PinCodeWidget( key: pinCodeStateKey, hasLengthSwitcher: true, + setPinRandomized: pinCodeViewModel.setPinRandomized, + initialPinRandomized: pinCodeViewModel.pinRandomized, onFullPin: (String pin, PinCodeState state) async { if (pinCodeViewModel.isOriginalPinCodeFull && !pinCodeViewModel.isRepeatedPinCodeFull) { diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index 6873ffde8..0f3b678f4 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -73,6 +73,7 @@ abstract class SettingsStoreBase with Store { required ExchangeApiMode initialExchangeStatus, required ThemeBase initialTheme, required int initialPinLength, + required bool initialRandomizePinCode, required String initialLanguageCode, required SyncMode initialSyncMode, required bool initialSyncAll, @@ -150,6 +151,7 @@ abstract class SettingsStoreBase with Store { exchangeStatus = initialExchangeStatus, currentTheme = initialTheme, pinCodeLength = initialPinLength, + randomizePinCode = initialRandomizePinCode, languageCode = initialLanguageCode, shouldRequireTOTP2FAForAccessingWallet = initialShouldRequireTOTP2FAForAccessingWallet, shouldRequireTOTP2FAForSendsToContact = initialShouldRequireTOTP2FAForSendsToContact, @@ -369,6 +371,9 @@ abstract class SettingsStoreBase with Store { reaction((_) => pinCodeLength, (int pinLength) => sharedPreferences.setInt(PreferencesKey.currentPinLength, pinLength)); + reaction((_) => randomizePinCode, + (bool randomizePinCode) => sharedPreferences.setBool(PreferencesKey.randomizePinCode, randomizePinCode)); + reaction( (_) => languageCode, (String languageCode) => @@ -683,6 +688,9 @@ abstract class SettingsStoreBase with Store { @observable int pinCodeLength; + @observable + bool randomizePinCode; + @observable PinCodeRequiredDuration pinTimeOutDuration; @@ -931,6 +939,8 @@ abstract class SettingsStoreBase with Store { pinLength = defaultPinLength; } + final randomizePinCode = sharedPreferences.getBool(PreferencesKey.randomizePinCode) ?? false; + final savedLanguageCode = sharedPreferences.getString(PreferencesKey.currentLanguageCode) ?? await LanguageService.localeDetection(); final nodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey); @@ -1171,6 +1181,7 @@ abstract class SettingsStoreBase with Store { initialTheme: savedTheme, actionlistDisplayMode: actionListDisplayMode, initialPinLength: pinLength, + initialRandomizePinCode: randomizePinCode, pinTimeOutDuration: pinCodeTimeOutDuration, seedPhraseLength: seedPhraseWordCount, initialLanguageCode: savedLanguageCode, @@ -1327,6 +1338,8 @@ abstract class SettingsStoreBase with Store { } pinCodeLength = pinLength; + randomizePinCode = sharedPreferences.getBool(PreferencesKey.randomizePinCode) ?? false; + languageCode = sharedPreferences.getString(PreferencesKey.currentLanguageCode) ?? languageCode; shouldShowYatPopup = sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? shouldShowYatPopup; diff --git a/lib/view_model/auth_view_model.dart b/lib/view_model/auth_view_model.dart index 6f6e29662..80cf573e3 100644 --- a/lib/view_model/auth_view_model.dart +++ b/lib/view_model/auth_view_model.dart @@ -30,8 +30,15 @@ abstract class AuthViewModelBase with Store { int get pinLength => _settingsStore.pinCodeLength; + bool get pinRandomized => _settingsStore.randomizePinCode; + bool get isBiometricalAuthenticationAllowed => _settingsStore.allowBiometricalAuthentication; + @action + void setPinRandomized(bool randomized) { + _settingsStore.randomizePinCode = randomized; + } + @observable int _failureCounter; @@ -121,4 +128,4 @@ abstract class AuthViewModelBase with Store { _authService.saveLastAuthTime(); } } -} \ No newline at end of file +} diff --git a/lib/view_model/setup_pin_code_view_model.dart b/lib/view_model/setup_pin_code_view_model.dart index ad503b7dc..29b00a13f 100644 --- a/lib/view_model/setup_pin_code_view_model.dart +++ b/lib/view_model/setup_pin_code_view_model.dart @@ -67,4 +67,10 @@ class SetupPinCodeViewModel { await _authService.setPassword(repeatedPinCode); _settingsStore.pinCodeLength = pinCodeLength; } + + bool get pinRandomized => _settingsStore.randomizePinCode; + + void setPinRandomized(bool randomized) { + _settingsStore.randomizePinCode = randomized; + } }