diff --git a/lib/assets/json/event_categories.json b/lib/assets/json/event_categories.json new file mode 100644 index 0000000..3b7ccb0 --- /dev/null +++ b/lib/assets/json/event_categories.json @@ -0,0 +1,68 @@ +{ + "categories": { + "Événements Professionnels": [ + "Conférence", + "Séminaire", + "Webinar", + "Atelier", + "Networking", + "Réunion d'affaires", + "Salon", + "Forum", + "Rencontre professionnelle", + "Conférence académique", + "Conférence TEDx", + "Lancement de produit", + "Assemblée générale", + "Réunion de crise", + "Lancement de startup" + ], + "Événements Culturels": [ + "Concert", + "Festival", + "Exposition", + "Projection de film", + "Rencontre littéraire", + "Spectacle de théâtre", + "Spectacle de danse", + "Festival de cinéma", + "Exposition d'art", + "Projection en avant-première", + "Festival de bande dessinée", + "Projection de documentaire", + "Festival de rue" + ], + "Événements Sportifs": [ + "Compétition sportive", + "Marathon", + "Tournoi de jeux vidéo", + "Hackathon", + "Compétition de sport électronique", + "Tournoi sportif" + ], + "Événements Caritatifs": [ + "Dîner caritatif", + "Collecte de fonds", + "Vente aux enchères", + "Vente de charité" + ], + "Événements Sociaux": [ + "Fête nationale", + "Fête de mariage", + "Anniversaire", + "Réveillon du nouvel an", + "Fête de Noël", + "Soirée de gala", + "Fête de fin d'année" + ], + "Ateliers et Formations": [ + "Atelier culinaire", + "Cours de yoga", + "Session de formation", + "Atelier de design", + "Atelier d'écriture", + "Atelier de peinture", + "Atelier de bricolage" + ] + } +} diff --git a/lib/data/services/category_service.dart b/lib/data/services/category_service.dart new file mode 100644 index 0000000..9e8de48 --- /dev/null +++ b/lib/data/services/category_service.dart @@ -0,0 +1,32 @@ +import 'dart:convert'; +import 'package:flutter/services.dart'; + +/// Service pour gérer le chargement des catégories depuis un fichier JSON. +class CategoryService { + /// Méthode pour charger les catégories depuis un fichier JSON. + /// Cette méthode retourne un Map où chaque clé représente un type de catégorie + /// et chaque valeur est une liste de catégories associées à ce type. + Future>> loadCategories() async { + try { + // Charger le fichier JSON à partir des assets + print('Chargement du fichier JSON des catégories...'); + final String response = await rootBundle.loadString('lib/assets/json/event_categories.json'); + + // Décoder le contenu du fichier JSON + final Map data = json.decode(response); + print('Données JSON décodées avec succès.'); + + // Transformer les données en un Map de catégories par type + final Map> categoriesByType = (data['categories'] as Map).map( + (key, value) => MapEntry(key, List.from(value)), + ); + + print('Catégories chargées: $categoriesByType'); + return categoriesByType; + } catch (e) { + // Gérer les erreurs de chargement ou de décodage + print('Erreur lors du chargement des catégories: $e'); + throw Exception('Impossible de charger les catégories.'); + } + } +} diff --git a/lib/presentation/screens/dialogs/add_event_dialog.dart b/lib/presentation/screens/dialogs/add_event_dialog.dart index 47bd9e9..d714bf9 100644 --- a/lib/presentation/screens/dialogs/add_event_dialog.dart +++ b/lib/presentation/screens/dialogs/add_event_dialog.dart @@ -1,15 +1,15 @@ import 'package:flutter/material.dart'; -import 'package:fluttertoast/fluttertoast.dart'; -import 'package:google_maps_flutter/google_maps_flutter.dart'; -import 'package:http/http.dart' as http; -import 'dart:convert'; import 'package:afterwork/data/models/event_model.dart'; import 'package:afterwork/data/models/user_model.dart'; import 'package:afterwork/core/constants/urls.dart'; +import 'package:afterwork/data/services/category_service.dart'; import '../location/location_picker_screen.dart'; +import 'dart:convert'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:google_maps_flutter/google_maps_flutter.dart'; +import 'package:http/http.dart' as http; /// Classe représentant la boîte de dialogue pour ajouter un nouvel événement. -/// Cette boîte de dialogue permet à l'utilisateur de remplir les détails d'un nouvel événement. class AddEventDialog extends StatefulWidget { final String userId; final String userName; @@ -27,15 +27,36 @@ class AddEventDialog extends StatefulWidget { } class _AddEventDialogState extends State { - final _formKey = GlobalKey(); // Clé globale pour valider le formulaire + final _formKey = GlobalKey(); + + // Variables pour stocker les données de l'événement String _title = ''; String _description = ''; DateTime? _selectedDate; String? _imagePath; - String _location = 'Abidjan'; // Par défaut à Cocody, Abidjan + String _location = 'Abidjan'; String _category = ''; String _link = ''; - LatLng? _selectedLatLng = const LatLng(5.348722, -3.985038); // Par défaut à Cocody, Abidjan + LatLng? _selectedLatLng = const LatLng(5.348722, -3.985038); + Map> _categories = {}; + List _currentCategories = []; + String? _selectedCategoryType; + + @override + void initState() { + super.initState(); + _loadCategories(); + } + + void _loadCategories() async { + final CategoryService categoryService = CategoryService(); + final categories = await categoryService.loadCategories(); + setState(() { + _categories = categories; + _selectedCategoryType = categories.keys.first; + _currentCategories = categories[_selectedCategoryType] ?? []; + }); + } @override Widget build(BuildContext context) { @@ -48,7 +69,7 @@ class _AddEventDialogState extends State { child: Padding( padding: const EdgeInsets.all(16.0), child: Form( - key: _formKey, // Formulaire clé pour valider l'entrée des utilisateurs + key: _formKey, child: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -75,7 +96,6 @@ class _AddEventDialogState extends State { ); } - /// Construction du champ de saisie du titre de l'événement. Widget _buildTitleField() { return TextFormField( decoration: InputDecoration( @@ -104,7 +124,6 @@ class _AddEventDialogState extends State { ); } - /// Construction du champ de saisie de la description de l'événement. Widget _buildDescriptionField() { return TextFormField( decoration: InputDecoration( @@ -134,7 +153,6 @@ class _AddEventDialogState extends State { ); } - /// Widget pour le sélecteur de date pour l'événement. Widget _buildDatePicker() { return GestureDetector( onTap: () async { @@ -175,7 +193,6 @@ class _AddEventDialogState extends State { ); } - /// Construction du champ de localisation pour l'événement. Widget _buildLocationField(BuildContext context) { return GestureDetector( onTap: () async { @@ -217,29 +234,76 @@ class _AddEventDialogState extends State { ); } - /// Construction du champ de saisie de la catégorie de l'événement. + /// Construction du champ de catégorie avec sélection du type et de la catégorie. Widget _buildCategoryField() { - return TextFormField( - decoration: InputDecoration( - labelText: 'Catégorie', - labelStyle: const TextStyle(color: Colors.white70), - filled: true, - fillColor: Colors.white.withOpacity(0.1), - border: const OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(10.0)), - borderSide: BorderSide.none, + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + DropdownButtonFormField( + decoration: InputDecoration( + labelText: 'Type de catégorie', + labelStyle: const TextStyle(color: Colors.white70), + filled: true, + fillColor: Colors.white.withOpacity(0.1), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(10.0)), + borderSide: BorderSide.none, + ), + ), + value: _selectedCategoryType, + items: _categories.keys.map((String type) { + return DropdownMenuItem( + value: type, + child: Text(type, style: const TextStyle(color: Colors.black)), + ); + }).toList(), + onChanged: (String? newValue) { + setState(() { + _selectedCategoryType = newValue; + _currentCategories = _categories[newValue] ?? []; + _category = ''; // Réinitialiser la catégorie sélectionnée + print('Type de catégorie sélectionné : $_selectedCategoryType'); + print('Catégories disponibles pour ce type : $_currentCategories'); + }); + }, ), - prefixIcon: const Icon(Icons.category, color: Colors.white70), - ), - style: const TextStyle(color: Colors.white), - onSaved: (value) { - _category = value ?? ''; - print('Catégorie sauvegardée: $_category'); - }, + const SizedBox(height: 10), + DropdownButtonFormField( + decoration: InputDecoration( + labelText: 'Catégorie', + labelStyle: const TextStyle(color: Colors.white70), + filled: true, + fillColor: Colors.white.withOpacity(0.1), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(10.0)), + borderSide: BorderSide.none, + ), + ), + value: _category.isNotEmpty ? _category : null, + items: _currentCategories.map((String category) { + return DropdownMenuItem( + value: category, + child: Text(category, style: const TextStyle(color: Colors.black)), + ); + }).toList(), + onChanged: (String? newValue) { + setState(() { + _category = newValue ?? ''; + print('Catégorie sélectionnée : $_category'); + }); + }, + validator: (value) { + if (value == null || value.isEmpty) { + print('Erreur: Catégorie non sélectionnée'); + return 'Veuillez sélectionner une catégorie'; + } + return null; + }, + ), + ], ); } - /// Construction du champ de sélection d'image pour l'événement. Widget _buildImagePicker() { return GestureDetector( onTap: () { @@ -268,7 +332,6 @@ class _AddEventDialogState extends State { ); } - /// Construction du champ de saisie du lien associé à l'événement. Widget _buildLinkField() { return TextFormField( decoration: InputDecoration( @@ -290,7 +353,6 @@ class _AddEventDialogState extends State { ); } - /// Construction du bouton de soumission pour ajouter l'événement. Widget _buildSubmitButton() { return ElevatedButton( onPressed: () async { @@ -298,7 +360,6 @@ class _AddEventDialogState extends State { _formKey.currentState!.save(); print('Formulaire validé'); - // Créer l'événement en utilisant l'ID réel de l'utilisateur pour le créateur et les participants EventModel newEvent = EventModel( id: '', title: _title, @@ -308,7 +369,7 @@ class _AddEventDialogState extends State { category: _category, link: _link, imageUrl: _imagePath ?? '', - status: '', + status: 'OPEN', creator: UserModel( userId: widget.userId, nom: widget.userName, @@ -327,11 +388,9 @@ class _AddEventDialogState extends State { ], ); - // Convertir l'événement en JSON Map eventData = newEvent.toJson(); print('Données JSON de l\'événement: $eventData'); - // Envoyer la requête POST à l'API final response = await http.post( Uri.parse('${Urls.baseUrl}/events'), headers: {'Content-Type': 'application/json'}, @@ -342,7 +401,6 @@ class _AddEventDialogState extends State { print('Réponse brute: ${response.body}'); if (response.statusCode == 201) { - // Création réussie print('Événement créé avec succès'); Fluttertoast.showToast( msg: "Événement créé avec succès!", @@ -352,9 +410,8 @@ class _AddEventDialogState extends State { textColor: Colors.white, fontSize: 16.0, ); - Navigator.of(context).pop(); // Ne passez pas de valeur ici + Navigator.of(context).pop(); // Fermer la boîte de dialogue } else { - // Gérer l'erreur print('Erreur lors de la création de l\'événement: ${response.reasonPhrase}'); Fluttertoast.showToast( msg: "Erreur lors de la création de l'événement", @@ -383,5 +440,4 @@ class _AddEventDialogState extends State { child: const Text('Ajouter l\'événement', style: TextStyle(color: Colors.white)), ); } - } diff --git a/lib/presentation/screens/event/event_card.dart b/lib/presentation/screens/event/event_card.dart index eb8c2de..8e442ee 100644 --- a/lib/presentation/screens/event/event_card.dart +++ b/lib/presentation/screens/event/event_card.dart @@ -2,27 +2,53 @@ import 'package:flutter/material.dart'; import 'package:afterwork/data/datasources/event_remote_data_source.dart'; /// Widget pour afficher une carte d'événement. +/// Cette classe est utilisée pour afficher les détails d'un événement, +/// incluant son titre, sa description, son image, et des actions possibles +/// telles que réagir, commenter, partager, participer, et fermer l'événement. class EventCard extends StatelessWidget { + // Identifiant unique de l'événement final String eventId; + // Source de données distante pour les opérations sur l'événement final EventRemoteDataSource eventRemoteDataSource; + // Identifiant de l'utilisateur final String userId; + // Nom de l'utilisateur final String userName; + // Prénom de l'utilisateur final String userLastName; + // URL de l'image de profil de l'utilisateur final String profileImage; + // Nom complet de l'utilisateur (nom + prénom) final String name; + // Date de publication de l'événement final String datePosted; + // Titre de l'événement final String eventTitle; + // Description de l'événement final String eventDescription; + // URL de l'image de l'événement final String eventImageUrl; - final String eventStatus; // Ajout du statut de l'événement + // Statut de l'événement (e.g., "OPEN", "CLOSED") + final String eventStatus; + // Catégorie de l'événement + final String eventCategory; + // Nombre de réactions à l'événement final int reactionsCount; + // Nombre de commentaires sur l'événement final int commentsCount; + // Nombre de partages de l'événement final int sharesCount; + // Callback pour l'action "Réagir" final VoidCallback onReact; + // Callback pour l'action "Commenter" final VoidCallback onComment; + // Callback pour l'action "Partager" final VoidCallback onShare; + // Callback pour l'action "Participer" final VoidCallback onParticipate; + // Callback pour afficher plus d'options final VoidCallback onMoreOptions; + // Callback pour fermer l'événement final VoidCallback onCloseEvent; const EventCard({ @@ -38,7 +64,8 @@ class EventCard extends StatelessWidget { required this.eventTitle, required this.eventDescription, required this.eventImageUrl, - required this.eventStatus, // Initialisation du statut de l'événement + required this.eventStatus, + required this.eventCategory, required this.reactionsCount, required this.commentsCount, required this.sharesCount, @@ -52,13 +79,20 @@ class EventCard extends StatelessWidget { @override Widget build(BuildContext context) { + // Log du rendu de la carte d'événement + print('Rendu de l\'EventCard pour l\'événement $eventId avec statut $eventStatus'); + return AnimatedOpacity( opacity: 1.0, duration: const Duration(milliseconds: 300), child: Dismissible( key: ValueKey(eventId), direction: DismissDirection.startToEnd, - onDismissed: (direction) => onCloseEvent(), + onDismissed: (direction) { + // Log du déclenchement de la fermeture de l'événement + print('Tentative de fermeture de l\'événement $eventId'); + onCloseEvent(); + }, background: Container( color: Colors.red, alignment: Alignment.centerLeft, @@ -80,13 +114,15 @@ class EventCard extends StatelessWidget { children: [ _buildHeader(context), const SizedBox(height: 10), + _buildEventCategory(), // Affichage de la catégorie de l'événement + const SizedBox(height: 5), _buildEventDetails(), const SizedBox(height: 10), _buildEventImage(), const SizedBox(height: 10), Divider(color: Colors.white.withOpacity(0.2)), _buildInteractionRow(), - const SizedBox(height: 10), + const SizedBox(height: 5), _buildParticipateButton(), ], ), @@ -99,7 +135,12 @@ class EventCard extends StatelessWidget { } /// Construire l'en-tête de la carte avec les informations de l'utilisateur. + /// Cette méthode affiche l'image de profil, le nom de l'utilisateur, la date + /// de publication de l'événement, et le statut de l'événement. Widget _buildHeader(BuildContext context) { + // Log du rendu de l'en-tête de la carte + print('Rendu de l\'en-tête pour l\'événement $eventId'); + return Row( children: [ CircleAvatar( @@ -134,19 +175,48 @@ class EventCard extends StatelessWidget { ), IconButton( icon: const Icon(Icons.more_vert, color: Colors.white), - onPressed: onMoreOptions, + onPressed: () { + // Log du déclenchement du bouton "Plus d'options" + print('Plus d\'options déclenché pour l\'événement $eventId'); + onMoreOptions(); + }, ), if (eventStatus != 'CLOSED') // Masquer le bouton de fermeture si l'événement est fermé IconButton( icon: const Icon(Icons.close, color: Colors.white), - onPressed: onCloseEvent, + onPressed: () { + // Log du déclenchement du bouton de fermeture de l'événement + print('Tentative de fermeture de l\'événement $eventId'); + onCloseEvent(); + }, ), ], ); } + /// Afficher la catégorie de l'événement au-dessus du titre. + /// Cette méthode affiche la catégorie en italique pour distinguer le type d'événement. + Widget _buildEventCategory() { + // Log du rendu de la catégorie de l'événement + print('Affichage de la catégorie pour l\'événement $eventId: $eventCategory'); + + return Text( + eventCategory, + style: const TextStyle( + color: Colors.blueAccent, + fontSize: 14, + fontStyle: FontStyle.italic, // Style en italique + fontWeight: FontWeight.w400, // Titre fin + ), + ); + } + /// Afficher les détails de l'événement. + /// Cette méthode affiche le titre et la description de l'événement. Widget _buildEventDetails() { + // Log du rendu des détails de l'événement + print('Affichage des détails pour l\'événement $eventId'); + return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -168,7 +238,11 @@ class EventCard extends StatelessWidget { } /// Afficher l'image de l'événement. + /// Cette méthode affiche l'image associée à l'événement. Widget _buildEventImage() { + // Log du rendu de l'image de l'événement + print('Affichage de l\'image pour l\'événement $eventId'); + return ClipRRect( borderRadius: BorderRadius.circular(10.0), child: Image.network( @@ -177,7 +251,8 @@ class EventCard extends StatelessWidget { width: double.infinity, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { - print('Erreur de chargement de l\'image: $error'); + // Log de l'erreur lors du chargement de l'image + print('Erreur de chargement de l\'image pour l\'événement $eventId: $error'); return Image.asset( 'lib/assets/images/placeholder.png', height: 180, @@ -190,29 +265,51 @@ class EventCard extends StatelessWidget { } /// Afficher les icônes d'interaction (réagir, commenter, partager). + /// Cette méthode affiche les boutons pour réagir, commenter, et partager l'événement. Widget _buildInteractionRow() { + // Log du rendu de la ligne d'interaction de l'événement + print('Affichage des interactions pour l\'événement $eventId'); + return Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0), + padding: const EdgeInsets.symmetric(horizontal: 0.0), // Réduire le padding vertical child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: MainAxisAlignment.spaceAround, // Utiliser spaceAround pour réduire l'espace children: [ - _buildIconButton( - icon: Icons.thumb_up_alt_outlined, - label: 'Réagir', - count: reactionsCount, - onPressed: onReact, + Flexible( + child: _buildIconButton( + icon: Icons.thumb_up_alt_outlined, + label: 'Réagir', + count: reactionsCount, + onPressed: () { + // Log de l'action "Réagir" + print('Réaction à l\'événement $eventId'); + onReact(); + }, + ), ), - _buildIconButton( - icon: Icons.comment_outlined, - label: 'Commenter', - count: commentsCount, - onPressed: onComment, + Flexible( + child: _buildIconButton( + icon: Icons.comment_outlined, + label: 'Commenter', + count: commentsCount, + onPressed: () { + // Log de l'action "Commenter" + print('Commentaire sur l\'événement $eventId'); + onComment(); + }, + ), ), - _buildIconButton( - icon: Icons.share_outlined, - label: 'Partager', - count: sharesCount, - onPressed: onShare, + Flexible( + child: _buildIconButton( + icon: Icons.share_outlined, + label: 'Partager', + count: sharesCount, + onPressed: () { + // Log de l'action "Partager" + print('Partage de l\'événement $eventId'); + onShare(); + }, + ), ), ], ), @@ -220,12 +317,16 @@ class EventCard extends StatelessWidget { } /// Bouton d'interaction personnalisé. + /// Cette méthode construit un bouton avec une icône et un label pour l'interaction. Widget _buildIconButton({ required IconData icon, required String label, required int count, required VoidCallback onPressed, }) { + // Log de la construction du bouton d'interaction + print('Construction du bouton $label pour l\'événement $eventId'); + return Expanded( child: TextButton.icon( onPressed: onPressed, @@ -233,13 +334,19 @@ class EventCard extends StatelessWidget { label: Text( '$label ($count)', style: const TextStyle(color: Colors.white70, fontSize: 12), + overflow: TextOverflow.ellipsis, ), ), ); } /// Bouton pour participer à l'événement. + /// Cette méthode construit un bouton qui permet de participer à l'événement. + /// Si l'événement est fermé, le bouton est désactivé. Widget _buildParticipateButton() { + // Log de la construction du bouton "Participer" + print('Construction du bouton "Participer" pour l\'événement $eventId avec statut $eventStatus'); + return ElevatedButton( onPressed: eventStatus == 'CLOSED' ? null : onParticipate, // Désactiver si l'événement est fermé style: ElevatedButton.styleFrom( @@ -258,7 +365,11 @@ class EventCard extends StatelessWidget { } /// Construire un badge pour afficher le statut de l'événement. + /// Cette méthode affiche un badge avec le statut de l'événement ("OPEN" ou "CLOSED"). Widget _buildStatusBadge() { + // Log de la construction du badge de statut + print('Construction du badge de statut pour l\'événement $eventId: $eventStatus'); + Color badgeColor; switch (eventStatus) { case 'CLOSED': diff --git a/lib/presentation/screens/event/event_screen.dart b/lib/presentation/screens/event/event_screen.dart index d591409..4867e95 100644 --- a/lib/presentation/screens/event/event_screen.dart +++ b/lib/presentation/screens/event/event_screen.dart @@ -112,7 +112,8 @@ class _EventScreenState extends State { userLastName: widget.userLastName, profileImage: 'lib/assets/images/profile_picture.png', name: '${widget.userName} ${widget.userLastName}', - datePosted: 'Posté le 24/08/2024', + eventCategory: event.category, + datePosted: event.date, eventTitle: event.title, eventDescription: event.description, eventImageUrl: event.imageUrl ?? 'lib/assets/images/placeholder.png', diff --git a/pubspec.yaml b/pubspec.yaml index b6f1e3c..8b933de 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -47,7 +47,7 @@ flutter: - lib/assets/images/background.webp - lib/assets/images/profile_picture.png - lib/assets/images/placeholder.png - + - lib/assets/json/event_categories.json # To add assets to your application, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg