Skip to content

Commit 35e3e28

Browse files
committed
feat(authentication): add cooldown timer for sign-in code requests
- Implement cooldown timer functionality in EmailLinkForm - Add state management for cooldown seconds and timer - Update UI to reflect cooldown state - Integrate cooldown logic with AuthenticationBloc
1 parent ead6401 commit 35e3e28

File tree

1 file changed

+100
-45
lines changed

1 file changed

+100
-45
lines changed

lib/authentication/view/request_code_page.dart

Lines changed: 100 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -183,20 +183,59 @@ class _EmailLinkForm extends StatefulWidget {
183183
class _EmailLinkFormState extends State<_EmailLinkForm> {
184184
final _emailController = TextEditingController();
185185
final _formKey = GlobalKey<FormState>();
186+
Timer? _cooldownTimer;
187+
int _cooldownSeconds = 0;
188+
189+
@override
190+
void initState() {
191+
super.initState();
192+
final authState = context.read<AuthenticationBloc>().state;
193+
if (authState.status == AuthenticationStatus.requestCodeCooldown &&
194+
authState.cooldownEndTime != null) {
195+
_startCooldownTimer(authState.cooldownEndTime!);
196+
}
197+
}
186198

187199
@override
188200
void dispose() {
189201
_emailController.dispose();
202+
_cooldownTimer?.cancel();
190203
super.dispose();
191204
}
192205

206+
void _startCooldownTimer(DateTime endTime) {
207+
final now = DateTime.now();
208+
if (now.isBefore(endTime)) {
209+
setState(() {
210+
_cooldownSeconds = endTime.difference(now).inSeconds;
211+
});
212+
_cooldownTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
213+
final remaining = endTime.difference(DateTime.now()).inSeconds;
214+
if (remaining > 0) {
215+
setState(() {
216+
_cooldownSeconds = remaining;
217+
});
218+
} else {
219+
timer.cancel();
220+
setState(() {
221+
_cooldownSeconds = 0;
222+
});
223+
// Optionally, trigger an event to reset the bloc state if needed
224+
context
225+
.read<AuthenticationBloc>()
226+
.add(const AuthenticationCooldownCompleted());
227+
}
228+
});
229+
}
230+
}
231+
193232
void _submitForm() {
194233
if (_formKey.currentState!.validate()) {
195234
context.read<AuthenticationBloc>().add(
196-
AuthenticationRequestSignInCodeRequested(
197-
email: _emailController.text.trim(),
198-
),
199-
);
235+
AuthenticationRequestSignInCodeRequested(
236+
email: _emailController.text.trim(),
237+
),
238+
);
200239
}
201240
}
202241

@@ -206,49 +245,65 @@ class _EmailLinkFormState extends State<_EmailLinkForm> {
206245
final textTheme = Theme.of(context).textTheme;
207246
final colorScheme = Theme.of(context).colorScheme;
208247

209-
return Form(
210-
key: _formKey,
211-
child: Column(
212-
crossAxisAlignment: CrossAxisAlignment.stretch,
213-
children: [
214-
TextFormField(
215-
controller: _emailController,
216-
decoration: InputDecoration(
217-
labelText: l10n.requestCodeEmailLabel,
218-
hintText: l10n.requestCodeEmailHint,
219-
// border: const OutlineInputBorder(),
248+
return BlocListener<AuthenticationBloc, AuthenticationState>(
249+
listener: (context, state) {
250+
if (state.status == AuthenticationStatus.requestCodeCooldown &&
251+
state.cooldownEndTime != null) {
252+
_cooldownTimer?.cancel();
253+
_startCooldownTimer(state.cooldownEndTime!);
254+
}
255+
},
256+
child: Form(
257+
key: _formKey,
258+
child: Column(
259+
crossAxisAlignment: CrossAxisAlignment.stretch,
260+
children: [
261+
TextFormField(
262+
controller: _emailController,
263+
decoration: InputDecoration(
264+
labelText: l10n.requestCodeEmailLabel,
265+
hintText: l10n.requestCodeEmailHint,
266+
),
267+
keyboardType: TextInputType.emailAddress,
268+
autocorrect: false,
269+
textInputAction: TextInputAction.done,
270+
enabled: !widget.isLoading && _cooldownSeconds == 0,
271+
validator: (value) {
272+
if (value == null || value.isEmpty || !value.contains('@')) {
273+
return l10n.accountLinkingEmailValidationError;
274+
}
275+
return null;
276+
},
277+
onFieldSubmitted:
278+
widget.isLoading || _cooldownSeconds > 0 ? null : (_) => _submitForm(),
220279
),
221-
keyboardType: TextInputType.emailAddress,
222-
autocorrect: false,
223-
textInputAction: TextInputAction.done,
224-
enabled: !widget.isLoading,
225-
validator: (value) {
226-
if (value == null || value.isEmpty || !value.contains('@')) {
227-
return l10n.accountLinkingEmailValidationError;
228-
}
229-
return null;
230-
},
231-
onFieldSubmitted: (_) => _submitForm(),
232-
),
233-
const SizedBox(height: AppSpacing.lg),
234-
ElevatedButton(
235-
onPressed: widget.isLoading ? null : _submitForm,
236-
style: ElevatedButton.styleFrom(
237-
padding: const EdgeInsets.symmetric(vertical: AppSpacing.md),
238-
textStyle: textTheme.labelLarge,
280+
const SizedBox(height: AppSpacing.lg),
281+
ElevatedButton(
282+
onPressed:
283+
widget.isLoading || _cooldownSeconds > 0 ? null : _submitForm,
284+
style: ElevatedButton.styleFrom(
285+
padding: const EdgeInsets.symmetric(vertical: AppSpacing.md),
286+
textStyle: textTheme.labelLarge,
287+
),
288+
child: widget.isLoading
289+
? SizedBox(
290+
height: AppSpacing.xl,
291+
width: AppSpacing.xl,
292+
child: CircularProgressIndicator(
293+
strokeWidth: 2,
294+
color: colorScheme.onPrimary,
295+
),
296+
)
297+
: _cooldownSeconds > 0
298+
? Text(
299+
l10n.requestCodeResendButtonCooldown(
300+
_cooldownSeconds,
301+
),
302+
)
303+
: Text(l10n.requestCodeSendCodeButton),
239304
),
240-
child: widget.isLoading
241-
? SizedBox(
242-
height: AppSpacing.xl,
243-
width: AppSpacing.xl,
244-
child: CircularProgressIndicator(
245-
strokeWidth: 2,
246-
color: colorScheme.onPrimary,
247-
),
248-
)
249-
: Text(l10n.requestCodeSendCodeButton),
250-
),
251-
],
305+
],
306+
),
252307
),
253308
);
254309
}

0 commit comments

Comments
 (0)