diff --git a/lib/screens/login_screen.dart b/lib/screens/login_screen.dart index faa02b3..61d0b98 100644 --- a/lib/screens/login_screen.dart +++ b/lib/screens/login_screen.dart @@ -2,6 +2,7 @@ library; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:pocketbase/pocketbase.dart'; import 'package:provider/provider.dart'; @@ -46,6 +47,8 @@ class _LoginScreenState extends State { email: _emailController.text, password: _passwordController.text, ); + // Lets iOS/Android password managers offer to save / confirm the login. + TextInput.finishAutofillContext(); if (!mounted) return; if (!widget.asRoot) { Navigator.of(context).pop(true); @@ -96,68 +99,77 @@ class _LoginScreenState extends State { ), ), const SizedBox(height: 24), - Form( - key: _formKey, - child: Column( - children: [ - AppTextField( - controller: _emailController, - label: 'Email', - keyboardType: TextInputType.emailAddress, - textInputAction: TextInputAction.next, - autofocus: true, - validator: (value) { - final text = value?.trim() ?? ''; - if (text.isEmpty) return 'Enter your email'; - if (!text.contains('@')) return 'Enter a valid email'; - return null; - }, - ), - const SizedBox(height: 16), - TextFormField( - controller: _passwordController, - obscureText: _obscurePassword, - textInputAction: TextInputAction.done, - onFieldSubmitted: (_) => _submit(), - validator: (value) { - if (value == null || value.isEmpty) { - return 'Enter your password'; - } - return null; - }, - decoration: InputDecoration( - labelText: 'Password', - suffixIcon: IconButton( - tooltip: _obscurePassword - ? 'Show password' - : 'Hide password', - onPressed: () => setState( - () => _obscurePassword = !_obscurePassword, - ), - icon: Icon( - _obscurePassword - ? Icons.visibility_outlined - : Icons.visibility_off_outlined, + AutofillGroup( + child: Form( + key: _formKey, + child: Column( + children: [ + AppTextField( + controller: _emailController, + label: 'Email', + keyboardType: TextInputType.emailAddress, + textInputAction: TextInputAction.next, + autofocus: true, + autofillHints: const [ + AutofillHints.email, + AutofillHints.username, + ], + validator: (value) { + final text = value?.trim() ?? ''; + if (text.isEmpty) return 'Enter your email'; + if (!text.contains('@')) return 'Enter a valid email'; + return null; + }, + ), + const SizedBox(height: 16), + TextFormField( + controller: _passwordController, + obscureText: _obscurePassword, + textInputAction: TextInputAction.done, + autofillHints: const [AutofillHints.password], + enableSuggestions: false, + autocorrect: false, + onFieldSubmitted: (_) => _submit(), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Enter your password'; + } + return null; + }, + decoration: InputDecoration( + labelText: 'Password', + suffixIcon: IconButton( + tooltip: _obscurePassword + ? 'Show password' + : 'Hide password', + onPressed: () => setState( + () => _obscurePassword = !_obscurePassword, + ), + icon: Icon( + _obscurePassword + ? Icons.visibility_outlined + : Icons.visibility_off_outlined, + ), ), ), ), - ), - const SizedBox(height: 24), - FilledButton.icon( - onPressed: _busy ? null : _submit, - icon: _busy - ? SizedBox( - width: 18, - height: 18, - child: CircularProgressIndicator( - strokeWidth: 2, - color: theme.colorScheme.onPrimary, - ), - ) - : const Icon(Icons.login), - label: Text(_busy ? 'Signing in…' : 'Sign in'), - ), - ], + const SizedBox(height: 24), + FilledButton.icon( + onPressed: _busy ? null : _submit, + icon: _busy + ? SizedBox( + width: 18, + height: 18, + child: CircularProgressIndicator( + strokeWidth: 2, + color: theme.colorScheme.onPrimary, + ), + ) + : const Icon(Icons.login), + label: Text(_busy ? 'Signing in…' : 'Sign in'), + ), + ], + ), ), ), ], diff --git a/lib/widgets/app_text_field.dart b/lib/widgets/app_text_field.dart index 4cdeb3e..6d3ffbb 100644 --- a/lib/widgets/app_text_field.dart +++ b/lib/widgets/app_text_field.dart @@ -14,6 +14,10 @@ class AppTextField extends StatelessWidget { this.keyboardType, this.validator, this.autofocus = false, + this.autofillHints, + this.obscureText = false, + this.enableSuggestions, + this.autocorrect, }); final TextEditingController controller; @@ -24,6 +28,10 @@ class AppTextField extends StatelessWidget { final TextInputType? keyboardType; final String? Function(String?)? validator; final bool autofocus; + final Iterable? autofillHints; + final bool obscureText; + final bool? enableSuggestions; + final bool? autocorrect; @override Widget build(BuildContext context) { @@ -31,8 +39,12 @@ class AppTextField extends StatelessWidget { controller: controller, maxLines: maxLines, autofocus: autofocus, + obscureText: obscureText, textInputAction: textInputAction, keyboardType: keyboardType, + autofillHints: autofillHints, + enableSuggestions: enableSuggestions ?? !obscureText, + autocorrect: autocorrect ?? !obscureText, validator: validator, decoration: InputDecoration( labelText: label,