mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-12-30 15:29:41 +00:00
321 lines
12 KiB
Dart
321 lines
12 KiB
Dart
/*
|
|
* This file is part of Stack Wallet.
|
|
*
|
|
* Copyright (c) 2023 Cypher Stack
|
|
* All Rights Reserved.
|
|
* The code is distributed under GPLv3 license, see LICENSE file for details.
|
|
* Generated by Cypher Stack on 2023-05-26
|
|
*
|
|
*/
|
|
|
|
import 'dart:async';
|
|
import 'dart:io';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
import '../../notifications/show_flush_bar.dart';
|
|
import '../../providers/global/prefs_provider.dart';
|
|
import '../../providers/global/secure_store_provider.dart';
|
|
import '../../themes/stack_colors.dart';
|
|
import '../../utilities/assets.dart';
|
|
import '../../utilities/biometrics.dart';
|
|
import '../../utilities/flutter_secure_storage_interface.dart';
|
|
import '../../utilities/text_styles.dart';
|
|
import '../../widgets/background.dart';
|
|
import '../../widgets/custom_buttons/app_bar_icon_button.dart';
|
|
import '../../widgets/custom_pin_put/custom_pin_put.dart';
|
|
import '../home_view/home_view.dart';
|
|
|
|
class CreatePinView extends ConsumerStatefulWidget {
|
|
const CreatePinView({
|
|
super.key,
|
|
this.popOnSuccess = false,
|
|
this.biometrics = const Biometrics(),
|
|
});
|
|
|
|
static const String routeName = "/createPin";
|
|
|
|
final Biometrics biometrics;
|
|
final bool popOnSuccess;
|
|
|
|
@override
|
|
ConsumerState<CreatePinView> createState() => _CreatePinViewState();
|
|
}
|
|
|
|
class _CreatePinViewState extends ConsumerState<CreatePinView> {
|
|
BoxDecoration get _pinPutDecoration {
|
|
return BoxDecoration(
|
|
color: Theme.of(context).extension<StackColors>()!.infoItemIcons,
|
|
border: Border.all(
|
|
width: 1,
|
|
color: Theme.of(context).extension<StackColors>()!.infoItemIcons,
|
|
),
|
|
borderRadius: BorderRadius.circular(6),
|
|
);
|
|
}
|
|
|
|
final PageController _pageController =
|
|
PageController(initialPage: 0, keepPage: true);
|
|
|
|
// Attributes for Page 1 of the pageview
|
|
final TextEditingController _pinPutController1 = TextEditingController();
|
|
final FocusNode _pinPutFocusNode1 = FocusNode();
|
|
|
|
// Attributes for Page 2 of the pageview
|
|
final TextEditingController _pinPutController2 = TextEditingController();
|
|
final FocusNode _pinPutFocusNode2 = FocusNode();
|
|
|
|
late SecureStorageInterface _secureStore;
|
|
late Biometrics biometrics;
|
|
|
|
int pinCount = 1;
|
|
|
|
@override
|
|
initState() {
|
|
_secureStore = ref.read(secureStoreProvider);
|
|
biometrics = widget.biometrics;
|
|
|
|
super.initState();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_pageController.dispose();
|
|
_pinPutController1.dispose();
|
|
_pinPutController2.dispose();
|
|
_pinPutFocusNode1.dispose();
|
|
_pinPutFocusNode2.dispose();
|
|
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
// int pinCount = 1;
|
|
return Background(
|
|
child: Scaffold(
|
|
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
|
|
appBar: AppBar(
|
|
leading: AppBarBackButton(
|
|
onPressed: () async {
|
|
if (FocusScope.of(context).hasFocus) {
|
|
FocusScope.of(context).unfocus();
|
|
await Future<void>.delayed(const Duration(milliseconds: 70));
|
|
}
|
|
if (mounted) {
|
|
Navigator.of(context).pop(widget.popOnSuccess);
|
|
}
|
|
},
|
|
),
|
|
),
|
|
body: SafeArea(
|
|
child: PageView(
|
|
controller: _pageController,
|
|
physics: const NeverScrollableScrollPhysics(),
|
|
children: [
|
|
// page 1
|
|
Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Text(
|
|
"Create a PIN",
|
|
style: STextStyles.pageTitleH1(context),
|
|
),
|
|
const SizedBox(
|
|
height: 8,
|
|
),
|
|
Text(
|
|
"This PIN protects access to your wallet.",
|
|
style: STextStyles.subtitle(context),
|
|
),
|
|
const SizedBox(
|
|
height: 36,
|
|
),
|
|
CustomPinPut(
|
|
fieldsCount: pinCount,
|
|
eachFieldHeight: 12,
|
|
eachFieldWidth: 12,
|
|
textStyle: STextStyles.label(context).copyWith(
|
|
fontSize: 1,
|
|
),
|
|
focusNode: _pinPutFocusNode1,
|
|
controller: _pinPutController1,
|
|
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<StackColors>()!
|
|
.background,
|
|
counterText: "",
|
|
),
|
|
isRandom:
|
|
ref.read(prefsChangeNotifierProvider).randomizePIN,
|
|
submittedFieldDecoration: _pinPutDecoration,
|
|
selectedFieldDecoration: _pinPutDecoration,
|
|
followingFieldDecoration: _pinPutDecoration,
|
|
onPinLengthChanged: (newLength) {
|
|
setState(() {
|
|
pinCount = newLength;
|
|
});
|
|
},
|
|
onSubmit: (String pin) {
|
|
if (pin.length < 4) {
|
|
showFloatingFlushBar(
|
|
type: FlushBarType.warning,
|
|
message: "PIN not long enough!",
|
|
iconAsset: Assets.svg.alertCircle,
|
|
context: context,
|
|
);
|
|
} else {
|
|
_pageController.nextPage(
|
|
duration: const Duration(milliseconds: 300),
|
|
curve: Curves.linear,
|
|
);
|
|
}
|
|
},
|
|
),
|
|
],
|
|
),
|
|
|
|
// page 2
|
|
Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Text(
|
|
"Confirm PIN",
|
|
style: STextStyles.pageTitleH1(context),
|
|
),
|
|
const SizedBox(
|
|
height: 8,
|
|
),
|
|
Text(
|
|
"This PIN protects access to your wallet.",
|
|
style: STextStyles.subtitle(context),
|
|
),
|
|
const SizedBox(
|
|
height: 36,
|
|
),
|
|
CustomPinPut(
|
|
fieldsCount: pinCount,
|
|
eachFieldHeight: 12,
|
|
eachFieldWidth: 12,
|
|
textStyle: STextStyles.infoSmall(context).copyWith(
|
|
color: Theme.of(context)
|
|
.extension<StackColors>()!
|
|
.textSubtitle3,
|
|
fontSize: 1,
|
|
),
|
|
focusNode: _pinPutFocusNode2,
|
|
controller: _pinPutController2,
|
|
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<StackColors>()!
|
|
.background,
|
|
counterText: "",
|
|
),
|
|
submittedFieldDecoration: _pinPutDecoration.copyWith(
|
|
color: Theme.of(context)
|
|
.extension<StackColors>()!
|
|
.infoItemIcons,
|
|
border: Border.all(
|
|
width: 1,
|
|
color: Theme.of(context)
|
|
.extension<StackColors>()!
|
|
.infoItemIcons,
|
|
),
|
|
),
|
|
selectedFieldDecoration: _pinPutDecoration,
|
|
followingFieldDecoration: _pinPutDecoration,
|
|
isRandom:
|
|
ref.read(prefsChangeNotifierProvider).randomizePIN,
|
|
onSubmit: (String pin) async {
|
|
// _onSubmitCount++;
|
|
// if (_onSubmitCount - _onSubmitFailCount > 1) return;
|
|
|
|
if (_pinPutController1.text == _pinPutController2.text) {
|
|
// ask if want to use biometrics
|
|
final bool useBiometrics = (Platform.isLinux)
|
|
? false
|
|
: await biometrics.authenticate(
|
|
cancelButtonText: "SKIP",
|
|
localizedReason:
|
|
"You can use your fingerprint to unlock the wallet and confirm transactions.",
|
|
title: "Enable fingerprint authentication",
|
|
);
|
|
|
|
//TODO investigate why this crashes IOS, maybe ios persists securestorage even after an uninstall?
|
|
// This should never fail as we are writing a new pin
|
|
// assert(
|
|
// (await _secureStore.read(key: "stack_pin")) == null);
|
|
// possible alternative to the above but it does not guarantee we aren't overwriting a pin
|
|
// if (!Platform.isLinux)
|
|
// assert((await _secureStore.read(key: "stack_pin")) ==
|
|
// null);
|
|
assert(
|
|
ref.read(prefsChangeNotifierProvider).hasPin == false,
|
|
);
|
|
|
|
await _secureStore.write(key: "stack_pin", value: pin);
|
|
|
|
ref.read(prefsChangeNotifierProvider).useBiometrics =
|
|
useBiometrics;
|
|
ref.read(prefsChangeNotifierProvider).hasPin = true;
|
|
|
|
await Future<void>.delayed(
|
|
const Duration(milliseconds: 200),
|
|
);
|
|
|
|
if (mounted) {
|
|
if (!widget.popOnSuccess) {
|
|
Navigator.of(context).pushNamedAndRemoveUntil(
|
|
HomeView.routeName,
|
|
(route) => false,
|
|
);
|
|
} else {
|
|
Navigator.of(context).pop();
|
|
}
|
|
}
|
|
} else {
|
|
// _onSubmitFailCount++;
|
|
_pageController.animateTo(
|
|
0,
|
|
duration: const Duration(milliseconds: 300),
|
|
curve: Curves.linear,
|
|
);
|
|
|
|
showFloatingFlushBar(
|
|
type: FlushBarType.warning,
|
|
message: "PIN codes do not match. Try again.",
|
|
context: context,
|
|
iconAsset: Assets.svg.alertCircle,
|
|
);
|
|
|
|
_pinPutController1.text = '';
|
|
_pinPutController2.text = '';
|
|
}
|
|
},
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|