1
1
//
2
2
// ignore_for_file: lines_longer_than_80_chars
3
3
4
+ import 'dart:async' ;
5
+
4
6
import 'package:flutter/material.dart' ;
5
7
import 'package:flutter_bloc/flutter_bloc.dart' ;
6
8
import 'package:flutter_news_app_mobile_client_full_source_code/authentication/bloc/authentication_bloc.dart' ;
@@ -161,20 +163,58 @@ class _EmailLinkForm extends StatefulWidget {
161
163
class _EmailLinkFormState extends State <_EmailLinkForm > {
162
164
final _emailController = TextEditingController ();
163
165
final _formKey = GlobalKey <FormState >();
166
+ Timer ? _cooldownTimer;
167
+ int _cooldownSeconds = 0 ;
168
+
169
+ @override
170
+ void initState () {
171
+ super .initState ();
172
+ final authState = context.read <AuthenticationBloc >().state;
173
+ if (authState.cooldownEndTime != null &&
174
+ authState.cooldownEndTime! .isAfter (DateTime .now ())) {
175
+ _startCooldownTimer (authState.cooldownEndTime! );
176
+ }
177
+ }
164
178
165
179
@override
166
180
void dispose () {
167
181
_emailController.dispose ();
182
+ _cooldownTimer? .cancel ();
168
183
super .dispose ();
169
184
}
170
185
186
+ void _startCooldownTimer (DateTime endTime) {
187
+ final now = DateTime .now ();
188
+ if (now.isBefore (endTime)) {
189
+ setState (() {
190
+ _cooldownSeconds = endTime.difference (now).inSeconds;
191
+ });
192
+ _cooldownTimer = Timer .periodic (const Duration (seconds: 1 ), (timer) {
193
+ final remaining = endTime.difference (DateTime .now ()).inSeconds;
194
+ if (remaining > 0 ) {
195
+ setState (() {
196
+ _cooldownSeconds = remaining;
197
+ });
198
+ } else {
199
+ timer.cancel ();
200
+ setState (() {
201
+ _cooldownSeconds = 0 ;
202
+ });
203
+ context
204
+ .read <AuthenticationBloc >()
205
+ .add (const AuthenticationCooldownCompleted ());
206
+ }
207
+ });
208
+ }
209
+ }
210
+
171
211
void _submitForm () {
172
212
if (_formKey.currentState! .validate ()) {
173
213
context.read <AuthenticationBloc >().add (
174
- AuthenticationRequestSignInCodeRequested (
175
- email: _emailController.text.trim (),
176
- ),
177
- );
214
+ AuthenticationRequestSignInCodeRequested (
215
+ email: _emailController.text.trim (),
216
+ ),
217
+ );
178
218
}
179
219
}
180
220
@@ -184,49 +224,67 @@ class _EmailLinkFormState extends State<_EmailLinkForm> {
184
224
final textTheme = Theme .of (context).textTheme;
185
225
final colorScheme = Theme .of (context).colorScheme;
186
226
187
- return Form (
188
- key: _formKey,
189
- child: Column (
190
- crossAxisAlignment: CrossAxisAlignment .stretch,
191
- children: [
192
- TextFormField (
193
- controller: _emailController,
194
- decoration: InputDecoration (
195
- labelText: l10n.requestCodeEmailLabel,
196
- hintText: l10n.requestCodeEmailHint,
197
- // border: const OutlineInputBorder(),
227
+ return BlocListener <AuthenticationBloc , AuthenticationState >(
228
+ listenWhen: (previous, current) =>
229
+ previous.cooldownEndTime != current.cooldownEndTime,
230
+ listener: (context, state) {
231
+ if (state.cooldownEndTime != null &&
232
+ state.cooldownEndTime! .isAfter (DateTime .now ())) {
233
+ _cooldownTimer? .cancel ();
234
+ _startCooldownTimer (state.cooldownEndTime! );
235
+ }
236
+ },
237
+ child: Form (
238
+ key: _formKey,
239
+ child: Column (
240
+ crossAxisAlignment: CrossAxisAlignment .stretch,
241
+ children: [
242
+ TextFormField (
243
+ controller: _emailController,
244
+ decoration: InputDecoration (
245
+ labelText: l10n.requestCodeEmailLabel,
246
+ hintText: l10n.requestCodeEmailHint,
247
+ ),
248
+ keyboardType: TextInputType .emailAddress,
249
+ autocorrect: false ,
250
+ textInputAction: TextInputAction .done,
251
+ enabled: ! widget.isLoading && _cooldownSeconds == 0 ,
252
+ validator: (value) {
253
+ if (value == null || value.isEmpty || ! value.contains ('@' )) {
254
+ return l10n.accountLinkingEmailValidationError;
255
+ }
256
+ return null ;
257
+ },
258
+ onFieldSubmitted:
259
+ widget.isLoading || _cooldownSeconds > 0 ? null : (_) => _submitForm (),
198
260
),
199
- keyboardType: TextInputType .emailAddress,
200
- autocorrect: false ,
201
- textInputAction: TextInputAction .done,
202
- enabled: ! widget.isLoading,
203
- validator: (value) {
204
- if (value == null || value.isEmpty || ! value.contains ('@' )) {
205
- return l10n.accountLinkingEmailValidationError;
206
- }
207
- return null ;
208
- },
209
- onFieldSubmitted: (_) => _submitForm (),
210
- ),
211
- const SizedBox (height: AppSpacing .lg),
212
- ElevatedButton (
213
- onPressed: widget.isLoading ? null : _submitForm,
214
- style: ElevatedButton .styleFrom (
215
- padding: const EdgeInsets .symmetric (vertical: AppSpacing .md),
216
- textStyle: textTheme.labelLarge,
261
+ const SizedBox (height: AppSpacing .lg),
262
+ ElevatedButton (
263
+ onPressed:
264
+ widget.isLoading || _cooldownSeconds > 0 ? null : _submitForm,
265
+ style: ElevatedButton .styleFrom (
266
+ padding: const EdgeInsets .symmetric (vertical: AppSpacing .md),
267
+ textStyle: textTheme.labelLarge,
268
+ ),
269
+ child: widget.isLoading
270
+ ? SizedBox (
271
+ height: AppSpacing .xl,
272
+ width: AppSpacing .xl,
273
+ child: CircularProgressIndicator (
274
+ strokeWidth: 2 ,
275
+ color: colorScheme.onPrimary,
276
+ ),
277
+ )
278
+ : _cooldownSeconds > 0
279
+ ? Text (
280
+ l10n.requestCodeResendButtonCooldown (
281
+ _cooldownSeconds,
282
+ ),
283
+ )
284
+ : Text (l10n.requestCodeSendCodeButton),
217
285
),
218
- child: widget.isLoading
219
- ? SizedBox (
220
- height: AppSpacing .xl,
221
- width: AppSpacing .xl,
222
- child: CircularProgressIndicator (
223
- strokeWidth: 2 ,
224
- color: colorScheme.onPrimary,
225
- ),
226
- )
227
- : Text (l10n.requestCodeSendCodeButton),
228
- ),
229
- ],
286
+ ],
287
+ ),
230
288
),
231
289
);
232
290
}
0 commit comments