From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [45.144.208.40]) by lore.proxmox.com (Postfix) with ESMTPS id 65E731FF0C1 for ; Tue, 25 Aug 2026 10:10:56 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 6D99E21824; Tue, 25 Aug 2026 10:09:41 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=azharul.com; s=t28hkp5; t=1787284218; x=1787889018; darn=lists.proxmox.com; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=II3vYTnSnWnnlPHIpbZeR4IEZAEar12i3zx4crqqs8U=; b=K7wWKj0qcop36zLAUumWUlp8BIz4swOx4Kp9qt3aVmMo58jyi4FwHvlqgegybGZM2m PXBVFsBttvY6KcS6ZRv1TK9WOjaf+xoAPNLHc1mIURvF16b4NMachoL6tGjsTq8FepaB ynz9n0C/OcrU84lIg3+vMbSmL2/wgLxbYeO6Rkyj6JI0nKyd9ekqjzu4k8xSKcQr0YtZ siVvsq8Ez1QIX0hPWrLjmoC8OY5zPk210r14j97oNQG76wZtrsF0MaVw7Z401iz7eXjR /2TDrqkwzTjxdpBfSiqqCON6Cop6Q8RVkJhIZ2YGGyjMrEhcrv+OEuLE/xkQjW2Ok8ZO e7Yg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1787284218; x=1787889018; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=II3vYTnSnWnnlPHIpbZeR4IEZAEar12i3zx4crqqs8U=; b=cn8ubTNIfwU3DXUwVNMsBxL9+hqDA0rUwZQei6lAVZZ22LpSGbXOGstsElAdI1mICC kk7QxsnfT3k51YBZAgSxo9W605DYieRVqE8kUwBm2v3NoTn/mND5Zxd5tcqfIAUAlujU djD/TMtRsXyMQuJZz8NzWe0guUH+LN3/R2fK1uDSjhtVAlHuZEVMfhzUPOutw5i1dskz oE4cEQCjJRAUN3ktgK01etue8j43NG0uvdWo1hgFwJV2ySPYNJQYOSEu3+kpQAS4qRfb Rf4uWyuk9HknlXp0badHkQp4D74bbZCfY/Qq5EIUXXJ6JmDHEmbRWJZGabN8a2gdENip 9ywQ== X-Gm-Message-State: AOJu0YxaxCocCaDKgEXnPxLvW68ilVwY6LqunevOxjz1ZdnJxuhkGhM0 2isx41aGQovb23LQTizSnaNuAx72jWk64ZsvDIZPIWHOwiLYKG9qfpyMEHVQLOT2CmEQl5132yG LY22WHQ== X-Gm-Gg: AR+sD11I6s+UpDCx0/XsbXNKGCKQynBeV5VjC/6fiITX21uviE1MtyEDCqaBukkAao1 vbRsfUq7gOzg50huiUW/emrO4wx/SlUxSxRTGwpuqEkNYGy5mOUNQdWKNm2MM2WU7bbjUI3vB7T XhXcPpuvxfnuceW8aqh4G4qP1zSrir7eBzvwCIvhoOOT9S0eroCWbs88LqgKjmhy+4SmBgXBCYH mpNv6xXyE35+znztBONS60ziIX+/TeolT3Xh9nvslnUaj4+YG7O12qDWIM9ZupeVNk9B+4L67rB nAs+E4G3V92D8VWSN1Bi0uQeDz11r+pJ1xXms5w4YGkXZXwUvdRIO4QjXCzcuzpMxyT3yCJIqV4 lT5xdt+qWdsHC/n1B8oCg3/XmfZdm2z9AaQfqppKRm0u3x0x09fCrQW734occQlS36rIibl/YTk Wh5vEgO6fwvwiwfZxPSCpfZqc3Bztgue3UK2v6A2WM09jjXUePKizReRikes9hIkyOc35BNLvWc PVGQ8vrVV7qpoY4xzPlL/nsoHT2kTA0QKmW2KtUxxTxWr1dEcZDNmlVrbku8HdD/kPrzK0yNDpe eewKp58Wb8ZlDVBstUNlxpHDRxKTx3c6rFk8xbcDcIyk7TfemniP X-Received: by 2002:a05:620a:5d9a:b0:937:2c39:712f with SMTP id af79cd13be357-937395378f2mr193158085a.24.1787283737859; Thu, 20 Aug 2026 20:42:17 -0700 (PDT) From: Azharul Haque To: pve-devel@lists.proxmox.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 Message-ID: <20260821034147.30194-6-haque@azharul.com> X-Mailer: git-send-email 2.50.1 In-Reply-To: <20260821034147.30194-1-haque@azharul.com> References: <20260810144713.75806-1-haque@azharul.com> <20260821034147.30194-1-haque@azharul.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.115 Adjusted score from AWL reputation of From: address DKIM_SIGNED 0.1 Message has a DKIM or DK signature, not necessarily valid DKIM_VALID -0.1 Message has at least one valid DKIM or DK signature DKIM_VALID_AU -0.1 Message has a valid DKIM or DK signature from author's domain DKIM_VALID_EF -0.1 Message has a valid DKIM or DK signature from envelope-from domain DMARC_PASS -0.1 DMARC pass policy RCVD_IN_DNSWL_NONE -0.0001 Sender listed at https://www.dnswl.org/, no trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record X-MailFrom: haque@azharul.com X-Mailman-Rule-Hits: nonmember-moderation X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation Message-ID-Hash: 6DD5QLSZGN6BHM3W2G4OPBUP4TABBGVJ X-Message-ID-Hash: 6DD5QLSZGN6BHM3W2G4OPBUP4TABBGVJ X-Mailman-Approved-At: Tue, 25 Aug 2026 10:09:15 +0200 CC: haque@azharul.com X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: 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 Signed-off-by: Azharul Haque --- 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 { - bool _obscure = true; - bool? _savePwCheckbox; - FocusNode? passwordFocusNode; - @override Widget build(BuildContext context) { if (widget.accessDomains == null) { @@ -97,20 +93,6 @@ class _ProxmoxLoginFormState extends State { 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 { 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)