refactoring

This commit is contained in:
DahoudG
2024-09-01 04:08:50 +00:00
parent 7e1cb85160
commit a1fce6bf27
31 changed files with 1651 additions and 441 deletions

View File

@@ -1,8 +1,26 @@
import 'package:flutter/material.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/creator_model.dart';
import 'package:afterwork/data/models/event_model.dart';
import 'package:afterwork/data/models/participant_model.dart';
import 'package:provider/provider.dart';
import 'package:afterwork/data/providers/user_provider.dart';
import 'package:afterwork/core/constants/urls.dart';
import '../location/location_picker_screen.dart';
class AddEventDialog extends StatefulWidget {
const AddEventDialog({super.key});
final String userId;
final String userName;
final String userLastName;
const AddEventDialog({
super.key,
required this.userId,
required this.userName,
required this.userLastName,
});
@override
_AddEventDialogState createState() => _AddEventDialogState();
@@ -14,10 +32,10 @@ class _AddEventDialogState extends State<AddEventDialog> {
String _description = '';
DateTime? _selectedDate;
String? _imagePath;
String _location = '';
String _location = 'Abidjan'; // Par défaut à Cocody, Abidjan
String _category = '';
String _link = '';
LatLng? _selectedLatLng;
LatLng? _selectedLatLng = const LatLng(5.348722, -3.985038); // Par défaut à Cocody, Abidjan
@override
Widget build(BuildContext context) {
@@ -26,29 +44,31 @@ class _AddEventDialogState extends State<AddEventDialog> {
borderRadius: BorderRadius.circular(15.0),
),
backgroundColor: const Color(0xFF2C2C3E),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_buildTitleField(),
const SizedBox(height: 10),
_buildDescriptionField(),
const SizedBox(height: 10),
_buildDatePicker(),
const SizedBox(height: 10),
_buildLocationField(context),
const SizedBox(height: 10),
_buildCategoryField(),
const SizedBox(height: 10),
_buildImagePicker(),
const SizedBox(height: 10),
_buildLinkField(),
const SizedBox(height: 20),
_buildSubmitButton(),
],
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_buildTitleField(),
const SizedBox(height: 10),
_buildDescriptionField(),
const SizedBox(height: 10),
_buildDatePicker(),
const SizedBox(height: 10),
_buildLocationField(context),
const SizedBox(height: 10),
_buildCategoryField(),
const SizedBox(height: 10),
_buildImagePicker(),
const SizedBox(height: 10),
_buildLinkField(),
const SizedBox(height: 20),
_buildSubmitButton(),
],
),
),
),
),
@@ -71,12 +91,14 @@ class _AddEventDialogState extends State<AddEventDialog> {
style: const TextStyle(color: Colors.white),
validator: (value) {
if (value == null || value.isEmpty) {
print('Erreur: Titre est vide');
return 'Veuillez entrer un titre';
}
return null;
},
onSaved: (value) {
_title = value ?? '';
print('Titre sauvegardé: $_title');
},
);
}
@@ -98,12 +120,14 @@ class _AddEventDialogState extends State<AddEventDialog> {
maxLines: 3,
validator: (value) {
if (value == null || value.isEmpty) {
print('Erreur: Description est vide');
return 'Veuillez entrer une description';
}
return null;
},
onSaved: (value) {
_description = value ?? '';
print('Description sauvegardée: $_description');
},
);
}
@@ -120,7 +144,10 @@ class _AddEventDialogState extends State<AddEventDialog> {
if (picked != null && picked != _selectedDate) {
setState(() {
_selectedDate = picked;
print('Date sélectionnée: $_selectedDate');
});
} else {
print('Date non sélectionnée ou égale à la précédente');
}
},
child: Container(
@@ -158,7 +185,10 @@ class _AddEventDialogState extends State<AddEventDialog> {
setState(() {
_selectedLatLng = pickedLocation;
_location = '${pickedLocation.latitude}, ${pickedLocation.longitude}';
print('Localisation sélectionnée: $_location');
});
} else {
print('Localisation non sélectionnée');
}
},
child: Container(
@@ -199,6 +229,7 @@ class _AddEventDialogState extends State<AddEventDialog> {
style: const TextStyle(color: Colors.white),
onSaved: (value) {
_category = value ?? '';
print('Catégorie sauvegardée: $_category');
},
);
}
@@ -207,6 +238,7 @@ class _AddEventDialogState extends State<AddEventDialog> {
return GestureDetector(
onTap: () {
// Logique pour sélectionner une image
print('Image Picker activé');
},
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 16.0),
@@ -218,7 +250,9 @@ class _AddEventDialogState extends State<AddEventDialog> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
_imagePath == null ? 'Sélectionnez une image' : 'Image sélectionnée',
_imagePath == null
? 'Sélectionnez une image'
: 'Image sélectionnée: $_imagePath',
style: const TextStyle(color: Colors.white70),
),
const Icon(Icons.image, color: Colors.white70),
@@ -244,17 +278,69 @@ class _AddEventDialogState extends State<AddEventDialog> {
style: const TextStyle(color: Colors.white),
onSaved: (value) {
_link = value ?? '';
print('Lien sauvegardé: $_link');
},
);
}
Widget _buildSubmitButton() {
return ElevatedButton(
onPressed: () {
onPressed: () async {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
// Logique pour soumettre les données
Navigator.of(context).pop();
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(
title: _title,
description: _description,
date: _selectedDate?.toIso8601String() ?? '',
location: _location,
category: _category,
link: _link,
imageUrl: _imagePath ?? '',
creator: CreatorModel(
id: widget.userId,
nom: widget.userName,
prenoms: widget.userLastName,
),
participants: [
ParticipantModel(
id: widget.userId,
nom: widget.userName,
prenoms: widget.userLastName,
)
],
id: '',
);
// Convertir l'événement en JSON
Map<String, dynamic> 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'},
body: jsonEncode(eventData),
);
print('Statut de la réponse: ${response.statusCode}');
print('Réponse brute: ${response.body}');
if (response.statusCode == 201) {
// Création réussie
print('Événement créé avec succès');
Navigator.of(context).pop(true);
} else {
// Gérer l'erreur
print('Erreur lors de la création de l\'événement: ${response.reasonPhrase}');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Erreur: ${response.reasonPhrase}')),
);
}
} else {
print('Le formulaire n\'est pas valide');
}
},
style: ElevatedButton.styleFrom(
@@ -265,40 +351,8 @@ class _AddEventDialogState extends State<AddEventDialog> {
padding: const EdgeInsets.symmetric(vertical: 12.0),
minimumSize: const Size(double.infinity, 40),
),
child: const Text('Ajouter l\'événement', style: TextStyle(color: Colors.white)),
);
}
}
class LocationPickerScreen extends StatelessWidget {
const LocationPickerScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Sélectionnez une localisation'),
backgroundColor: const Color(0xFF1E1E2C),
),
body: GoogleMap(
initialCameraPosition: const CameraPosition(
target: LatLng(48.8566, 2.3522), // Paris par défaut
zoom: 12.0,
),
markers: Set<Marker>.of(<Marker>[
Marker(
markerId: const MarkerId('selectedLocation'),
position: LatLng(48.8566, 2.3522), // Position par défaut
draggable: true,
onDragEnd: (newPosition) {
Navigator.of(context).pop(newPosition);
},
)
]),
onTap: (position) {
Navigator.of(context).pop(position);
},
),
child: const Text('Ajouter l\'événement',
style: TextStyle(color: Colors.white)),
);
}
}

