From 7bc77615914af1538ced047ac78bdf0b3b071dae Mon Sep 17 00:00:00 2001 From: DahoudG Date: Mon, 2 Sep 2024 03:04:40 +0000 Subject: [PATCH] =?UTF-8?q?Refactoring=20et=20am=C3=A9lioration=20de=20la?= =?UTF-8?q?=20cr=C3=A9ation=20et=20l'affichage=20d'un=20=C3=A9v=C3=A8nemen?= =?UTF-8?q?t.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/core/errors/exceptions.dart | 19 +- .../datasources/event_remote_data_source.dart | 25 +++ lib/data/models/event_model.dart | 3 + lib/domain/entities/event.dart | 4 + .../screens/dialogs/add_event_dialog.dart | 18 ++ .../screens/event/event_card.dart | 169 ++++++++++-------- .../screens/event/event_screen.dart | 74 ++++++-- pubspec.lock | 8 + pubspec.yaml | 1 + 9 files changed, 226 insertions(+), 95 deletions(-) diff --git a/lib/core/errors/exceptions.dart b/lib/core/errors/exceptions.dart index 3d1ab7a..cc7ff5f 100644 --- a/lib/core/errors/exceptions.dart +++ b/lib/core/errors/exceptions.dart @@ -1,4 +1,11 @@ -class ServerException implements Exception {} +class ServerException implements Exception { + final String message; + + ServerException([this.message = 'Une erreur serveur est survenue']); + + @override + String toString() => 'ServerException: $message'; +} class CacheException implements Exception {} @@ -10,3 +17,13 @@ class AuthenticationException implements Exception { @override String toString() => 'AuthenticationException: $message'; } + +class ServerExceptionWithMessage implements Exception { + final String message; + + ServerExceptionWithMessage(this.message); + + @override + String toString() => 'ServerException: $message'; +} + diff --git a/lib/data/datasources/event_remote_data_source.dart b/lib/data/datasources/event_remote_data_source.dart index 66864f3..4ebf539 100644 --- a/lib/data/datasources/event_remote_data_source.dart +++ b/lib/data/datasources/event_remote_data_source.dart @@ -143,4 +143,29 @@ class EventRemoteDataSource { print('Réaction réussie'); } } + + /// Fermer un événement. + Future closeEvent(String eventId) async { + print('Fermeture de l\'événement avec l\'ID: $eventId'); + + final response = await client.post( + Uri.parse('${Urls.eventsUrl}/$eventId/close'), + headers: {'Content-Type': 'application/json'}, + ); + + print('Statut de la réponse: ${response.statusCode}'); + + if (response.statusCode == 200) { + print('Événement fermé avec succès'); + } else if (response.statusCode == 400) { + // Si le serveur retourne une erreur 400, vérifiez le corps du message + final responseBody = json.decode(response.body); + final errorMessage = responseBody['message'] ?? 'Erreur inconnue'; + print('Erreur lors de la fermeture de l\'événement: $errorMessage'); + throw ServerExceptionWithMessage(errorMessage); // Utiliser la nouvelle exception ici + } else { + print('Erreur lors de la fermeture de l\'événement: ${response.body}'); + throw ServerExceptionWithMessage('Une erreur est survenue lors de la fermeture de l\'événement.'); + } + } } diff --git a/lib/data/models/event_model.dart b/lib/data/models/event_model.dart index 3d7a6dc..ab81dd8 100644 --- a/lib/data/models/event_model.dart +++ b/lib/data/models/event_model.dart @@ -12,6 +12,7 @@ class EventModel { final String? imageUrl; final UserModel creator; final List participants; + final String status; /// Constructeur pour initialiser toutes les propriétés de l'événement. EventModel({ @@ -25,6 +26,7 @@ class EventModel { this.imageUrl, required this.creator, required this.participants, + required this.status, }); /// Convertit un objet JSON en `EventModel`. @@ -38,6 +40,7 @@ class EventModel { category: json['category'], link: json['link'] ?? '', // Assure qu'il ne soit pas null imageUrl: json['imageUrl'] ?? '', // Assure qu'il ne soit pas null + status: json['status'], creator: UserModel.fromJson(json['creator']), participants: json['participants'] != null ? (json['participants'] as List) diff --git a/lib/domain/entities/event.dart b/lib/domain/entities/event.dart index 7e468e8..2838b78 100644 --- a/lib/domain/entities/event.dart +++ b/lib/domain/entities/event.dart @@ -8,6 +8,7 @@ class EventModel { final String? link; final String? imageUrl; final String creatorId; + final String status; EventModel({ required this.eventId, @@ -19,6 +20,7 @@ class EventModel { this.link, this.imageUrl, required this.creatorId, + required this.status, }); // Méthode pour créer un EventModel à partir d'un JSON @@ -33,6 +35,7 @@ class EventModel { link: json['link'], imageUrl: json['imageUrl'], creatorId: json['creator']['id'], // Assurez-vous que le JSON a ce format + status: json['status'], ); } @@ -48,6 +51,7 @@ class EventModel { 'link': link, 'imageUrl': imageUrl, 'creator': {'id': creatorId}, // Structure du JSON pour l'API + 'status': status, }; } diff --git a/lib/presentation/screens/dialogs/add_event_dialog.dart b/lib/presentation/screens/dialogs/add_event_dialog.dart index a1b945a..47bd9e9 100644 --- a/lib/presentation/screens/dialogs/add_event_dialog.dart +++ b/lib/presentation/screens/dialogs/add_event_dialog.dart @@ -1,4 +1,5 @@ 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'; @@ -307,6 +308,7 @@ class _AddEventDialogState extends State { category: _category, link: _link, imageUrl: _imagePath ?? '', + status: '', creator: UserModel( userId: widget.userId, nom: widget.userName, @@ -342,10 +344,26 @@ class _AddEventDialogState extends State { if (response.statusCode == 201) { // Création réussie print('Événement créé avec succès'); + Fluttertoast.showToast( + msg: "Événement créé avec succès!", + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.BOTTOM, + backgroundColor: Colors.green, + textColor: Colors.white, + fontSize: 16.0, + ); Navigator.of(context).pop(); // Ne passez pas de valeur ici } 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", + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.BOTTOM, + backgroundColor: Colors.red, + textColor: Colors.white, + fontSize: 16.0, + ); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Erreur: ${response.reasonPhrase}')), ); diff --git a/lib/presentation/screens/event/event_card.dart b/lib/presentation/screens/event/event_card.dart index 025e421..eb8c2de 100644 --- a/lib/presentation/screens/event/event_card.dart +++ b/lib/presentation/screens/event/event_card.dart @@ -14,6 +14,7 @@ class EventCard extends StatelessWidget { final String eventTitle; final String eventDescription; final String eventImageUrl; + final String eventStatus; // Ajout du statut de l'événement final int reactionsCount; final int commentsCount; final int sharesCount; @@ -21,8 +22,8 @@ class EventCard extends StatelessWidget { final VoidCallback onComment; final VoidCallback onShare; final VoidCallback onParticipate; - final VoidCallback onCloseEvent; final VoidCallback onMoreOptions; + final VoidCallback onCloseEvent; const EventCard({ Key? key, @@ -37,6 +38,7 @@ 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.reactionsCount, required this.commentsCount, required this.sharesCount, @@ -44,33 +46,52 @@ class EventCard extends StatelessWidget { required this.onComment, required this.onShare, required this.onParticipate, - required this.onCloseEvent, required this.onMoreOptions, + required this.onCloseEvent, }) : super(key: key); @override Widget build(BuildContext context) { - return Card( - color: const Color(0xFF2C2C3E), - margin: const EdgeInsets.symmetric(vertical: 10.0), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(15.0), - ), - child: Padding( - padding: const EdgeInsets.all(12.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + return AnimatedOpacity( + opacity: 1.0, + duration: const Duration(milliseconds: 300), + child: Dismissible( + key: ValueKey(eventId), + direction: DismissDirection.startToEnd, + onDismissed: (direction) => onCloseEvent(), + background: Container( + color: Colors.red, + alignment: Alignment.centerLeft, + padding: const EdgeInsets.only(left: 20.0), + child: const Icon(Icons.delete, color: Colors.white), + ), + child: Stack( children: [ - _buildHeader(), - const SizedBox(height: 10), - _buildEventDetails(), - const SizedBox(height: 10), - _buildEventImage(), - const SizedBox(height: 10), - Divider(color: Colors.white.withOpacity(0.2)), - _buildInteractionRow(), - const SizedBox(height: 10), - _buildParticipateButton(), + Card( + color: const Color(0xFF2C2C3E), + margin: const EdgeInsets.symmetric(vertical: 10.0), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(15.0), + ), + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildHeader(context), + const SizedBox(height: 10), + _buildEventDetails(), + const SizedBox(height: 10), + _buildEventImage(), + const SizedBox(height: 10), + Divider(color: Colors.white.withOpacity(0.2)), + _buildInteractionRow(), + const SizedBox(height: 10), + _buildParticipateButton(), + ], + ), + ), + ), ], ), ), @@ -78,7 +99,7 @@ class EventCard extends StatelessWidget { } /// Construire l'en-tête de la carte avec les informations de l'utilisateur. - Widget _buildHeader() { + Widget _buildHeader(BuildContext context) { return Row( children: [ CircleAvatar( @@ -98,21 +119,28 @@ class EventCard extends StatelessWidget { fontWeight: FontWeight.bold, ), ), - Text( - datePosted, - style: const TextStyle(color: Colors.white70, fontSize: 14), + Row( + children: [ + Text( + datePosted, + style: const TextStyle(color: Colors.white70, fontSize: 14), + ), + const SizedBox(width: 10), + _buildStatusBadge(), // Badge de statut aligné sur la même ligne que la date du post + ], ), ], ), ), IconButton( icon: const Icon(Icons.more_vert, color: Colors.white), - onPressed: _onMoreOptions, - ), - IconButton( - icon: const Icon(Icons.close, color: Colors.white), - onPressed: _onCloseEvent, + onPressed: 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, + ), ], ); } @@ -213,7 +241,7 @@ class EventCard extends StatelessWidget { /// Bouton pour participer à l'événement. Widget _buildParticipateButton() { return ElevatedButton( - onPressed: _onParticipate, + onPressed: eventStatus == 'CLOSED' ? null : onParticipate, // Désactiver si l'événement est fermé style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF1DBF73), shape: RoundedRectangleBorder( @@ -222,57 +250,40 @@ class EventCard extends StatelessWidget { padding: const EdgeInsets.symmetric(vertical: 12.0), minimumSize: const Size(double.infinity, 40), ), - child: const Text('Participer', style: TextStyle(color: Colors.white)), + child: Text( + eventStatus == 'CLOSED' ? 'Événement fermé' : 'Participer', + style: const TextStyle(color: Colors.white), + ), ); } - // Logique pour réagir à l'événement. - void _onReact() async { - try { - print('Tentative de réaction à l\'événement $eventId par l\'utilisateur $userId'); - await eventRemoteDataSource.reactToEvent(eventId, userId); - print('Réaction à l\'événement réussie'); - // Mettre à jour l'interface utilisateur, par exemple augmenter le compteur de réactions. - } catch (e) { - // Gérer l'erreur. - print('Erreur lors de la réaction à l\'événement: $e'); + /// Construire un badge pour afficher le statut de l'événement. + Widget _buildStatusBadge() { + Color badgeColor; + switch (eventStatus) { + case 'CLOSED': + badgeColor = Colors.redAccent; + break; + case 'OPEN': + default: + badgeColor = Colors.greenAccent; + break; } - } - // Logique pour commenter l'événement. - void _onComment() { - // Implémenter la logique pour commenter un événement. - print('Commentaire sur l\'événement $eventId par l\'utilisateur $userId'); - } - - // Logique pour partager l'événement. - void _onShare() { - // Implémenter la logique pour partager un événement. - print('Partage de l\'événement $eventId par l\'utilisateur $userId'); - } - - // Logique pour participer à l'événement. - void _onParticipate() async { - try { - print('Tentative de participation à l\'événement $eventId par l\'utilisateur $userId'); - await eventRemoteDataSource.participateInEvent(eventId, userId); - print('Participation à l\'événement réussie'); - // Mettre à jour l'interface utilisateur, par exemple afficher un message de succès. - } catch (e) { - // Gérer l'erreur. - print('Erreur lors de la participation à l\'événement: $e'); - } - } - - // Logique pour fermer l'événement. - void _onCloseEvent() { - // Implémenter la logique pour fermer un événement. - print('Fermeture de l\'événement $eventId'); - } - - // Logique pour afficher plus d'options. - void _onMoreOptions() { - // Implémenter la logique pour afficher plus d'options. - print('Affichage des options supplémentaires pour l\'événement $eventId'); + return Container( + padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), + decoration: BoxDecoration( + color: badgeColor.withOpacity(0.7), + borderRadius: BorderRadius.circular(8.0), + ), + child: Text( + eventStatus.toUpperCase(), + style: const TextStyle( + color: Colors.white, + fontSize: 10, // Réduction de la taille du texte + fontWeight: FontWeight.bold, + ), + ), + ); } } diff --git a/lib/presentation/screens/event/event_screen.dart b/lib/presentation/screens/event/event_screen.dart index ebf671e..d591409 100644 --- a/lib/presentation/screens/event/event_screen.dart +++ b/lib/presentation/screens/event/event_screen.dart @@ -5,11 +5,12 @@ import 'event_card.dart'; import '../dialogs/add_event_dialog.dart'; /// Écran principal pour afficher les événements. -class EventScreen extends StatelessWidget { +class EventScreen extends StatefulWidget { final EventRemoteDataSource eventRemoteDataSource; final String userId; final String userName; // Nom de l'utilisateur final String userLastName; // Prénom de l'utilisateur + const EventScreen({ Key? key, required this.eventRemoteDataSource, @@ -18,6 +19,19 @@ class EventScreen extends StatelessWidget { required this.userLastName, }) : super(key: key); + @override + _EventScreenState createState() => _EventScreenState(); +} + +class _EventScreenState extends State { + late Future> _eventsFuture; + + @override + void initState() { + super.initState(); + _eventsFuture = widget.eventRemoteDataSource.getAllEvents(); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -37,9 +51,9 @@ class EventScreen extends StatelessWidget { context: context, builder: (BuildContext context) { return AddEventDialog( - userId: userId, - userName: userName, - userLastName: userLastName, + userId: widget.userId, + userName: widget.userName, + userLastName: widget.userLastName, ); }, ); @@ -47,11 +61,15 @@ class EventScreen extends StatelessWidget { if (eventData != null) { // Appeler l'API pour créer un nouvel événement. try { - print('Tentative de création d\'un nouvel événement par l\'utilisateur $userId'); - await eventRemoteDataSource.createEvent(eventData as EventModel); + print('Tentative de création d\'un nouvel événement par l\'utilisateur ${widget.userId}'); + await widget.eventRemoteDataSource.createEvent(eventData as EventModel); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Événement ajouté avec succès !')), ); + // Réactualiser la liste des événements après création + setState(() { + _eventsFuture = widget.eventRemoteDataSource.getAllEvents(); + }); } catch (e) { print('Erreur lors de la création de l\'événement: $e'); ScaffoldMessenger.of(context).showSnackBar( @@ -64,7 +82,7 @@ class EventScreen extends StatelessWidget { ], ), body: FutureBuilder>( - future: eventRemoteDataSource.getAllEvents(), + future: _eventsFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); @@ -86,17 +104,19 @@ class EventScreen extends StatelessWidget { print('Affichage de l\'événement ${event.id}'); return EventCard( - eventRemoteDataSource: eventRemoteDataSource, - userId: userId, + key: ValueKey(event.id), // Pour une gestion correcte du Key dans l'animation + eventRemoteDataSource: widget.eventRemoteDataSource, + userId: widget.userId, eventId: event.id, - userName: userName, - userLastName: userLastName, + userName: widget.userName, + userLastName: widget.userLastName, profileImage: 'lib/assets/images/profile_picture.png', - name: '$userName $userLastName', + name: '${widget.userName} ${widget.userLastName}', datePosted: 'Posté le 24/08/2024', eventTitle: event.title, eventDescription: event.description, eventImageUrl: event.imageUrl ?? 'lib/assets/images/placeholder.png', + eventStatus: event.status, // Ajouter le statut de l'événement ici reactionsCount: 120, // Exemple de valeur commentsCount: 45, // Exemple de valeur sharesCount: 30, // Exemple de valeur @@ -112,9 +132,7 @@ class EventScreen extends StatelessWidget { onParticipate: () { print('Participation à l\'événement ${event.id}'); }, - onCloseEvent: () { - print('Fermeture de l\'événement ${event.id}'); - }, + onCloseEvent: () => _onCloseEvent(context, event.id, index), onMoreOptions: () { print('Affichage des options pour l\'événement ${event.id}'); }, @@ -126,4 +144,30 @@ class EventScreen extends StatelessWidget { backgroundColor: const Color(0xFF1E1E2C), ); } + + /// Logique pour fermer un événement + void _onCloseEvent(BuildContext context, String eventId, int index) async { + try { + print('Tentative de fermeture de l\'événement $eventId'); + await widget.eventRemoteDataSource.closeEvent(eventId); + print('Événement fermé avec succès'); + + // Montrer un message de succès + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('L\'événement a été fermé avec succès.')), + ); + + // Actualiser la liste des événements après fermeture avec un effet de fondu + setState(() { + _eventsFuture.then((events) { + events.removeAt(index); + }); + }); + } catch (e) { + print('Erreur lors de la fermeture de l\'événement: $e'); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Erreur lors de la fermeture de l\'événement : $e')), + ); + } + } } diff --git a/pubspec.lock b/pubspec.lock index 2317490..dbcd41d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -200,6 +200,14 @@ packages: description: flutter source: sdk version: "0.0.0" + fluttertoast: + dependency: "direct main" + description: + name: fluttertoast + sha256: "95f349437aeebe524ef7d6c9bde3e6b4772717cf46a0eb6a3ceaddc740b297cc" + url: "https://pub.dev" + source: hosted + version: "8.2.8" get_it: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 926b245..b6f1e3c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,6 +11,7 @@ dependencies: flutter: sdk: flutter + fluttertoast: ^8.0.8 flutter_secure_storage: ^7.0.1 crypto: ^3.0.1 google_maps_flutter: ^2.0.10