Refactoring et amélioration de la création et l'affichage d'un évènement.

This commit is contained in:
DahoudG
2024-09-02 03:04:40 +00:00
parent b8d7cfcb8d
commit 7bc7761591
9 changed files with 226 additions and 95 deletions

View File

@@ -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 {} class CacheException implements Exception {}
@@ -10,3 +17,13 @@ class AuthenticationException implements Exception {
@override @override
String toString() => 'AuthenticationException: $message'; String toString() => 'AuthenticationException: $message';
} }
class ServerExceptionWithMessage implements Exception {
final String message;
ServerExceptionWithMessage(this.message);
@override
String toString() => 'ServerException: $message';
}

View File

@@ -143,4 +143,29 @@ class EventRemoteDataSource {
print('Réaction réussie'); print('Réaction réussie');
} }
} }
/// Fermer un événement.
Future<void> 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.');
}
}
} }

View File

@@ -12,6 +12,7 @@ class EventModel {
final String? imageUrl; final String? imageUrl;
final UserModel creator; final UserModel creator;
final List<UserModel> participants; final List<UserModel> participants;
final String status;
/// Constructeur pour initialiser toutes les propriétés de l'événement. /// Constructeur pour initialiser toutes les propriétés de l'événement.
EventModel({ EventModel({
@@ -25,6 +26,7 @@ class EventModel {
this.imageUrl, this.imageUrl,
required this.creator, required this.creator,
required this.participants, required this.participants,
required this.status,
}); });
/// Convertit un objet JSON en `EventModel`. /// Convertit un objet JSON en `EventModel`.
@@ -38,6 +40,7 @@ class EventModel {
category: json['category'], category: json['category'],
link: json['link'] ?? '', // Assure qu'il ne soit pas null link: json['link'] ?? '', // Assure qu'il ne soit pas null
imageUrl: json['imageUrl'] ?? '', // 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']), creator: UserModel.fromJson(json['creator']),
participants: json['participants'] != null participants: json['participants'] != null
? (json['participants'] as List) ? (json['participants'] as List)

View File

@@ -8,6 +8,7 @@ class EventModel {
final String? link; final String? link;
final String? imageUrl; final String? imageUrl;
final String creatorId; final String creatorId;
final String status;
EventModel({ EventModel({
required this.eventId, required this.eventId,
@@ -19,6 +20,7 @@ class EventModel {
this.link, this.link,
this.imageUrl, this.imageUrl,
required this.creatorId, required this.creatorId,
required this.status,
}); });
// Méthode pour créer un EventModel à partir d'un JSON // Méthode pour créer un EventModel à partir d'un JSON
@@ -33,6 +35,7 @@ class EventModel {
link: json['link'], link: json['link'],
imageUrl: json['imageUrl'], imageUrl: json['imageUrl'],
creatorId: json['creator']['id'], // Assurez-vous que le JSON a ce format creatorId: json['creator']['id'], // Assurez-vous que le JSON a ce format
status: json['status'],
); );
} }
@@ -48,6 +51,7 @@ class EventModel {
'link': link, 'link': link,
'imageUrl': imageUrl, 'imageUrl': imageUrl,
'creator': {'id': creatorId}, // Structure du JSON pour l'API 'creator': {'id': creatorId}, // Structure du JSON pour l'API
'status': status,
}; };
} }

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'dart:convert'; import 'dart:convert';
@@ -307,6 +308,7 @@ class _AddEventDialogState extends State<AddEventDialog> {
category: _category, category: _category,
link: _link, link: _link,
imageUrl: _imagePath ?? '', imageUrl: _imagePath ?? '',
status: '',
creator: UserModel( creator: UserModel(
userId: widget.userId, userId: widget.userId,
nom: widget.userName, nom: widget.userName,
@@ -342,10 +344,26 @@ class _AddEventDialogState extends State<AddEventDialog> {
if (response.statusCode == 201) { if (response.statusCode == 201) {
// Création réussie // Création réussie
print('Événement créé avec succès'); 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 Navigator.of(context).pop(); // Ne passez pas de valeur ici
} else { } else {
// Gérer l'erreur // Gérer l'erreur
print('Erreur lors de la création de l\'événement: ${response.reasonPhrase}'); 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( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Erreur: ${response.reasonPhrase}')), SnackBar(content: Text('Erreur: ${response.reasonPhrase}')),
); );

View File

@@ -14,6 +14,7 @@ class EventCard extends StatelessWidget {
final String eventTitle; final String eventTitle;
final String eventDescription; final String eventDescription;
final String eventImageUrl; final String eventImageUrl;
final String eventStatus; // Ajout du statut de l'événement
final int reactionsCount; final int reactionsCount;
final int commentsCount; final int commentsCount;
final int sharesCount; final int sharesCount;
@@ -21,8 +22,8 @@ class EventCard extends StatelessWidget {
final VoidCallback onComment; final VoidCallback onComment;
final VoidCallback onShare; final VoidCallback onShare;
final VoidCallback onParticipate; final VoidCallback onParticipate;
final VoidCallback onCloseEvent;
final VoidCallback onMoreOptions; final VoidCallback onMoreOptions;
final VoidCallback onCloseEvent;
const EventCard({ const EventCard({
Key? key, Key? key,
@@ -37,6 +38,7 @@ class EventCard extends StatelessWidget {
required this.eventTitle, required this.eventTitle,
required this.eventDescription, required this.eventDescription,
required this.eventImageUrl, required this.eventImageUrl,
required this.eventStatus, // Initialisation du statut de l'événement
required this.reactionsCount, required this.reactionsCount,
required this.commentsCount, required this.commentsCount,
required this.sharesCount, required this.sharesCount,
@@ -44,13 +46,28 @@ class EventCard extends StatelessWidget {
required this.onComment, required this.onComment,
required this.onShare, required this.onShare,
required this.onParticipate, required this.onParticipate,
required this.onCloseEvent,
required this.onMoreOptions, required this.onMoreOptions,
required this.onCloseEvent,
}) : super(key: key); }) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Card( 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: [
Card(
color: const Color(0xFF2C2C3E), color: const Color(0xFF2C2C3E),
margin: const EdgeInsets.symmetric(vertical: 10.0), margin: const EdgeInsets.symmetric(vertical: 10.0),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
@@ -61,7 +78,7 @@ class EventCard extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_buildHeader(), _buildHeader(context),
const SizedBox(height: 10), const SizedBox(height: 10),
_buildEventDetails(), _buildEventDetails(),
const SizedBox(height: 10), const SizedBox(height: 10),
@@ -74,11 +91,15 @@ class EventCard extends StatelessWidget {
], ],
), ),
), ),
),
],
),
),
); );
} }
/// Construire l'en-tête de la carte avec les informations de l'utilisateur. /// Construire l'en-tête de la carte avec les informations de l'utilisateur.
Widget _buildHeader() { Widget _buildHeader(BuildContext context) {
return Row( return Row(
children: [ children: [
CircleAvatar( CircleAvatar(
@@ -98,20 +119,27 @@ class EventCard extends StatelessWidget {
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
Row(
children: [
Text( Text(
datePosted, datePosted,
style: const TextStyle(color: Colors.white70, fontSize: 14), 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( IconButton(
icon: const Icon(Icons.more_vert, color: Colors.white), icon: const Icon(Icons.more_vert, color: Colors.white),
onPressed: _onMoreOptions, onPressed: onMoreOptions,
), ),
if (eventStatus != 'CLOSED') // Masquer le bouton de fermeture si l'événement est fermé
IconButton( IconButton(
icon: const Icon(Icons.close, color: Colors.white), icon: const Icon(Icons.close, color: Colors.white),
onPressed: _onCloseEvent, onPressed: onCloseEvent,
), ),
], ],
); );
@@ -213,7 +241,7 @@ class EventCard extends StatelessWidget {
/// Bouton pour participer à l'événement. /// Bouton pour participer à l'événement.
Widget _buildParticipateButton() { Widget _buildParticipateButton() {
return ElevatedButton( return ElevatedButton(
onPressed: _onParticipate, onPressed: eventStatus == 'CLOSED' ? null : onParticipate, // Désactiver si l'événement est fermé
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF1DBF73), backgroundColor: const Color(0xFF1DBF73),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
@@ -222,57 +250,40 @@ class EventCard extends StatelessWidget {
padding: const EdgeInsets.symmetric(vertical: 12.0), padding: const EdgeInsets.symmetric(vertical: 12.0),
minimumSize: const Size(double.infinity, 40), 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. /// Construire un badge pour afficher le statut de l'événement.
void _onReact() async { Widget _buildStatusBadge() {
try { Color badgeColor;
print('Tentative de réaction à l\'événement $eventId par l\'utilisateur $userId'); switch (eventStatus) {
await eventRemoteDataSource.reactToEvent(eventId, userId); case 'CLOSED':
print('Réaction à l\'événement réussie'); badgeColor = Colors.redAccent;
// Mettre à jour l'interface utilisateur, par exemple augmenter le compteur de réactions. break;
} catch (e) { case 'OPEN':
// Gérer l'erreur. default:
print('Erreur lors de la réaction à l\'événement: $e'); badgeColor = Colors.greenAccent;
} break;
} }
// Logique pour commenter l'événement. return Container(
void _onComment() { padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
// Implémenter la logique pour commenter un événement. decoration: BoxDecoration(
print('Commentaire sur l\'événement $eventId par l\'utilisateur $userId'); color: badgeColor.withOpacity(0.7),
} borderRadius: BorderRadius.circular(8.0),
),
// Logique pour partager l'événement. child: Text(
void _onShare() { eventStatus.toUpperCase(),
// Implémenter la logique pour partager un événement. style: const TextStyle(
print('Partage de l\'événement $eventId par l\'utilisateur $userId'); color: Colors.white,
} fontSize: 10, // Réduction de la taille du texte
fontWeight: FontWeight.bold,
// 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');
} }
} }

View File

@@ -5,11 +5,12 @@ import 'event_card.dart';
import '../dialogs/add_event_dialog.dart'; import '../dialogs/add_event_dialog.dart';
/// Écran principal pour afficher les événements. /// Écran principal pour afficher les événements.
class EventScreen extends StatelessWidget { class EventScreen extends StatefulWidget {
final EventRemoteDataSource eventRemoteDataSource; final EventRemoteDataSource eventRemoteDataSource;
final String userId; final String userId;
final String userName; // Nom de l'utilisateur final String userName; // Nom de l'utilisateur
final String userLastName; // Prénom de l'utilisateur final String userLastName; // Prénom de l'utilisateur
const EventScreen({ const EventScreen({
Key? key, Key? key,
required this.eventRemoteDataSource, required this.eventRemoteDataSource,
@@ -18,6 +19,19 @@ class EventScreen extends StatelessWidget {
required this.userLastName, required this.userLastName,
}) : super(key: key); }) : super(key: key);
@override
_EventScreenState createState() => _EventScreenState();
}
class _EventScreenState extends State<EventScreen> {
late Future<List<EventModel>> _eventsFuture;
@override
void initState() {
super.initState();
_eventsFuture = widget.eventRemoteDataSource.getAllEvents();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@@ -37,9 +51,9 @@ class EventScreen extends StatelessWidget {
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return AddEventDialog( return AddEventDialog(
userId: userId, userId: widget.userId,
userName: userName, userName: widget.userName,
userLastName: userLastName, userLastName: widget.userLastName,
); );
}, },
); );
@@ -47,11 +61,15 @@ class EventScreen extends StatelessWidget {
if (eventData != null) { if (eventData != null) {
// Appeler l'API pour créer un nouvel événement. // Appeler l'API pour créer un nouvel événement.
try { try {
print('Tentative de création d\'un nouvel événement par l\'utilisateur $userId'); print('Tentative de création d\'un nouvel événement par l\'utilisateur ${widget.userId}');
await eventRemoteDataSource.createEvent(eventData as EventModel); await widget.eventRemoteDataSource.createEvent(eventData as EventModel);
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Événement ajouté avec succès !')), 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) { } catch (e) {
print('Erreur lors de la création de l\'événement: $e'); print('Erreur lors de la création de l\'événement: $e');
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
@@ -64,7 +82,7 @@ class EventScreen extends StatelessWidget {
], ],
), ),
body: FutureBuilder<List<EventModel>>( body: FutureBuilder<List<EventModel>>(
future: eventRemoteDataSource.getAllEvents(), future: _eventsFuture,
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) { if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
@@ -86,17 +104,19 @@ class EventScreen extends StatelessWidget {
print('Affichage de l\'événement ${event.id}'); print('Affichage de l\'événement ${event.id}');
return EventCard( return EventCard(
eventRemoteDataSource: eventRemoteDataSource, key: ValueKey(event.id), // Pour une gestion correcte du Key dans l'animation
userId: userId, eventRemoteDataSource: widget.eventRemoteDataSource,
userId: widget.userId,
eventId: event.id, eventId: event.id,
userName: userName, userName: widget.userName,
userLastName: userLastName, userLastName: widget.userLastName,
profileImage: 'lib/assets/images/profile_picture.png', profileImage: 'lib/assets/images/profile_picture.png',
name: '$userName $userLastName', name: '${widget.userName} ${widget.userLastName}',
datePosted: 'Posté le 24/08/2024', datePosted: 'Posté le 24/08/2024',
eventTitle: event.title, eventTitle: event.title,
eventDescription: event.description, eventDescription: event.description,
eventImageUrl: event.imageUrl ?? 'lib/assets/images/placeholder.png', eventImageUrl: event.imageUrl ?? 'lib/assets/images/placeholder.png',
eventStatus: event.status, // Ajouter le statut de l'événement ici
reactionsCount: 120, // Exemple de valeur reactionsCount: 120, // Exemple de valeur
commentsCount: 45, // Exemple de valeur commentsCount: 45, // Exemple de valeur
sharesCount: 30, // Exemple de valeur sharesCount: 30, // Exemple de valeur
@@ -112,9 +132,7 @@ class EventScreen extends StatelessWidget {
onParticipate: () { onParticipate: () {
print('Participation à l\'événement ${event.id}'); print('Participation à l\'événement ${event.id}');
}, },
onCloseEvent: () { onCloseEvent: () => _onCloseEvent(context, event.id, index),
print('Fermeture de l\'événement ${event.id}');
},
onMoreOptions: () { onMoreOptions: () {
print('Affichage des options pour l\'événement ${event.id}'); print('Affichage des options pour l\'événement ${event.id}');
}, },
@@ -126,4 +144,30 @@ class EventScreen extends StatelessWidget {
backgroundColor: const Color(0xFF1E1E2C), 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')),
);
}
}
} }

View File

@@ -200,6 +200,14 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" 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: get_it:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@@ -11,6 +11,7 @@ dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
fluttertoast: ^8.0.8
flutter_secure_storage: ^7.0.1 flutter_secure_storage: ^7.0.1
crypto: ^3.0.1 crypto: ^3.0.1
google_maps_flutter: ^2.0.10 google_maps_flutter: ^2.0.10