From 9ed1a00a38ca560bcbf1f8d341fdf8226b1f1a1b Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 8 Jan 2026 20:32:57 +0300 Subject: [PATCH] minio fix --- .../widgets/expiring_card_image.dart | 124 +++++++++++++++++- 1 file changed, 117 insertions(+), 7 deletions(-) diff --git a/mnemo_cards_web_v2/lib/presentation/widgets/expiring_card_image.dart b/mnemo_cards_web_v2/lib/presentation/widgets/expiring_card_image.dart index 38b9b1f..a23ced4 100644 --- a/mnemo_cards_web_v2/lib/presentation/widgets/expiring_card_image.dart +++ b/mnemo_cards_web_v2/lib/presentation/widgets/expiring_card_image.dart @@ -173,6 +173,22 @@ class _ExpiringCardImageState extends State { ); if (mounted) { + // Clear cache for old URL before updating + if (_currentUrl != null && widget.useCachedNetworkImage) { + try { + await CachedNetworkImage.evictFromCache(_currentUrl!); + log( + 'Cleared cache for old URL: $_currentUrl', + name: 'ExpiringCardImage', + ); + } catch (e) { + log( + 'Failed to clear cache: $e', + name: 'ExpiringCardImage', + ); + } + } + setState(() { _currentUrl = newUrl; _isRefreshing = false; @@ -194,22 +210,111 @@ class _ExpiringCardImageState extends State { } bool _isExpiredError(dynamic error) { - // Check if error is a 403 or 404 (expired or not found) - if (error is Exception) { - final errorString = error.toString().toLowerCase(); - return errorString.contains('403') || - errorString.contains('forbidden') || - errorString.contains('expired'); + if (error == null) return false; + + // Log error details for debugging + log( + 'Checking error for 403/expired: type=${error.runtimeType}, value=$error', + name: 'ExpiringCardImage', + ); + + // Check error string representation for HTTP status codes + final errorString = error.toString().toLowerCase(); + final contains403 = errorString.contains('403') || + errorString.contains('forbidden') || + errorString.contains('expired') || + errorString.contains('http status 403') || + errorString.contains('status code 403'); + + if (contains403) { + log( + 'Detected expired error: $errorString', + name: 'ExpiringCardImage', + ); + return true; } + + // Try to extract status code from error object if it has statusCode property + try { + final statusCode = (error as dynamic).statusCode; + if (statusCode != null) { + final code = statusCode is int ? statusCode : int.tryParse(statusCode.toString()); + if (code == 403 || code == 401) { + log( + 'Detected expired error via statusCode: $code', + name: 'ExpiringCardImage', + ); + return true; + } + } + } catch (_) { + // statusCode property doesn't exist, continue + } + + // Try to check if error has response property with statusCode + try { + final response = (error as dynamic).response; + if (response != null) { + final statusCode = (response as dynamic).statusCode; + if (statusCode != null) { + final code = statusCode is int ? statusCode : int.tryParse(statusCode.toString()); + if (code == 403 || code == 401) { + log( + 'Detected expired error via response.statusCode: $code', + name: 'ExpiringCardImage', + ); + return true; + } + } + } + } catch (_) { + // response property doesn't exist, continue + } + + // Check if error is a NetworkImageLoadException (Flutter's image loading error) + try { + if (error.toString().contains('NetworkImageLoadException') || + error.toString().contains('Failed to load network image')) { + // For network image errors, assume it might be 403 if we have a valid objectId + final objectId = _getObjectId(); + if (objectId != null && _isUuid(objectId)) { + log( + 'Detected NetworkImageLoadException with valid objectId, assuming expired', + name: 'ExpiringCardImage', + ); + return true; + } + } + } catch (_) { + // Continue + } + return false; } Widget _buildErrorWidget(BuildContext context, String url, dynamic error) { + // Log error for debugging + log( + 'Error widget called: url=$url, error=$error, errorType=${error?.runtimeType}', + name: 'ExpiringCardImage', + ); + // Check if we can refresh the URL final objectId = _getObjectId(); - final canRefresh = objectId != null && _isUuid(objectId) && _isExpiredError(error); + final isExpired = _isExpiredError(error); + final hasValidObjectId = objectId != null && _isUuid(objectId); + final canRefresh = hasValidObjectId && isExpired; + + log( + 'Refresh check: objectId=$objectId, isUuid=$hasValidObjectId, isExpired=$isExpired, canRefresh=$canRefresh, isRefreshing=$_isRefreshing', + name: 'ExpiringCardImage', + ); if (canRefresh && !_isRefreshing) { + log( + 'Scheduling presigned URL refresh for objectId: $objectId', + name: 'ExpiringCardImage', + ); // Automatically try to refresh WidgetsBinding.instance.addPostFrameCallback((_) { _refreshPresignedUrl(); @@ -238,7 +343,9 @@ class _ExpiringCardImageState extends State { } if (widget.useCachedNetworkImage) { + // Use key based on URL to force rebuild when URL changes return CachedNetworkImage( + key: ValueKey('cached_image_$_currentUrl'), imageUrl: _currentUrl!, fit: widget.fit, width: widget.width, @@ -249,10 +356,13 @@ class _ExpiringCardImageState extends State { fadeOutDuration: widget.fadeOutDuration, placeholder: widget.placeholder ?? (context, url) => const SizedBox.shrink(), errorWidget: _buildErrorWidget, + // Clear cache on error to allow retry with new URL + cacheKey: _currentUrl, ); } else { return Image.network( _currentUrl!, + key: ValueKey('network_image_$_currentUrl'), fit: widget.fit, width: widget.width, height: widget.height,