2022-08-26 08:11:35 +00:00
|
|
|
import 'dart:io';
|
|
|
|
|
|
|
|
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/providers/global/prefs_provider.dart';
|
2022-11-09 22:43:26 +00:00
|
|
|
import 'package:stackwallet/providers/global/secure_store_provider.dart';
|
2022-08-26 08:11:35 +00:00
|
|
|
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';
|
2022-09-22 23:48:50 +00:00
|
|
|
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
2022-08-26 08:11:35 +00:00
|
|
|
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
|
|
|
import 'package:stackwallet/widgets/custom_pin_put/custom_pin_put.dart';
|
|
|
|
|
|
|
|
class CreatePinView extends ConsumerStatefulWidget {
|
|
|
|
const CreatePinView({
|
|
|
|
Key? key,
|
|
|
|
this.popOnSuccess = false,
|
|
|
|
this.biometrics = const Biometrics(),
|
|
|
|
}) : super(key: key);
|
|
|
|
|
|
|
|
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(
|
2022-09-22 23:48:50 +00:00
|
|
|
color: Theme.of(context).extension<StackColors>()!.textSubtitle3,
|
|
|
|
border: Border.all(
|
|
|
|
width: 1,
|
|
|
|
color: Theme.of(context).extension<StackColors>()!.textSubtitle3),
|
2022-08-26 08:11:35 +00:00
|
|
|
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();
|
|
|
|
|
2022-11-09 23:48:43 +00:00
|
|
|
late SecureStorageInterface _secureStore;
|
2022-08-26 08:11:35 +00:00
|
|
|
late Biometrics biometrics;
|
|
|
|
|
|
|
|
@override
|
|
|
|
initState() {
|
2022-11-09 22:43:26 +00:00
|
|
|
_secureStore = ref.read(secureStoreProvider);
|
2022-08-26 08:11:35 +00:00
|
|
|
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) {
|
|
|
|
return Scaffold(
|
2022-09-22 23:48:50 +00:00
|
|
|
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
|
2022-08-26 08:11:35 +00:00
|
|
|
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",
|
2022-09-22 22:17:21 +00:00
|
|
|
style: STextStyles.pageTitleH1(context),
|
2022-08-26 08:11:35 +00:00
|
|
|
),
|
|
|
|
const SizedBox(
|
|
|
|
height: 8,
|
|
|
|
),
|
|
|
|
Text(
|
|
|
|
"This PIN protects access to your wallet.",
|
2022-09-22 22:17:21 +00:00
|
|
|
style: STextStyles.subtitle(context),
|
2022-08-26 08:11:35 +00:00
|
|
|
),
|
|
|
|
const SizedBox(
|
|
|
|
height: 36,
|
|
|
|
),
|
|
|
|
CustomPinPut(
|
|
|
|
fieldsCount: Constants.pinLength,
|
|
|
|
eachFieldHeight: 12,
|
|
|
|
eachFieldWidth: 12,
|
2022-09-22 22:17:21 +00:00
|
|
|
textStyle: STextStyles.label(context).copyWith(
|
2022-08-26 08:11:35 +00:00
|
|
|
fontSize: 1,
|
|
|
|
),
|
|
|
|
focusNode: _pinPutFocusNode1,
|
|
|
|
controller: _pinPutController1,
|
|
|
|
useNativeKeyboard: false,
|
|
|
|
obscureText: "",
|
2022-09-21 00:46:07 +00:00
|
|
|
inputDecoration: InputDecoration(
|
2022-08-26 08:11:35 +00:00
|
|
|
border: InputBorder.none,
|
|
|
|
enabledBorder: InputBorder.none,
|
|
|
|
focusedBorder: InputBorder.none,
|
|
|
|
disabledBorder: InputBorder.none,
|
|
|
|
errorBorder: InputBorder.none,
|
|
|
|
focusedErrorBorder: InputBorder.none,
|
2022-09-22 23:48:50 +00:00
|
|
|
fillColor:
|
|
|
|
Theme.of(context).extension<StackColors>()!.background,
|
2022-08-26 08:11:35 +00:00
|
|
|
counterText: "",
|
|
|
|
),
|
|
|
|
submittedFieldDecoration: _pinPutDecoration.copyWith(
|
2022-09-22 23:48:50 +00:00
|
|
|
color: Theme.of(context)
|
|
|
|
.extension<StackColors>()!
|
|
|
|
.infoItemIcons,
|
2022-08-26 08:11:35 +00:00
|
|
|
border: Border.all(
|
|
|
|
width: 1,
|
2022-09-22 23:48:50 +00:00
|
|
|
color: Theme.of(context)
|
|
|
|
.extension<StackColors>()!
|
|
|
|
.infoItemIcons,
|
2022-08-26 08:11:35 +00:00
|
|
|
),
|
|
|
|
),
|
|
|
|
selectedFieldDecoration: _pinPutDecoration,
|
|
|
|
followingFieldDecoration: _pinPutDecoration,
|
|
|
|
onSubmit: (String pin) {
|
|
|
|
if (pin.length == Constants.pinLength) {
|
|
|
|
_pageController.nextPage(
|
|
|
|
duration: const Duration(milliseconds: 300),
|
|
|
|
curve: Curves.linear,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
|
|
|
|
// page 2
|
|
|
|
Column(
|
|
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
children: [
|
|
|
|
Text(
|
|
|
|
"Confirm PIN",
|
2022-09-22 22:17:21 +00:00
|
|
|
style: STextStyles.pageTitleH1(context),
|
2022-08-26 08:11:35 +00:00
|
|
|
),
|
|
|
|
const SizedBox(
|
|
|
|
height: 8,
|
|
|
|
),
|
|
|
|
Text(
|
|
|
|
"This PIN protects access to your wallet.",
|
2022-09-22 22:17:21 +00:00
|
|
|
style: STextStyles.subtitle(context),
|
2022-08-26 08:11:35 +00:00
|
|
|
),
|
|
|
|
const SizedBox(
|
|
|
|
height: 36,
|
|
|
|
),
|
|
|
|
CustomPinPut(
|
|
|
|
fieldsCount: Constants.pinLength,
|
|
|
|
eachFieldHeight: 12,
|
|
|
|
eachFieldWidth: 12,
|
2022-09-26 17:34:41 +00:00
|
|
|
textStyle: STextStyles.infoSmall(context).copyWith(
|
|
|
|
color: Theme.of(context)
|
|
|
|
.extension<StackColors>()!
|
|
|
|
.textSubtitle3,
|
2022-08-26 08:11:35 +00:00
|
|
|
fontSize: 1,
|
|
|
|
),
|
|
|
|
focusNode: _pinPutFocusNode2,
|
|
|
|
controller: _pinPutController2,
|
|
|
|
useNativeKeyboard: false,
|
|
|
|
obscureText: "",
|
2022-09-21 00:46:07 +00:00
|
|
|
inputDecoration: InputDecoration(
|
2022-08-26 08:11:35 +00:00
|
|
|
border: InputBorder.none,
|
|
|
|
enabledBorder: InputBorder.none,
|
|
|
|
focusedBorder: InputBorder.none,
|
|
|
|
disabledBorder: InputBorder.none,
|
|
|
|
errorBorder: InputBorder.none,
|
|
|
|
focusedErrorBorder: InputBorder.none,
|
2022-09-22 23:48:50 +00:00
|
|
|
fillColor:
|
|
|
|
Theme.of(context).extension<StackColors>()!.background,
|
2022-08-26 08:11:35 +00:00
|
|
|
counterText: "",
|
|
|
|
),
|
|
|
|
submittedFieldDecoration: _pinPutDecoration.copyWith(
|
2022-09-22 23:48:50 +00:00
|
|
|
color: Theme.of(context)
|
|
|
|
.extension<StackColors>()!
|
|
|
|
.infoItemIcons,
|
2022-08-26 08:11:35 +00:00
|
|
|
border: Border.all(
|
|
|
|
width: 1,
|
2022-09-22 23:48:50 +00:00
|
|
|
color: Theme.of(context)
|
|
|
|
.extension<StackColors>()!
|
|
|
|
.infoItemIcons,
|
2022-08-26 08:11:35 +00:00
|
|
|
),
|
|
|
|
),
|
|
|
|
selectedFieldDecoration: _pinPutDecoration,
|
|
|
|
followingFieldDecoration: _pinPutDecoration,
|
|
|
|
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 = '';
|
|
|
|
}
|
|
|
|
},
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|