View File

@@ -1,6 +1,13 @@
import 'package:flutter/material.dart';
import 'package:afterwork/data/datasources/event_remote_data_source.dart';
/// Widget pour afficher une carte d'événement.
class EventCard extends StatelessWidget {
final String eventId;
final EventRemoteDataSource eventRemoteDataSource;
final String userId;
final String userName;
final String userLastName;
final String profileImage;
final String name;
final String datePosted;
@@ -19,6 +26,11 @@ class EventCard extends StatelessWidget {
const EventCard({
Key? key,
required this.eventId,
required this.eventRemoteDataSource,
required this.userId,
required this.userName,
required this.userLastName,
required this.profileImage,
required this.name,
required this.datePosted,
@@ -33,7 +45,7 @@ class EventCard extends StatelessWidget {
required this.onShare,
required this.onParticipate,
required this.onCloseEvent,
required this.onMoreOptions, required String assetImage,
required this.onMoreOptions,
}) : super(key: key);
@override
@@ -65,6 +77,7 @@ class EventCard extends StatelessWidget {
);
}
/// Construire l'en-tête de la carte avec les informations de l'utilisateur.
Widget _buildHeader() {
return Row(
children: [
@@ -94,16 +107,17 @@ class EventCard extends StatelessWidget {
),
IconButton(
icon: const Icon(Icons.more_vert, color: Colors.white),
onPressed: onMoreOptions,
onPressed: _onMoreOptions,
),
IconButton(
icon: const Icon(Icons.close, color: Colors.white),
onPressed: onCloseEvent,
onPressed: _onCloseEvent,
),
],
);
}
/// Afficher les détails de l'événement.
Widget _buildEventDetails() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -125,6 +139,7 @@ class EventCard extends StatelessWidget {
);
}
/// Afficher l'image de l'événement.
Widget _buildEventImage() {
return ClipRRect(
borderRadius: BorderRadius.circular(10.0),
@@ -134,6 +149,7 @@ class EventCard extends StatelessWidget {
width: double.infinity,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
print('Erreur de chargement de l\'image: $error');
return Image.asset(
'lib/assets/images/placeholder.png',
height: 180,
@@ -145,6 +161,7 @@ class EventCard extends StatelessWidget {
);
}
/// Afficher les icônes d'interaction (réagir, commenter, partager).
Widget _buildInteractionRow() {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
@@ -174,6 +191,7 @@ class EventCard extends StatelessWidget {
);
}
/// Bouton d'interaction personnalisé.
Widget _buildIconButton({
required IconData icon,
required String label,
@@ -192,9 +210,10 @@ class EventCard extends StatelessWidget {
);
}
/// Bouton pour participer à l'événement.
Widget _buildParticipateButton() {
return ElevatedButton(
onPressed: onParticipate,
onPressed: _onParticipate,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF1DBF73),
shape: RoundedRectangleBorder(
@@ -206,4 +225,54 @@ class EventCard extends StatelessWidget {
child: const Text('Participer', style: 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');
}
}
// 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');
}
}

View File

@@ -1,71 +1,129 @@
import 'package:flutter/material.dart';
import '../dialogs/add_event_dialog.dart';
import 'package:afterwork/data/models/event_model.dart';
import 'package:afterwork/data/datasources/event_remote_data_source.dart';
import 'event_card.dart';
import '../dialogs/add_event_dialog.dart';
/// Écran principal pour afficher les événements.
class EventScreen extends StatelessWidget {
const EventScreen({super.key});
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,
required this.userId,
required this.userName,
required this.userLastName,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Événements'),
title: const Text(
'Événements',
style: TextStyle(
color: Color(0xFF1DBF73), // Définit la couleur verte du texte
),
),
backgroundColor: const Color(0xFF1E1E2C),
actions: [
IconButton(
icon: const Icon(Icons.add_circle_outline, size: 28, color: Color(0xFF1DBF73)),
onPressed: () {
_showAddEventDialog(context);
onPressed: () async {
final eventData = await showDialog<Map<String, dynamic>>(
context: context,
builder: (BuildContext context) {
return AddEventDialog(
userId: userId,
userName: userName,
userLastName: userLastName,
);
},
);
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);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Événement ajouté avec succès !')),
);
} catch (e) {
print('Erreur lors de la création de l\'événement: $e');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Erreur : $e')),
);
}
}
},
),
],
),
body: ListView.builder(
padding: const EdgeInsets.all(16.0),
itemCount: 10,
itemBuilder: (context, index) {
return EventCard(
profileImage: 'lib/assets/images/profile_picture.png',
name: 'Nom Prénom',
datePosted: 'Posté le 24/08/2024',
eventTitle: 'Titre de l\'événement',
eventDescription: 'Description détaillée de l\'événement...',
eventImageUrl: 'lib/assets/images/profile_picture.png',
reactionsCount: 120,
commentsCount: 45,
sharesCount: 30,
onReact: () {
// Logique pour réagir à l'événement
body: FutureBuilder<List<EventModel>>(
future: eventRemoteDataSource.getAllEvents(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
print('Erreur lors de la récupération des événements: ${snapshot.error}');
return Center(child: Text('Erreur: ${snapshot.error}'));
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
return const Center(child: Text('Aucun événement trouvé.'));
}
final events = snapshot.data!;
print('Nombre d\'événements récupérés: ${events.length}');
return ListView.builder(
padding: const EdgeInsets.all(16.0),
itemCount: events.length,
itemBuilder: (context, index) {
final event = events[index];
print('Affichage de l\'événement ${event.id}');
return EventCard(
eventRemoteDataSource: eventRemoteDataSource,
userId: userId,
eventId: event.id,
userName: userName,
userLastName: userLastName,
profileImage: 'lib/assets/images/profile_picture.png',
name: '$userName $userLastName',
datePosted: 'Posté le 24/08/2024',
eventTitle: event.title,
eventDescription: event.description,
eventImageUrl: event.imageUrl ?? 'lib/assets/images/placeholder.png',
reactionsCount: 120, // Exemple de valeur
commentsCount: 45, // Exemple de valeur
sharesCount: 30, // Exemple de valeur
onReact: () {
print('Réaction à l\'événement ${event.id}');
},
onComment: () {
print('Commentaire sur l\'événement ${event.id}');
},
onShare: () {
print('Partage de l\'événement ${event.id}');
},
onParticipate: () {
print('Participation à l\'événement ${event.id}');
},
onCloseEvent: () {
print('Fermeture de l\'événement ${event.id}');
},
onMoreOptions: () {
print('Affichage des options pour l\'événement ${event.id}');
},
);
},
onComment: () {
// Logique pour commenter l'événement
},
onShare: () {
// Logique pour partager l'événement
},
onParticipate: () {
// Logique pour participer à l'événement
},
onCloseEvent: () {
// Logique pour fermer l'événement
},
onMoreOptions: () {
// Logique pour afficher plus d'options
},
assetImage: 'lib/assets/images/placeholder.png', // Ajoutez ce paramètre requis
);
},
),
backgroundColor: const Color(0xFF1E1E2C),
);
}
void _showAddEventDialog(BuildContext context) {
showDialog(
context: context,
builder: (BuildContext context) {
return const AddEventDialog();
},
);
}
}

View File

@@ -4,9 +4,26 @@ import 'package:afterwork/presentation/screens/profile/profile_screen.dart';
import 'package:afterwork/presentation/screens/social/social_screen.dart';
import 'package:afterwork/presentation/screens/establishments/establishments_screen.dart';
import 'package:afterwork/presentation/screens/home/home_content.dart';
import 'package:afterwork/data/datasources/event_remote_data_source.dart';
/// Classe principale pour l'écran d'accueil de l'application.
/// Cette classe gère la navigation entre les différentes sections de l'application
/// en utilisant un [TabController] pour contrôler les différents onglets.
/// Les actions de l'AppBar sont également personnalisées pour offrir des fonctionnalités
/// spécifiques comme la recherche, la publication et la messagerie.
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
final EventRemoteDataSource eventRemoteDataSource;
final String userId;
final String userName;
final String userLastName;
const HomeScreen({
Key? key,
required this.eventRemoteDataSource,
required this.userId,
required this.userName,
required this.userLastName,
}) : super(key: key);
@override
_HomeScreenState createState() => _HomeScreenState();
@@ -18,25 +35,32 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
@override
void initState() {
super.initState();
// Initialisation du TabController avec 5 onglets.
_tabController = TabController(length: 5, vsync: this);
debugPrint('HomeScreen initialisé avec userId: ${widget.userId}, userName: ${widget.userName}, userLastName: ${widget.userLastName}');
}
@override
void dispose() {
// Nettoyage du TabController pour éviter les fuites de mémoire.
_tabController.dispose();
super.dispose();
debugPrint('HomeScreen dispose appelé');
}
/// Gestion des sélections dans le menu contextuel de l'AppBar.
void _onMenuSelected(BuildContext context, String option) {
// Implémente la logique pour chaque option ici
switch (option) {
case 'Publier':
// Redirige vers la page de publication
debugPrint('Option "Publier" sélectionnée');
// Rediriger vers la page de publication.
break;
case 'Story':
// Redirige vers la page de création de Story
debugPrint('Option "Story" sélectionnée');
// Rediriger vers la page de création de Story.
break;
default:
debugPrint('Option inconnue sélectionnée: $option');
break;
}
}
@@ -45,22 +69,25 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.black, // Fond noir pour l'AppBar
elevation: 0, // Enlève l'ombre sous l'AppBar
backgroundColor: Colors.black,
elevation: 0,
leading: Padding(
padding: const EdgeInsets.all(8.0),
child: Image.asset(
'lib/assets/images/logo.png', // Chemin correct de ton logo
'lib/assets/images/logo.png', // Chemin correct de votre logo.
height: 40,
),
),
actions: [
// Bouton +
// Bouton pour ajouter du contenu (Publier, Story).
CircleAvatar(
backgroundColor: Colors.white, // Cercle blanc
radius: 18, // Réduit la taille du bouton
backgroundColor: Colors.white,
radius: 18,
child: PopupMenuButton<String>(
onSelected: (value) => _onMenuSelected(context, value),
onSelected: (value) {
_onMenuSelected(context, value);
debugPrint('Menu contextuel sélectionné: $value');
},
itemBuilder: (context) => [
const PopupMenuItem(
value: 'Publier',
@@ -71,39 +98,48 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
child: Text('Story'),
),
],
icon: const Icon(Icons.add, color: Colors.blueAccent, size: 20), // Icône bleue légèrement plus petite
color: Colors.white, // Menu contextuel en blanc
icon: const Icon(Icons.add, color: Colors.blueAccent, size: 20),
color: Colors.white,
),
),
const SizedBox(width: 8), // Réduit l'espacement entre les boutons
// Bouton Recherche
const SizedBox(width: 8), // Espacement entre les boutons.
// Bouton Recherche.
CircleAvatar(
backgroundColor: Colors.white,
radius: 18, // Réduit la taille du bouton
radius: 18,
child: IconButton(
icon: const Icon(Icons.search, color: Colors.blueAccent, size: 20), // Icône bleue légèrement plus petite
icon: const Icon(Icons.search, color: Colors.blueAccent, size: 20),
onPressed: () {
// Implémente la logique de recherche ici
debugPrint('Bouton Recherche appuyé');
// Implémenter la logique de recherche ici.
},
),
),
const SizedBox(width: 8), // Réduit l'espacement entre les boutons
// Bouton Messagerie
const SizedBox(width: 8), // Espacement entre les boutons.
// Bouton Messagerie.
CircleAvatar(
backgroundColor: Colors.white,
radius: 18, // Réduit la taille du bouton
radius: 18,
child: IconButton(
icon: const Icon(Icons.message, color: Colors.blueAccent, size: 20), // Icône bleue légèrement plus petite
icon: const Icon(Icons.message, color: Colors.blueAccent, size: 20),
onPressed: () {
// Implémente la logique de messagerie ici
debugPrint('Bouton Messagerie appuyé');
// Implémenter la logique de messagerie ici.
},
),
),
const SizedBox(width: 8), // Réduit l'espacement entre les boutons
const SizedBox(width: 8), // Espacement entre les boutons.
],
bottom: TabBar(
controller: _tabController,
indicatorColor: Colors.blueAccent,
labelColor: Colors.white, // Couleur du texte sélectionné.
unselectedLabelColor: Colors.grey[400], // Couleur du texte non sélectionné.
onTap: (index) {
debugPrint('Onglet sélectionné: $index');
},
tabs: const [
Tab(icon: Icon(Icons.home), text: 'Accueil'),
Tab(icon: Icon(Icons.event), text: 'Événements'),
@@ -115,15 +151,20 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
),
body: TabBarView(
controller: _tabController,
children: const [
HomeContentScreen(), // Contenu de l'accueil
EventScreen(), // Écran des événements
EstablishmentsScreen(), // Écran des établissements
SocialScreen(), // Écran social
ProfileScreen(), // Écran du profil
children: [
const HomeContentScreen(), // Contenu de l'accueil.
EventScreen(
eventRemoteDataSource: widget.eventRemoteDataSource,
userId: widget.userId,
userName: widget.userName,
userLastName: widget.userLastName,
), // Écran des événements.
const EstablishmentsScreen(), // Écran des établissements.
const SocialScreen(), // Écran social.
const ProfileScreen(), // Écran du profil.
],
),
backgroundColor: Colors.black, // Arrière-plan de l'écran en noir
backgroundColor: Colors.black, // Arrière-plan de l'écran en noir.
);
}
}

