import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'dart:math' as math; import '../../blocs/activity/activity_bloc.dart'; import '../../blocs/activity/activity_event.dart'; import '../../models/activity.dart'; import '../../models/trip.dart'; import '../../services/error_service.dart'; /// Bottom sheet pour ajouter une activité personnalisée class AddActivityBottomSheet extends StatefulWidget { final Trip trip; const AddActivityBottomSheet({super.key, required this.trip}); @override State createState() => _AddActivityBottomSheetState(); } class _AddActivityBottomSheetState extends State { final _formKey = GlobalKey(); final _nameController = TextEditingController(); final _descriptionController = TextEditingController(); final _addressController = TextEditingController(); final ErrorService _errorService = ErrorService(); ActivityCategory _selectedCategory = ActivityCategory.attraction; bool _isLoading = false; @override void dispose() { _nameController.dispose(); _descriptionController.dispose(); _addressController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final theme = Theme.of(context); final mediaQuery = MediaQuery.of(context); final keyboardHeight = mediaQuery.viewInsets.bottom; return AnimatedContainer( duration: const Duration(milliseconds: 200), height: mediaQuery.size.height * 0.85, margin: EdgeInsets.only(left: 16, right: 16, top: 16, bottom: 16), decoration: BoxDecoration( color: theme.cardColor, borderRadius: BorderRadius.circular(20), ), child: Column( children: [ // Handle bar Container( width: 40, height: 4, margin: const EdgeInsets.symmetric(vertical: 12), decoration: BoxDecoration( color: theme.colorScheme.onSurface.withOpacity(0.3), borderRadius: BorderRadius.circular(2), ), ), // Header Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: Row( children: [ Text( 'Ajouter une activité', style: theme.textTheme.titleLarge?.copyWith( fontWeight: FontWeight.bold, ), ), const Spacer(), IconButton( onPressed: () => Navigator.pop(context), icon: const Icon(Icons.close), ), ], ), ), const Divider(), // Formulaire avec SingleChildScrollView pour le scroll automatique Expanded( child: SingleChildScrollView( padding: EdgeInsets.only( left: 20, right: 20, top: 20, bottom: math.max(20, keyboardHeight), ), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Nom de l'activité _buildSectionTitle('Nom de l\'activité'), const SizedBox(height: 8), _buildTextField( controller: _nameController, hintText: 'Ex: Visite du Louvre', icon: Icons.event, validator: (value) { if (value == null || value.trim().isEmpty) { return 'Nom requis'; } return null; }, ), const SizedBox(height: 20), // Description _buildSectionTitle('Description'), const SizedBox(height: 8), _buildTextField( controller: _descriptionController, hintText: 'Décrivez cette activité...', icon: Icons.description, maxLines: 3, validator: (value) { if (value == null || value.trim().isEmpty) { return 'Description requise'; } return null; }, ), const SizedBox(height: 20), // Catégorie _buildSectionTitle('Catégorie'), const SizedBox(height: 8), _buildCategorySelector(), const SizedBox(height: 20), // Adresse (optionnel) _buildSectionTitle('Adresse (optionnel)'), const SizedBox(height: 8), _buildTextField( controller: _addressController, hintText: 'Adresse ou lieu', icon: Icons.location_on, ), const SizedBox(height: 40), // Boutons d'action Row( children: [ Expanded( child: OutlinedButton( onPressed: () => Navigator.pop(context), style: OutlinedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 16), side: BorderSide( color: theme.colorScheme.outline, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), child: const Text('Annuler'), ), ), const SizedBox(width: 16), Expanded( child: ElevatedButton( onPressed: _isLoading ? null : _addActivity, style: ElevatedButton.styleFrom( backgroundColor: Colors.blue, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), child: _isLoading ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation( Colors.white, ), ), ) : const Text('Ajouter'), ), ), ], ), ], ), ), ), ), ], ), ); } Widget _buildSectionTitle(String title) { return Text( title, style: Theme.of( context, ).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w600), ); } Widget _buildTextField({ required TextEditingController controller, required String hintText, required IconData icon, int maxLines = 1, String? Function(String?)? validator, }) { final theme = Theme.of(context); final isDarkMode = theme.brightness == Brightness.dark; return TextFormField( controller: controller, maxLines: maxLines, validator: validator, decoration: InputDecoration( hintText: hintText, prefixIcon: Icon(icon), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( color: isDarkMode ? Colors.white.withValues(alpha: 0.2) : Colors.black.withValues(alpha: 0.2), ), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( color: isDarkMode ? Colors.white.withValues(alpha: 0.2) : Colors.black.withValues(alpha: 0.2), ), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: const BorderSide(color: Colors.blue, width: 2), ), filled: true, fillColor: theme.colorScheme.surface, ), ); } Widget _buildCategorySelector() { final theme = Theme.of(context); return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: theme.colorScheme.surface, borderRadius: BorderRadius.circular(12), border: Border.all( color: theme.colorScheme.outline.withValues(alpha: 0.5), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Sélectionnez une catégorie', style: theme.textTheme.bodyMedium?.copyWith( color: theme.colorScheme.onSurface.withValues(alpha: 0.7), ), ), const SizedBox(height: 12), Wrap( spacing: 8, runSpacing: 8, children: ActivityCategory.values.map((category) { final isSelected = _selectedCategory == category; return GestureDetector( onTap: () { setState(() { _selectedCategory = category; }); }, child: Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 8, ), decoration: BoxDecoration( color: isSelected ? Colors.blue : Colors.transparent, borderRadius: BorderRadius.circular(20), border: Border.all( color: isSelected ? Colors.blue : theme.colorScheme.outline, ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( _getCategoryIcon(category), size: 16, color: isSelected ? Colors.white : theme.colorScheme.onSurface, ), const SizedBox(width: 6), Text( category.displayName, style: theme.textTheme.bodySmall?.copyWith( color: isSelected ? Colors.white : theme.colorScheme.onSurface, fontWeight: FontWeight.w500, ), ), ], ), ), ); }).toList(), ), ], ), ); } void _addActivity() async { if (!_formKey.currentState!.validate()) { return; } if (widget.trip.id == null) { _errorService.showSnackbar( message: 'Erreur: ID du voyage manquant', isError: true, ); return; } setState(() { _isLoading = true; }); try { final activity = Activity( id: '', // Sera généré par Firestore tripId: widget.trip.id!, name: _nameController.text.trim(), description: _descriptionController.text.trim(), category: _selectedCategory.displayName, address: _addressController.text.trim().isNotEmpty ? _addressController.text.trim() : null, votes: {}, createdAt: DateTime.now(), updatedAt: DateTime.now(), ); context.read().add(AddActivity(activity)); // Fermer le bottom sheet Navigator.pop(context); } catch (e) { _errorService.showSnackbar( message: 'Erreur lors de l\'ajout de l\'activité', isError: true, ); } finally { if (mounted) { setState(() { _isLoading = false; }); } } } IconData _getCategoryIcon(ActivityCategory category) { switch (category) { case ActivityCategory.museum: return Icons.museum; case ActivityCategory.restaurant: return Icons.restaurant; case ActivityCategory.attraction: return Icons.place; case ActivityCategory.entertainment: return Icons.sports_esports; case ActivityCategory.shopping: return Icons.shopping_bag; case ActivityCategory.nature: return Icons.nature; case ActivityCategory.culture: return Icons.palette; case ActivityCategory.nightlife: return Icons.nightlife; case ActivityCategory.sports: return Icons.sports; case ActivityCategory.relaxation: return Icons.spa; } } }