]> git.proxmox.com Git - flutter/proxmox_login_manager.git/blobdiff - lib/proxmox_login_selector.dart
optionally save passwords with biometric storage
[flutter/proxmox_login_manager.git] / lib / proxmox_login_selector.dart
index 72770013445900417c0b972192ae86d8afb3ec65..e1af146caa85560815765e80c842a2581d6055f8 100644 (file)
@@ -1,23 +1,26 @@
 import 'package:flutter/material.dart';
+import 'package:built_collection/built_collection.dart';
 import 'package:proxmox_login_manager/proxmox_general_settings_form.dart';
 import 'package:proxmox_login_manager/proxmox_login_form.dart';
 import 'package:proxmox_login_manager/proxmox_login_model.dart';
 import 'package:proxmox_dart_api_client/proxmox_dart_api_client.dart'
     as proxclient;
+import 'package:proxmox_login_manager/extension.dart';
+import 'package:proxmox_login_manager/proxmox_password_store.dart';
 
 typedef OnLoginCallback = Function(proxclient.ProxmoxApiClient client);
 
 class ProxmoxLoginSelector extends StatefulWidget {
-  final OnLoginCallback onLogin;
+  final OnLoginCallback? onLogin;
 
-  const ProxmoxLoginSelector({Key key, this.onLogin}) : super(key: key);
+  const ProxmoxLoginSelector({Key? key, this.onLogin}) : super(key: key);
 
   @override
   _ProxmoxLoginSelectorState createState() => _ProxmoxLoginSelectorState();
 }
 
 class _ProxmoxLoginSelectorState extends State<ProxmoxLoginSelector> {
-  Future<ProxmoxLoginStorage> loginStorage;
+  Future<ProxmoxLoginStorage?>? loginStorage;
   @override
   void initState() {
     super.initState();
@@ -28,6 +31,7 @@ class _ProxmoxLoginSelectorState extends State<ProxmoxLoginSelector> {
   Widget build(BuildContext context) {
     return SafeArea(
       child: Scaffold(
+        backgroundColor: Theme.of(context).colorScheme.background,
         appBar: AppBar(
           title: Column(
             crossAxisAlignment: CrossAxisAlignment.start,
@@ -56,43 +60,128 @@ class _ProxmoxLoginSelectorState extends State<ProxmoxLoginSelector> {
                 })
           ],
         ),
-        body: FutureBuilder<ProxmoxLoginStorage>(
+        body: FutureBuilder<ProxmoxLoginStorage?>(
             future: loginStorage,
             builder: (context, snapshot) {
-              if (snapshot.hasData && (snapshot.data.logins?.isEmpty ?? true)) {
+              if (!snapshot.hasData) {
+                return Center(
+                  child: CircularProgressIndicator(),
+                );
+              }
+              if (snapshot.hasData &&
+                  (snapshot.data!.logins?.isEmpty ?? true)) {
                 return Center(
                   child: Text('Add an account'),
                 );
               }
+              var items = <Widget>[];
+              final BuiltList<ProxmoxLoginModel> logins =
+                  snapshot.data?.logins ?? BuiltList<ProxmoxLoginModel>();
+
+              final activeSessions =
+                  logins.rebuild((b) => b.where((b) => b.activeSession));
+
+              if (activeSessions.isNotEmpty) {
+                items.addAll([
+                  Padding(
+                    padding: const EdgeInsets.all(12.0),
+                    child: Text(
+                      'Active Sessions',
+                      style: TextStyle(
+                        fontSize: 18,
+                        fontWeight: FontWeight.bold,
+                      ),
+                    ),
+                  ),
+                  ...activeSessions.map((s) => ListTile(
+                        title: Text(s.fullHostname),
+                        subtitle: Text(s.fullUsername),
+                        trailing: Icon(Icons.navigate_next),
+                        leading: PopupMenuButton(
+                            icon: Icon(Icons.more_vert, color: Colors.green),
+                            itemBuilder: (context) => [
+                                  PopupMenuItem(
+                                    child: ListTile(
+                                      dense: true,
+                                      leading: Icon(Icons.logout),
+                                      title: Text('Logout'),
+                                      onTap: () async {
+                                        await snapshot.data!
+                                            .rebuild((b) => b.logins
+                                                .rebuildWhere((m) => s == m,
+                                                    (b) => b..ticket = ''))
+                                            .saveToDisk();
+                                        refreshFromStorage();
+                                        Navigator.of(context).pop();
+                                      },
+                                    ),
+                                  ),
+                                ]),
+                        onTap: () => _login(user: s),
+                      )),
+                ]);
+              }
+              items.addAll([
+                Padding(
+                  padding: const EdgeInsets.all(12.0),
+                  child: Text(
+                    'Available Sites',
+                    style: TextStyle(
+                      fontSize: 18,
+                      fontWeight: FontWeight.bold,
+                    ),
+                  ),
+                ),
+                ...logins.where((b) => !b.activeSession).map((login) =>
+                    ListTile(
+                      title: Text(login.fullHostname),
+                      subtitle: Text(login.fullUsername),
+                      trailing: Icon(Icons.navigate_next),
+                      leading: PopupMenuButton(
+                          itemBuilder: (context) => [
+                                if (login.passwordSaved ?? false)
+                                  PopupMenuItem(
+                                    child: ListTile(
+                                      dense: true,
+                                      leading: Icon(Icons.key_off),
+                                      title: Text('Delete Password'),
+                                      onTap: () async {
+                                        await deletePassword(login.identifier!);
 
+                                        await snapshot.data!
+                                            .rebuild((b) => b
+                                              ..logins.rebuildWhere(
+                                                  (m) => m == login,
+                                                  (b) =>
+                                                      b..passwordSaved = false))
+                                            .saveToDisk();
+                                        refreshFromStorage();
+                                        Navigator.of(context).pop();
+                                      },
+                                    ),
+                                  ),
+                                PopupMenuItem(
+                                  child: ListTile(
+                                    dense: true,
+                                    leading: Icon(Icons.delete),
+                                    title: Text('Delete'),
+                                    onTap: () async {
+                                      await deletePassword(login.identifier!);
+                                      await snapshot.data!
+                                          .rebuild(
+                                              (b) => b.logins.remove(login))
+                                          .saveToDisk();
+                                      refreshFromStorage();
+                                      Navigator.of(context).pop();
+                                    },
+                                  ),
+                                ),
+                              ]),
+                      onTap: () => _login(user: login),
+                    ))
+              ]);
               return ListView(
-                children: snapshot.data?.logins
-                        ?.map((l) => ListTile(
-                              title: Text(l.origin.host),
-                              subtitle: Text(l.fullUsername),
-                              trailing: Icon(Icons.navigate_next),
-                              leading: PopupMenuButton(
-                                  itemBuilder: (context) => [
-                                        PopupMenuItem(
-                                          child: ListTile(
-                                            dense: true,
-                                            leading: Icon(Icons.delete),
-                                            title: Text('Delete'),
-                                            onTap: () {
-                                              snapshot.data
-                                                  .rebuild(
-                                                      (b) => b.logins.remove(l))
-                                                  .saveToDisk();
-                                              refreshFromStorage();
-                                              Navigator.of(context).pop();
-                                            },
-                                          ),
-                                        )
-                                      ]),
-                              onTap: () => _login(user: l),
-                            ))
-                        ?.toList() ??
-                    [],
+                children: items,
               );
             }),
         floatingActionButton: FloatingActionButton.extended(
@@ -104,15 +193,25 @@ class _ProxmoxLoginSelectorState extends State<ProxmoxLoginSelector> {
     );
   }
 
-  Future<void> _login({ProxmoxLoginModel user, bool isCreate = false}) async {
+  Future<void> _login({ProxmoxLoginModel? user, bool isCreate = false}) async {
+    String? password;
+    String ticket = user?.ticket ?? '';
+    bool passwordSaved = user != null && (user.passwordSaved ?? false);
+
+    if (ticket == '' && passwordSaved) {
+      password = await getPassword(user.identifier!);
+    }
+
     final client = await Navigator.of(context).push(MaterialPageRoute(
         builder: (context) => ProxmoxLoginPage(
               userModel: user,
               isCreate: isCreate,
+              ticket: user?.ticket,
+              password: password,
             )));
     refreshFromStorage();
     if (client != null) {
-      widget.onLogin(client);
+      widget.onLogin!(client);
     }
   }