import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/home_view/home_view.dart'; import 'package:stackwallet/pages/wallet_view/wallet_view.dart'; // import 'package:stackwallet/providers/global/has_authenticated_start_state_provider.dart'; import 'package:stackwallet/providers/global/prefs_provider.dart'; import 'package:stackwallet/providers/global/secure_store_provider.dart'; import 'package:stackwallet/providers/global/wallets_provider.dart'; // import 'package:stackwallet/providers/global/should_show_lockscreen_on_resume_state_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/biometrics.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_pin_put/custom_pin_put.dart'; import 'package:stackwallet/widgets/shake/shake.dart'; import 'package:tuple/tuple.dart'; class LockscreenView extends ConsumerStatefulWidget { const LockscreenView({ Key? key, required this.routeOnSuccess, required this.biometricsAuthenticationTitle, required this.biometricsLocalizedReason, required this.biometricsCancelButtonString, this.showBackButton = false, this.popOnSuccess = false, this.isInitialAppLogin = false, this.routeOnSuccessArguments, this.biometrics = const Biometrics(), this.onSuccess, }) : super(key: key); static const String routeName = "/lockscreen"; final String routeOnSuccess; final Object? routeOnSuccessArguments; final bool showBackButton; final bool popOnSuccess; final bool isInitialAppLogin; final String biometricsAuthenticationTitle; final String biometricsLocalizedReason; final String biometricsCancelButtonString; final Biometrics biometrics; final VoidCallback? onSuccess; @override ConsumerState createState() => _LockscreenViewState(); } class _LockscreenViewState extends ConsumerState { late final ShakeController _shakeController; late int _attempts; bool _attemptLock = false; late Duration _timeout; static const maxAttemptsBeforeThrottling = 3; Future _onUnlock() async { final now = DateTime.now().toUtc(); ref.read(prefsChangeNotifierProvider).lastUnlocked = now.millisecondsSinceEpoch ~/ 1000; // if (widget.isInitialAppLogin) { // ref.read(hasAuthenticatedOnStartStateProvider.state).state = true; // ref.read(shouldShowLockscreenOnResumeStateProvider.state).state = true; // } widget.onSuccess?.call(); if (widget.popOnSuccess) { Navigator.of(context).pop(widget.routeOnSuccessArguments); } else { unawaited(Navigator.of(context).pushReplacementNamed( widget.routeOnSuccess, arguments: widget.routeOnSuccessArguments, )); if (widget.routeOnSuccess == HomeView.routeName && widget.routeOnSuccessArguments is String) { final walletId = widget.routeOnSuccessArguments as String; unawaited(Navigator.of(context).pushNamed(WalletView.routeName, arguments: Tuple2( walletId, ref .read(walletsChangeNotifierProvider) .getManagerProvider(walletId)))); } } } Future _checkUseBiometrics() async { if (!ref.read(prefsChangeNotifierProvider).isInitialized) { await ref.read(prefsChangeNotifierProvider).init(); } final bool useBiometrics = ref.read(prefsChangeNotifierProvider).useBiometrics; final title = widget.biometricsAuthenticationTitle; final localizedReason = widget.biometricsLocalizedReason; final cancelButtonText = widget.biometricsCancelButtonString; if (useBiometrics) { if (await biometrics.authenticate( title: title, localizedReason: localizedReason, cancelButtonText: cancelButtonText)) { // check if initial log in // if (widget.routeOnSuccess == "/mainview") { // await logIn(await walletsService.networkName, currentWalletName, // await walletsService.getWalletId(currentWalletName)); // } unawaited(_onUnlock()); } // leave this commented to enable pin fall back should biometrics not work properly // else { // Navigator.pop(context); // } } } @override void initState() { _shakeController = ShakeController(); _secureStore = ref.read(secureStoreProvider); biometrics = widget.biometrics; _attempts = 0; _timeout = Duration.zero; _checkUseBiometrics(); super.initState(); } @override dispose() { // _shakeController.dispose(); super.dispose(); } BoxDecoration get _pinPutDecoration { return BoxDecoration( color: Theme.of(context).extension()!.textSubtitle2, border: Border.all( width: 1, color: Theme.of(context).extension()!.textSubtitle2), borderRadius: BorderRadius.circular(6), ); } final _pinTextController = TextEditingController(); final FocusNode _pinFocusNode = FocusNode(); late SecureStorageInterface _secureStore; late Biometrics biometrics; Widget get _body => Background( child: Scaffold( backgroundColor: Theme.of(context).extension()!.background, appBar: AppBar( leading: widget.showBackButton ? AppBarBackButton( onPressed: () async { if (FocusScope.of(context).hasFocus) { FocusScope.of(context).unfocus(); await Future.delayed( const Duration(milliseconds: 70)); } if (mounted) { Navigator.of(context).pop(); } }, ) : Container(), ), body: SafeArea( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Shake( animationDuration: const Duration(milliseconds: 700), animationRange: 12, controller: _shakeController, child: Center( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Center( child: Text( "Enter PIN", style: STextStyles.pageTitleH1(context), ), ), const SizedBox( height: 52, ), CustomPinPut( fieldsCount: Constants.pinLength, eachFieldHeight: 12, eachFieldWidth: 12, textStyle: STextStyles.label(context).copyWith( fontSize: 1, ), focusNode: _pinFocusNode, controller: _pinTextController, useNativeKeyboard: false, obscureText: "", inputDecoration: InputDecoration( border: InputBorder.none, enabledBorder: InputBorder.none, focusedBorder: InputBorder.none, disabledBorder: InputBorder.none, errorBorder: InputBorder.none, focusedErrorBorder: InputBorder.none, fillColor: Theme.of(context) .extension()! .background, counterText: "", ), submittedFieldDecoration: _pinPutDecoration.copyWith( color: Theme.of(context) .extension()! .infoItemIcons, border: Border.all( width: 1, color: Theme.of(context) .extension()! .infoItemIcons, ), ), selectedFieldDecoration: _pinPutDecoration, followingFieldDecoration: _pinPutDecoration, onSubmit: (String pin) async { _attempts++; if (_attempts > maxAttemptsBeforeThrottling) { _attemptLock = true; switch (_attempts) { case 4: _timeout = const Duration(seconds: 30); break; case 5: _timeout = const Duration(seconds: 60); break; case 6: _timeout = const Duration(minutes: 5); break; case 7: _timeout = const Duration(minutes: 10); break; case 8: _timeout = const Duration(minutes: 20); break; case 9: _timeout = const Duration(minutes: 30); break; default: _timeout = const Duration(minutes: 60); } unawaited( Future.delayed(_timeout).then((_) { _attemptLock = false; _attempts = 0; })); } if (_attemptLock) { String prettyTime = ""; if (_timeout.inSeconds >= 60) { prettyTime += "${_timeout.inMinutes} minutes"; } else { prettyTime += "${_timeout.inSeconds} seconds"; } unawaited(showFloatingFlushBar( type: FlushBarType.warning, message: "Incorrect PIN entered too many times. Please wait $prettyTime", context: context, iconAsset: Assets.svg.alertCircle, )); await Future.delayed( const Duration(milliseconds: 100)); _pinTextController.text = ''; return; } final storedPin = await _secureStore.read(key: 'stack_pin'); if (storedPin == pin) { await Future.delayed( const Duration(milliseconds: 200)); unawaited(_onUnlock()); } else { unawaited(_shakeController.shake()); unawaited(showFloatingFlushBar( type: FlushBarType.warning, message: "Incorrect PIN. Please try again", context: context, iconAsset: Assets.svg.alertCircle, )); await Future.delayed( const Duration(milliseconds: 100)); _pinTextController.text = ''; } }, ), ], ), ), ), ], ), ), ), ); @override Widget build(BuildContext context) { return widget.showBackButton ? _body : WillPopScope( onWillPop: () async { return widget.showBackButton; }, child: _body, ); } }