]> git.proxmox.com Git - flutter/proxmox_login_manager.git/blob - lib/proxmox_tfa_form.dart
tree-wide: use super-initializer parameters where possible
[flutter/proxmox_login_manager.git] / lib / proxmox_tfa_form.dart
1 import 'package:flutter/material.dart';
2 import 'package:proxmox_dart_api_client/proxmox_dart_api_client.dart';
3 import 'package:proxmox_login_manager/proxmox_login_form.dart';
4
5 class ProxmoxTfaForm extends StatefulWidget {
6 final ProxmoxApiClient? apiClient;
7
8 const ProxmoxTfaForm({super.key, this.apiClient});
9
10 @override
11 State<ProxmoxTfaForm> createState() => _ProxmoxTfaFormState();
12 }
13
14 class _ProxmoxTfaFormState extends State<ProxmoxTfaForm> {
15 final TextEditingController _codeController = TextEditingController();
16 bool _isLoading = false;
17 List<String> _tfaKinds = [];
18 String _selectedTfaKind = "";
19
20 @override
21 void initState() {
22 super.initState();
23 _tfaKinds = widget.apiClient!.credentials.tfa!.kinds().toList();
24 _selectedTfaKind = _tfaKinds[0];
25 }
26
27 @override
28 Widget build(BuildContext context) {
29 return Theme(
30 data: ThemeData.dark().copyWith(
31 colorScheme: const ColorScheme.dark().copyWith(
32 secondary: ProxmoxColors.orange,
33 onSecondary: ProxmoxColors.supportGrey)),
34 child: Scaffold(
35 backgroundColor: ProxmoxColors.supportBlue,
36 extendBodyBehindAppBar: true,
37 appBar: AppBar(
38 elevation: 0.0,
39 backgroundColor: Colors.transparent,
40 leading: IconButton(
41 icon: const Icon(Icons.close),
42 onPressed: () => Navigator.of(context).pop(),
43 ),
44 ),
45 body: Stack(
46 alignment: Alignment.center,
47 children: [
48 SingleChildScrollView(
49 child: ConstrainedBox(
50 constraints: BoxConstraints.tightFor(
51 height: MediaQuery.of(context).size.height),
52 child: SafeArea(
53 child: Padding(
54 padding: const EdgeInsets.all(8.0),
55 child: Column(
56 mainAxisAlignment: MainAxisAlignment.start,
57 crossAxisAlignment: CrossAxisAlignment.center,
58 children: <Widget>[
59 const Padding(
60 padding: EdgeInsets.fromLTRB(0, 100.0, 0, 30.0),
61 child: Icon(
62 Icons.lock,
63 size: 48,
64 ),
65 ),
66 const Text(
67 'Verify',
68 style: TextStyle(
69 fontSize: 36,
70 color: Colors.white,
71 fontWeight: FontWeight.bold),
72 ),
73 const Text(
74 'Check your second factor provider',
75 style: TextStyle(
76 color: Colors.white38,
77 fontWeight: FontWeight.bold),
78 ),
79 Padding(
80 padding: const EdgeInsets.fromLTRB(0, 50.0, 0, 8.0),
81 child: SizedBox(
82 width: 175,
83 child: Column(
84 children: <Widget>[
85 DropdownButtonFormField(
86 decoration: const InputDecoration(
87 labelText: 'Method',
88 icon: Icon(Icons.input)),
89 items: _tfaKinds
90 .map((e) => DropdownMenuItem(
91 value: e,
92 child: ListTile(title: Text(e)),
93 ))
94 .toList(),
95 onChanged: (String? value) {
96 setState(() {
97 _selectedTfaKind = value!;
98 });
99 },
100 selectedItemBuilder: (context) =>
101 _tfaKinds.map((e) => Text(e)).toList(),
102 value: _selectedTfaKind,
103 ),
104 TextField(
105 controller: _codeController,
106 textAlign: TextAlign.center,
107 decoration: const InputDecoration(
108 labelText: 'Code',
109 icon: Icon(Icons.pin)),
110 keyboardType: _selectedTfaKind == 'totp'
111 ? TextInputType.number
112 : TextInputType.visiblePassword,
113 autofocus: true,
114 onSubmitted: (value) => _submitTfaCode()),
115 ],
116 ),
117 ),
118 ),
119 Expanded(
120 child: Align(
121 alignment: Alignment.bottomCenter,
122 child: SizedBox(
123 width: MediaQuery.of(context).size.width,
124 child: TextButton(
125 style: TextButton.styleFrom(
126 foregroundColor: Colors.white,
127 backgroundColor: const Color(0xFFE47225),
128 disabledBackgroundColor: Colors.grey,
129 ),
130 onPressed: () => _submitTfaCode(),
131 child: const Text('Continue'),
132 ),
133 ),
134 ),
135 ),
136 ],
137 ),
138 ),
139 ),
140 ),
141 ),
142 if (_isLoading)
143 const ProxmoxProgressOverlay(
144 message: 'Verifying second-factor...',
145 )
146 ],
147 ),
148 ),
149 );
150 }
151
152 Future<void> _submitTfaCode() async {
153 setState(() {
154 _isLoading = true;
155 });
156 try {
157 final client = await widget.apiClient!
158 .finishTfaChallenge(_selectedTfaKind, _codeController.text);
159 if (mounted) Navigator.of(context).pop(client);
160 } on ProxmoxApiException catch (e) {
161 if (mounted) {
162 showDialog(
163 context: context,
164 builder: (context) => ProxmoxApiErrorDialog(
165 exception: e,
166 ),
167 );
168 }
169 } catch (e, trace) {
170 print(e);
171 print(trace);
172 if (mounted) {
173 showDialog(
174 context: context,
175 builder: (context) => ConnectionErrorDialog(
176 exception: e,
177 ),
178 );
179 }
180 }
181 setState(() {
182 _isLoading = false;
183 });
184 }
185 }