@@ -183,20 +183,59 @@ class _EmailLinkForm extends StatefulWidget {
183
183
class _EmailLinkFormState extends State <_EmailLinkForm > {
184
184
final _emailController = TextEditingController ();
185
185
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
+ }
186
198
187
199
@override
188
200
void dispose () {
189
201
_emailController.dispose ();
202
+ _cooldownTimer? .cancel ();
190
203
super .dispose ();
191
204
}
192
205
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
+
193
232
void _submitForm () {
194
233
if (_formKey.currentState! .validate ()) {
195
234
context.read <AuthenticationBloc >().add (
196
- AuthenticationRequestSignInCodeRequested (
197
- email: _emailController.text.trim (),
198
- ),
199
- );
235
+ AuthenticationRequestSignInCodeRequested (
236
+ email: _emailController.text.trim (),
237
+ ),
238
+ );
200
239
}
201
240
}
202
241
@@ -206,49 +245,65 @@ class _EmailLinkFormState extends State<_EmailLinkForm> {
206
245
final textTheme = Theme .of (context).textTheme;
207
246
final colorScheme = Theme .of (context).colorScheme;
208
247
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 (),
220
279
),
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),
239
304
),
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
+ ),
252
307
),
253
308
);
254
309
}
0 commit comments