import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:go_router/go_router.dart'; import 'dart:math' as math; import 'custom_decorated_text_field.dart'; import 'app_custom_checkbox.dart'; import 'custom_navigation_button.dart'; import 'hover_relief_widget.dart'; import '../models/card_assets.dart'; import '../config/display_config.dart'; /// Widget générique pour le formulaire de présentation avec texte libre + CGU /// Supporte mode éditable et readonly, responsive mobile/desktop class PresentationFormScreen extends StatefulWidget { final DisplayMode mode; final String stepText; // Ex: "Étape 3/4" ou "Étape 4/5" final String title; // Ex: "Présentation et Conditions" ou "Motivation de votre demande" final CardColorHorizontal cardColor; final String textFieldHint; final String initialText; final bool initialCguAccepted; final String previousRoute; final Function(String text, bool cguAccepted) onSubmit; final bool embedContentOnly; final VoidCallback? onEdit; const PresentationFormScreen({ super.key, this.mode = DisplayMode.editable, required this.stepText, required this.title, required this.cardColor, required this.textFieldHint, required this.initialText, required this.initialCguAccepted, required this.previousRoute, required this.onSubmit, this.embedContentOnly = false, this.onEdit, }); @override State createState() => _PresentationFormScreenState(); } class _PresentationFormScreenState extends State { late TextEditingController _textController; late bool _cguAccepted; @override void initState() { super.initState(); _textController = TextEditingController(text: widget.initialText); _cguAccepted = widget.initialCguAccepted; } @override void dispose() { _textController.dispose(); super.dispose(); } void _handleSubmit() { if (!_cguAccepted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Vous devez accepter les CGU pour continuer.'), backgroundColor: Colors.red, ), ); return; } widget.onSubmit(_textController.text, _cguAccepted); } @override Widget build(BuildContext context) { final screenSize = MediaQuery.of(context).size; final config = DisplayConfig.fromContext(context, mode: widget.mode); if (widget.embedContentOnly) { return _buildCard(context, config, screenSize); } return Scaffold( body: Stack( children: [ Positioned.fill( child: Image.asset('assets/images/paper2.png', fit: BoxFit.cover, repeat: ImageRepeat.repeat), ), config.isMobile ? _buildMobileLayout(context, config, screenSize) : _buildDesktopLayout(context, config, screenSize), // Chevrons desktop uniquement if (!config.isMobile) ...[ // Chevron Gauche (Retour) Positioned( top: screenSize.height / 2 - 20, left: 40, child: IconButton( icon: Transform( alignment: Alignment.center, transform: Matrix4.rotationY(math.pi), child: Image.asset('assets/images/chevron_right.png', height: 40), ), onPressed: () { if (context.canPop()) { context.pop(); } else { context.go(widget.previousRoute); } }, tooltip: 'Retour', ), ), // Chevron Droit (Suivant) Positioned( top: screenSize.height / 2 - 20, right: 40, child: IconButton( icon: Image.asset('assets/images/chevron_right.png', height: 40), onPressed: _cguAccepted ? _handleSubmit : null, tooltip: 'Suivant', ), ), ], ], ), ); } /// Layout MOBILE : Plein écran sans scroll global Widget _buildMobileLayout(BuildContext context, DisplayConfig config, Size screenSize) { return Column( children: [ // Header fixe Padding( padding: const EdgeInsets.only(top: 20.0), child: Column( children: [ Text( widget.stepText, style: GoogleFonts.merienda( fontSize: 13, color: Colors.black54, ), ), const SizedBox(height: 6), Text( widget.title, style: GoogleFonts.merienda( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black87, ), textAlign: TextAlign.center, ), ], ), ), const SizedBox(height: 16), // Carte qui prend tout l'espace restant Expanded( child: _buildCard(context, config, screenSize), ), // Boutons en bas const SizedBox(height: 20), _buildMobileButtons(context, config, screenSize), const SizedBox(height: 10), ], ); } /// Layout DESKTOP : Avec scroll Widget _buildDesktopLayout(BuildContext context, DisplayConfig config, Size screenSize) { return Center( child: SingleChildScrollView( padding: const EdgeInsets.symmetric(vertical: 40.0, horizontal: 50.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( widget.stepText, style: GoogleFonts.merienda(fontSize: 16, color: Colors.black54), ), const SizedBox(height: 20), Text( widget.title, style: GoogleFonts.merienda( fontSize: 22, fontWeight: FontWeight.bold, color: Colors.black87, ), textAlign: TextAlign.center, ), const SizedBox(height: 30), _buildCard(context, config, screenSize), ], ), ), ); } /// Wrapper pour la carte (Mobile ou Desktop) Widget _buildCard(BuildContext context, DisplayConfig config, Size screenSize) { // Si mode Readonly Desktop : Layout spécial "Vintage" horizontal (2:1) if (config.isReadonly && !config.isMobile && widget.embedContentOnly) { return _buildReadonlyDesktopCard(context, config, screenSize); } // Si mode Readonly Mobile : Layout spécial "Vintage" vertical (1:2) if (config.isReadonly && config.isMobile && widget.embedContentOnly) { return Padding( padding: EdgeInsets.symmetric(horizontal: screenSize.width * 0.05), child: _buildMobileReadonlyCard(context, config, screenSize), ); } final Widget cardContent = config.isMobile ? _buildMobileCard(context, config, screenSize) : _buildDesktopCard(context, config, screenSize); return Stack( clipBehavior: Clip.none, children: [ if (widget.embedContentOnly) Column( mainAxisSize: MainAxisSize.min, children: [ Text( widget.title, style: GoogleFonts.merienda( fontSize: config.isMobile ? 18 : 22, fontWeight: FontWeight.bold, color: Colors.black87, ), textAlign: TextAlign.center, ), const SizedBox(height: 20), cardContent, ], ) else cardContent, if (config.isReadonly && widget.onEdit != null) Positioned( top: widget.embedContentOnly ? 50 : 10, right: 10, child: IconButton( icon: const Icon(Icons.edit, color: Colors.black54), onPressed: widget.onEdit, tooltip: 'Modifier', ), ), ], ); } /// Carte en mode readonly MOBILE avec hauteur adaptative Widget _buildMobileReadonlyCard(BuildContext context, DisplayConfig config, Size screenSize) { return Container( width: double.infinity, // Pas de height fixe, s'adapte au contenu padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 24.0), decoration: BoxDecoration( image: DecorationImage( image: AssetImage(_getVerticalCardAsset()), fit: BoxFit.fill, // Fill pour que l'image s'étire selon la hauteur du contenu ), borderRadius: BorderRadius.circular(15), ), child: Stack( children: [ Column( mainAxisSize: MainAxisSize.min, // S'adapte au contenu children: [ // Titre + Edit Button Row( children: [ Expanded( child: Text( widget.title, style: GoogleFonts.merienda( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black87 ), textAlign: TextAlign.center, ), ), if (widget.onEdit != null) const SizedBox(width: 28), ], ), // Contenu aligné en haut (Texte scrollable + Checkbox) Padding( padding: const EdgeInsets.only(top: 20.0), child: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ // Champ texte scrollable // On utilise ConstrainedBox pour limiter la hauteur max ConstrainedBox( constraints: const BoxConstraints(maxHeight: 300), // Max height pour éviter une carte infinie child: CustomDecoratedTextField( controller: _textController, hintText: widget.textFieldHint, fieldHeight: null, // Flexible maxLines: 100, expandDynamically: true, // Scrollable fontSize: 14.0, readOnly: config.isReadonly, ), ), const SizedBox(height: 16), // Checkbox Transform.scale( scale: 0.85, child: AppCustomCheckbox( label: 'J\'accepte les CGU et la\nPolitique de confidentialité', value: _cguAccepted, onChanged: (v) {}, ), ), ], ), ), ], ), if (widget.onEdit != null) Positioned( top: 0, right: 0, child: IconButton( padding: EdgeInsets.zero, constraints: const BoxConstraints(), icon: const Icon(Icons.edit, color: Colors.black54, size: 24), onPressed: widget.onEdit, tooltip: 'Modifier', ), ), ], ), ); } /// Carte en mode readonly desktop avec AspectRatio 2:1 (format de l'ancien récapitulatif) Widget _buildReadonlyDesktopCard(BuildContext context, DisplayConfig config, Size screenSize) { // Largeur de la carte : 50% de l'écran final cardWidth = screenSize.width / 2.0; return SizedBox( width: cardWidth, child: AspectRatio( aspectRatio: 2.0, child: Container( padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 25.0), decoration: BoxDecoration( image: DecorationImage( image: AssetImage(widget.cardColor.path), fit: BoxFit.cover, ), borderRadius: BorderRadius.circular(15), ), child: Column( children: [ // Titre + Edit Button Row( children: [ Expanded( child: Text( widget.title, style: GoogleFonts.merienda(fontSize: 22, fontWeight: FontWeight.bold, color: Colors.black87), textAlign: TextAlign.center, ), ), if (widget.onEdit != null) IconButton( icon: const Icon(Icons.edit, color: Colors.black54, size: 28), onPressed: widget.onEdit, tooltip: 'Modifier', ), ], ), const SizedBox(height: 18), // Texte de motivation Expanded( child: CustomDecoratedTextField( controller: _textController, hintText: '', fieldHeight: double.infinity, // Remplit l'espace disponible maxLines: 10, expandDynamically: false, // Fixe pour le readonly fontSize: 18.0, readOnly: true, ), ), const SizedBox(height: 20), // CGU AppCustomCheckbox( label: 'J\'accepte les Conditions Générales\nd\'Utilisation et la Politique de confidentialité', value: _cguAccepted, onChanged: (v) {}, // Readonly checkboxSize: 22.0, fontSize: 16.0, ), ], ), ), ), ); } /// Carte DESKTOP : Format horizontal 2:1 Widget _buildDesktopCard(BuildContext context, DisplayConfig config, Size screenSize) { final cardWidth = screenSize.width * 0.6; final double imageAspectRatio = 2.0; final cardHeight = cardWidth / imageAspectRatio; return Container( width: cardWidth, height: cardHeight, decoration: BoxDecoration( image: DecorationImage( image: AssetImage(widget.cardColor.path), fit: BoxFit.fill, ), ), child: Padding( padding: const EdgeInsets.all(40.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Expanded( child: CustomDecoratedTextField( controller: _textController, hintText: widget.textFieldHint, fieldHeight: cardHeight * 0.6, maxLines: 10, expandDynamically: true, fontSize: 18.0, readOnly: config.isReadonly, ), ), const SizedBox(height: 20), AppCustomCheckbox( label: 'J\'accepte les Conditions Générales\nd\'Utilisation et la Politique de confidentialité', value: _cguAccepted, onChanged: config.isReadonly ? (v) {} : (value) => setState(() => _cguAccepted = value ?? false), ), ], ), ), ); } /// Carte MOBILE : Prend tout l'espace disponible Widget _buildMobileCard(BuildContext context, DisplayConfig config, Size screenSize) { // Le contenu du champ texte Widget textFieldContent = LayoutBuilder( builder: (context, constraints) { // En mode embed (récap), constraints.maxHeight peut être infini, donc on fixe une hauteur par défaut // En mode standalone, on utilise la hauteur disponible double height = constraints.maxHeight; if (height.isInfinite) height = 200.0; return CustomDecoratedTextField( controller: _textController, hintText: widget.textFieldHint, fieldHeight: height, maxLines: 100, expandDynamically: false, fontSize: 14.0, readOnly: config.isReadonly, ); }, ); return Padding( padding: EdgeInsets.symmetric(horizontal: screenSize.width * 0.05), child: Container( decoration: BoxDecoration( image: DecorationImage( image: AssetImage(_getVerticalCardAsset()), fit: BoxFit.fill, ), ), padding: const EdgeInsets.symmetric(vertical: 24, horizontal: 20), child: Column( children: [ // Champ de texte if (widget.embedContentOnly) // En mode récapitulatif, on donne une hauteur fixe pour éviter l'erreur d'Expanded SizedBox(height: 200, child: textFieldContent) else // En mode écran complet, on prend tout l'espace restant Expanded(child: textFieldContent), const SizedBox(height: 16), // Checkbox en bas Transform.scale( scale: 0.85, child: AppCustomCheckbox( label: 'J\'accepte les CGU et la\nPolitique de confidentialité', value: _cguAccepted, onChanged: config.isReadonly ? (v) {} : (value) => setState(() => _cguAccepted = value ?? false), ), ), ], ), ), ); } /// Boutons mobile Widget _buildMobileButtons(BuildContext context, DisplayConfig config, Size screenSize) { return Padding( padding: EdgeInsets.symmetric( horizontal: screenSize.width * 0.05, ), child: Row( children: [ Expanded( child: HoverReliefWidget( child: CustomNavigationButton( text: 'Précédent', style: NavigationButtonStyle.purple, onPressed: () { if (context.canPop()) { context.pop(); } else { context.go(widget.previousRoute); } }, width: double.infinity, height: 50, fontSize: 16, ), ), ), const SizedBox(width: 16), Expanded( child: HoverReliefWidget( child: CustomNavigationButton( text: 'Suivant', style: NavigationButtonStyle.green, onPressed: _handleSubmit, width: double.infinity, height: 50, fontSize: 16, ), ), ), ], ), ); } /// Retourne l'asset de carte vertical correspondant à la couleur String _getVerticalCardAsset() { switch (widget.cardColor) { case CardColorHorizontal.blue: return CardColorVertical.blue.path; case CardColorHorizontal.green: return CardColorVertical.green.path; case CardColorHorizontal.lavender: return CardColorVertical.lavender.path; case CardColorHorizontal.lime: return CardColorVertical.lime.path; case CardColorHorizontal.peach: return CardColorVertical.peach.path; case CardColorHorizontal.pink: return CardColorVertical.pink.path; case CardColorHorizontal.red: return CardColorVertical.red.path; } } }