]> git.proxmox.com Git - flutter/proxmox_login_manager.git/blob - lib/proxmox_login_selector.dart
tree-wide: use super-initializer parameters where possible
[flutter/proxmox_login_manager.git] / lib / proxmox_login_selector.dart
1 import 'package:flutter/material.dart';
2 import 'package:built_collection/built_collection.dart';
3 import 'package:proxmox_login_manager/proxmox_general_settings_form.dart';
4 import 'package:proxmox_login_manager/proxmox_login_form.dart';
5 import 'package:proxmox_login_manager/proxmox_login_model.dart';
6 import 'package:proxmox_dart_api_client/proxmox_dart_api_client.dart'
7 as proxclient;
8 import 'package:proxmox_login_manager/extension.dart';
9 import 'package:proxmox_login_manager/proxmox_password_store.dart';
10
11 typedef OnLoginCallback = Function(proxclient.ProxmoxApiClient client);
12
13 class ProxmoxLoginSelector extends StatefulWidget {
14 final OnLoginCallback? onLogin;
15
16 const ProxmoxLoginSelector({super.key, this.onLogin});
17
18 @override
19 State<ProxmoxLoginSelector> createState() => _ProxmoxLoginSelectorState();
20 }
21
22 class _ProxmoxLoginSelectorState extends State<ProxmoxLoginSelector> {
23 Future<ProxmoxLoginStorage?>? loginStorage;
24 @override
25 void initState() {
26 super.initState();
27 loginStorage = ProxmoxLoginStorage.fromLocalStorage();
28 }
29
30 @override
31 Widget build(BuildContext context) {
32 return SafeArea(
33 child: Scaffold(
34 backgroundColor: Theme.of(context).colorScheme.background,
35 appBar: AppBar(
36 title: const Column(
37 crossAxisAlignment: CrossAxisAlignment.start,
38 children: [
39 Text(
40 'Proxmox',
41 style: TextStyle(
42 fontSize: 14,
43 ),
44 ),
45 Text(
46 'Virtual Environment',
47 style: TextStyle(
48 fontSize: 14,
49 ),
50 )
51 ],
52 ),
53 actions: [
54 IconButton(
55 icon: const Icon(Icons.settings),
56 onPressed: () {
57 Navigator.of(context).push(MaterialPageRoute(
58 builder: (context) => const ProxmoxGeneralSettingsForm(),
59 ));
60 })
61 ],
62 ),
63 body: FutureBuilder<ProxmoxLoginStorage?>(
64 future: loginStorage,
65 builder: (context, snapshot) {
66 if (!snapshot.hasData) {
67 return const Center(
68 child: CircularProgressIndicator(),
69 );
70 }
71 if (snapshot.hasData &&
72 (snapshot.data!.logins?.isEmpty ?? true)) {
73 return const Center(
74 child: Text('Add an account'),
75 );
76 }
77 var items = <Widget>[];
78 final BuiltList<ProxmoxLoginModel> logins =
79 snapshot.data?.logins ?? BuiltList<ProxmoxLoginModel>();
80
81 final activeSessions =
82 logins.rebuild((b) => b.where((b) => b.activeSession));
83
84 if (activeSessions.isNotEmpty) {
85 items.addAll([
86 const Padding(
87 padding: EdgeInsets.all(12.0),
88 child: Text(
89 'Active Sessions',
90 style: TextStyle(
91 fontSize: 18,
92 fontWeight: FontWeight.bold,
93 ),
94 ),
95 ),
96 ...activeSessions.map((s) => ListTile(
97 title: Text(s.fullHostname),
98 subtitle: Text(s.fullUsername),
99 trailing: const Icon(Icons.navigate_next),
100 leading: PopupMenuButton(
101 icon: const Icon(Icons.more_vert,
102 color: Colors.green),
103 itemBuilder: (context) => [
104 PopupMenuItem(
105 child: ListTile(
106 dense: true,
107 leading: const Icon(Icons.logout),
108 title: const Text('Logout'),
109 onTap: () async {
110 await snapshot.data!
111 .rebuild((b) => b.logins
112 .rebuildWhere((m) => s == m,
113 (b) => b..ticket = ''))
114 .saveToDisk();
115 refreshFromStorage();
116 if (context.mounted) {
117 Navigator.of(context).pop();
118 }
119 },
120 ),
121 ),
122 ]),
123 onTap: () => _login(user: s),
124 )),
125 ]);
126 }
127 items.addAll([
128 const Padding(
129 padding: EdgeInsets.all(12.0),
130 child: Text(
131 'Available Sites',
132 style: TextStyle(
133 fontSize: 18,
134 fontWeight: FontWeight.bold,
135 ),
136 ),
137 ),
138 ...logins.where((b) => !b.activeSession).map((login) =>
139 ListTile(
140 title: Text(login.fullHostname),
141 subtitle: Text(login.fullUsername),
142 trailing: const Icon(Icons.navigate_next),
143 leading: PopupMenuButton(
144 itemBuilder: (context) => [
145 if (login.passwordSaved ?? false)
146 PopupMenuItem(
147 child: ListTile(
148 dense: true,
149 leading: const Icon(Icons.key_off),
150 title: const Text('Delete Password'),
151 onTap: () async {
152 await deletePassword(login.identifier!);
153
154 await snapshot.data!
155 .rebuild((b) => b
156 ..logins.rebuildWhere(
157 (m) => m == login,
158 (b) =>
159 b..passwordSaved = false))
160 .saveToDisk();
161 refreshFromStorage();
162 if (context.mounted) {
163 Navigator.of(context).pop();
164 }
165 },
166 ),
167 ),
168 PopupMenuItem(
169 child: ListTile(
170 dense: true,
171 leading: const Icon(Icons.delete),
172 title: const Text('Delete'),
173 onTap: () async {
174 await deletePassword(login.identifier!);
175 await snapshot.data!
176 .rebuild(
177 (b) => b.logins.remove(login))
178 .saveToDisk();
179 refreshFromStorage();
180 if (context.mounted) {
181 Navigator.of(context).pop();
182 }
183 },
184 ),
185 ),
186 ]),
187 onTap: () => _login(user: login),
188 ))
189 ]);
190 return ListView(
191 children: items,
192 );
193 }),
194 floatingActionButton: FloatingActionButton.extended(
195 onPressed: () => _login(isCreate: true),
196 label: const Text('Add'),
197 icon: const Icon(Icons.account_circle),
198 ),
199 ),
200 );
201 }
202
203 Future<void> _login({ProxmoxLoginModel? user, bool isCreate = false}) async {
204 String? password;
205 bool activeSession = user?.activeSession ?? false;
206 String ticket = (activeSession ? user?.ticket : null) ?? '';
207 bool passwordSaved = user != null && (user.passwordSaved ?? false);
208
209 if (ticket == '' && passwordSaved) {
210 password = await getPassword(user.identifier!);
211 }
212
213 if (mounted) {
214 final client = await Navigator.of(context).push(MaterialPageRoute(
215 builder: (context) => ProxmoxLoginPage(
216 userModel: user,
217 isCreate: isCreate,
218 ticket: ticket,
219 password: password,
220 )));
221 refreshFromStorage();
222 if (client != null) {
223 widget.onLogin!(client);
224 }
225 }
226 }
227
228 void refreshFromStorage() {
229 setState(() {
230 loginStorage = ProxmoxLoginStorage.fromLocalStorage();
231 });
232 }
233 }