Line data Source code
1 : // Copyright 2026 The terCAD team. All rights reserved.
2 : // Use of this source code is governed by a CC BY-NC-ND 4.0 license that can be found in the LICENSE file.
3 :
4 : import 'package:app_finance/_classes/controller/encryption_handler.dart';
5 : import 'package:app_finance/_classes/controller/focus_controller.dart';
6 : import 'package:app_finance/_classes/herald/app_design.dart';
7 : import 'package:app_finance/_classes/herald/app_locale.dart';
8 : import 'package:app_finance/_classes/storage/app_preferences.dart';
9 : import 'package:app_finance/_configs/theme_helper.dart';
10 : import 'package:app_finance/design/button/full_sized_button_widget.dart';
11 : import 'package:app_finance/design/generic/notification_bar.dart';
12 : import 'package:app_finance/design/wrapper/input_wrapper.dart';
13 : import 'package:app_finance/design/wrapper/single_scroll_wrapper.dart';
14 : import 'package:app_finance/pages/_interfaces/abstract_page_state.dart';
15 : import 'package:material_ui/material_ui.dart';
16 : import 'package:simple_totp_auth/simple_totp_auth.dart';
17 :
18 : class SecurityTab extends StatefulWidget {
19 : final VoidCallback onSuccess;
20 :
21 0 : const SecurityTab({super.key, required this.onSuccess});
22 :
23 0 : @override
24 0 : SecurityTabState createState() => SecurityTabState();
25 : }
26 :
27 : class SecurityTabState extends AbstractPageState<SecurityTab> {
28 : late FocusController focus;
29 : late TextEditingController password;
30 :
31 0 : @override
32 : void initState() {
33 0 : focus = FocusController();
34 0 : password = TextEditingController();
35 0 : super.initState();
36 : }
37 :
38 0 : @override
39 : void dispose() {
40 0 : focus.dispose();
41 0 : password.dispose();
42 0 : super.dispose();
43 : }
44 :
45 0 : @override
46 : Drawer? buildDrawer() => null;
47 :
48 0 : @override
49 0 : List<Widget> getBarActions(NavigatorState nav) => [];
50 :
51 0 : @override
52 : Widget? getBarLeading(NavigatorState nav) => null;
53 :
54 0 : @override
55 0 : String getTitle() => AppLocale.labels.securityHeadline;
56 :
57 0 : @override
58 0 : String getButtonName() => AppLocale.labels.btnConfirm;
59 :
60 0 : @override
61 0 : Widget buildButton(BuildContext context, BoxConstraints constraints) => FullSizedButtonWidget(
62 : constraints: constraints,
63 0 : controller: focus,
64 0 : onPressed: () => triggerActionButton(),
65 0 : title: getButtonName(),
66 : icon: Icons.password,
67 : );
68 :
69 0 : void triggerActionButton() {
70 0 : if (password.text.isEmpty) {
71 0 : NotificationBar.showSnackBar(context, AppLocale.labels.securePasswordEmpty, true);
72 : return;
73 : }
74 0 : final secret = AppPreferences.get(AppPreferences.prefPrivacyKey) ?? '';
75 0 : if (password.text != TOTP(secret: secret).now()) {
76 0 : NotificationBar.showSnackBar(context, AppLocale.labels.secureOtpCodeInvalid, true);
77 0 : final key = AppPreferences.get(AppPreferences.prefRecoveryKey) ?? '';
78 0 : if (EncryptionHandler.getHashString(password.text) != key) {
79 0 : NotificationBar.showSnackBar(context, AppLocale.labels.securePasswordNotMatch, true);
80 : return;
81 : }
82 : }
83 0 : widget.onSuccess();
84 : }
85 :
86 0 : @override
87 : Widget buildContent(BuildContext context, BoxConstraints constraints) {
88 0 : final indent = ThemeHelper.getIndent();
89 0 : return SingleScrollWrapper(
90 0 : controller: focus,
91 0 : child: Padding(
92 0 : padding: EdgeInsets.all(indent),
93 0 : child: Column(
94 0 : crossAxisAlignment: AppDesign.getAlignment(),
95 0 : children: [
96 0 : InputWrapper.text(
97 0 : title: '${AppLocale.labels.secureOtpCode} / ${AppLocale.labels.securePassword}',
98 0 : controller: password,
99 : isRequired: true,
100 : ),
101 : ],
102 : ),
103 : ),
104 : );
105 : }
106 : }
|