@@ -129,6 +129,9 @@ class _SupaEmailAuthState extends State<SupaEmailAuth> {
129129 /// Whether the user is signing in or signing up
130130 bool _isSigningIn = true ;
131131
132+ /// Focus node for email field
133+ final FocusNode _emailFocusNode = FocusNode ();
134+
132135 @override
133136 void initState () {
134137 super .initState ();
@@ -158,6 +161,9 @@ class _SupaEmailAuthState extends State<SupaEmailAuth> {
158161 TextFormField (
159162 keyboardType: TextInputType .emailAddress,
160163 autofillHints: const [AutofillHints .email],
164+ autovalidateMode: AutovalidateMode .onUserInteraction,
165+ autofocus: true ,
166+ focusNode: _emailFocusNode,
161167 textInputAction: _isRecoveringPassword
162168 ? TextInputAction .done
163169 : TextInputAction .next,
@@ -174,13 +180,19 @@ class _SupaEmailAuthState extends State<SupaEmailAuth> {
174180 label: Text (localization.enterEmail),
175181 ),
176182 controller: _emailController,
183+ onFieldSubmitted: (_) {
184+ if (_isRecoveringPassword) {
185+ _passwordRecovery ();
186+ }
187+ },
177188 ),
178189 if (! _isRecoveringPassword) ...[
179190 spacer (16 ),
180191 TextFormField (
181192 autofillHints: _isSigningIn
182193 ? [AutofillHints .password]
183194 : [AutofillHints .newPassword],
195+ autovalidateMode: AutovalidateMode .onUserInteraction,
184196 textInputAction: widget.metadataFields != null && ! _isSigningIn
185197 ? TextInputAction .next
186198 : TextInputAction .done,
@@ -196,6 +208,11 @@ class _SupaEmailAuthState extends State<SupaEmailAuth> {
196208 ),
197209 obscureText: true ,
198210 controller: _passwordController,
211+ onFieldSubmitted: (_) {
212+ if (widget.metadataFields == null || _isSigningIn) {
213+ _signInSignUp ();
214+ }
215+ },
199216 ),
200217 spacer (16 ),
201218 if (widget.metadataFields != null && ! _isSigningIn)
@@ -212,11 +229,20 @@ class _SupaEmailAuthState extends State<SupaEmailAuth> {
212229 prefixIcon: metadataField.prefixIcon,
213230 ),
214231 validator: metadataField.validator,
232+ onFieldSubmitted: (_) {
233+ if (metadataField !=
234+ widget.metadataFields! .last) {
235+ FocusScope .of (context).nextFocus ();
236+ } else {
237+ _signInSignUp ();
238+ }
239+ },
215240 ),
216241 spacer (16 ),
217242 ])
218243 .expand ((element) => element),
219244 ElevatedButton (
245+ onPressed: _signInSignUp,
220246 child: (_isLoading)
221247 ? SizedBox (
222248 height: 16 ,
@@ -229,64 +255,6 @@ class _SupaEmailAuthState extends State<SupaEmailAuth> {
229255 : Text (_isSigningIn
230256 ? localization.signIn
231257 : localization.signUp),
232- onPressed: () async {
233- if (! _formKey.currentState! .validate ()) {
234- return ;
235- }
236- setState (() {
237- _isLoading = true ;
238- });
239- try {
240- if (_isSigningIn) {
241- final response = await supabase.auth.signInWithPassword (
242- email: _emailController.text.trim (),
243- password: _passwordController.text.trim (),
244- );
245- widget.onSignInComplete.call (response);
246- } else {
247- final user = supabase.auth.currentUser;
248- late final AuthResponse response;
249- if (user? .isAnonymous == true ) {
250- await supabase.auth.updateUser (
251- UserAttributes (
252- email: _emailController.text.trim (),
253- password: _passwordController.text.trim (),
254- data: _resolveData (),
255- ),
256- emailRedirectTo: widget.redirectTo,
257- );
258- final newSession = supabase.auth.currentSession;
259- response = AuthResponse (session: newSession);
260- } else {
261- response = await supabase.auth.signUp (
262- email: _emailController.text.trim (),
263- password: _passwordController.text.trim (),
264- emailRedirectTo: widget.redirectTo,
265- data: _resolveData (),
266- );
267- }
268- widget.onSignUpComplete.call (response);
269- }
270- } on AuthException catch (error) {
271- if (widget.onError == null && context.mounted) {
272- context.showErrorSnackBar (error.message);
273- } else {
274- widget.onError? .call (error);
275- }
276- } catch (error) {
277- if (widget.onError == null && context.mounted) {
278- context.showErrorSnackBar (
279- '${localization .unexpectedError }: $error ' );
280- } else {
281- widget.onError? .call (error);
282- }
283- }
284- if (mounted) {
285- setState (() {
286- _isLoading = false ;
287- });
288- }
289- },
290258 ),
291259 spacer (16 ),
292260 if (_isSigningIn) ...[
@@ -318,40 +286,7 @@ class _SupaEmailAuthState extends State<SupaEmailAuth> {
318286 if (_isSigningIn && _isRecoveringPassword) ...[
319287 spacer (16 ),
320288 ElevatedButton (
321- onPressed: () async {
322- try {
323- if (! _formKey.currentState! .validate ()) {
324- return ;
325- }
326- setState (() {
327- _isLoading = true ;
328- });
329-
330- final email = _emailController.text.trim ();
331- await supabase.auth.resetPasswordForEmail (
332- email,
333- redirectTo:
334- widget.resetPasswordRedirectTo ?? widget.redirectTo,
335- );
336- widget.onPasswordResetEmailSent? .call ();
337- // FIX use_build_context_synchronously
338- if (! context.mounted) return ;
339- context.showSnackBar (localization.passwordResetSent);
340- setState (() {
341- _isRecoveringPassword = false ;
342- });
343- } on AuthException catch (error) {
344- widget.onError? .call (error);
345- } catch (error) {
346- widget.onError? .call (error);
347- } finally {
348- if (mounted) {
349- setState (() {
350- _isLoading = false ;
351- });
352- }
353- }
354- },
289+ onPressed: _passwordRecovery,
355290 child: Text (localization.sendPasswordReset),
356291 ),
357292 spacer (16 ),
@@ -371,6 +306,103 @@ class _SupaEmailAuthState extends State<SupaEmailAuth> {
371306 );
372307 }
373308
309+ void _signInSignUp () async {
310+ if (! _formKey.currentState! .validate ()) {
311+ return ;
312+ }
313+ setState (() {
314+ _isLoading = true ;
315+ });
316+ try {
317+ if (_isSigningIn) {
318+ final response = await supabase.auth.signInWithPassword (
319+ email: _emailController.text.trim (),
320+ password: _passwordController.text.trim (),
321+ );
322+ widget.onSignInComplete.call (response);
323+ } else {
324+ final user = supabase.auth.currentUser;
325+ late final AuthResponse response;
326+ if (user? .isAnonymous == true ) {
327+ await supabase.auth.updateUser (
328+ UserAttributes (
329+ email: _emailController.text.trim (),
330+ password: _passwordController.text.trim (),
331+ data: _resolveData (),
332+ ),
333+ emailRedirectTo: widget.redirectTo,
334+ );
335+ final newSession = supabase.auth.currentSession;
336+ response = AuthResponse (session: newSession);
337+ } else {
338+ response = await supabase.auth.signUp (
339+ email: _emailController.text.trim (),
340+ password: _passwordController.text.trim (),
341+ emailRedirectTo: widget.redirectTo,
342+ data: _resolveData (),
343+ );
344+ }
345+ widget.onSignUpComplete.call (response);
346+ }
347+ } on AuthException catch (error) {
348+ if (widget.onError == null && mounted) {
349+ context.showErrorSnackBar (error.message);
350+ } else {
351+ widget.onError? .call (error);
352+ }
353+ _emailFocusNode.requestFocus ();
354+ } catch (error) {
355+ if (widget.onError == null && mounted) {
356+ context.showErrorSnackBar (
357+ '${widget .localization .unexpectedError }: $error ' );
358+ } else {
359+ widget.onError? .call (error);
360+ }
361+ _emailFocusNode.requestFocus ();
362+ }
363+ if (mounted) {
364+ setState (() {
365+ _isLoading = false ;
366+ });
367+ }
368+ }
369+
370+ void _passwordRecovery () async {
371+ try {
372+ if (! _formKey.currentState! .validate ()) {
373+ // Focus on email field if validation fails
374+ _emailFocusNode.requestFocus ();
375+ return ;
376+ }
377+ setState (() {
378+ _isLoading = true ;
379+ });
380+
381+ final email = _emailController.text.trim ();
382+ await supabase.auth.resetPasswordForEmail (
383+ email,
384+ redirectTo: widget.resetPasswordRedirectTo ?? widget.redirectTo,
385+ );
386+ widget.onPasswordResetEmailSent? .call ();
387+ // FIX use_build_context_synchronously
388+ if (! mounted) return ;
389+ context.showSnackBar (widget.localization.passwordResetSent);
390+ setState (() {
391+ _isRecoveringPassword = false ;
392+ });
393+ } on AuthException catch (error) {
394+ widget.onError? .call (error);
395+ } catch (error) {
396+ widget.onError? .call (error);
397+ } finally {
398+ if (mounted) {
399+ setState (() {
400+ _isLoading = false ;
401+ });
402+ }
403+ }
404+ }
405+
374406 /// Resolve the user_metadata that we will send during sign-up
375407 ///
376408 /// In case both MetadataFields and extraMetadata have the same
0 commit comments