public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Azharul Haque <haque@azharul.com>
To: pve-devel@lists.proxmox.com
Cc: haque@azharul.com
Subject: [PATCH login-manager v3 3/5] refactor: ui: split password form into its own widget
Date: Thu, 20 Aug 2026 23:41:42 -0400	[thread overview]
Message-ID: <20260821034147.30194-6-haque@azharul.com> (raw)
In-Reply-To: <20260821034147.30194-1-haque@azharul.com>

Group the username field together with the password field below the
realm selector (it previously sat above it), and extract the credential
fields -- username, password and the save-password checkbox -- into a
private _ProxmoxPasswordForm widget. No functional change.

This prepares for OpenID Connect realm support in the following commit:
an OpenID realm has no username/password to enter in the app, so the
credential fields need to be shown or replaced as one unit depending on
the selected realm's type.

Suggested-by: Shan Shaji <s.shaji@proxmox.com>
Signed-off-by: Azharul Haque <haque@azharul.com>
---
 lib/proxmox_login_form.dart | 169 ++++++++++++++++++++++--------------
 1 file changed, 105 insertions(+), 64 deletions(-)

diff --git a/lib/proxmox_login_form.dart b/lib/proxmox_login_form.dart
index 6039407..8d8f2da 100644
--- a/lib/proxmox_login_form.dart
+++ b/lib/proxmox_login_form.dart
@@ -63,10 +63,6 @@ class ProxmoxLoginForm extends StatefulWidget {
 }
 
 class _ProxmoxLoginFormState extends State<ProxmoxLoginForm> {
-  bool _obscure = true;
-  bool? _savePwCheckbox;
-  FocusNode? passwordFocusNode;
-
   @override
   Widget build(BuildContext context) {
     if (widget.accessDomains == null) {
@@ -97,20 +93,6 @@ class _ProxmoxLoginFormState extends State<ProxmoxLoginForm> {
             controller: widget.originController,
             enabled: false,
           ),
-          TextFormField(
-            decoration: const InputDecoration(
-              icon: Icon(Icons.person),
-              labelText: 'Username',
-            ),
-            controller: widget.usernameController,
-            validator: (value) {
-              if (value!.isEmpty) {
-                return 'Please enter username';
-              }
-              return null;
-            },
-            autofillHints: const [AutofillHints.username],
-          ),
           DropdownButtonFormField(
             decoration: const InputDecoration(icon: Icon(Icons.domain)),
             items: widget.accessDomains!
@@ -127,56 +109,115 @@ class _ProxmoxLoginFormState extends State<ProxmoxLoginForm> {
                 widget.accessDomains!.map((e) => Text(e!.realm)).toList(),
             initialValue: widget.selectedDomain,
           ),
-          Stack(
-            children: [
-              TextFormField(
-                decoration: const InputDecoration(
-                  icon: Icon(Icons.lock),
-                  labelText: 'Password',
-                ),
-                controller: widget.passwordController,
-                obscureText: _obscure,
-                autocorrect: false,
-                focusNode: passwordFocusNode,
-                validator: (value) {
-                  if (value!.isEmpty) {
-                    return 'Please enter password';
-                  }
-                  return null;
-                },
-                onFieldSubmitted: (value) => widget.onPasswordSubmitted!(),
-                autofillHints: const [AutofillHints.password],
-              ),
-              Align(
-                alignment: Alignment.bottomRight,
-                child: IconButton(
-                  constraints: BoxConstraints.tight(const Size(58, 58)),
-                  iconSize: 24,
-                  tooltip: _obscure ? "Show password" : "Hide password",
-                  icon:
-                      Icon(_obscure ? Icons.visibility : Icons.visibility_off),
-                  onPressed: () => setState(() {
-                    _obscure = !_obscure;
-                  }),
-                ),
-              )
-            ],
+          _ProxmoxPasswordForm(
+            usernameController: widget.usernameController,
+            passwordController: widget.passwordController,
+            onPasswordSubmitted: widget.onPasswordSubmitted,
+            onSavePasswordChanged: widget.onSavePasswordChanged,
+            canSavePassword: widget.canSavePassword,
+            passwordSaved: widget.passwordSaved,
           ),
-          if (widget.canSavePassword ?? false)
-            CheckboxListTile(
-              title: const Text('Save password in biometric storage'),
-              value: _savePwCheckbox ?? widget.passwordSaved ?? false,
-              onChanged: (value) {
-                if (widget.onSavePasswordChanged != null) {
-                  widget.onSavePasswordChanged!(value!);
+        ],
+      ),
+    );
+  }
+}
+
+/// Username/password credential fields of the login form, including the
+/// optional "save password" checkbox.
+class _ProxmoxPasswordForm extends StatefulWidget {
+  final TextEditingController usernameController;
+  final TextEditingController passwordController;
+  final Function? onPasswordSubmitted;
+  final Function? onSavePasswordChanged;
+  final bool? canSavePassword;
+  final bool? passwordSaved;
+
+  const _ProxmoxPasswordForm({
+    required this.usernameController,
+    required this.passwordController,
+    this.onPasswordSubmitted,
+    this.onSavePasswordChanged,
+    this.canSavePassword,
+    this.passwordSaved,
+  });
+
+  @override
+  State<_ProxmoxPasswordForm> createState() => _ProxmoxPasswordFormState();
+}
+
+class _ProxmoxPasswordFormState extends State<_ProxmoxPasswordForm> {
+  bool _obscure = true;
+  bool? _savePwCheckbox;
+  FocusNode? passwordFocusNode;
+
+  @override
+  Widget build(BuildContext context) {
+    return Column(
+      mainAxisSize: MainAxisSize.min,
+      children: [
+        TextFormField(
+          decoration: const InputDecoration(
+            icon: Icon(Icons.person),
+            labelText: 'Username',
+          ),
+          controller: widget.usernameController,
+          validator: (value) {
+            if (value!.isEmpty) {
+              return 'Please enter username';
+            }
+            return null;
+          },
+          autofillHints: const [AutofillHints.username],
+        ),
+        Stack(
+          children: [
+            TextFormField(
+              decoration: const InputDecoration(
+                icon: Icon(Icons.lock),
+                labelText: 'Password',
+              ),
+              controller: widget.passwordController,
+              obscureText: _obscure,
+              autocorrect: false,
+              focusNode: passwordFocusNode,
+              validator: (value) {
+                if (value!.isEmpty) {
+                  return 'Please enter password';
                 }
-                setState(() {
-                  _savePwCheckbox = value!;
-                });
+                return null;
               },
+              onFieldSubmitted: (value) => widget.onPasswordSubmitted!(),
+              autofillHints: const [AutofillHints.password],
+            ),
+            Align(
+              alignment: Alignment.bottomRight,
+              child: IconButton(
+                constraints: BoxConstraints.tight(const Size(58, 58)),
+                iconSize: 24,
+                tooltip: _obscure ? "Show password" : "Hide password",
+                icon: Icon(_obscure ? Icons.visibility : Icons.visibility_off),
+                onPressed: () => setState(() {
+                  _obscure = !_obscure;
+                }),
+              ),
             )
-        ],
-      ),
+          ],
+        ),
+        if (widget.canSavePassword ?? false)
+          CheckboxListTile(
+            title: const Text('Save password in biometric storage'),
+            value: _savePwCheckbox ?? widget.passwordSaved ?? false,
+            onChanged: (value) {
+              if (widget.onSavePasswordChanged != null) {
+                widget.onSavePasswordChanged!(value!);
+              }
+              setState(() {
+                _savePwCheckbox = value!;
+              });
+            },
+          )
+      ],
     );
   }
 
-- 
2.50.1 (Apple Git-155)




  parent reply	other threads:[~2026-08-25  8:10 UTC|newest]

Thread overview: 33+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-10  5:40 [PATCH 0/2] android: register OpenID Connect callback activity for #4281 Azharul Haque
2026-08-10  5:40 ` [PATCH 1/2] fix #4281: android: register OpenID Connect callback activity Azharul Haque
2026-08-10  5:40 ` [PATCH 2/2] fix #4281: android: match renamed OpenID callback scheme Azharul Haque
2026-08-10 13:59 ` [PATCH 0/2] android: register OpenID Connect callback activity for #4281 Shan Shaji
2026-08-10 14:47   ` [PATCH v2 0/7] app: implement OpenID Connect (OAuth) realm login (#4281) Azharul Haque
2026-08-10 14:47     ` [PATCH dart-api-client v2 1/2] fix #4281: access: add `type` property to `PveAccessDomainModel` Azharul Haque
2026-08-20  9:17       ` Shan Shaji
2026-08-20 13:37         ` Azharul Haque
2026-08-20 14:39           ` Shan Shaji
2026-08-21  2:56             ` Azharul Haque
2026-08-21  8:00               ` Shan Shaji
2026-08-10 14:47     ` [PATCH dart-api-client v2 2/2] fix #4281: access: add OpenID Connect auth-url/login helpers Azharul Haque
2026-08-20  9:48       ` Shan Shaji
2026-08-20 13:41         ` Azharul Haque
2026-08-10 14:47     ` [PATCH login-manager v2 1/3] fix #4281: ui: add OpenID Connect login flow to login form Azharul Haque
2026-08-20 12:31       ` Shan Shaji
2026-08-10 14:47     ` [PATCH login-manager v2 2/3] fix #4281: ui: fix stale Continue button state on realm switch Azharul Haque
2026-08-10 14:47     ` [PATCH login-manager v2 3/3] fix #4281: ui: use a namespaced OpenID callback scheme Azharul Haque
2026-08-20 14:28       ` Shan Shaji
2026-08-10 14:47     ` [PATCH flutter-frontend v2 1/2] fix #4281: android: register OpenID Connect callback activity Azharul Haque
2026-08-20 14:01       ` Shan Shaji
2026-08-10 14:47     ` [PATCH flutter-frontend v2 2/2] fix #4281: android: match renamed OpenID callback scheme Azharul Haque
2026-08-21  3:41     ` [PATCH v3 00/10] app: implement OpenID Connect (OAuth) realm login (#4281) Azharul Haque
2026-08-21  3:41       ` [PATCH dart-api-client v3 1/2] fix #4281: access: add `type` property to `PveAccessDomainModel` Azharul Haque
2026-08-21  3:41       ` [PATCH dart-api-client v3 2/2] fix #4281: access: add OpenID Connect auth-url/login helpers Azharul Haque
2026-08-21  3:41       ` [PATCH login-manager v3 1/5] fix #4281: deps: add flutter_web_auth_2 dependency Azharul Haque
2026-08-21  3:41       ` [PATCH login-manager v3 2/5] refactor: ui: factor out shared login tail into _finishLogin Azharul Haque
2026-08-21  3:41       ` Azharul Haque [this message]
2026-08-21  3:41       ` [PATCH login-manager v3 4/5] fix #4281: ui: add OpenID Connect login flow to login form Azharul Haque
2026-08-21  3:41       ` [PATCH login-manager v3 5/5] fix #4281: ui: fix stale Continue button state on realm switch Azharul Haque
2026-08-21  3:41       ` [PATCH flutter-frontend v3 1/3] chore: regenerate plugin registrant for flutter_web_auth_2 Azharul Haque
2026-08-21  3:41       ` [PATCH flutter-frontend v3 2/3] fix #4281: android: set taskAffinity="" on MainActivity Azharul Haque
2026-08-21  3:41       ` [PATCH flutter-frontend v3 3/3] fix #4281: android: register OpenID Connect callback activity Azharul Haque

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260821034147.30194-6-haque@azharul.com \
    --to=haque@azharul.com \
    --cc=pve-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal