From 6a77704c8a8c34832766ac7d0d92c9e523ad2d64 Mon Sep 17 00:00:00 2001 From: salarshad Date: Fri, 15 Aug 2025 17:09:56 +0500 Subject: [PATCH 1/7] Update supa_email_auth.dart: Added email to the Password reset email sent callback The email was not accessible inside the callback, and was needed to implement OTP token verification in the app itself, instead of the password reset link flow. --- lib/src/components/supa_email_auth.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/components/supa_email_auth.dart b/lib/src/components/supa_email_auth.dart index 0fd2396..dfc5bd9 100644 --- a/lib/src/components/supa_email_auth.dart +++ b/lib/src/components/supa_email_auth.dart @@ -187,7 +187,7 @@ class SupaEmailAuth extends StatefulWidget { final void Function(AuthResponse response) onSignUpComplete; /// Callback for sending the password reset email - final void Function()? onPasswordResetEmailSent; + final void Function(String email)? onPasswordResetEmailSent; /// Callback for when the auth action threw an exception /// @@ -603,7 +603,7 @@ class _SupaEmailAuthState extends State { email, redirectTo: widget.resetPasswordRedirectTo ?? widget.redirectTo, ); - widget.onPasswordResetEmailSent?.call(); + widget.onPasswordResetEmailSent?.call(email); // FIX use_build_context_synchronously if (!mounted) return; context.showSnackBar(widget.localization.passwordResetSent); From e32e968a81e48613c714dd374b1c16b295bf2c82 Mon Sep 17 00:00:00 2001 From: salarshad Date: Thu, 4 Sep 2025 13:53:20 +0500 Subject: [PATCH 2/7] Add initialEmail parameter and update onSignInComplete Added an optional initialEmail to pass in a saved email address so that the email field is already populated. Passed in the email to the onSignInComplete() callback, so that the email can be used inside (eg. for saving to sharedPreferences) Also, added a focus node for the password field to move the focus to it if the initialEmail is non-empty. --- lib/src/components/supa_email_auth.dart | 38 ++++++++++++++++++++----- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/lib/src/components/supa_email_auth.dart b/lib/src/components/supa_email_auth.dart index dfc5bd9..14e0b0f 100644 --- a/lib/src/components/supa_email_auth.dart +++ b/lib/src/components/supa_email_auth.dart @@ -179,7 +179,7 @@ class SupaEmailAuth extends StatefulWidget { final String? Function(String?)? passwordValidator; /// Callback for the user to complete a sign in. - final void Function(AuthResponse response) onSignInComplete; + final void Function(AuthResponse response, String email) onSignInComplete; /// Callback for the user to complete a signUp. /// @@ -217,12 +217,15 @@ class SupaEmailAuth extends StatefulWidget { final Widget? prefixIconEmail; final Widget? prefixIconPassword; + final String? initialEmail; + /// Whether the confirm password field should be displayed final bool showConfirmPasswordField; /// {@macro supa_email_auth} const SupaEmailAuth({ super.key, + this.initialEmail = "", this.autofocus = true, this.redirectTo, this.resetPasswordRedirectTo, @@ -248,7 +251,7 @@ class SupaEmailAuth extends StatefulWidget { class _SupaEmailAuthState extends State { final _formKey = GlobalKey(); - final _emailController = TextEditingController(); + late final TextEditingController _emailController; final _passwordController = TextEditingController(); final _confirmPasswordController = TextEditingController(); late bool _isSigningIn; @@ -261,10 +264,12 @@ class _SupaEmailAuthState extends State { /// Focus node for email field final FocusNode _emailFocusNode = FocusNode(); + final FocusNode _passwordFocusNode = FocusNode(); @override void initState() { super.initState(); + _emailController = TextEditingController(text: widget.initialEmail); _isSigningIn = widget.isInitiallySigningIn; _metadataControllers = Map.fromEntries((widget.metadataFields ?? []).map( (metadataField) => MapEntry( @@ -274,6 +279,24 @@ class _SupaEmailAuthState extends State { : TextEditingController(), ), )); + + // Request focus on password field if email is pre-filled and not recovering password + if (widget.initialEmail != null && widget.initialEmail!.isNotEmpty && !_isRecoveringPassword) { + // It's important to request focus after the first frame has been built. + // WidgetsBinding.instance.addPostFrameCallback ensures that. + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + FocusScope.of(context).requestFocus(_passwordFocusNode); + } + }); + } else if (widget.autofocus && !_isRecoveringPassword) { + // Default autofocus behavior if no initial email or recovering password + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + FocusScope.of(context).requestFocus(_emailFocusNode); + } + }); + } } @override @@ -281,6 +304,8 @@ class _SupaEmailAuthState extends State { _emailController.dispose(); _passwordController.dispose(); _confirmPasswordController.dispose(); + _emailFocusNode.dispose(); + _passwordFocusNode.dispose(); for (final controller in _metadataControllers.values) { if (controller is TextEditingController) { controller.dispose(); @@ -333,6 +358,7 @@ class _SupaEmailAuthState extends State { ? [AutofillHints.password] : [AutofillHints.newPassword], autovalidateMode: AutovalidateMode.onUserInteraction, + focusNode: _passwordFocusNode, textInputAction: widget.metadataFields != null && !_isSigningIn ? TextInputAction.next : TextInputAction.done, @@ -468,7 +494,7 @@ class _SupaEmailAuthState extends State { height: 16, width: 16, child: CircularProgressIndicator( - color: Theme.of(context).colorScheme.onPrimary, + color: Theme.of(context).colorScheme.surface, strokeWidth: 1.5, ), ) @@ -539,7 +565,7 @@ class _SupaEmailAuthState extends State { email: _emailController.text.trim(), password: _passwordController.text.trim(), ); - widget.onSignInComplete.call(response); + widget.onSignInComplete.call(response, _emailController.text.trim()); } else { final user = supabase.auth.currentUser; late final AuthResponse response; @@ -607,9 +633,7 @@ class _SupaEmailAuthState extends State { // FIX use_build_context_synchronously if (!mounted) return; context.showSnackBar(widget.localization.passwordResetSent); - setState(() { - _isRecoveringPassword = false; - }); + } on AuthException catch (error) { widget.onError?.call(error); } catch (error) { From 5abeab0c7796fdc4b688bdba555cd62d31333d48 Mon Sep 17 00:00:00 2001 From: salarshad Date: Thu, 4 Sep 2025 16:57:21 +0500 Subject: [PATCH 3/7] Change CircularProgressIndicator color and fix loading state --- lib/src/components/supa_email_auth.dart | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/src/components/supa_email_auth.dart b/lib/src/components/supa_email_auth.dart index 14e0b0f..76a2a74 100644 --- a/lib/src/components/supa_email_auth.dart +++ b/lib/src/components/supa_email_auth.dart @@ -494,7 +494,7 @@ class _SupaEmailAuthState extends State { height: 16, width: 16, child: CircularProgressIndicator( - color: Theme.of(context).colorScheme.surface, + color: Theme.of(context).colorScheme.primary, strokeWidth: 1.5, ), ) @@ -559,6 +559,9 @@ class _SupaEmailAuthState extends State { setState(() { _isLoading = true; }); + + await Future.delayed(Duration(milliseconds: 50)); + try { if (_isSigningIn) { final response = await supabase.auth.signInWithPassword( @@ -605,11 +608,12 @@ class _SupaEmailAuthState extends State { widget.onError?.call(error); } _emailFocusNode.requestFocus(); - } - if (mounted) { - setState(() { - _isLoading = false; - }); + } finally { + if (mounted) { + setState(() { + _isLoading = false; + }); + } } } From 2c74590fc17d9ca6a0b292096b4079459a041118 Mon Sep 17 00:00:00 2001 From: salarshad Date: Fri, 7 Nov 2025 12:46:44 +0500 Subject: [PATCH 4/7] Add password field with toggle (from FatumaA) --- lib/src/components/_supa_password_field.dart | 77 ++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 lib/src/components/_supa_password_field.dart diff --git a/lib/src/components/_supa_password_field.dart b/lib/src/components/_supa_password_field.dart new file mode 100644 index 0000000..8cc66b9 --- /dev/null +++ b/lib/src/components/_supa_password_field.dart @@ -0,0 +1,77 @@ +import 'package:flutter/material.dart'; + +/// Internal password field component with visibility toggle +class SupaPasswordField extends StatefulWidget { + /// Controller for the password field + final TextEditingController controller; + + /// Validator function for the password field + final String? Function(String?)? validator; + + /// Label text for the password field + final String labelText; + + /// Prefix icon for the password field + final Widget? prefixIcon; + + /// Autofill hints for the password field + final Iterable? autofillHints; + + /// Text input action for the password field + final TextInputAction? textInputAction; + + /// Callback when field is submitted + final void Function(String)? onFieldSubmitted; + + /// Whether the field should auto-validate + final AutovalidateMode? autovalidateMode; + + const SupaPasswordField({ + super.key, + required this.controller, + this.validator, + required this.labelText, + this.prefixIcon, + this.autofillHints, + this.textInputAction, + this.onFieldSubmitted, + this.autovalidateMode, + }); + + @override + State createState() => _SupaPasswordFieldState(); +} + +class _SupaPasswordFieldState extends State { + bool _obscureText = true; + + @override + Widget build(BuildContext context) { + return TextFormField( + controller: widget.controller, + validator: widget.validator, + obscureText: _obscureText, + autofillHints: widget.autofillHints, + textInputAction: widget.textInputAction, + onFieldSubmitted: widget.onFieldSubmitted, + autovalidateMode: widget.autovalidateMode, + decoration: InputDecoration( + prefixIcon: widget.prefixIcon, + label: Text(widget.labelText), + suffixIcon: Tooltip( + message: _obscureText ? 'Show password' : 'Hide password', + child: IconButton( + icon: Icon( + _obscureText ? Icons.visibility_off : Icons.visibility, + ), + onPressed: () { + setState(() { + _obscureText = !_obscureText; + }); + }, + ), + ), + ), + ); + } +} From b0b285ca9aef0abf6adbd6eb357b00b2ad05c044 Mon Sep 17 00:00:00 2001 From: salarshad Date: Fri, 7 Nov 2025 16:29:08 +0500 Subject: [PATCH 5/7] Add focusNode property to SupaPasswordField --- lib/src/components/_supa_password_field.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/src/components/_supa_password_field.dart b/lib/src/components/_supa_password_field.dart index 8cc66b9..ce5c044 100644 --- a/lib/src/components/_supa_password_field.dart +++ b/lib/src/components/_supa_password_field.dart @@ -2,6 +2,9 @@ import 'package:flutter/material.dart'; /// Internal password field component with visibility toggle class SupaPasswordField extends StatefulWidget { + + final FocusNode? focusNode; + /// Controller for the password field final TextEditingController controller; @@ -36,6 +39,7 @@ class SupaPasswordField extends StatefulWidget { this.textInputAction, this.onFieldSubmitted, this.autovalidateMode, + this.focusNode, }); @override @@ -48,6 +52,7 @@ class _SupaPasswordFieldState extends State { @override Widget build(BuildContext context) { return TextFormField( + focusNode: widget.focusNode, controller: widget.controller, validator: widget.validator, obscureText: _obscureText, From 09e8ccf46738dc36563758c73a9e9b225549e2be Mon Sep 17 00:00:00 2001 From: salarshad Date: Fri, 7 Nov 2025 16:29:24 +0500 Subject: [PATCH 6/7] Refactor password fields to use SupaPasswordField --- lib/src/components/supa_email_auth.dart | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/lib/src/components/supa_email_auth.dart b/lib/src/components/supa_email_auth.dart index 76a2a74..9422750 100644 --- a/lib/src/components/supa_email_auth.dart +++ b/lib/src/components/supa_email_auth.dart @@ -1,5 +1,6 @@ import 'package:email_validator/email_validator.dart'; import 'package:flutter/material.dart'; +import 'package:supabase_auth_ui/src/components/_supa_password_field.dart'; import 'package:supabase_auth_ui/src/localizations/supa_email_auth_localization.dart'; import 'package:supabase_auth_ui/src/utils/constants.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; @@ -353,7 +354,10 @@ class _SupaEmailAuthState extends State { ), if (!_isRecoveringPassword) ...[ spacer(16), - TextFormField( + SupaPasswordField( + controller: _passwordController, + labelText: localization.enterPassword, + prefixIcon: widget.prefixIconPassword, autofillHints: _isSigningIn ? [AutofillHints.password] : [AutofillHints.newPassword], @@ -369,12 +373,6 @@ class _SupaEmailAuthState extends State { } return null; }, - decoration: InputDecoration( - prefixIcon: widget.prefixIconPassword, - label: Text(localization.enterPassword), - ), - obscureText: true, - controller: _passwordController, onFieldSubmitted: (_) { if (widget.metadataFields == null || _isSigningIn) { _signInSignUp(); @@ -383,13 +381,10 @@ class _SupaEmailAuthState extends State { ), if (widget.showConfirmPasswordField && !_isSigningIn) ...[ spacer(16), - TextFormField( + SupaPasswordField( controller: _confirmPasswordController, - decoration: InputDecoration( - prefixIcon: widget.prefixIconPassword, - label: Text(localization.confirmPassword), - ), - obscureText: true, + labelText: localization.confirmPassword, + prefixIcon: widget.prefixIconPassword, validator: (value) { if (value != _passwordController.text) { return localization.confirmPasswordError; From f19f979cc42fe3e8475bcbfa2f20f70d0839185a Mon Sep 17 00:00:00 2001 From: salarshad Date: Fri, 7 Nov 2025 18:03:30 +0500 Subject: [PATCH 7/7] Update dependencies for supabase_flutter and email_validator --- pubspec.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index 880ca10..c762d3b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,8 +11,8 @@ environment: dependencies: flutter: sdk: flutter - supabase_flutter: ^2.5.6 - email_validator: ^2.0.1 + supabase_flutter: ^2.10.3 + email_validator: ^3.0.0 font_awesome_flutter: ^10.6.0 google_sign_in: ^6.2.1 sign_in_with_apple: ^6.1.0