From 7700f09c14edd87606d41034a5495add5f1c9096 Mon Sep 17 00:00:00 2001 From: realth000 Date: Tue, 28 Nov 2023 00:42:24 +0800 Subject: [PATCH] feat(screens): Support refresh login captcha image --- lib/screens/login/captcha_image.dart | 78 ++++++++++++++++++++++------ 1 file changed, 62 insertions(+), 16 deletions(-) diff --git a/lib/screens/login/captcha_image.dart b/lib/screens/login/captcha_image.dart index 789a7f9e..5dbe23c2 100644 --- a/lib/screens/login/captcha_image.dart +++ b/lib/screens/login/captcha_image.dart @@ -7,6 +7,14 @@ import 'package:tsdm_client/generated/i18n/strings.g.dart'; import 'package:tsdm_client/providers/net_client_provider.dart'; import 'package:tsdm_client/utils/debug.dart'; +/// Captcha image size is 320x150. +const _captchaImageWidth = 320; +const _captchaImageHeight = 150; + +/// Row height is 60 (Default). +/// So indicator should use (150 / 60) * 320 width. +const _indicatorBoxWidth = (60 / _captchaImageHeight) * _captchaImageWidth; + class CaptchaImage extends ConsumerStatefulWidget { const CaptchaImage({super.key}); @@ -18,27 +26,65 @@ class CaptchaImage extends ConsumerStatefulWidget { } class _VerityImageState extends ConsumerState { + /// Debounce refreshing. + bool refreshDebounce = false; + + /// Need this variable to mark whether the future [f] is completed or not. + /// Because when refreshing state triggered by user interaction, it's weired + /// that the [FutureBuilder] below has the previous data and does not show + /// [CircularProgressIndicator] as planned. + bool futureComplete = false; + late Future> f; + @override Widget build(BuildContext context) { debug('fetching login captcha'); - return FutureBuilder( - future: ref.read(netClientProvider()).getUri( - CaptchaImage._fakeFormVerifyUri, - options: Options(responseType: ResponseType.bytes), - ), - builder: (context, snapshot) { - if (snapshot.hasError) { - final message = t.loginPage.failedToGetCaptcha(err: snapshot.error!); - debug(message); - return Text(message); + f = ref + .read(netClientProvider()) + .getUri( + CaptchaImage._fakeFormVerifyUri, + // Uri.parse('https://source.unsplash.com/random/320x150'), + options: Options(responseType: ResponseType.bytes), + ) + .whenComplete(() { + futureComplete = true; + }); + return GestureDetector( + onTap: () async { + if (refreshDebounce) { + return; } - - if (snapshot.hasData) { - final bytes = Uint8List.fromList(snapshot.data!.data as List); - return Image.memory(bytes); - } - return const CircularProgressIndicator(); + setState(() { + refreshDebounce = true; + futureComplete = false; + }); + debug('refresh login captcha'); + await Future.delayed(const Duration(milliseconds: 4000), () { + refreshDebounce = false; + }); }, + child: FutureBuilder( + future: f, + builder: (context, snapshot) { + if (snapshot.hasError) { + final message = + t.loginPage.failedToGetCaptcha(err: snapshot.error!); + debug(message); + return Text(message); + } + + if (snapshot.hasData && futureComplete) { + final bytes = Uint8List.fromList(snapshot.data!.data as List); + debug('fetch login captcha finished, ${f.hashCode}'); + return Image.memory(bytes, height: 60); + } + return const SizedBox( + width: _indicatorBoxWidth, + child: Center( + child: CircularProgressIndicator(), + )); + }, + ), ); } }