From 7c25ab7c6f034d727abe7865772dbba2d91a49d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20M=C3=BCllner?= Date: Thu, 5 Mar 2026 14:44:36 +0100 Subject: [PATCH] perf(images): lazy-load signed storage URLs for garage cards --- lib/screens/garage_screen.dart | 46 +++++++++++++++++++++------------- lib/widgets/car_card.dart | 32 ++++++++++++++++++++--- 2 files changed, 58 insertions(+), 20 deletions(-) diff --git a/lib/screens/garage_screen.dart b/lib/screens/garage_screen.dart index 55a186f..0a0e52e 100644 --- a/lib/screens/garage_screen.dart +++ b/lib/screens/garage_screen.dart @@ -109,21 +109,11 @@ class GarageScreenState extends State { .range(from, to); final rows = List>.from(data); - final withSignedUrls = await Future.wait( - rows.map((row) async { - final path = row['user_image_url'] as String?; - final signed = await StorageService.createSignedUrl(path); - return { - ...row, - 'signed_image_url': signed, - }; - }), - ); if (!mounted) return; setState(() { - _cars = reset ? withSignedUrls : [..._cars, ...withSignedUrls]; - _hasMore = withSignedUrls.length == _pageSize; + _cars = reset ? rows : [..._cars, ...rows]; + _hasMore = rows.length == _pageSize; if (_hasMore) _page += 1; _isLoading = false; _isLoadingMore = false; @@ -334,14 +324,19 @@ class GarageScreenState extends State { color: global?['color'] as String?, isVerified: global?['is_verified'] == true, imageUrl: car['signed_image_url'] as String?, + imagePath: car['user_image_url'] as String?, isSelected: _selectedIds.contains(carId), addedAt: car['created_at'] != null ? DateTime.tryParse(car['created_at']) : null, onImageError: () => _refreshSignedUrlForCar(carId), - onTap: () => _selectionMode - ? _toggleCarSelection(car) - : _showCarDetails(car), + onTap: () { + if (_selectionMode) { + _toggleCarSelection(car); + } else { + _showCarDetails(car); + } + }, onLongPress: widget.isViewer ? null : () => _toggleCarSelection(car), ); @@ -557,7 +552,7 @@ class GarageScreenState extends State { } } - void _showCarDetails(Map car) { + Future _showCarDetails(Map car) async { if (_selectionMode) { _toggleCarSelection(car); return; @@ -571,7 +566,24 @@ class GarageScreenState extends State { final verified = global?['is_verified'] == true; final confirmations = (global?['confirmation_count'] as num?)?.toInt() ?? 0; final notes = car['notes'] as String?; - final imageUrl = car['signed_image_url'] as String?; + String? imageUrl = car['signed_image_url'] as String?; + if (imageUrl == null || imageUrl.isEmpty) { + final path = car['user_image_url'] as String?; + final signed = await StorageService.createSignedUrl(path); + if (!mounted) return; + if (signed != null && signed.isNotEmpty) { + imageUrl = signed; + final index = _cars.indexWhere((c) => c['id'] == car['id']); + if (index != -1) { + setState(() { + _cars[index] = { + ..._cars[index], + 'signed_image_url': signed, + }; + }); + } + } + } showModalBottomSheet( context: context, diff --git a/lib/widgets/car_card.dart b/lib/widgets/car_card.dart index 97941e4..62caed0 100644 --- a/lib/widgets/car_card.dart +++ b/lib/widgets/car_card.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:cached_network_image/cached_network_image.dart'; +import '../services/storage_service.dart'; import '../theme/app_colors.dart'; /// A styled card for displaying a single Hot Wheels car in the garage. @@ -10,6 +11,7 @@ class CarCard extends StatelessWidget { final int? year; final String? color; final String? imageUrl; + final String? imagePath; final bool isVerified; final DateTime? addedAt; final VoidCallback? onTap; @@ -25,6 +27,7 @@ class CarCard extends StatelessWidget { this.year, this.color, this.imageUrl, + this.imagePath, this.isVerified = false, this.addedAt, this.onTap, @@ -37,6 +40,9 @@ class CarCard extends StatelessWidget { Widget build(BuildContext context) { final theme = Theme.of(context); final isDark = theme.brightness == Brightness.dark; + final resolvedImageUrl = imageUrl; + final hasResolvedUrl = resolvedImageUrl != null && resolvedImageUrl.isNotEmpty; + final hasImagePath = imagePath != null && imagePath!.isNotEmpty; return Card( clipBehavior: Clip.antiAlias, @@ -66,9 +72,9 @@ class CarCard extends StatelessWidget { ) : AppColors.brandGradientSoft, ), - child: imageUrl != null && imageUrl!.isNotEmpty + child: hasResolvedUrl ? CachedNetworkImage( - imageUrl: imageUrl!, + imageUrl: resolvedImageUrl, fit: BoxFit.cover, fadeInDuration: Duration.zero, fadeOutDuration: Duration.zero, @@ -77,7 +83,27 @@ class CarCard extends StatelessWidget { return _PlaceholderIcon(isDark: isDark); }, ) - : _PlaceholderIcon(isDark: isDark), + : hasImagePath + ? FutureBuilder( + future: StorageService.createSignedUrl(imagePath), + builder: (context, snapshot) { + final signedUrl = snapshot.data; + if (signedUrl == null || signedUrl.isEmpty) { + return _PlaceholderIcon(isDark: isDark); + } + return CachedNetworkImage( + imageUrl: signedUrl, + fit: BoxFit.cover, + fadeInDuration: Duration.zero, + fadeOutDuration: Duration.zero, + errorWidget: (context, url, error) { + onImageError?.call(); + return _PlaceholderIcon(isDark: isDark); + }, + ); + }, + ) + : _PlaceholderIcon(isDark: isDark), ), ),