View File

@@ -0,0 +1,38 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
class LocationPickerScreen extends StatelessWidget {
const LocationPickerScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Sélectionnez une localisation'),
backgroundColor: const Color(0xFF1E1E2C),
),
body: GoogleMap(
initialCameraPosition: const CameraPosition(
target: LatLng(48.8566, 2.3522), // Paris par défaut
zoom: 12.0,
),
markers: <Marker>{
Marker(
markerId: const MarkerId('selectedLocation'),
position: const LatLng(48.8566, 2.3522), // Position par défaut
draggable: true,
onDragEnd: (newPosition) {
print('Nouvelle position sélectionnée: $newPosition');
Navigator.of(context).pop(newPosition);
},
)
},
onTap: (position) {
print('Position tapée: $position');
Navigator.of(context).pop(position);
},
),
);
}
}

View File

@@ -1,8 +1,14 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:afterwork/data/datasources/user_remote_data_source.dart';
import 'package:afterwork/data/models/user_model.dart';
import 'package:afterwork/presentation/screens/home/home_screen.dart';
import 'package:http/http.dart' as http;
import 'package:afterwork/data/services/hash_password.dart';
import 'package:afterwork/data/services/secure_storage.dart';
import 'package:afterwork/data/services/preferences_helper.dart';
import '../../../data/datasources/event_remote_data_source.dart';
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
@@ -13,12 +19,15 @@ class LoginScreen extends StatefulWidget {
class _LoginScreenState extends State<LoginScreen> with SingleTickerProviderStateMixin {
final _formKey = GlobalKey<FormState>();
String _userId = '';
String _email = '';
String _password = '';
bool _isPasswordVisible = false;
bool _isSubmitting = false;
final UserRemoteDataSource _userRemoteDataSource = UserRemoteDataSource(http.Client());
final SecureStorage _secureStorage = SecureStorage();
final PreferencesHelper _preferencesHelper = PreferencesHelper();
late AnimationController _controller;
late Animation<double> _buttonScaleAnimation;
@@ -41,12 +50,14 @@ class _LoginScreenState extends State<LoginScreen> with SingleTickerProviderStat
super.dispose();
}
/// Afficher/Masquer le mot de passe
void _togglePasswordVisibility() {
setState(() {
_isPasswordVisible = !_isPasswordVisible;
});
}
/// Soumission du formulaire d'authentification
void _submit() async {
if (_formKey.currentState!.validate()) {
setState(() {
@@ -54,25 +65,65 @@ class _LoginScreenState extends State<LoginScreen> with SingleTickerProviderStat
});
_formKey.currentState!.save();
try {
UserModel user = await _userRemoteDataSource.authenticateUser(_email, _password);
print('Connexion réussie : ${user.email}');
print("===== DEBUT DE LA SOUMISSION DU FORMULAIRE =====");
print("Email: $_email");
print("Mot de passe: $_password");
// Navigation vers la page d'accueil
try {
print('Début de l\'authentification'); // Débogage
// Hachage du mot de passe avec SHA-256
String hashedPassword = hashPassword(_password);
print("Mot de passe haché: $hashedPassword");
// Authentification via l'API avec un timeout
UserModel user = await _userRemoteDataSource
.authenticateUser(_email, hashedPassword, "unique_user_id")
.timeout(
Duration(seconds: 10),
onTimeout: () {
throw TimeoutException('Le temps de connexion a expiré. Veuillez réessayer.');
},
);
print('Connexion réussie : ${user.userId} - ${user.email}');
// Sauvegarde des données de l'utilisateur après authentification
await _secureStorage.saveUserId(user.userId);
await _preferencesHelper.saveUserName(user.nom);
await _preferencesHelper.saveUserLastName(user.prenoms);
print("===== SAUVEGARDE DES DONNÉES UTILISATEUR =====");
print("User ID: ${user.userId}");
print("User Name: ${user.nom}");
print("User Last Name: ${user.prenoms}");
// Navigation vers l'écran d'accueil
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const HomeScreen()),
MaterialPageRoute(
builder: (context) => HomeScreen(
eventRemoteDataSource: EventRemoteDataSource(http.Client()),
userId: user.userId,
userName: user.nom,
userLastName: user.prenoms,
),
),
);
print("===== NAVIGATION VERS HOME SCREEN =====");
} catch (e) {
print('Erreur lors de la connexion: $e');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(e.toString())),
SnackBar(content: Text('Erreur : ${e.toString()}')),
);
} finally {
print('Fin du processus d\'authentification'); // Débogage
setState(() {
_isSubmitting = false;
});
}
} else {
print("===== FORMULAIRE NON VALIDE =====");
}
}
@@ -102,7 +153,7 @@ class _LoginScreenState extends State<LoginScreen> with SingleTickerProviderStat
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Logo avec légère animation
// Logo animé
AnimatedBuilder(
animation: _controller,
builder: (context, child) {
@@ -154,15 +205,18 @@ class _LoginScreenState extends State<LoginScreen> with SingleTickerProviderStat
style: const TextStyle(color: Colors.white),
validator: (value) {
if (value == null || value.isEmpty) {
print("Erreur: Le champ email est vide");
return 'Veuillez entrer votre email';
}
if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value)) {
print("Erreur: Le format de l'email est invalide");
return 'Veuillez entrer un email valide';
}
return null;
},
onSaved: (value) {
_email = value ?? ''; // Utiliser une chaîne vide si value est null
_email = value ?? ''; // Utiliser une chaîne vide si value est null
print("Email sauvegardé: $_email");
},
),
const SizedBox(height: 20),
@@ -180,9 +234,8 @@ class _LoginScreenState extends State<LoginScreen> with SingleTickerProviderStat
prefixIcon: const Icon(Icons.lock, color: Colors.white),
suffixIcon: IconButton(
icon: Icon(
_isPasswordVisible ? Icons.visibility : Icons.visibility_off,
color: Colors.white,
),
_isPasswordVisible ? Icons.visibility : Icons.visibility_off,
color: Colors.white),
onPressed: _togglePasswordVisibility,
),
),
@@ -190,15 +243,18 @@ class _LoginScreenState extends State<LoginScreen> with SingleTickerProviderStat
style: const TextStyle(color: Colors.white),
validator: (value) {
if (value == null || value.isEmpty) {
print("Erreur: Le champ mot de passe est vide");
return 'Veuillez entrer votre mot de passe';
}
if (value.length < 6) {
print("Erreur: Le mot de passe est trop court");
return 'Le mot de passe doit comporter au moins 6 caractères';
}
return null;
},
onSaved: (value) {
_password = value ?? ''; // Utiliser une chaîne vide si value est null
_password = value ?? ''; // Utiliser une chaîne vide si value est null
print("Mot de passe sauvegardé: $_password");
},
),
const SizedBox(height: 20),
@@ -225,6 +281,7 @@ class _LoginScreenState extends State<LoginScreen> with SingleTickerProviderStat
TextButton(
onPressed: () {
// Naviguer vers la page d'inscription
print("Redirection vers la page d'inscription");
},
child: const Text(
'Pas encore de compte ? Inscrivez-vous',

View File

@@ -7,274 +7,388 @@ class ProfileScreen extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Profil'),
backgroundColor: Colors.black,
title: const Text('Profil',
style: TextStyle(
color: Color(0xFF1DBF73), // Définit la couleur verte du texte
),
),
backgroundColor: const Color(0xFF1E1E2C),
actions: [
IconButton(
icon: const Icon(Icons.settings),
icon: const Icon(Icons.settings, color: Colors.white),
onPressed: () {
// Naviguer vers la page des paramètres
},
),
],
),
body: CustomScrollView(
slivers: [
SliverList(
delegate: SliverChildListDelegate([
// Informations de l'Utilisateur
_buildUserInfoSection(context),
const Divider(),
// Options de Modification
_buildEditOptions(context),
const Divider(),
// Statistiques Personnelles
_buildStatisticsSection(context),
const Divider(),
// Historique
_buildHistorySection(context),
const Divider(),
// Préférences et Paramètres
_buildPreferencesSection(context),
const Divider(),
// Autres Fonctions
_buildSupportSection(context),
const Divider(),
// Suppression de Compte
_buildAccountDeletionSection(context),
]),
body: ListView(
padding: const EdgeInsets.all(16.0),
children: [
_buildUserInfoCard(),
const SizedBox(height: 20),
_buildEditOptionsCard(),
const SizedBox(height: 20),
_buildStatisticsSectionCard(),
const SizedBox(height: 20),
_buildExpandableSectionCard(
title: 'Historique',
icon: Icons.history,
children: [
_buildAnimatedListTile(
icon: Icons.event_note,
label: 'Historique des Événements',
onTap: () {
// Naviguer vers l'historique des événements
},
),
_buildAnimatedListTile(
icon: Icons.history,
label: 'Historique des Publications',
onTap: () {
// Naviguer vers l'historique des publications
},
),
_buildAnimatedListTile(
icon: Icons.bookmark,
label: 'Historique de Réservations',
onTap: () {
// Naviguer vers l'historique des réservations
},
),
],
),
const SizedBox(height: 20),
_buildExpandableSectionCard(
title: 'Préférences et Paramètres',
icon: Icons.settings,
children: [
_buildAnimatedListTile(
icon: Icons.privacy_tip,
label: 'Paramètres de confidentialité',
onTap: () {
// Naviguer vers les paramètres de confidentialité
},
),
_buildAnimatedListTile(
icon: Icons.notifications,
label: 'Notifications',
onTap: () {
// Naviguer vers les paramètres de notification
},
),
_buildAnimatedListTile(
icon: Icons.language,
label: 'Langue de l\'application',
onTap: () {
// Naviguer vers les paramètres de langue
},
),
_buildAnimatedListTile(
icon: Icons.format_paint,
label: 'Thème de l\'application',
onTap: () {
// Naviguer vers les paramètres de thème
},
),
],
),
const SizedBox(height: 20),
_buildSupportSectionCard(),
const SizedBox(height: 20),
_buildAccountDeletionCard(context),
],
),
backgroundColor: const Color(0xFF1E1E2C),
);
}
Widget _buildUserInfoCard() {
return Card(
color: const Color(0xFF292B37),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
const CircleAvatar(
radius: 50,
backgroundImage: AssetImage('lib/assets/images/profile_picture.png'),
backgroundColor: Colors.transparent,
),
const SizedBox(height: 10),
const Text(
'GBANE Dahoud',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white,
letterSpacing: 1.2,
),
),
const SizedBox(height: 5),
Text(
'pseudo',
style: TextStyle(
fontSize: 16,
color: Colors.grey[400],
fontStyle: FontStyle.italic,
),
),
const SizedBox(height: 5),
Text(
'gbanedahoud@lions.dev',
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
decoration: TextDecoration.underline,
),
),
],
),
),
);
}
Widget _buildEditOptionsCard() {
return Card(
color: const Color(0xFF292B37),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
child: Column(
children: [
_buildAnimatedListTile(
icon: Icons.edit,
label: 'Éditer le profil',
onTap: () {
// Naviguer vers la page d'édition de profil
},
),
_buildAnimatedListTile(
icon: Icons.camera_alt,
label: 'Changer la photo de profil',
onTap: () {
// Naviguer vers la page de changement de photo de profil
},
),
_buildAnimatedListTile(
icon: Icons.lock,
label: 'Changer le mot de passe',
onTap: () {
// Naviguer vers la page de changement de mot de passe
},
),
],
),
);
}
Widget _buildUserInfoSection(BuildContext context) {
return const Column(
children: [
CircleAvatar(
radius: 50,
backgroundImage: AssetImage('lib/assets/images/profile_picture.png'), //Photo de profil
Widget _buildStatisticsSectionCard() {
return Card(
color: const Color(0xFF292B37),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Statistiques Personnelles',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
const SizedBox(height: 10),
_buildStatTile(
icon: Icons.event,
label: 'Événements Participés',
value: '12',
),
_buildStatTile(
icon: Icons.place,
label: 'Établissements Visités',
value: '8',
),
_buildStatTile(
icon: Icons.post_add,
label: 'Publications',
value: '24',
),
_buildStatTile(
icon: Icons.group,
label: 'Amis/Followers',
value: '150',
),
],
),
SizedBox(height: 10),
Text(
'GBANE Dahoud',
style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: Colors.white),
),
SizedBox(height: 5),
Text(
'pseudo',
style: TextStyle(fontSize: 16, color: Colors.grey),
),
SizedBox(height: 5),
Text(
'gbanedahoud@lions.dev',
style: TextStyle(fontSize: 14, color: Colors.grey),
),
],
);
}
Widget _buildEditOptions(BuildContext context) {
return Column(
children: [
ListTile(
leading: const Icon(Icons.edit, color: Colors.blueAccent),
title: const Text('Éditer le profil'),
onTap: () {
// Naviguer vers la page d'édition de profil
},
),
ListTile(
leading: const Icon(Icons.camera_alt, color: Colors.blueAccent),
title: const Text('Changer la photo de profil'),
onTap: () {
// Naviguer vers la page de changement de photo de profil
},
),
ListTile(
leading: const Icon(Icons.lock, color: Colors.blueAccent),
title: const Text('Changer le mot de passe'),
onTap: () {
// Naviguer vers la page de changement de mot de passe
},
),
],
);
}
Widget _buildStatisticsSection(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Padding(
padding: EdgeInsets.all(16.0),
child: Text(
'Statistiques Personnelles',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.white),
),
),
ListTile(
leading: const Icon(Icons.event, color: Colors.blueAccent),
title: const Text('Événements Participés'),
trailing: const Text('12'), // Exemple de valeur
onTap: () {
// Naviguer vers la page des événements participés
},
),
ListTile(
leading: const Icon(Icons.place, color: Colors.blueAccent),
title: const Text('Établissements Visités'),
trailing: const Text('8'), // Exemple de valeur
onTap: () {
// Naviguer vers la page des établissements visités
},
),
ListTile(
leading: const Icon(Icons.post_add, color: Colors.blueAccent),
title: const Text('Publications'),
trailing: const Text('24'), // Exemple de valeur
onTap: () {
// Naviguer vers la page des publications
},
),
ListTile(
leading: const Icon(Icons.group, color: Colors.blueAccent),
title: const Text('Amis/Followers'),
trailing: const Text('150'), // Exemple de valeur
onTap: () {
// Naviguer vers la page des amis ou followers
},
),
],
);
}
Widget _buildHistorySection(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Padding(
padding: EdgeInsets.all(16.0),
child: Text(
'Historique',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.white),
),
),
ListTile(
leading: const Icon(Icons.event_note, color: Colors.blueAccent),
title: const Text('Historique des Événements'),
onTap: () {
// Naviguer vers l'historique des événements
},
),
ListTile(
leading: const Icon(Icons.history, color: Colors.blueAccent),
title: const Text('Historique des Publications'),
onTap: () {
// Naviguer vers l'historique des publications
},
),
ListTile(
leading: const Icon(Icons.bookmark, color: Colors.blueAccent),
title: const Text('Historique de Réservations'),
onTap: () {
// Naviguer vers l'historique des réservations
},
),
],
);
}
Widget _buildPreferencesSection(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Padding(
padding: EdgeInsets.all(16.0),
child: Text(
'Préférences et Paramètres',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.white),
),
),
ListTile(
leading: const Icon(Icons.privacy_tip, color: Colors.blueAccent),
title: const Text('Paramètres de confidentialité'),
onTap: () {
// Naviguer vers les paramètres de confidentialité
},
),
ListTile(
leading: const Icon(Icons.notifications, color: Colors.blueAccent),
title: const Text('Notifications'),
onTap: () {
// Naviguer vers les paramètres de notification
},
),
ListTile(
leading: const Icon(Icons.language, color: Colors.blueAccent),
title: const Text('Langue de l\'application'),
onTap: () {
// Naviguer vers les paramètres de langue
},
),
ListTile(
leading: const Icon(Icons.format_paint, color: Colors.blueAccent),
title: const Text('Thème de l\'application'),
onTap: () {
// Naviguer vers les paramètres de thème
},
),
],
);
}
Widget _buildSupportSection(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Padding(
padding: EdgeInsets.all(16.0),
child: Text(
'Support et Assistance',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.white),
),
),
ListTile(
leading: const Icon(Icons.help, color: Colors.blueAccent),
title: const Text('Support et Assistance'),
onTap: () {
// Naviguer vers la page de support
},
),
ListTile(
leading: const Icon(Icons.article, color: Colors.blueAccent),
title: const Text('Conditions d\'utilisation'),
onTap: () {
// Naviguer vers les conditions d'utilisation
},
),
ListTile(
leading: const Icon(Icons.privacy_tip, color: Colors.blueAccent),
title: const Text('Politique de confidentialité'),
onTap: () {
// Naviguer vers la politique de confidentialité
},
),
],
);
}
Widget _buildAccountDeletionSection(BuildContext context) {
return ListTile(
leading: const Icon(Icons.delete, color: Colors.redAccent),
title: const Text(
'Supprimer le compte',
style: TextStyle(color: Colors.redAccent),
),
onTap: () {
// Implémenter la logique de suppression de compte
);
}
Widget _buildExpandableSectionCard({
required String title,
required IconData icon,
required List<Widget> children,
}) {
return Card(
color: const Color(0xFF292B37),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
child: ExpansionTile(
title: Text(
title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
leading: Icon(icon, color: const Color(0xFF1DBF73)),
iconColor: const Color(0xFF1DBF73),
collapsedIconColor: const Color(0xFF1DBF73),
children: children,
),
);
}
Widget _buildSupportSectionCard() {
return Card(
color: const Color(0xFF292B37),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
child: Column(
children: [
const Padding(
padding: EdgeInsets.all(16.0),
child: Text(
'Support et Assistance',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
_buildAnimatedListTile(
icon: Icons.help,
label: 'Support et Assistance',
onTap: () {
// Naviguer vers la page de support
},
),
_buildAnimatedListTile(
icon: Icons.article,
label: 'Conditions d\'utilisation',
onTap: () {
// Naviguer vers les conditions d'utilisation
},
),
_buildAnimatedListTile(
icon: Icons.privacy_tip,
label: 'Politique de confidentialité',
onTap: () {
// Naviguer vers la politique de confidentialité
},
),
],
),
);
}
Widget _buildAccountDeletionCard(BuildContext context) {
return Card(
color: const Color(0xFF292B37),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
child: ListTile(
leading: const Icon(Icons.delete, color: Colors.redAccent),
title: const Text(
'Supprimer le compte',
style: TextStyle(color: Colors.redAccent, fontWeight: FontWeight.bold),
),
onTap: () {
_showDeleteConfirmationDialog(context);
},
hoverColor: Colors.red.withOpacity(0.1),
),
);
}
void _showDeleteConfirmationDialog(BuildContext context) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: const Color(0xFF1E1E2C),
title: const Text(
'Confirmer la suppression',
style: TextStyle(color: Colors.white),
),
content: const Text(
'Êtes-vous sûr de vouloir supprimer votre compte ? Cette action est irréversible.',
style: TextStyle(color: Colors.white70),
),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop(); // Fermer le popup
},
child: const Text(
'Annuler',
style: TextStyle(color: Color(0xFF1DBF73)),
),
),
TextButton(
onPressed: () {
// Logique de suppression du compte ici
Navigator.of(context).pop(); // Fermer le popup après la suppression
},
child: const Text(
'Supprimer',
style: TextStyle(color: Colors.redAccent),
),
),
],
);
},
);
}
Widget _buildAnimatedListTile({
required IconData icon,
required String label,
required VoidCallback onTap,
}) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(10),
splashColor: Colors.blueAccent.withOpacity(0.2),
child: ListTile(
leading: Icon(icon, color: const Color(0xFF1DBF73)),
title: Text(
label,
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w600),
),
hoverColor: Colors.blue.withOpacity(0.1),
),
);
}
Widget _buildStatTile({
required IconData icon,
required String label,
required String value,
}) {
return ListTile(
leading: Icon(icon, color: const Color(0xFF1DBF73)),
title: Text(label, style: const TextStyle(color: Colors.white)),
trailing: Text(
value,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
hoverColor: Colors.blue.withOpacity(0.1),
);
}
}