diff --git a/lib/features/garage/widgets/motorcycle_photo.dart b/lib/features/garage/widgets/motorcycle_photo.dart new file mode 100644 index 0000000..ec59177 --- /dev/null +++ b/lib/features/garage/widgets/motorcycle_photo.dart @@ -0,0 +1,123 @@ +import 'package:flutter/material.dart'; + +import '../../../services/motorcycle_image_service.dart'; + +class MotorcyclePhoto extends StatelessWidget { + final int year; + final String make; + final String model; + final double height; + final BorderRadius borderRadius; + final bool showAttribution; + + const MotorcyclePhoto({ + super.key, + required this.year, + required this.make, + required this.model, + this.height = 190, + this.borderRadius = const BorderRadius.all(Radius.circular(14)), + this.showAttribution = true, + }); + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: MotorcycleImageService.instance.resolve( + year: year, + make: make, + model: model, + ), + builder: (context, snapshot) { + final image = snapshot.data; + return ClipRRect( + borderRadius: borderRadius, + child: Container( + height: height, + width: double.infinity, + color: const Color(0xFF111111), + child: image == null + ? const _FallbackMotorcycleImage() + : Stack( + fit: StackFit.expand, + children: [ + Image.network( + image.imageUrl, + fit: BoxFit.cover, + filterQuality: FilterQuality.medium, + loadingBuilder: (context, child, progress) { + if (progress == null) return child; + return const Center( + child: CircularProgressIndicator( + strokeWidth: 2, + color: Colors.cyanAccent, + ), + ); + }, + errorBuilder: (_, _, _) => + const _FallbackMotorcycleImage(), + ), + if (showAttribution) + Positioned( + left: 10, + right: 10, + bottom: 8, + child: Align( + alignment: Alignment.bottomLeft, + child: DecoratedBox( + decoration: BoxDecoration( + color: Colors.black.withValues(alpha: 0.68), + borderRadius: BorderRadius.circular(5), + ), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 7, + vertical: 3, + ), + child: Text( + _attribution(image), + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + color: Colors.white70, + fontSize: 9, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ), + ), + ], + ), + ), + ); + }, + ); + } + + String _attribution(MotorcycleImage image) { + final credit = image.credit?.trim(); + final license = image.license?.trim(); + if (credit != null && credit.isNotEmpty && license != null && license.isNotEmpty) { + return 'Photo: $credit • $license'; + } + if (credit != null && credit.isNotEmpty) return 'Photo: $credit'; + return 'Photo: Wikimedia Commons'; + } +} + +class _FallbackMotorcycleImage extends StatelessWidget { + const _FallbackMotorcycleImage(); + + @override + Widget build(BuildContext context) { + return const Center( + child: Icon( + Icons.two_wheeler, + size: 64, + color: Color(0xFF00E5FF), + ), + ); + } +}