From 8afc54a5a9baa977100ee9d99c2adb6d0354f07c Mon Sep 17 00:00:00 2001 From: lowrt Date: Sun, 28 Dec 2025 12:07:12 +0800 Subject: [PATCH 1/2] fix(report): handle generated images with retry UI --- lib/app/map/_lib/managers/report.dart | 99 ++++++++++++++++++++--- lib/widgets/report/enlargeable_image.dart | 10 ++- 2 files changed, 99 insertions(+), 10 deletions(-) diff --git a/lib/app/map/_lib/managers/report.dart b/lib/app/map/_lib/managers/report.dart index 1a1296ad6..d221d6ca9 100644 --- a/lib/app/map/_lib/managers/report.dart +++ b/lib/app/map/_lib/managers/report.dart @@ -390,6 +390,81 @@ class ReportMapLayerSheet extends StatefulWidget { State createState() => _ReportMapLayerSheetState(); } +class SafeImageSection extends StatefulWidget { + final Widget Function(VoidCallback onError) builder; + + const SafeImageSection({ + super.key, + required this.builder, + }); + + @override + State createState() => _SafeImageSectionState(); +} + +class _SafeImageSectionState extends State { + bool _hasError = false; + int _retryKey = 0; + + void _onImageError() { + if (_hasError) return; + + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted || _hasError) return; + + setState(() { + _hasError = true; + }); + }); + } + + void _retry() { + setState(() { + _hasError = false; + _retryKey++; + }); + } + + @override + Widget build(BuildContext context) { + if (_hasError) { + return _CwaGeneratingView(onRetry: _retry); + } + + return KeyedSubtree( + key: ValueKey(_retryKey), + child: widget.builder(_onImageError), + ); + } +} + +class _CwaGeneratingView extends StatelessWidget { + final VoidCallback onRetry; + + const _CwaGeneratingView ({ + required this.onRetry, + }); + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 180, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('CWA 正在製圖中'.i18n), + const SizedBox(height: 12), + TextButton.icon( + onPressed: onRetry, + icon: const Icon(Icons.refresh), + label: Text('重新載入'.i18n), + ), + ], + ), + ); + } +} + class _ReportMapLayerSheetState extends State { final morphingSheetController = MorphingSheetController(); @@ -982,48 +1057,54 @@ class _ReportMapLayerSheetState extends State { ), ], ), - if (report.hasNumber) + if (report.hasNumber && report.intensityMapImageUrl != null) Section( label: Text('震度圖'.i18n), children: [ Padding( padding: .symmetric(horizontal: 8), - child: EnlargeableImage( + child: SafeImageSection( + builder: (onError) =>EnlargeableImage( aspectRatio: 2334 / 2977, heroTag: 'intensity-image-${report.id}', imageUrl: report.intensityMapImageUrl!, imageName: report.intensityMapImageName!, - ), + onLoadFailed: onError, + ),), ), ], ), - if (report.hasNumber) + if (report.hasNumber && report.pgaMapImageUrl != null) Section( label: Text('最大地動加速度圖'.i18n), children: [ Padding( padding: .symmetric(horizontal: 8), - child: EnlargeableImage( + child: SafeImageSection( + builder: (onError) =>EnlargeableImage( aspectRatio: 2334 / 2977, heroTag: 'pga-image-${report.id}', imageUrl: report.pgaMapImageUrl!, imageName: report.pgaMapImageName!, - ), + onLoadFailed: onError, + ),), ), ], ), - if (report.hasNumber) + if (report.hasNumber && report.pgvMapImageUrl != null) Section( label: Text('最大地動速度圖'.i18n), children: [ Padding( padding: .symmetric(horizontal: 8), - child: EnlargeableImage( + child: SafeImageSection( + builder: (onError) =>EnlargeableImage( aspectRatio: 2334 / 2977, heroTag: 'pgv-image-${report.id}', imageUrl: report.pgvMapImageUrl!, imageName: report.pgvMapImageName!, - ), + onLoadFailed: onError, + ),), ), ], ), diff --git a/lib/widgets/report/enlargeable_image.dart b/lib/widgets/report/enlargeable_image.dart index 7c104f9f7..af09274b5 100644 --- a/lib/widgets/report/enlargeable_image.dart +++ b/lib/widgets/report/enlargeable_image.dart @@ -7,6 +7,7 @@ class EnlargeableImage extends StatelessWidget { final String heroTag; final String imageUrl; final String imageName; + final VoidCallback? onLoadFailed; const EnlargeableImage({ super.key, @@ -14,6 +15,7 @@ class EnlargeableImage extends StatelessWidget { required this.heroTag, required this.imageUrl, required this.imageName, + this.onLoadFailed, }); @override @@ -28,7 +30,13 @@ class EnlargeableImage extends StatelessWidget { children: [ Hero( tag: heroTag, - child: CachedNetworkImage(imageUrl: imageUrl), + child: CachedNetworkImage(imageUrl: imageUrl, + fit: BoxFit.cover, + errorWidget: (context, url, error) { + onLoadFailed?.call(); + return const SizedBox.shrink(); + }, + ), ), Positioned.fill( child: Material( From 1ed8778b708de9465182883bb8f8c17d9ca58545 Mon Sep 17 00:00:00 2001 From: lowrt Date: Sun, 28 Dec 2025 14:31:37 +0800 Subject: [PATCH 2/2] fix: report --- lib/app/map/_lib/managers/report.dart | 66 ++++++++++++++------------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/lib/app/map/_lib/managers/report.dart b/lib/app/map/_lib/managers/report.dart index d221d6ca9..fe96b736c 100644 --- a/lib/app/map/_lib/managers/report.dart +++ b/lib/app/map/_lib/managers/report.dart @@ -408,13 +408,11 @@ class _SafeImageSectionState extends State { void _onImageError() { if (_hasError) return; + _hasError = true; WidgetsBinding.instance.addPostFrameCallback((_) { - if (!mounted || _hasError) return; - - setState(() { - _hasError = true; - }); + if (!mounted) return; + setState(() {}); }); } @@ -428,7 +426,7 @@ class _SafeImageSectionState extends State { @override Widget build(BuildContext context) { if (_hasError) { - return _CwaGeneratingView(onRetry: _retry); + return _GeneratingView(onRetry: _retry); } return KeyedSubtree( @@ -438,17 +436,17 @@ class _SafeImageSectionState extends State { } } -class _CwaGeneratingView extends StatelessWidget { +class _GeneratingView extends StatelessWidget { final VoidCallback onRetry; - const _CwaGeneratingView ({ + const _GeneratingView({ required this.onRetry, }); @override Widget build(BuildContext context) { - return SizedBox( - height: 180, + return AspectRatio( + aspectRatio: 2334 / 2977, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -1057,20 +1055,22 @@ class _ReportMapLayerSheetState extends State { ), ], ), - if (report.hasNumber && report.intensityMapImageUrl != null) + if (report.hasNumber && + report.intensityMapImageUrl != null) Section( label: Text('震度圖'.i18n), children: [ Padding( padding: .symmetric(horizontal: 8), child: SafeImageSection( - builder: (onError) =>EnlargeableImage( - aspectRatio: 2334 / 2977, - heroTag: 'intensity-image-${report.id}', - imageUrl: report.intensityMapImageUrl!, - imageName: report.intensityMapImageName!, - onLoadFailed: onError, - ),), + builder: (onError) => EnlargeableImage( + aspectRatio: 2334 / 2977, + heroTag: 'intensity-image-${report.id}', + imageUrl: report.intensityMapImageUrl!, + imageName: report.intensityMapImageName!, + onLoadFailed: onError, + ), + ), ), ], ), @@ -1081,13 +1081,14 @@ class _ReportMapLayerSheetState extends State { Padding( padding: .symmetric(horizontal: 8), child: SafeImageSection( - builder: (onError) =>EnlargeableImage( - aspectRatio: 2334 / 2977, - heroTag: 'pga-image-${report.id}', - imageUrl: report.pgaMapImageUrl!, - imageName: report.pgaMapImageName!, - onLoadFailed: onError, - ),), + builder: (onError) => EnlargeableImage( + aspectRatio: 2334 / 2977, + heroTag: 'pga-image-${report.id}', + imageUrl: report.pgaMapImageUrl!, + imageName: report.pgaMapImageName!, + onLoadFailed: onError, + ), + ), ), ], ), @@ -1098,13 +1099,14 @@ class _ReportMapLayerSheetState extends State { Padding( padding: .symmetric(horizontal: 8), child: SafeImageSection( - builder: (onError) =>EnlargeableImage( - aspectRatio: 2334 / 2977, - heroTag: 'pgv-image-${report.id}', - imageUrl: report.pgvMapImageUrl!, - imageName: report.pgvMapImageName!, - onLoadFailed: onError, - ),), + builder: (onError) => EnlargeableImage( + aspectRatio: 2334 / 2977, + heroTag: 'pgv-image-${report.id}', + imageUrl: report.pgvMapImageUrl!, + imageName: report.pgvMapImageName!, + onLoadFailed: onError, + ), + ), ), ], ),