dev
dart 243 lines 8.6 KB
Raw
1 import 'package:another_flushbar/flushbar.dart';
2 import 'package:cake_wallet/core/execution_state.dart';
3 import 'package:cake_wallet/core/totp_request_details.dart';
4 import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
5 import 'package:cake_wallet/utils/show_bar.dart';
6 import 'package:cake_wallet/view_model/auth_state.dart';
7 import 'package:cw_core/utils/print_verbose.dart';
8 import 'package:flutter/material.dart';
9
10 import 'package:cake_wallet/generated/i18n.dart';
11 import 'package:cake_wallet/src/screens/base_page.dart';
12 import 'package:cake_wallet/src/screens/setup_2fa/widgets/popup_cancellable_alert.dart';
13 import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
14 import 'package:cake_wallet/src/widgets/primary_button.dart';
15 import 'package:cake_wallet/utils/show_pop_up.dart';
16 import 'package:cake_wallet/view_model/set_up_2fa_viewmodel.dart';
17 import 'package:flutter_mobx/flutter_mobx.dart';
18 import 'package:mobx/mobx.dart';
19
20 import '../../../routes.dart';
21
22 typedef OnTotpAuthenticationFinished = void Function(bool, TotpAuthCodePageState);
23
24 class TotpAuthCodePage extends StatefulWidget {
25 TotpAuthCodePage(
26 this.setup2FAViewModel, {
27 required this.totpArguments,
28 });
29
30 final Setup2FAViewModel setup2FAViewModel;
31
32 final TotpAuthArgumentsModel totpArguments;
33
34 @override
35 TotpAuthCodePageState createState() => TotpAuthCodePageState();
36 }
37
38 class TotpAuthCodePageState extends State<TotpAuthCodePage> {
39 final _key = GlobalKey<ScaffoldState>();
40
41 ReactionDisposer? _reaction;
42 Flushbar<void>? _authBar;
43 Flushbar<void>? _progressBar;
44
45 @override
46 void initState() {
47 if (widget.totpArguments.onTotpAuthenticationFinished != null) {
48 _reaction ??= reaction((_) => widget.setup2FAViewModel.state, (ExecutionState state) {
49 WidgetsBinding.instance.addPostFrameCallback((_) {
50 if (state is ExecutedSuccessfullyState) {
51 widget.totpArguments.onTotpAuthenticationFinished!(true, this);
52 }
53
54 if (state is FailureState) {
55 printV(state.error);
56 widget.totpArguments.onTotpAuthenticationFinished!(false, this);
57 }
58
59 if (state is AuthenticationBanned) {
60 widget.totpArguments.onTotpAuthenticationFinished!(false, this);
61 }
62 });
63 });
64 }
65
66 super.initState();
67 }
68
69 @override
70 void dispose() {
71 _reaction?.reaction.dispose();
72 super.dispose();
73 }
74
75 void changeProcessText(String text) {
76 dismissFlushBar(_authBar);
77 _progressBar = createBar<void>(text, context, duration: null)..show(_key.currentContext!);
78 }
79
80 Future<void> close({String? route, dynamic arguments}) async {
81 if (_key.currentContext == null) {
82 throw Exception('Key context is null. Should be not happened');
83 }
84 await Future<void>.delayed(Duration(milliseconds: 50));
85 if (route != null) {
86 Navigator.of(_key.currentContext!).pushReplacementNamed(route, arguments: arguments);
87 } else {
88 Navigator.of(_key.currentContext!).pop();
89 }
90 }
91
92 @override
93 Widget build(BuildContext context) {
94 return Scaffold(
95 key: _key,
96 body: TOTPEnterCode(
97 setup2FAViewModel: widget.setup2FAViewModel,
98 isForSetup: widget.totpArguments.isForSetup ?? false,
99 isClosable: widget.totpArguments.isClosable ?? true,
100 ),
101 );
102 }
103
104 void dismissFlushBar(Flushbar<dynamic>? bar) {
105 WidgetsBinding.instance.addPostFrameCallback((_) async {
106 await bar?.dismiss();
107 });
108 }
109 }
110
111 class TOTPEnterCode extends StatefulWidget {
112 const TOTPEnterCode({
113 required this.setup2FAViewModel,
114 required this.isForSetup,
115 required this.isClosable,
116 });
117
118 final Setup2FAViewModel setup2FAViewModel;
119 final bool isForSetup;
120 final bool isClosable;
121
122 @override
123 State<TOTPEnterCode> createState() => _TOTPEnterCodeState();
124 }
125
126 class _TOTPEnterCodeState extends State<TOTPEnterCode> {
127 final totpController = TextEditingController();
128
129 @override
130 void initState() {
131 super.initState();
132 totpController.addListener(() {
133 widget.setup2FAViewModel.enteredOTPCode = totpController.text;
134 });
135 }
136
137 @override
138 Widget build(BuildContext context) {
139 return Container(
140 color: Theme.of(context).colorScheme.surface,
141 child: SafeArea(
142 child: Column(
143 children: [
144 ModalTopBar(
145 title: widget.isForSetup ? S.current.setup_2fa : S.current.verify_with_2fa,
146 leadingIcon: Icon(Icons.arrow_back_ios_new),
147 leadingSemanticLabel: S.current.seed_alert_back,
148 onLeadingPressed: Navigator.of(context).pop,
149 ),
150 Expanded(
151 child: Padding(
152 padding: const EdgeInsets.symmetric(
153 horizontal: 24,
154 ),
155 child: Column(
156 children: [
157 BaseTextFormField(
158 textAlign: TextAlign.left,
159 hintText: S.current.totp_code,
160 controller: totpController,
161 keyboardType: TextInputType.number,
162 placeholderTextStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(
163 color: Theme.of(context).colorScheme.onSurfaceVariant,
164 fontSize: 16,
165 ),
166 ),
167 SizedBox(height: 16),
168 Text(
169 S.current.please_fill_totp,
170 style: Theme.of(context).textTheme.bodySmall?.copyWith(
171 height: 1.2,
172 color: Theme.of(context).colorScheme.onSurfaceVariant,
173 ),
174 textAlign: TextAlign.center,
175 ),
176 Spacer(),
177 Observer(
178 builder: (context) {
179 return PrimaryButton(
180 isDisabled: widget.setup2FAViewModel.enteredOTPCode.length != 8,
181 onPressed: () async {
182 final result = await widget.setup2FAViewModel
183 .totp2FAAuth(totpController.text, widget.isForSetup);
184 final bannedState =
185 widget.setup2FAViewModel.state is AuthenticationBanned;
186
187 await showPopUp<void>(
188 context: context,
189 builder: (BuildContext context) {
190 return PopUpCancellableAlertDialog(
191 contentText:
192 _textDisplayedInPopupOnResult(result, bannedState, context),
193 actionButtonText: S.of(context).ok,
194 buttonAction: () {
195 result ? widget.setup2FAViewModel.success() : null;
196 if (widget.isForSetup && result) {
197 Navigator.pop(context);
198 // Navigator.of(context)
199 // .popAndPushNamed(Routes.modify2FAPage);
200 } else {
201 Navigator.of(context).pop(result);
202 }
203 },
204 );
205 },
206 );
207 if (widget.isForSetup && result) {
208 if (context.mounted) {
209 Navigator.pushReplacementNamed(context, Routes.modify2FAPage);
210 }
211 }
212 },
213 text: S.of(context).continue_text,
214 color: Theme.of(context).colorScheme.primary,
215 textColor: Theme.of(context).colorScheme.onPrimary,
216 );
217 },
218 ),
219 SizedBox(height: 24),
220 ],
221 ),
222 ),
223 ),
224 ],
225 ),
226 ),
227 );
228 }
229
230 String _textDisplayedInPopupOnResult(bool result, bool bannedState, BuildContext context) {
231 switch (result) {
232 case true:
233 return widget.isForSetup ? S.current.totp_2fa_success : S.current.totp_verification_success;
234 case false:
235 if (bannedState) {
236 final state = widget.setup2FAViewModel.state as AuthenticationBanned;
237 return S.of(context).failed_authentication(state.error);
238 } else {
239 return S.current.totp_2fa_failure;
240 }
241 }
242 }
243 }