mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-06-28 12:29:51 +00:00
add pin randomization
This commit is contained in:
parent
32e119e24f
commit
3e17d31435
9 changed files with 133 additions and 51 deletions
BIN
assets/images/shuffle.png
Normal file
BIN
assets/images/shuffle.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
|
@ -33,6 +33,7 @@ class PreferencesKey {
|
|||
static const currentTheme = 'current_theme';
|
||||
static const displayActionListModeKey = 'display_list_mode';
|
||||
static const currentPinLength = 'current_pin_length';
|
||||
static const randomizePinCode = 'randomize_pin_code';
|
||||
static const currentLanguageCode = 'language_code';
|
||||
static const currentSeedPhraseLength = 'current_seed_phrase_length';
|
||||
static const currentDefaultSettingsMigrationVersion =
|
||||
|
|
|
@ -19,9 +19,7 @@ abstract class AuthPageState<T extends StatefulWidget> extends State<T> {
|
|||
}
|
||||
|
||||
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<AuthPage> {
|
||||
final _key = GlobalKey<ScaffoldState>();
|
||||
final _pinCodeKey = GlobalKey<PinCodeState>();
|
||||
final _backArrowImageDarkTheme =
|
||||
Image.asset('assets/images/close_button.png');
|
||||
final _backArrowImageDarkTheme = Image.asset('assets/images/close_button.png');
|
||||
ReactionDisposer? _reaction;
|
||||
Flushbar<void>? _authBar;
|
||||
Flushbar<void>? _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<AuthPage> {
|
|||
if (state is IsExecutingState) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
// null duration to make it indefinite until its disposed
|
||||
_authBar =
|
||||
createBar<void>(S.of(context).authentication, duration: null)
|
||||
..show(context);
|
||||
_authBar = createBar<void>(S.of(context).authentication, duration: null)..show(context);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -64,8 +58,7 @@ class AuthPagePinCodeStateImpl extends AuthPageState<AuthPage> {
|
|||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
_pinCodeKey.currentState?.clear();
|
||||
dismissFlushBar(_authBar);
|
||||
showBar<void>(
|
||||
context, S.of(context).failed_authentication(state.error));
|
||||
showBar<void>(context, S.of(context).failed_authentication(state.error));
|
||||
|
||||
widget.onAuthenticationFinished(false, this);
|
||||
});
|
||||
|
@ -75,8 +68,7 @@ class AuthPagePinCodeStateImpl extends AuthPageState<AuthPage> {
|
|||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
_pinCodeKey.currentState?.clear();
|
||||
dismissFlushBar(_authBar);
|
||||
showBar<void>(
|
||||
context, S.of(context).failed_authentication(state.error));
|
||||
showBar<void>(context, S.of(context).failed_authentication(state.error));
|
||||
|
||||
widget.onAuthenticationFinished(false, this);
|
||||
});
|
||||
|
@ -102,8 +94,7 @@ class AuthPagePinCodeStateImpl extends AuthPageState<AuthPage> {
|
|||
@override
|
||||
void changeProcessText(String text) {
|
||||
dismissFlushBar(_authBar);
|
||||
_progressBar = createBar<void>(text, duration: null)
|
||||
..show(_key.currentContext!);
|
||||
_progressBar = createBar<void>(text, duration: null)..show(_key.currentContext!);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -134,25 +125,33 @@ class AuthPagePinCodeStateImpl extends AuthPageState<AuthPage> {
|
|||
@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<dynamic>? bar) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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<StatefulWidget> createState() => PinCodeState();
|
||||
|
@ -44,6 +48,8 @@ class PinCodeState<T extends PinCodeWidget> extends State<T> {
|
|||
String title;
|
||||
double _aspectRatio;
|
||||
Flushbar<void>? _progressBar;
|
||||
late List<int> numbers = [];
|
||||
bool randomizePin = false;
|
||||
|
||||
int currentPinLength() => pin.length;
|
||||
|
||||
|
@ -54,6 +60,12 @@ class PinCodeState<T extends PinCodeWidget> extends State<T> {
|
|||
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<T extends PinCodeWidget> extends State<T> {
|
|||
'assets/images/face.png',
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
);
|
||||
final shuffleImage = Image.asset(
|
||||
'assets/images/shuffle.png',
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
);
|
||||
|
||||
return RawKeyboardListener(
|
||||
focusNode: FocusNode(),
|
||||
|
@ -144,8 +160,7 @@ class PinCodeState<T extends PinCodeWidget> extends State<T> {
|
|||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
color:
|
||||
Theme.of(context).extension<CakeTextTheme>()!.titleColor)),
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor)),
|
||||
Spacer(flex: 3),
|
||||
Container(
|
||||
width: 180,
|
||||
|
@ -162,7 +177,9 @@ class PinCodeState<T extends PinCodeWidget> extends State<T> {
|
|||
shape: BoxShape.circle,
|
||||
color: isFilled
|
||||
? Theme.of(context).extension<CakeTextTheme>()!.titleColor
|
||||
: Theme.of(context).extension<PinCodeTheme>()!.indicatorsColor
|
||||
: Theme.of(context)
|
||||
.extension<PinCodeTheme>()!
|
||||
.indicatorsColor
|
||||
.withOpacity(0.25),
|
||||
));
|
||||
}),
|
||||
|
@ -208,9 +225,26 @@ class PinCodeState<T extends PinCodeWidget> extends State<T> {
|
|||
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<T extends PinCodeWidget> extends State<T> {
|
|||
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<T extends PinCodeWidget> extends State<T> {
|
|||
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<CakeTextTheme>()!.titleColor)),
|
||||
color: Theme.of(context)
|
||||
.extension<CakeTextTheme>()!
|
||||
.titleColor)),
|
||||
),
|
||||
);
|
||||
}),
|
||||
|
@ -290,6 +327,18 @@ class PinCodeState<T extends PinCodeWidget> extends State<T> {
|
|||
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
|
||||
|
|
|
@ -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<PinCodeWidget> state) async {
|
||||
if (pinCodeViewModel.isOriginalPinCodeFull &&
|
||||
!pinCodeViewModel.isRepeatedPinCodeFull) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue