perf(images): lazy-load signed storage URLs for garage cards
This commit is contained in:
parent
c2d8e754a2
commit
7c25ab7c6f
2 changed files with 58 additions and 20 deletions
|
|
@ -109,21 +109,11 @@ class GarageScreenState extends State<GarageScreen> {
|
|||
.range(from, to);
|
||||
|
||||
final rows = List<Map<String, dynamic>>.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<GarageScreen> {
|
|||
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<GarageScreen> {
|
|||
}
|
||||
}
|
||||
|
||||
void _showCarDetails(Map<String, dynamic> car) {
|
||||
Future<void> _showCarDetails(Map<String, dynamic> car) async {
|
||||
if (_selectionMode) {
|
||||
_toggleCarSelection(car);
|
||||
return;
|
||||
|
|
@ -571,7 +566,24 @@ class GarageScreenState extends State<GarageScreen> {
|
|||
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,
|
||||
|
|
|
|||
|
|
@ -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,6 +83,26 @@ class CarCard extends StatelessWidget {
|
|||
return _PlaceholderIcon(isDark: isDark);
|
||||
},
|
||||
)
|
||||
: hasImagePath
|
||||
? FutureBuilder<String?>(
|
||||
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),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
Loading…
Reference in a new issue