]> git.proxmox.com Git - flutter/proxmox_login_manager.git/blobdiff - lib/proxmox_login_form.dart
tree wide: avoid using private types in public APIs for State
[flutter/proxmox_login_manager.git] / lib / proxmox_login_form.dart
index d92417316da6442d713d3afde1d2333049abfcd8..5776ea516ea50796bdaa7be0c7bb387fa5dc1be7 100644 (file)
@@ -24,9 +24,9 @@ class ProxmoxProgressModel {
 
 // FIXME: copied from pve_flutter_frontend, re-use common set
 class ProxmoxColors {
-  static final Color orange = Color(0xFFE57000);
-  static final Color supportGrey = Color(0xFFABBABA);
-  static final Color supportBlue = Color(0xFF00617F);
+  static const Color orange = Color(0xFFE57000);
+  static const Color supportGrey = Color(0xFFABBABA);
+  static const Color supportBlue = Color(0xFF00617F);
 }
 
 class ProxmoxLoginForm extends StatefulWidget {
@@ -38,7 +38,7 @@ class ProxmoxLoginForm extends StatefulWidget {
   final PveAccessDomainModel? selectedDomain;
   final ValueChanged<PveAccessDomainModel?> onDomainChanged;
   final Function? onPasswordSubmitted;
-  final Function? onOriginSubmitted;
+  final Function onOriginSubmitted;
   final Function? onSavePasswordChanged;
   final bool? canSavePassword;
   final bool? passwordSaved;
@@ -53,14 +53,14 @@ class ProxmoxLoginForm extends StatefulWidget {
     this.selectedDomain,
     required this.onDomainChanged,
     this.onPasswordSubmitted,
-    this.onOriginSubmitted,
+    required this.onOriginSubmitted,
     this.onSavePasswordChanged,
     this.canSavePassword,
     this.passwordSaved,
   }) : super(key: key);
 
   @override
-  _ProxmoxLoginFormState createState() => _ProxmoxLoginFormState();
+  State<ProxmoxLoginForm> createState() => _ProxmoxLoginFormState();
 }
 
 class _ProxmoxLoginFormState extends State<ProxmoxLoginForm> {
@@ -72,15 +72,16 @@ class _ProxmoxLoginFormState extends State<ProxmoxLoginForm> {
   Widget build(BuildContext context) {
     if (widget.accessDomains == null) {
       return TextFormField(
-        decoration: InputDecoration(
+        decoration: const InputDecoration(
             icon: Icon(Icons.vpn_lock),
             labelText: 'Origin',
             hintText: 'e.g. 192.168.1.2',
             helperText:
                 'Protocol (https) and default port (8006 or 443) implied'),
+        textInputAction: TextInputAction.next,
         controller: widget.originController,
         validator: widget.originValidator,
-        onFieldSubmitted: (value) => widget.onOriginSubmitted!(),
+        onFieldSubmitted: (value) => widget.onOriginSubmitted(),
       );
     }
 
@@ -89,7 +90,7 @@ class _ProxmoxLoginFormState extends State<ProxmoxLoginForm> {
         mainAxisAlignment: MainAxisAlignment.center,
         children: [
           TextFormField(
-            decoration: InputDecoration(
+            decoration: const InputDecoration(
               icon: Icon(Icons.vpn_lock),
               labelText: 'Origin',
             ),
@@ -97,7 +98,7 @@ class _ProxmoxLoginFormState extends State<ProxmoxLoginForm> {
             enabled: false,
           ),
           TextFormField(
-            decoration: InputDecoration(
+            decoration: const InputDecoration(
               icon: Icon(Icons.person),
               labelText: 'Username',
             ),
@@ -108,17 +109,17 @@ class _ProxmoxLoginFormState extends State<ProxmoxLoginForm> {
               }
               return null;
             },
-            autofillHints: [AutofillHints.username],
+            autofillHints: const [AutofillHints.username],
           ),
           DropdownButtonFormField(
-            decoration: InputDecoration(icon: Icon(Icons.domain)),
+            decoration: const InputDecoration(icon: Icon(Icons.domain)),
             items: widget.accessDomains!
                 .map((e) => DropdownMenuItem(
+                      value: e,
                       child: ListTile(
                         title: Text(e!.realm),
                         subtitle: Text(e.comment ?? ''),
                       ),
-                      value: e,
                     ))
                 .toList(),
             onChanged: widget.onDomainChanged,
@@ -129,7 +130,7 @@ class _ProxmoxLoginFormState extends State<ProxmoxLoginForm> {
           Stack(
             children: [
               TextFormField(
-                decoration: InputDecoration(
+                decoration: const InputDecoration(
                   icon: Icon(Icons.lock),
                   labelText: 'Password',
                 ),
@@ -144,12 +145,12 @@ class _ProxmoxLoginFormState extends State<ProxmoxLoginForm> {
                   return null;
                 },
                 onFieldSubmitted: (value) => widget.onPasswordSubmitted!(),
-                autofillHints: [AutofillHints.password],
+                autofillHints: const [AutofillHints.password],
               ),
               Align(
                 alignment: Alignment.bottomRight,
                 child: IconButton(
-                  constraints: BoxConstraints.tight(Size(58, 58)),
+                  constraints: BoxConstraints.tight(const Size(58, 58)),
                   iconSize: 24,
                   icon:
                       Icon(_obscure ? Icons.visibility : Icons.visibility_off),
@@ -199,7 +200,7 @@ class ProxmoxLoginPage extends StatefulWidget {
     this.password,
   }) : super(key: key);
   @override
-  _ProxmoxLoginPageState createState() => _ProxmoxLoginPageState();
+  State<ProxmoxLoginPage> createState() => _ProxmoxLoginPageState();
 }
 
 class _ProxmoxLoginPageState extends State<ProxmoxLoginPage> {
@@ -250,11 +251,56 @@ class _ProxmoxLoginPageState extends State<ProxmoxLoginPage> {
             disabledBackgroundColor: Colors.grey,
           ),
         ),
-        toggleableActiveColor: ProxmoxColors.orange,
-        colorScheme: ColorScheme.dark().copyWith(
+        colorScheme: const ColorScheme.dark().copyWith(
             primary: ProxmoxColors.orange,
             secondary: ProxmoxColors.orange,
             onSecondary: ProxmoxColors.supportGrey),
+        checkboxTheme: CheckboxThemeData(
+          fillColor: MaterialStateProperty.resolveWith<Color?>(
+              (Set<MaterialState> states) {
+            if (states.contains(MaterialState.disabled)) {
+              return null;
+            }
+            if (states.contains(MaterialState.selected)) {
+              return ProxmoxColors.orange;
+            }
+            return null;
+          }),
+        ),
+        radioTheme: RadioThemeData(
+          fillColor: MaterialStateProperty.resolveWith<Color?>(
+              (Set<MaterialState> states) {
+            if (states.contains(MaterialState.disabled)) {
+              return null;
+            }
+            if (states.contains(MaterialState.selected)) {
+              return ProxmoxColors.orange;
+            }
+            return null;
+          }),
+        ),
+        switchTheme: SwitchThemeData(
+          thumbColor: MaterialStateProperty.resolveWith<Color?>(
+              (Set<MaterialState> states) {
+            if (states.contains(MaterialState.disabled)) {
+              return null;
+            }
+            if (states.contains(MaterialState.selected)) {
+              return ProxmoxColors.orange;
+            }
+            return null;
+          }),
+          trackColor: MaterialStateProperty.resolveWith<Color?>(
+              (Set<MaterialState> states) {
+            if (states.contains(MaterialState.disabled)) {
+              return null;
+            }
+            if (states.contains(MaterialState.selected)) {
+              return ProxmoxColors.orange;
+            }
+            return null;
+          }),
+        ),
       ),
       child: Scaffold(
         backgroundColor: ProxmoxColors.supportBlue,
@@ -263,7 +309,7 @@ class _ProxmoxLoginPageState extends State<ProxmoxLoginPage> {
           elevation: 0.0,
           backgroundColor: Colors.transparent,
           leading: IconButton(
-            icon: Icon(Icons.close),
+            icon: const Icon(Icons.close),
             onPressed: () => Navigator.of(context).pop(),
           ),
         ),
@@ -333,21 +379,18 @@ class _ProxmoxLoginPageState extends State<ProxmoxLoginPage> {
                                       _selectedDomain = value;
                                     });
                                   },
-                                  onOriginSubmitted: _submittButtonEnabled
-                                      ? () {
-                                          final isValid =
-                                              _formKey.currentState!.validate();
-                                          setState(() {
-                                            _submittButtonEnabled = isValid;
-                                          });
-                                          if (isValid) {
-                                            setState(() {
-                                              _accessDomains =
-                                                  _getAccessDomains();
-                                            });
-                                          }
-                                        }
-                                      : null,
+                                  onOriginSubmitted: () {
+                                    final isValid =
+                                        _formKey.currentState!.validate();
+                                    setState(() {
+                                      _submittButtonEnabled = isValid;
+                                    });
+                                    if (isValid) {
+                                      setState(() {
+                                        _accessDomains = _getAccessDomains();
+                                      });
+                                    }
+                                  },
                                   onPasswordSubmitted: _submittButtonEnabled
                                       ? () {
                                           final isValid =
@@ -364,7 +407,7 @@ class _ProxmoxLoginPageState extends State<ProxmoxLoginPage> {
                                 Expanded(
                                   child: Align(
                                     alignment: Alignment.bottomCenter,
-                                    child: Container(
+                                    child: SizedBox(
                                       width: MediaQuery.of(context).size.width,
                                       child: TextButton(
                                         onPressed: _submittButtonEnabled
@@ -388,7 +431,7 @@ class _ProxmoxLoginPageState extends State<ProxmoxLoginPage> {
                                                 }
                                               }
                                             : null,
-                                        child: Text('Continue'),
+                                        child: const Text('Continue'),
                                       ),
                                     ),
                                   ),
@@ -431,12 +474,24 @@ class _ProxmoxLoginPageState extends State<ProxmoxLoginPage> {
       var client = await proxclient.authenticate(
           '$username@$realm', password, origin, settings.sslValidation!);
 
-      if (client.credentials.tfa != null) {
-        client = await Navigator.of(context).push(MaterialPageRoute(
+      if (client.credentials.tfa != null &&
+          client.credentials.tfa!.kinds().isNotEmpty) {
+        if (!mounted) return;
+        ProxmoxApiClient? tfaclient =
+            await Navigator.of(context).push(MaterialPageRoute(
           builder: (context) => ProxmoxTfaForm(
             apiClient: client,
           ),
         ));
+
+        if (tfaclient != null) {
+          client = tfaclient;
+        } else {
+          setState(() {
+            _progressModel.inProgress -= 1;
+          });
+          return;
+        }
       }
 
       final status = await client.getClusterStatus();
@@ -483,20 +538,23 @@ class _ProxmoxLoginPageState extends State<ProxmoxLoginPage> {
       }
       await loginStorage.saveToDisk();
 
-      Navigator.of(context).pop(client);
+      if (mounted) {
+        Navigator.of(context).pop(client);
+      }
     } on proxclient.ProxmoxApiException catch (e) {
       print(e);
+      if (!mounted) return;
       if (e.message.contains('No ticket')) {
         showDialog(
           context: context,
           builder: (context) => AlertDialog(
-            title: Text('Version Error'),
-            content: Text(
+            title: const Text('Version Error'),
+            content: const Text(
                 'Proxmox VE version not supported, please update your instance to use this app.'),
             actions: [
               TextButton(
                 onPressed: () => Navigator.of(context).pop(),
-                child: Text('Close'),
+                child: const Text('Close'),
               ),
             ],
           ),
@@ -512,16 +570,18 @@ class _ProxmoxLoginPageState extends State<ProxmoxLoginPage> {
     } catch (e, trace) {
       print(e);
       print(trace);
-      if (e.runtimeType == HandshakeException) {
-        showDialog(
-          context: context,
-          builder: (context) => ProxmoxCertificateErrorDialog(),
-        );
-      } else {
-        showDialog(
-          context: context,
-          builder: (context) => ConnectionErrorDialog(exception: e),
-        );
+      if (mounted) {
+        if (e.runtimeType == HandshakeException) {
+          showDialog(
+            context: context,
+            builder: (context) => const ProxmoxCertificateErrorDialog(),
+          );
+        } else {
+          showDialog(
+            context: context,
+            builder: (context) => ConnectionErrorDialog(exception: e),
+          );
+        }
       }
     }
     setState(() {
@@ -535,17 +595,21 @@ class _ProxmoxLoginPageState extends State<ProxmoxLoginPage> {
     try {
       response = await proxclient.accessDomains(uri, settings.sslValidation!);
     } on proxclient.ProxmoxApiException catch (e) {
-      showDialog(
-        context: context,
-        builder: (context) => ProxmoxApiErrorDialog(
-          exception: e,
-        ),
-      );
+      if (mounted) {
+        showDialog(
+          context: context,
+          builder: (context) => ProxmoxApiErrorDialog(
+            exception: e,
+          ),
+        );
+      }
     } on HandshakeException {
-      showDialog(
-        context: context,
-        builder: (context) => ProxmoxCertificateErrorDialog(),
-      );
+      if (mounted) {
+        showDialog(
+          context: context,
+          builder: (context) => const ProxmoxCertificateErrorDialog(),
+        );
+      }
     }
     return response;
   }
@@ -555,12 +619,14 @@ class _ProxmoxLoginPageState extends State<ProxmoxLoginPage> {
     try {
       response = await _loadAccessDomains(uri);
     } catch (e) {
-      showDialog(
-        context: context,
-        builder: (context) => ConnectionErrorDialog(
-          exception: e,
-        ),
-      );
+      if (mounted) {
+        showDialog(
+          context: context,
+          builder: (context) => ConnectionErrorDialog(
+            exception: e,
+          ),
+        );
+      }
     }
     return response;
   }
@@ -580,7 +646,7 @@ class _ProxmoxLoginPageState extends State<ProxmoxLoginPage> {
     var host = _originController.text.trim();
     var apiBaseUrl = normalizeUrl(host);
 
-    RegExp portRE = new RegExp(r":\d{1,5}$");
+    RegExp portRE = RegExp(r":\d{1,5}$");
 
     List<PveAccessDomainModel?>? response;
 
@@ -639,19 +705,19 @@ class ProxmoxProgressOverlay extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     return Container(
-      decoration: new BoxDecoration(color: Colors.black.withOpacity(0.5)),
+      decoration: BoxDecoration(color: Colors.black.withOpacity(0.5)),
       child: Center(
         child: Column(
           mainAxisAlignment: MainAxisAlignment.center,
           children: [
             Text(
               message,
-              style: TextStyle(
+              style: const TextStyle(
                 fontSize: 20,
               ),
             ),
-            Padding(
-              padding: const EdgeInsets.only(top: 20.0),
+            const Padding(
+              padding: EdgeInsets.only(top: 20.0),
               child: CircularProgressIndicator(),
             )
           ],
@@ -662,7 +728,7 @@ class ProxmoxProgressOverlay extends StatelessWidget {
 }
 
 class ConnectionErrorDialog extends StatelessWidget {
-  final exception;
+  final Object exception;
 
   const ConnectionErrorDialog({
     Key? key,
@@ -672,12 +738,12 @@ class ConnectionErrorDialog extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     return AlertDialog(
-      title: Text('Connection error'),
-      content: Text('Could not establish connection: ${this.exception}'),
+      title: const Text('Connection error'),
+      content: Text('Could not establish connection: $exception'),
       actions: [
         TextButton(
           onPressed: () => Navigator.of(context).pop(),
-          child: Text('Close'),
+          child: const Text('Close'),
         ),
       ],
     );
@@ -695,14 +761,14 @@ class ProxmoxApiErrorDialog extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     return AlertDialog(
-      title: Text('API Error'),
+      title: const Text('API Error'),
       content: SingleChildScrollView(
         child: Text(exception.message),
       ),
       actions: [
         TextButton(
           onPressed: () => Navigator.of(context).pop(),
-          child: Text('Close'),
+          child: const Text('Close'),
         ),
       ],
     );
@@ -717,16 +783,16 @@ class ProxmoxCertificateErrorDialog extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     return AlertDialog(
-      title: Text('Certificate error'),
+      title: const Text('Certificate error'),
       content: SingleChildScrollView(
         child: Column(
           crossAxisAlignment: CrossAxisAlignment.start,
           children: [
-            Text('Your connection is not private.'),
+            const Text('Your connection is not private.'),
             Text(
               'Note: Consider to disable SSL validation,'
               ' if you use a self signed, not commonly trusted, certificate.',
-              style: Theme.of(context).textTheme.caption,
+              style: Theme.of(context).textTheme.bodySmall,
             ),
           ],
         ),
@@ -734,13 +800,13 @@ class ProxmoxCertificateErrorDialog extends StatelessWidget {
       actions: [
         TextButton(
           onPressed: () => Navigator.of(context).pop(),
-          child: Text('Close'),
+          child: const Text('Close'),
         ),
         TextButton(
           onPressed: () => Navigator.of(context).pushReplacement(
               MaterialPageRoute(
-                  builder: (context) => ProxmoxGeneralSettingsForm())),
-          child: Text('Settings'),
+                  builder: (context) => const ProxmoxGeneralSettingsForm())),
+          child: const Text('Settings'),
         )
       ],
     );
@@ -752,7 +818,7 @@ Uri normalizeUrl(String urlText) {
     urlText = urlText.substring('https://'.length);
   }
   if (urlText.startsWith('http://')) {
-    throw new Exception("HTTP without TLS is not supported");
+    throw Exception("HTTP without TLS is not supported");
   }
 
   return Uri.https(urlText, '');