From 7f37c0307821b9026f172d24720c2f19f8490109 Mon Sep 17 00:00:00 2001 From: PianoNic <79938743+PianoNic@users.noreply.github.com> Date: Sun, 19 Jul 2026 13:18:30 +0200 Subject: [PATCH] Centered add-authenticator form and made the QR scanner a square viewfinder --- lib/ui/authenticator/add_totp_screen.dart | 130 ++++++++++++--------- lib/ui/authenticator/totp_scan_screen.dart | 23 +++- 2 files changed, 95 insertions(+), 58 deletions(-) diff --git a/lib/ui/authenticator/add_totp_screen.dart b/lib/ui/authenticator/add_totp_screen.dart index 91b1e17..7cf1afd 100644 --- a/lib/ui/authenticator/add_totp_screen.dart +++ b/lib/ui/authenticator/add_totp_screen.dart @@ -34,13 +34,15 @@ class _AddTotpScreenState extends State { String _newId() => DateTime.now().microsecondsSinceEpoch.toString(); Future _scan() async { - final scanned = await Navigator.of(context).push( - MaterialPageRoute(builder: (_) => const TotpScanScreen()), - ); + final scanned = await Navigator.of( + context, + ).push(MaterialPageRoute(builder: (_) => const TotpScanScreen())); if (scanned == null || scanned.isEmpty || !mounted) return; final entry = TotpEntry.fromPayload(_newId(), scanned); if (entry == null) { - setState(() => _error = "That QR code doesn't contain a valid TOTP secret."); + setState( + () => _error = "That QR code doesn't contain a valid TOTP secret.", + ); return; } await _save(entry); @@ -49,7 +51,9 @@ class _AddTotpScreenState extends State { Future _saveManual() async { final secret = _secretCtrl.text.trim(); if (TotpConfig.tryParse(secret) == null) { - setState(() => _error = 'Enter a valid TOTP secret (base32) or otpauth:// URI.'); + setState( + () => _error = 'Enter a valid TOTP secret (base32) or otpauth:// URI.', + ); return; } final entry = TotpEntry.fromPayload( @@ -59,7 +63,9 @@ class _AddTotpScreenState extends State { account: _accountCtrl.text.trim(), ); if (entry == null) { - setState(() => _error = 'Enter a valid TOTP secret (base32) or otpauth:// URI.'); + setState( + () => _error = 'Enter a valid TOTP secret (base32) or otpauth:// URI.', + ); return; } await _save(entry); @@ -87,54 +93,74 @@ class _AddTotpScreenState extends State { return FScaffold( header: FHeader.nested( title: const Text('Add authenticator'), - prefixes: [FHeaderAction.back(onPress: () => Navigator.of(context).pop())], + prefixes: [ + FHeaderAction.back(onPress: () => Navigator.of(context).pop()), + ], ), - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - spacing: 16, - children: [ - FButton( - onPress: _busy ? null : _scan, - prefix: const Icon(FIcons.scanQrCode), - child: const Text('Scan QR code'), - ), - Row( - children: [ - const Expanded(child: FDivider()), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 12), - child: Text('or enter manually', style: typography.sm.copyWith(color: colors.mutedForeground)), - ), - const Expanded(child: FDivider()), - ], - ), - FTextField( - control: FTextFieldControl.managed(controller: _issuerCtrl), - label: const Text('Provider (optional)'), - hint: 'e.g. Schulnetz, GitHub', - autocorrect: false, - ), - FTextField( - control: FTextFieldControl.managed(controller: _accountCtrl), - label: const Text('Account (optional)'), - hint: 'e.g. your username or email', - autocorrect: false, - ), - FTextField( - control: FTextFieldControl.managed(controller: _secretCtrl), - label: const Text('Setup key'), - hint: 'TOTP secret or otpauth:// URI', - autocorrect: false, - ), - FButton( - style: FButtonStyle.outline(), - onPress: _busy ? null : _saveManual, - child: Text(_busy ? 'Saving…' : 'Save'), + child: LayoutBuilder( + builder: (context, constraints) => SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints(minHeight: constraints.maxHeight), + child: IntrinsicHeight( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.stretch, + spacing: 16, + children: [ + FButton( + onPress: _busy ? null : _scan, + prefix: const Icon(FIcons.scanQrCode), + child: const Text('Scan QR code'), + ), + Row( + children: [ + const Expanded(child: FDivider()), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Text( + 'or enter manually', + style: typography.sm.copyWith( + color: colors.mutedForeground, + ), + ), + ), + const Expanded(child: FDivider()), + ], + ), + FTextField( + control: FTextFieldControl.managed(controller: _issuerCtrl), + label: const Text('Provider (optional)'), + hint: 'e.g. Schulnetz, GitHub', + autocorrect: false, + ), + FTextField( + control: FTextFieldControl.managed( + controller: _accountCtrl, + ), + label: const Text('Account (optional)'), + hint: 'e.g. your username or email', + autocorrect: false, + ), + FTextField( + control: FTextFieldControl.managed(controller: _secretCtrl), + label: const Text('Setup key'), + hint: 'TOTP secret or otpauth:// URI', + autocorrect: false, + ), + FButton( + style: FButtonStyle.outline(), + onPress: _busy ? null : _saveManual, + child: Text(_busy ? 'Saving…' : 'Save'), + ), + if (_error != null) + SelectableText( + _error!, + style: TextStyle(color: colors.destructive), + ), + ], + ), ), - if (_error != null) - SelectableText(_error!, style: TextStyle(color: colors.destructive)), - ], + ), ), ), ); diff --git a/lib/ui/authenticator/totp_scan_screen.dart b/lib/ui/authenticator/totp_scan_screen.dart index bbb8e6a..ffd1122 100644 --- a/lib/ui/authenticator/totp_scan_screen.dart +++ b/lib/ui/authenticator/totp_scan_screen.dart @@ -42,13 +42,24 @@ class _TotpScanScreenState extends State { ], ), child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - spacing: 16, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + spacing: 24, children: [ - Expanded( - child: ClipRRect( - borderRadius: BorderRadius.circular(12), - child: MobileScanner(onDetect: _onDetect), + Center( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 280), + child: AspectRatio( + aspectRatio: 1, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + border: Border.all(color: colors.border, width: 2), + ), + clipBehavior: Clip.antiAlias, + child: MobileScanner(onDetect: _onDetect), + ), + ), ), ), Text(