Merge pull request #10 from DieLustigenTierwesen/copilot/sub-pr-6-yet-again

fix(CarCard): cache signed URL future in state to prevent rebuild flashing
This commit is contained in:
Lukas Müllner 2026-03-09 06:26:42 +01:00 committed by GitHub
commit bf969d0ae6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -4,7 +4,7 @@ import '../services/storage_service.dart';
import '../theme/app_colors.dart'; import '../theme/app_colors.dart';
/// A styled card for displaying a single die-cast car in the garage. /// A styled card for displaying a single die-cast car in the garage.
class CarCard extends StatelessWidget { class CarCard extends StatefulWidget {
final String hwId; final String hwId;
final String? name; final String? name;
final String? series; final String? series;
@ -36,22 +36,50 @@ class CarCard extends StatelessWidget {
this.isSelected = false, this.isSelected = false,
}); });
@override
State<CarCard> createState() => _CarCardState();
}
class _CarCardState extends State<CarCard> {
Future<String?>? _signedUrlFuture;
@override
void initState() {
super.initState();
_initSignedUrlFuture();
}
@override
void didUpdateWidget(CarCard oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.imagePath != widget.imagePath) {
_initSignedUrlFuture();
}
}
void _initSignedUrlFuture() {
final hasImagePath = widget.imagePath != null && widget.imagePath!.isNotEmpty;
_signedUrlFuture = hasImagePath
? StorageService.createSignedUrl(widget.imagePath!)
: null;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context); final theme = Theme.of(context);
final isDark = theme.brightness == Brightness.dark; final isDark = theme.brightness == Brightness.dark;
final resolvedImageUrl = imageUrl; final resolvedImageUrl = widget.imageUrl;
final hasResolvedUrl = resolvedImageUrl != null && resolvedImageUrl.isNotEmpty; final hasResolvedUrl = resolvedImageUrl != null && resolvedImageUrl.isNotEmpty;
final hasImagePath = imagePath != null && imagePath!.isNotEmpty; final hasImagePath = widget.imagePath != null && widget.imagePath!.isNotEmpty;
return Card( return Card(
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
color: isSelected color: widget.isSelected
? AppColors.orange.withValues(alpha: 0.18) ? AppColors.orange.withValues(alpha: 0.18)
: Theme.of(context).cardTheme.color, : Theme.of(context).cardTheme.color,
child: InkWell( child: InkWell(
onTap: onTap, onTap: widget.onTap,
onLongPress: onLongPress, onLongPress: widget.onLongPress,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -79,13 +107,13 @@ class CarCard extends StatelessWidget {
fadeInDuration: Duration.zero, fadeInDuration: Duration.zero,
fadeOutDuration: Duration.zero, fadeOutDuration: Duration.zero,
errorWidget: (context, url, error) { errorWidget: (context, url, error) {
onImageError?.call(); widget.onImageError?.call();
return _PlaceholderIcon(isDark: isDark); return _PlaceholderIcon(isDark: isDark);
}, },
) )
: hasImagePath : hasImagePath
? FutureBuilder<String?>( ? FutureBuilder<String?>(
future: StorageService.createSignedUrl(imagePath), future: _signedUrlFuture,
builder: (context, snapshot) { builder: (context, snapshot) {
final signedUrl = snapshot.data; final signedUrl = snapshot.data;
if (signedUrl == null || signedUrl.isEmpty) { if (signedUrl == null || signedUrl.isEmpty) {
@ -97,7 +125,7 @@ class CarCard extends StatelessWidget {
fadeInDuration: Duration.zero, fadeInDuration: Duration.zero,
fadeOutDuration: Duration.zero, fadeOutDuration: Duration.zero,
errorWidget: (context, url, error) { errorWidget: (context, url, error) {
onImageError?.call(); widget.onImageError?.call();
return _PlaceholderIcon(isDark: isDark); return _PlaceholderIcon(isDark: isDark);
}, },
); );
@ -113,7 +141,7 @@ class CarCard extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (isSelected) if (widget.isSelected)
Padding( Padding(
padding: const EdgeInsets.only(bottom: 6), padding: const EdgeInsets.only(bottom: 6),
child: Row( child: Row(
@ -141,7 +169,7 @@ class CarCard extends StatelessWidget {
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
), ),
child: Text( child: Text(
hwId, widget.hwId,
style: const TextStyle( style: const TextStyle(
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
@ -154,19 +182,19 @@ class CarCard extends StatelessWidget {
Row( Row(
children: [ children: [
Icon( Icon(
isVerified widget.isVerified
? Icons.verified ? Icons.verified
: Icons.hourglass_bottom, : Icons.hourglass_bottom,
size: 12, size: 12,
color: isVerified color: widget.isVerified
? AppColors.success ? AppColors.success
: AppColors.textHint, : AppColors.textHint,
), ),
const SizedBox(width: 4), const SizedBox(width: 4),
Text( Text(
isVerified ? 'Verified' : 'Pending', widget.isVerified ? 'Verified' : 'Pending',
style: theme.textTheme.bodySmall?.copyWith( style: theme.textTheme.bodySmall?.copyWith(
color: isVerified color: widget.isVerified
? AppColors.success ? AppColors.success
: AppColors.textHint, : AppColors.textHint,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -174,10 +202,10 @@ class CarCard extends StatelessWidget {
), ),
], ],
), ),
if (name != null && name!.isNotEmpty) ...[ if (widget.name != null && widget.name!.isNotEmpty) ...[
const SizedBox(height: 6), const SizedBox(height: 6),
Text( Text(
name!, widget.name!,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: theme.textTheme.titleSmall?.copyWith( style: theme.textTheme.titleSmall?.copyWith(
@ -185,10 +213,10 @@ class CarCard extends StatelessWidget {
), ),
), ),
], ],
if (series != null && series!.isNotEmpty) ...[ if (widget.series != null && widget.series!.isNotEmpty) ...[
const SizedBox(height: 2), const SizedBox(height: 2),
Text( Text(
series!, widget.series!,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: theme.textTheme.bodySmall?.copyWith( style: theme.textTheme.bodySmall?.copyWith(
@ -196,7 +224,7 @@ class CarCard extends StatelessWidget {
), ),
), ),
], ],
if (year != null) ...[ if (widget.year != null) ...[
const SizedBox(height: 4), const SizedBox(height: 4),
Row( Row(
children: [ children: [
@ -204,7 +232,7 @@ class CarCard extends StatelessWidget {
size: 12, color: AppColors.textHint), size: 12, color: AppColors.textHint),
const SizedBox(width: 4), const SizedBox(width: 4),
Text( Text(
'$year', '${widget.year}',
style: theme.textTheme.bodySmall?.copyWith( style: theme.textTheme.bodySmall?.copyWith(
color: AppColors.textHint, color: AppColors.textHint,
), ),