/// Page dédiée à l'envoi de commentaires / feedback library feedback_page; import 'package:flutter/material.dart'; import '../../../../core/di/injection.dart'; import '../../../../core/network/api_client.dart'; import '../../../../core/utils/logger.dart'; import '../../../../shared/design_system/components/uf_app_bar.dart'; import '../../../../shared/design_system/unionflow_design_system.dart'; import '../../../../shared/widgets/core_card.dart'; // ───────────────────────────────────────────────────────────────────────────── // Données statiques // ───────────────────────────────────────────────────────────────────────────── const _kCategories = [ _FeedbackCategory('suggestion', 'Suggestion', Icons.lightbulb_outline, AppColors.primary), _FeedbackCategory('bug', 'Bug / Problème', Icons.bug_report_outlined, AppColors.error), _FeedbackCategory('amelioration', 'Amélioration', Icons.trending_up, AppColors.success), _FeedbackCategory('autre', 'Autre', Icons.help_outline, AppColors.primaryDark), ]; const _kMaxLength = 1000; // ───────────────────────────────────────────────────────────────────────────── // Page // ───────────────────────────────────────────────────────────────────────────── class FeedbackPage extends StatefulWidget { const FeedbackPage({super.key}); @override State createState() => _FeedbackPageState(); } class _FeedbackPageState extends State { final _messageController = TextEditingController(); String _selectedCategory = 'suggestion'; bool _isSending = false; int _charCount = 0; @override void initState() { super.initState(); _messageController.addListener( () => setState(() => _charCount = _messageController.text.length), ); } @override void dispose() { _messageController.dispose(); super.dispose(); } Future _submitFeedback() async { final message = _messageController.text.trim(); if (message.isEmpty) { _showSnackBar('Veuillez saisir un message.', isError: true); return; } setState(() => _isSending = true); try { final cat = _kCategories.firstWhere((c) => c.id == _selectedCategory); await getIt().post( '/api/feedback', data: { 'subject': '[${cat.label}] Feedback mobile', 'message': message, }, ); if (mounted) { _messageController.clear(); _showSnackBar('Merci pour votre retour !'); } } catch (e, st) { AppLogger.error('FeedbackPage: envoi feedback échoué', error: e, stackTrace: st); if (mounted) _showSnackBar('Envoi échoué. Réessayez plus tard.', isError: true); } finally { if (mounted) setState(() => _isSending = false); } } void _showSnackBar(String message, {bool isError = false}) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(message), backgroundColor: isError ? AppColors.error : AppColors.success, behavior: SnackBarBehavior.floating, ), ); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Theme.of(context).scaffoldBackgroundColor, appBar: UFAppBar( title: 'Commentaires', moduleGradient: ModuleColors.supportGradient, ), body: SafeArea( top: false, child: ListView( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), children: [ const SizedBox(height: 4), _buildCategorySection(), const SizedBox(height: 8), _buildMessageSection(), const SizedBox(height: 12), _buildSubmitButton(), const SizedBox(height: 80), ], ), ), ); } // ── Section catégories ──────────────────────────────────────────────────── Widget _buildCategorySection() { final scheme = Theme.of(context).colorScheme; return CoreCard( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(Icons.category_outlined, color: scheme.onSurfaceVariant, size: 16), const SizedBox(width: 8), Text( 'TYPE DE RETOUR', style: AppTypography.subtitleSmall.copyWith( fontWeight: FontWeight.bold, letterSpacing: 1.1, color: scheme.onSurfaceVariant, ), ), ], ), const SizedBox(height: 12), Wrap( spacing: 8, runSpacing: 8, children: _kCategories.map(_buildCategoryChip).toList(), ), ], ), ); } Widget _buildCategoryChip(_FeedbackCategory cat) { final scheme = Theme.of(context).colorScheme; final isSelected = _selectedCategory == cat.id; return InkWell( onTap: () => setState(() => _selectedCategory = cat.id), borderRadius: BorderRadius.circular(8), child: AnimatedContainer( duration: const Duration(milliseconds: 180), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( color: isSelected ? cat.color.withOpacity(0.1) : scheme.surface, borderRadius: BorderRadius.circular(8), border: Border.all( color: isSelected ? cat.color.withOpacity(0.5) : scheme.outlineVariant, width: isSelected ? 1.5 : 1, ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( cat.icon, size: 15, color: isSelected ? cat.color : scheme.onSurfaceVariant, ), const SizedBox(width: 6), Text( cat.label, style: AppTypography.actionText.copyWith( fontSize: 12, fontWeight: FontWeight.w600, color: isSelected ? cat.color : scheme.onSurfaceVariant, ), ), ], ), ), ); } // ── Section message ──────────────────────────────────────────────────────── Widget _buildMessageSection() { final scheme = Theme.of(context).colorScheme; final isNearLimit = _charCount > _kMaxLength * 0.85; return CoreCard( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(Icons.edit_note_outlined, color: scheme.onSurfaceVariant, size: 16), const SizedBox(width: 8), Text( 'VOTRE MESSAGE', style: AppTypography.subtitleSmall.copyWith( fontWeight: FontWeight.bold, letterSpacing: 1.1, color: scheme.onSurfaceVariant, ), ), const Spacer(), Text( '$_charCount / $_kMaxLength', style: AppTypography.subtitleSmall.copyWith( fontSize: 10, color: isNearLimit ? AppColors.error : scheme.onSurfaceVariant, fontWeight: isNearLimit ? FontWeight.bold : FontWeight.normal, ), ), ], ), const SizedBox(height: 12), TextField( controller: _messageController, maxLines: 7, maxLength: _kMaxLength, buildCounter: (_, {required currentLength, required isFocused, maxLength}) => const SizedBox.shrink(), style: AppTypography.bodyTextSmall.copyWith( color: scheme.onSurface, fontSize: 13, ), decoration: InputDecoration( hintText: 'Décrivez votre suggestion, problème ou idée...', hintStyle: AppTypography.subtitleSmall.copyWith( color: scheme.onSurfaceVariant, ), filled: true, fillColor: scheme.surfaceContainerHighest.withOpacity(0.4), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide(color: scheme.outlineVariant), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide(color: scheme.outlineVariant), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide(color: ModuleColors.support, width: 1.5), ), contentPadding: const EdgeInsets.all(12), ), ), ], ), ); } // ── Bouton envoi ────────────────────────────────────────────────────────── Widget _buildSubmitButton() { return SizedBox( width: double.infinity, child: ElevatedButton.icon( onPressed: _isSending ? null : _submitFeedback, icon: _isSending ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white), ) : const Icon(Icons.send_rounded, color: Colors.white, size: 16), label: Text( _isSending ? 'Envoi en cours...' : 'Envoyer le commentaire', style: AppTypography.actionText.copyWith( fontWeight: FontWeight.w600, color: Colors.white, ), ), style: ElevatedButton.styleFrom( backgroundColor: ModuleColors.support, disabledBackgroundColor: ModuleColors.support.withOpacity(0.5), padding: const EdgeInsets.symmetric(vertical: 10), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), elevation: 0, ), ), ); } } // ───────────────────────────────────────────────────────────────────────────── // Modèle de catégorie // ───────────────────────────────────────────────────────────────────────────── class _FeedbackCategory { final String id; final String label; final IconData icon; final Color color; const _FeedbackCategory(this.id, this.label, this.icon, this.color); }