fix: refactor password reset dialog to its own StatefulWidget
Fixes red screen crash ('_dependents.isEmpty is not true') caused by
auth state change rebuilding the widget tree while the dialog was
still open and sharing the parent context across an async gap.
This commit is contained in:
parent
2c3c6d990b
commit
8c453e3f13
1 changed files with 82 additions and 48 deletions
130
lib/main.dart
130
lib/main.dart
|
|
@ -90,59 +90,13 @@ class _AuthGateState extends State<AuthGate> {
|
|||
/// Shows a dialog so the user can type a new password after clicking
|
||||
/// the "Reset Password" link from their email.
|
||||
Future<void> _showResetPasswordDialog() async {
|
||||
final passwordController = TextEditingController();
|
||||
if (!mounted) return;
|
||||
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: const Text('Set New Password'),
|
||||
content: TextField(
|
||||
controller: passwordController,
|
||||
obscureText: true,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'New password',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(ctx).pop(),
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
final newPassword = passwordController.text.trim();
|
||||
if (newPassword.length < 6) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Password must be at least 6 characters.')),
|
||||
);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await supabase.auth.updateUser(
|
||||
UserAttributes(password: newPassword),
|
||||
);
|
||||
if (!ctx.mounted) return;
|
||||
Navigator.of(ctx).pop();
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Password updated successfully!')),
|
||||
);
|
||||
} on AuthException catch (e) {
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(e.message), backgroundColor: Colors.red),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: const Text('Save'),
|
||||
),
|
||||
],
|
||||
),
|
||||
builder: (_) => const _ResetPasswordDialog(),
|
||||
);
|
||||
|
||||
passwordController.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -161,6 +115,86 @@ class _AuthGateState extends State<AuthGate> {
|
|||
}
|
||||
}
|
||||
|
||||
// ── Reset Password Dialog ─────────────────────────────────────────────
|
||||
class _ResetPasswordDialog extends StatefulWidget {
|
||||
const _ResetPasswordDialog();
|
||||
|
||||
@override
|
||||
State<_ResetPasswordDialog> createState() => _ResetPasswordDialogState();
|
||||
}
|
||||
|
||||
class _ResetPasswordDialogState extends State<_ResetPasswordDialog> {
|
||||
final _passwordController = TextEditingController();
|
||||
bool _isSaving = false;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_passwordController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _save() async {
|
||||
final newPassword = _passwordController.text.trim();
|
||||
if (newPassword.length < 6) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Password must be at least 6 characters.')),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() => _isSaving = true);
|
||||
|
||||
try {
|
||||
await supabase.auth.updateUser(
|
||||
UserAttributes(password: newPassword),
|
||||
);
|
||||
if (!mounted) return;
|
||||
Navigator.of(context).pop();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Password updated successfully!')),
|
||||
);
|
||||
} on AuthException catch (e) {
|
||||
if (!mounted) return;
|
||||
setState(() => _isSaving = false);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(e.message), backgroundColor: Colors.red),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Set New Password'),
|
||||
content: TextField(
|
||||
controller: _passwordController,
|
||||
obscureText: true,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'New password',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
onSubmitted: (_) => _save(),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: _isSaving ? null : _save,
|
||||
child: _isSaving
|
||||
? const SizedBox(
|
||||
height: 18,
|
||||
width: 18,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Text('Save'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Login Screen ──────────────────────────────────────────────────────
|
||||
class LoginScreen extends StatefulWidget {
|
||||
const LoginScreen({super.key});
|
||||
|
|
|
|||
Loading…
Reference in a new issue