@@ -129,6 +129,9 @@ class _SupaEmailAuthState extends State<SupaEmailAuth> {
129
129
/// Whether the user is signing in or signing up
130
130
bool _isSigningIn = true ;
131
131
132
+ /// Focus node for email field
133
+ final FocusNode _emailFocusNode = FocusNode ();
134
+
132
135
@override
133
136
void initState () {
134
137
super .initState ();
@@ -158,6 +161,9 @@ class _SupaEmailAuthState extends State<SupaEmailAuth> {
158
161
TextFormField (
159
162
keyboardType: TextInputType .emailAddress,
160
163
autofillHints: const [AutofillHints .email],
164
+ autovalidateMode: AutovalidateMode .onUserInteraction,
165
+ autofocus: true ,
166
+ focusNode: _emailFocusNode,
161
167
textInputAction: _isRecoveringPassword
162
168
? TextInputAction .done
163
169
: TextInputAction .next,
@@ -174,13 +180,19 @@ class _SupaEmailAuthState extends State<SupaEmailAuth> {
174
180
label: Text (localization.enterEmail),
175
181
),
176
182
controller: _emailController,
183
+ onFieldSubmitted: (_) {
184
+ if (_isRecoveringPassword) {
185
+ _passwordRecovery ();
186
+ }
187
+ },
177
188
),
178
189
if (! _isRecoveringPassword) ...[
179
190
spacer (16 ),
180
191
TextFormField (
181
192
autofillHints: _isSigningIn
182
193
? [AutofillHints .password]
183
194
: [AutofillHints .newPassword],
195
+ autovalidateMode: AutovalidateMode .onUserInteraction,
184
196
textInputAction: widget.metadataFields != null && ! _isSigningIn
185
197
? TextInputAction .next
186
198
: TextInputAction .done,
@@ -196,6 +208,11 @@ class _SupaEmailAuthState extends State<SupaEmailAuth> {
196
208
),
197
209
obscureText: true ,
198
210
controller: _passwordController,
211
+ onFieldSubmitted: (_) {
212
+ if (widget.metadataFields == null || _isSigningIn) {
213
+ _signInSignUp ();
214
+ }
215
+ },
199
216
),
200
217
spacer (16 ),
201
218
if (widget.metadataFields != null && ! _isSigningIn)
@@ -212,11 +229,20 @@ class _SupaEmailAuthState extends State<SupaEmailAuth> {
212
229
prefixIcon: metadataField.prefixIcon,
213
230
),
214
231
validator: metadataField.validator,
232
+ onFieldSubmitted: (_) {
233
+ if (metadataField !=
234
+ widget.metadataFields! .last) {
235
+ FocusScope .of (context).nextFocus ();
236
+ } else {
237
+ _signInSignUp ();
238
+ }
239
+ },
215
240
),
216
241
spacer (16 ),
217
242
])
218
243
.expand ((element) => element),
219
244
ElevatedButton (
245
+ onPressed: _signInSignUp,
220
246
child: (_isLoading)
221
247
? SizedBox (
222
248
height: 16 ,
@@ -229,64 +255,6 @@ class _SupaEmailAuthState extends State<SupaEmailAuth> {
229
255
: Text (_isSigningIn
230
256
? localization.signIn
231
257
: 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
- },
290
258
),
291
259
spacer (16 ),
292
260
if (_isSigningIn) ...[
@@ -318,40 +286,7 @@ class _SupaEmailAuthState extends State<SupaEmailAuth> {
318
286
if (_isSigningIn && _isRecoveringPassword) ...[
319
287
spacer (16 ),
320
288
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,
355
290
child: Text (localization.sendPasswordReset),
356
291
),
357
292
spacer (16 ),
@@ -371,6 +306,103 @@ class _SupaEmailAuthState extends State<SupaEmailAuth> {
371
306
);
372
307
}
373
308
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
+
374
406
/// Resolve the user_metadata that we will send during sign-up
375
407
///
376
408
/// In case both MetadataFields and extraMetadata have the same
0 commit comments