import 'package:flutter/material.dart'; import 'fullscreen_image_viewer.dart'; /// Widget pour afficher l'image d'un événement avec placeholder. /// /// Ce widget gère l'affichage des images d'événements avec un ratio d'aspect /// configurable et un placeholder en cas d'erreur ou d'absence d'image. /// L'image peut être cliquée pour l'afficher en plein écran avec Hero animation. /// /// **Usage:** /// ```dart /// EventImage( /// imageUrl: 'https://example.com/image.jpg', /// heroTag: 'event_image_123', /// aspectRatio: 16 / 9, /// ) /// ``` class EventImage extends StatelessWidget { const EventImage({ super.key, this.imageUrl, this.heroTag, this.eventTitle, this.aspectRatio = 16 / 9, }); /// URL de l'image à afficher final String? imageUrl; /// Tag Hero pour l'animation (doit être unique) final String? heroTag; /// Titre de l'événement (affiché dans la vue plein écran) final String? eventTitle; /// Ratio d'aspect de l'image (largeur/hauteur) final double aspectRatio; @override Widget build(BuildContext context) { final theme = Theme.of(context); // Si pas d'image ou pas de heroTag, afficher simplement sans Hero if (imageUrl == null || imageUrl!.isEmpty || heroTag == null) { return ClipRRect( borderRadius: BorderRadius.circular(12), child: AspectRatio( aspectRatio: aspectRatio, child: _buildImage(theme), ), ); } // Avec Hero animation et navigation vers fullscreen return GestureDetector( onTap: () => _openFullscreen(context), child: ClipRRect( borderRadius: BorderRadius.circular(12), child: AspectRatio( aspectRatio: aspectRatio, child: Hero( tag: heroTag!, child: _buildImage(theme), ), ), ), ); } /// Ouvre l'image en plein écran void _openFullscreen(BuildContext context) { Navigator.push( context, PageRouteBuilder( opaque: false, barrierColor: Colors.black, pageBuilder: (context, animation, secondaryAnimation) { return FadeTransition( opacity: animation, child: FullscreenImageViewer( imageUrl: imageUrl!, heroTag: heroTag!, title: eventTitle, ), ); }, ), ); } /// Construit l'image ou le placeholder. Widget _buildImage(ThemeData theme) { if (imageUrl == null || imageUrl!.isEmpty) { return _buildPlaceholder(theme); } return Image.network( imageUrl!, width: double.infinity, fit: BoxFit.cover, loadingBuilder: (context, child, loadingProgress) { if (loadingProgress == null) return child; return _buildLoadingPlaceholder(theme, loadingProgress); }, errorBuilder: (context, error, stackTrace) { return _buildPlaceholder(theme); }, ); } /// Construit le placeholder. Widget _buildPlaceholder(ThemeData theme) { return Container( color: theme.colorScheme.surfaceVariant, child: Center( child: Icon( Icons.image_not_supported_outlined, color: theme.colorScheme.onSurfaceVariant.withOpacity(0.5), size: 40, ), ), ); } /// Construit le placeholder de chargement. Widget _buildLoadingPlaceholder( ThemeData theme, ImageChunkEvent loadingProgress, ) { final progress = loadingProgress.expectedTotalBytes != null ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes! : null; return Container( color: theme.colorScheme.surfaceVariant, child: Center( child: CircularProgressIndicator( value: progress, color: theme.colorScheme.primary, ), ), ); } }