import 'dart:io'; import 'dart:typed_data'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; import 'hover_relief_widget.dart'; /// Cadre « croquis » par-dessus la photo (même ressource que les cartes enfant). const String kRegistrationPhotoFrameAsset = 'assets/images/photo_frame.png'; /// Indique si une photo « réelle » est présente (hors seul placeholder asset). bool registrationPhotoSlotHasImage({ Uint8List? imageBytes, File? imageFile, String? imagePathOrAsset, }) { if (imageBytes != null && imageBytes.isNotEmpty) return true; if (imageFile != null) return true; if (imagePathOrAsset == null || imagePathOrAsset.isEmpty) return false; return !imagePathOrAsset.startsWith('assets/'); } /// Zone photo uniforme : cadre gris / placeholder, tap pour choisir, croix pour retirer, cadre dessin si photo. /// /// Utilisé inscription **enfants** ([ChildCardWidget]) et **AM** ([ProfessionalInfoFormScreen]). class RegistrationPhotoSlot extends StatelessWidget { final double side; final double scaleFactor; final Uint8List? imageBytes; final File? imageFile; /// Chemin fichier local (hors `assets/`) ou URI asset `assets/...`. final String? imagePathOrAsset; final VoidCallback? onTapPick; final VoidCallback? onClear; final Color baseShadowColor; final String placeholderAsset; final bool isMobile; const RegistrationPhotoSlot({ super.key, required this.side, this.scaleFactor = 1.0, this.imageBytes, this.imageFile, this.imagePathOrAsset, this.onTapPick, this.onClear, required this.baseShadowColor, this.placeholderAsset = 'assets/images/photo.png', this.isMobile = false, }); bool get _hasImage => registrationPhotoSlotHasImage( imageBytes: imageBytes, imageFile: imageFile, imagePathOrAsset: imagePathOrAsset, ); Widget _buildImage({required BoxFit fit}) { final b = imageBytes; if (b != null && b.isNotEmpty) { return Image.memory(b, fit: fit); } final f = imageFile; if (f != null) { return kIsWeb ? Image.network(f.path, fit: fit) : Image.file(f, fit: fit); } final p = imagePathOrAsset; if (p != null && p.isNotEmpty) { if (p.startsWith('assets/')) { return Image.asset(p, fit: fit); } if (!kIsWeb) { try { final file = File(p); if (file.existsSync()) { return Image.file(file, fit: fit); } } catch (_) {} } } return Image.asset(placeholderAsset, fit: BoxFit.contain); } @override Widget build(BuildContext context) { final canPick = onTapPick != null; final canClear = onClear != null && _hasImage; final outerRadius = BorderRadius.circular(10 * scaleFactor); final photoClipRadius = BorderRadius.circular(side * 0.14); final initialPhotoShadow = Color.alphaBlend(Colors.black.withOpacity(0.22), baseShadowColor); final hoverPhotoShadow = Color.alphaBlend(Colors.black.withOpacity(0.32), baseShadowColor); return Stack( clipBehavior: Clip.none, alignment: Alignment.center, children: [ HoverReliefWidget( onPressed: canPick ? onTapPick : null, borderRadius: outerRadius, initialElevation: 10, hoverElevation: 16, initialShadowColor: initialPhotoShadow, hoverShadowColor: hoverPhotoShadow, clipBehavior: _hasImage ? Clip.none : Clip.antiAlias, child: SizedBox( width: side, height: side, child: !_hasImage ? ClipRRect( borderRadius: outerRadius, child: Center( child: Image.asset( placeholderAsset, fit: BoxFit.contain, width: side, height: side, ), ), ) : Stack( fit: StackFit.expand, children: [ Positioned.fill( child: ClipRRect( borderRadius: photoClipRadius, child: _buildImage(fit: BoxFit.cover), ), ), Positioned.fill( child: Image.asset( kRegistrationPhotoFrameAsset, fit: BoxFit.fill, errorBuilder: (context, error, stackTrace) => const SizedBox.shrink(), ), ), ], ), ), ), if (canClear) Positioned( top: 8 * scaleFactor, right: 8 * scaleFactor, child: Material( color: Colors.transparent, child: InkWell( onTap: onClear, customBorder: const CircleBorder(), child: Padding( padding: const EdgeInsets.all(4), child: Image.asset( 'assets/images/cross.png', width: isMobile ? 26 : 30, height: isMobile ? 26 : 30, fit: BoxFit.contain, ), ), ), ), ), ], ); } }