From a1fce6bf27a3d2b1813c675cf6880b07ac99f587 Mon Sep 17 00:00:00 2001 From: DahoudG Date: Sun, 1 Sep 2024 04:08:50 +0000 Subject: [PATCH] refactoring --- android/app/src/main/AndroidManifest.xml | 3 +- lib/config/router.dart | 34 +- lib/core/constants/urls.dart | 4 +- .../datasources/event_remote_data_source.dart | 145 +++++ .../datasources/user_remote_data_source.dart | 50 +- lib/data/models/creator_model.dart | 34 + lib/data/models/event_model.dart | 65 ++ lib/data/models/participant_model.dart | 34 + lib/data/models/user_model.dart | 10 +- lib/data/providers/user_provider.dart | 18 + .../repositories/user_repository_impl.dart | 4 +- lib/data/services/hash_password.dart | 8 + lib/data/services/preferences_helper.dart | 50 ++ lib/data/services/secure_storage.dart | 47 ++ lib/domain/entities/event.dart | 64 ++ lib/domain/entities/user.dart | 6 +- lib/main.dart | 77 ++- .../screens/dialogs/add_event_dialog.dart | 182 ++++-- .../screens/event/event_card.dart | 77 ++- .../screens/event/event_screen.dart | 150 +++-- .../screens/home/home_screen.dart | 103 ++- .../location/location_picker_Screen.dart | 38 ++ .../screens/login/login_screen.dart | 81 ++- .../screens/profile/profile_screen.dart | 616 +++++++++++------- linux/flutter/generated_plugin_registrant.cc | 4 + linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 4 + pubspec.lock | 176 +++++ pubspec.yaml | 3 + .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 31 files changed, 1651 insertions(+), 441 deletions(-) create mode 100644 lib/data/datasources/event_remote_data_source.dart create mode 100644 lib/data/models/creator_model.dart create mode 100644 lib/data/models/event_model.dart create mode 100644 lib/data/models/participant_model.dart create mode 100644 lib/data/providers/user_provider.dart create mode 100644 lib/data/services/hash_password.dart create mode 100644 lib/data/services/preferences_helper.dart create mode 100644 lib/data/services/secure_storage.dart create mode 100644 lib/domain/entities/event.dart create mode 100644 lib/presentation/screens/location/location_picker_Screen.dart diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 7c59885..8d833d6 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -2,7 +2,8 @@ + android:icon="@mipmap/ic_launcher" + android:enableOnBackInvokedCallback="true"> generateRoute(RouteSettings settings) { + final EventRemoteDataSource eventRemoteDataSource; + final String userId; + final String userName; + final String userLastName; + + AppRouter({ + required this.eventRemoteDataSource, + required this.userId, + required this.userName, + required this.userLastName, + }); + + Route generateRoute(RouteSettings settings) { switch (settings.name) { case '/': return MaterialPageRoute(builder: (_) => const LoginScreen()); case '/home': - return MaterialPageRoute(builder: (_) => const HomeScreen()); + return MaterialPageRoute( + builder: (_) => HomeScreen( + eventRemoteDataSource: eventRemoteDataSource, + userId: userId, + userName: userName, + userLastName: userLastName, + ), + ); case '/event': - return MaterialPageRoute(builder: (_) => const EventScreen()); + return MaterialPageRoute( + builder: (_) => EventScreen( + eventRemoteDataSource: eventRemoteDataSource, + userId: userId, + userName: userName, + userLastName: userLastName, + ), + ); case '/story': return MaterialPageRoute(builder: (_) => const StoryScreen()); case '/profile': diff --git a/lib/core/constants/urls.dart b/lib/core/constants/urls.dart index 1f807fd..7c3aaaa 100644 --- a/lib/core/constants/urls.dart +++ b/lib/core/constants/urls.dart @@ -1,6 +1,6 @@ class Urls { - static const String baseUrl = 'http://192.168.1.145:8085'; + static const String baseUrl = 'http://192.168.1.14:8085'; // static const String login = baseUrl + 'auth/login'; - // static const String events = baseUrl + 'events'; + static const String eventsUrl = '$baseUrl/events'; // Ajoute d'autres URLs ici } diff --git a/lib/data/datasources/event_remote_data_source.dart b/lib/data/datasources/event_remote_data_source.dart new file mode 100644 index 0000000..33a41ee --- /dev/null +++ b/lib/data/datasources/event_remote_data_source.dart @@ -0,0 +1,145 @@ +import 'dart:convert'; +import 'package:afterwork/core/constants/urls.dart'; +import 'package:afterwork/data/models/event_model.dart'; +import 'package:http/http.dart' as http; +import '../../core/errors/exceptions.dart'; + +class EventRemoteDataSource { + final http.Client client; + + EventRemoteDataSource(this.client); + + /// Récupérer tous les événements depuis l'API. + Future> getAllEvents() async { + print('Récupération de tous les événements depuis ${Urls.baseUrl}/events'); + + final response = await client.get(Uri.parse('${Urls.baseUrl}/events')); + + print('Statut de la réponse: ${response.statusCode}'); + + if (response.statusCode == 200) { + final List jsonResponse = json.decode(response.body); + print('Réponse JSON reçue: $jsonResponse'); + return jsonResponse.map((event) => EventModel.fromJson(event)).toList(); + } else { + print('Erreur lors de la récupération des événements: ${response.body}'); + throw ServerException(); + } + } + + /// Créer un nouvel événement via l'API. + Future createEvent(EventModel event) async { + print('Création d\'un nouvel événement avec les données: ${event.toJson()}'); + + final response = await client.post( + Uri.parse(Urls.eventsUrl), + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(event.toJson()), + ); + + print('Statut de la réponse: ${response.statusCode}'); + + if (response.statusCode == 201) { + print('Événement créé avec succès'); + return EventModel.fromJson(json.decode(response.body)); + } else { + print('Erreur lors de la création de l\'événement: ${response.body}'); + throw ServerException(); + } + } + + /// Récupérer un événement spécifique par son ID. + Future getEventById(String id) async { + print('Récupération de l\'événement avec l\'ID: $id'); + + final response = await client.get(Uri.parse('${Urls.eventsUrl}/$id')); + + print('Statut de la réponse: ${response.statusCode}'); + + if (response.statusCode == 200) { + print('Événement récupéré avec succès'); + return EventModel.fromJson(json.decode(response.body)); + } else { + print('Erreur lors de la récupération de l\'événement: ${response.body}'); + throw ServerException(); + } + } + + /// Mettre à jour un événement existant. + Future updateEvent(String id, EventModel event) async { + print('Mise à jour de l\'événement avec l\'ID: $id, données: ${event.toJson()}'); + + final response = await client.put( + Uri.parse('${Urls.eventsUrl}/$id'), + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(event.toJson()), + ); + + print('Statut de la réponse: ${response.statusCode}'); + + if (response.statusCode == 200) { + print('Événement mis à jour avec succès'); + return EventModel.fromJson(json.decode(response.body)); + } else { + print('Erreur lors de la mise à jour de l\'événement: ${response.body}'); + throw ServerException(); + } + } + + /// Supprimer un événement par son ID. + Future deleteEvent(String id) async { + print('Suppression de l\'événement avec l\'ID: $id'); + + final response = await client.delete(Uri.parse('${Urls.eventsUrl}/$id')); + + print('Statut de la réponse: ${response.statusCode}'); + + if (response.statusCode != 204) { + print('Erreur lors de la suppression de l\'événement: ${response.body}'); + throw ServerException(); + } else { + print('Événement supprimé avec succès'); + } + } + + /// Participer à un événement. + Future participateInEvent(String eventId, String userId) async { + print('Participation à l\'événement avec l\'ID: $eventId, utilisateur: $userId'); + + final response = await client.post( + Uri.parse('${Urls.eventsUrl}/$eventId/participate'), + headers: {'Content-Type': 'application/json'}, + body: jsonEncode({'userId': userId}), + ); + + print('Statut de la réponse: ${response.statusCode}'); + + if (response.statusCode == 200) { + print('Participation réussie'); + return EventModel.fromJson(json.decode(response.body)); + } else { + print('Erreur lors de la participation à l\'événement: ${response.body}'); + throw ServerException(); + } + } + + /// Réagir à un événement. + Future reactToEvent(String eventId, String userId) async { + print('Réaction à l\'événement avec l\'ID: $eventId, utilisateur: $userId'); + + final response = await client.post( + Uri.parse('${Urls.eventsUrl}/$eventId/react'), + headers: {'Content-Type': 'application/json'}, + body: jsonEncode({'userId': userId}), + ); + + print('Statut de la réponse: ${response.statusCode}'); + + if (response.statusCode != 200) { + print('Erreur lors de la réaction à l\'événement: ${response.body}'); + throw ServerException(); + } else { + print('Réaction réussie'); + } + } +} diff --git a/lib/data/datasources/user_remote_data_source.dart b/lib/data/datasources/user_remote_data_source.dart index 447b5df..7cdd341 100644 --- a/lib/data/datasources/user_remote_data_source.dart +++ b/lib/data/datasources/user_remote_data_source.dart @@ -1,6 +1,7 @@ import 'dart:convert'; -import 'package:afterwork/data/models/user_model.dart'; + import 'package:afterwork/core/constants/urls.dart'; +import 'package:afterwork/data/models/user_model.dart'; import 'package:http/http.dart' as http; import '../../core/errors/exceptions.dart'; @@ -10,7 +11,8 @@ class UserRemoteDataSource { UserRemoteDataSource(this.client); - Future authenticateUser(String email, String password) async { + // Authentifier l'utilisateur + Future authenticateUser(String email, String password, String userId) async { if (email.isEmpty || password.isEmpty) { throw Exception('Email ou mot de passe vide'); } @@ -34,8 +36,9 @@ class UserRemoteDataSource { } } + // Récupérer un utilisateur par ID Future getUser(String id) async { - final response = await client.get(Uri.parse('${Urls.baseUrl}/user/$id')); + final response = await client.get(Uri.parse('${Urls.baseUrl}/users/$id')); if (response.statusCode == 200) { return UserModel.fromJson(json.decode(response.body)); @@ -43,4 +46,45 @@ class UserRemoteDataSource { throw ServerException(); } } + + // Créer un nouvel utilisateur + Future createUser(UserModel user) async { + final response = await client.post( + Uri.parse('${Urls.baseUrl}/users'), + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(user.toJson()), + ); + + if (response.statusCode == 201) { + return UserModel.fromJson(json.decode(response.body)); + } else { + throw ServerException(); + } + } + + // Mettre à jour un utilisateur + Future updateUser(UserModel user) async { + final response = await client.put( + Uri.parse('${Urls.baseUrl}/users/${user.userId}'), + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(user.toJson()), + ); + + if (response.statusCode == 200) { + return UserModel.fromJson(json.decode(response.body)); + } else { + throw ServerException(); + } + } + + // Supprimer un utilisateur par ID + Future deleteUser(String id) async { + final response = await client.delete( + Uri.parse('${Urls.baseUrl}/users/$id'), + ); + + if (response.statusCode != 204) { + throw ServerException(); + } + } } diff --git a/lib/data/models/creator_model.dart b/lib/data/models/creator_model.dart new file mode 100644 index 0000000..d633011 --- /dev/null +++ b/lib/data/models/creator_model.dart @@ -0,0 +1,34 @@ +import 'package:afterwork/data/models/user_model.dart'; + +/// Modèle représentant le créateur d'un événement. +class CreatorModel extends UserModel { + CreatorModel({ + required String id, + required String nom, + required String prenoms, + + }) : super( + userId: id, + nom: nom, + prenoms: prenoms, + email: '', // Valeur par défaut vide + motDePasse: '', // Valeur par défaut vide + ); + + factory CreatorModel.fromJson(Map json) { + return CreatorModel( + id: json['id'], + nom: json['nom'], + prenoms: json['prenoms'], + ); + } + + @override + Map toJson() { + return { + 'id': userId, + 'nom': nom, + 'prenoms': prenoms, + }; + } +} diff --git a/lib/data/models/event_model.dart b/lib/data/models/event_model.dart new file mode 100644 index 0000000..d733a9d --- /dev/null +++ b/lib/data/models/event_model.dart @@ -0,0 +1,65 @@ +import 'package:afterwork/data/models/user_model.dart'; + +/// Modèle de données représentant un événement. +class EventModel { + final String id; + final String title; + final String description; + final String date; + final String location; + final String category; + final String link; + final String? imageUrl; + final UserModel creator; + final List participants; + + /// Constructeur pour initialiser toutes les propriétés de l'événement. + EventModel({ + required this.id, + required this.title, + required this.description, + required this.date, + required this.location, + required this.category, + required this.link, + required this.imageUrl, + required this.creator, + required this.participants, + }); + + /// Convertit un objet JSON en `EventModel`. + factory EventModel.fromJson(Map json) { + return EventModel( + id: json['id'], + title: json['title'], + description: json['description'], + date: json['date'], + location: json['location'], + category: json['category'], + link: json['link'], + imageUrl: json['imageUrl'], + creator: UserModel.fromJson(json['creator']), + participants: json['participants'] != null + ? (json['participants'] as List) + .map((user) => UserModel.fromJson(user)) + .toList() + : [], + ); + } + + /// Convertit un `EventModel` en objet JSON. + Map toJson() { + return { + 'id': id, + 'title': title, + 'description': description, + 'date': date, + 'location': location, + 'category': category, + 'link': link, + 'imageUrl': imageUrl, + 'creator': creator.toJson(), + 'participants': participants.map((user) => user.toJson()).toList(), + }; + } +} diff --git a/lib/data/models/participant_model.dart b/lib/data/models/participant_model.dart new file mode 100644 index 0000000..f42a9f7 --- /dev/null +++ b/lib/data/models/participant_model.dart @@ -0,0 +1,34 @@ +import 'package:afterwork/data/models/user_model.dart'; + +/// Modèle représentant un participant à un événement. +class ParticipantModel extends UserModel { + ParticipantModel({ + required String id, + required String nom, + required String prenoms, + }) : super( + userId: id, + nom: nom, + prenoms: prenoms, + email: '', // Valeur par défaut vide + motDePasse: '', // Valeur par défaut vide + ); + + factory ParticipantModel.fromJson(Map json) { + return ParticipantModel( + id: json['id'], + nom: json['nom'], + prenoms: json['prenoms'], + + ); + } + + @override + Map toJson() { + return { + 'id': userId, + 'nom': nom, + 'prenoms': prenoms, + }; + } +} diff --git a/lib/data/models/user_model.dart b/lib/data/models/user_model.dart index a987f32..4c0276e 100644 --- a/lib/data/models/user_model.dart +++ b/lib/data/models/user_model.dart @@ -1,14 +1,14 @@ import 'package:afterwork/domain/entities/user.dart'; -class UserModel extends User { +class UserModel extends User { const UserModel({ - required String id, + required String userId, // Utilisez `id` pour correspondre à l'entité User required String nom, required String prenoms, required String email, required String motDePasse, }) : super( - id: id, + userId: userId, nom: nom, prenoms: prenoms, email: email, @@ -17,7 +17,7 @@ class UserModel extends User { factory UserModel.fromJson(Map json) { return UserModel( - id: json['id'] ?? '', + userId: json['id'] ?? '', nom: json['nom'] ?? '', prenoms: json['prenoms'] ?? '', email: json['email'] ?? '', @@ -27,7 +27,7 @@ class UserModel extends User { Map toJson() { return { - 'id': id, + 'id': userId, // Utilisez `id` pour correspondre à l'entité User 'nom': nom, 'prenoms': prenoms, 'email': email, diff --git a/lib/data/providers/user_provider.dart b/lib/data/providers/user_provider.dart new file mode 100644 index 0000000..6f0a68c --- /dev/null +++ b/lib/data/providers/user_provider.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; + +class UserProvider with ChangeNotifier { + String _userId = ''; + String _userName = ''; + String _userLastName = ''; + + String get userId => _userId; + String get userName => _userName; + String get userLastName => _userLastName; + + void setUser(String id, String name, String lastName) { + _userId = id; + _userName = name; + _userLastName = lastName; + notifyListeners(); + } +} diff --git a/lib/data/repositories/user_repository_impl.dart b/lib/data/repositories/user_repository_impl.dart index 020e840..7888fa2 100644 --- a/lib/data/repositories/user_repository_impl.dart +++ b/lib/data/repositories/user_repository_impl.dart @@ -14,8 +14,8 @@ class UserRepositoryImpl implements UserRepository { return userModel; // Retourne un UserModel qui est un sous-type de User } - Future authenticateUser(String email, String password) async { - UserModel userModel = await remoteDataSource.authenticateUser(email, password); + Future authenticateUser(String email, String password, String userId) async { + UserModel userModel = await remoteDataSource.authenticateUser(email, password, userId); return userModel; // Retourne un UserModel qui est un sous-type de User } } diff --git a/lib/data/services/hash_password.dart b/lib/data/services/hash_password.dart new file mode 100644 index 0000000..1de1c03 --- /dev/null +++ b/lib/data/services/hash_password.dart @@ -0,0 +1,8 @@ +import 'dart:convert'; +import 'package:crypto/crypto.dart'; + +String hashPassword(String password) { + var bytes = utf8.encode(password); // Convertir en bytes + var digest = sha256.convert(bytes); // Hachage SHA-256 + return digest.toString(); // Retourner le hash sous forme de chaîne +} diff --git a/lib/data/services/preferences_helper.dart b/lib/data/services/preferences_helper.dart new file mode 100644 index 0000000..194083c --- /dev/null +++ b/lib/data/services/preferences_helper.dart @@ -0,0 +1,50 @@ +import 'package:shared_preferences/shared_preferences.dart'; + +class PreferencesHelper { + final Future _prefs = SharedPreferences.getInstance(); + + Future setString(String key, String value) async { + final prefs = await _prefs; + await prefs.setString(key, value); + } + + Future getString(String key) async { + final prefs = await _prefs; + return prefs.getString(key); + } + + Future remove(String key) async { + final prefs = await _prefs; + await prefs.remove(key); + } + + Future saveUserId(String userId) async { + await setString('user_id', userId); + } + + Future getUserId() async { + return await getString('user_id'); + } + + Future saveUserName(String userName) async { + await setString('user_name', userName); + } + + Future getUserName() async { + return await getString('user_name'); + } + + Future saveUserLastName(String userLastName) async { + await setString('user_last_name', userLastName); + } + + Future getUserLastName() async { + return await getString('user_last_name'); + } + + Future clearUserInfo() async { + await remove('user_id'); + await remove('user_name'); + await remove('user_last_name'); + } +} diff --git a/lib/data/services/secure_storage.dart b/lib/data/services/secure_storage.dart new file mode 100644 index 0000000..7c61670 --- /dev/null +++ b/lib/data/services/secure_storage.dart @@ -0,0 +1,47 @@ +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; + +class SecureStorage { + final FlutterSecureStorage _storage = const FlutterSecureStorage(); + + Future write(String key, String value) async { + await _storage.write(key: key, value: value); + } + + Future read(String key) async { + return await _storage.read(key: key); + } + + Future delete(String key) async { + await _storage.delete(key: key); + } + + Future saveUserId(String userId) async { + await write('user_id', userId); + } + + Future getUserId() async { + return await read('user_id'); + } + + Future saveUserName(String userName) async { + await write('user_name', userName); + } + + Future getUserName() async { + return await read('user_name'); + } + + Future saveUserLastName(String userLastName) async { + await write('user_last_name', userLastName); + } + + Future getUserLastName() async { + return await read('user_last_name'); + } + + Future deleteUserInfo() async { + await delete('user_id'); + await delete('user_name'); + await delete('user_last_name'); + } +} diff --git a/lib/domain/entities/event.dart b/lib/domain/entities/event.dart new file mode 100644 index 0000000..7e468e8 --- /dev/null +++ b/lib/domain/entities/event.dart @@ -0,0 +1,64 @@ +class EventModel { + final String eventId; + final String title; + final String description; + final String eventDate; + final String location; + final String category; + final String? link; + final String? imageUrl; + final String creatorId; + + EventModel({ + required this.eventId, + required this.title, + required this.description, + required this.eventDate, + required this.location, + required this.category, + this.link, + this.imageUrl, + required this.creatorId, + }); + + // Méthode pour créer un EventModel à partir d'un JSON + factory EventModel.fromJson(Map json) { + return EventModel( + eventId: json['id'], + title: json['title'], + description: json['description'], + eventDate: json['event_date'], + location: json['location'], + category: json['category'], + link: json['link'], + imageUrl: json['imageUrl'], + creatorId: json['creator']['id'], // Assurez-vous que le JSON a ce format + ); + } + + // Méthode pour convertir un EventModel en JSON + Map toJson() { + return { + 'id': eventId, + 'title': title, + 'description': description, + 'event_date': eventDate, + 'location': location, + 'category': category, + 'link': link, + 'imageUrl': imageUrl, + 'creator': {'id': creatorId}, // Structure du JSON pour l'API + }; + } + + // Convertir une liste d'EventModel à partir d'une liste JSON + static List fromJsonList(List jsonList) { + return jsonList.map((json) => EventModel.fromJson(json)).toList(); + } + + // Convertir une liste d'EventModel en JSON + static List> toJsonList(List events) { + return events.map((event) => event.toJson()).toList(); + } +} + diff --git a/lib/domain/entities/user.dart b/lib/domain/entities/user.dart index 5a25f95..788e4d6 100644 --- a/lib/domain/entities/user.dart +++ b/lib/domain/entities/user.dart @@ -1,14 +1,14 @@ import 'package:equatable/equatable.dart'; class User extends Equatable { - final String id; + final String userId; final String nom; final String prenoms; final String email; final String motDePasse; const User({ - required this.id, + required this.userId, required this.nom, required this.prenoms, required this.email, @@ -16,5 +16,5 @@ class User extends Equatable { }); @override - List get props => [id, nom, prenoms, email, motDePasse]; + List get props => [userId, nom, prenoms, email, motDePasse]; } diff --git a/lib/main.dart b/lib/main.dart index b0b7c1b..abbb2d4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,23 +1,74 @@ import 'package:flutter/material.dart'; -import 'config/router.dart'; -import 'core/theme/app_theme.dart'; +import 'package:afterwork/config/router.dart'; +import 'package:afterwork/data/datasources/event_remote_data_source.dart'; +import 'package:afterwork/data/providers/user_provider.dart'; +import 'package:afterwork/data/services/secure_storage.dart'; +import 'package:afterwork/data/services/preferences_helper.dart'; +import 'package:http/http.dart' as http; +import 'package:provider/provider.dart'; -void main() { - runApp(const AfterWorkApp()); +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + + final eventRemoteDataSource = EventRemoteDataSource(http.Client()); + + // Remplacez ici par l'utilisation du stockage sécurisé ou des préférences + final SecureStorage secureStorage = SecureStorage(); + final PreferencesHelper preferencesHelper = PreferencesHelper(); + + String? userId = await secureStorage.getUserId(); + String? userName = await preferencesHelper.getUserName(); + String? userLastName = await preferencesHelper.getUserLastName(); + + // Si les valeurs sont nulles, vous pouvez définir des valeurs par défaut ou gérer autrement + userId ??= 'default_user_id'; // Remplacer par une valeur par défaut si nécessaire + userName ??= 'Default'; + userLastName ??= 'User'; + + runApp(MyApp( + eventRemoteDataSource: eventRemoteDataSource, + userId: userId, + userName: userName, + userLastName: userLastName, + )); } -class AfterWorkApp extends StatelessWidget { - const AfterWorkApp({super.key}); +class MyApp extends StatelessWidget { + final EventRemoteDataSource eventRemoteDataSource; + final String userId; + final String userName; + final String userLastName; + + const MyApp({ + super.key, + required this.eventRemoteDataSource, + required this.userId, + required this.userName, + required this.userLastName, + }); @override Widget build(BuildContext context) { - return MaterialApp( - title: 'AfterWork', - theme: AppTheme.lightTheme, - darkTheme: AppTheme.darkTheme, // Ajout du thème sombre - themeMode: ThemeMode.system, // Choix automatique du thème en fonction du système - onGenerateRoute: AppRouter.generateRoute, - initialRoute: '/', + return MultiProvider( + providers: [ + ChangeNotifierProvider( + create: (_) => UserProvider()..setUser(userId, userName, userLastName), + ), + // Ajouter d'autres providers ici si nécessaire + ], + child: MaterialApp( + title: 'AfterWork', + theme: ThemeData( + primarySwatch: Colors.blue, + ), + onGenerateRoute: AppRouter( + eventRemoteDataSource: eventRemoteDataSource, + userId: userId, + userName: userName, + userLastName: userLastName, + ).generateRoute, + initialRoute: '/', + ), ); } } diff --git a/lib/presentation/screens/dialogs/add_event_dialog.dart b/lib/presentation/screens/dialogs/add_event_dialog.dart index e895438..6932cbb 100644 --- a/lib/presentation/screens/dialogs/add_event_dialog.dart +++ b/lib/presentation/screens/dialogs/add_event_dialog.dart @@ -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 { 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 { 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 { 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 { 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 { 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 { 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 { style: const TextStyle(color: Colors.white), onSaved: (value) { _category = value ?? ''; + print('Catégorie sauvegardée: $_category'); }, ); } @@ -207,6 +238,7 @@ class _AddEventDialogState extends State { 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 { 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 { 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 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 { 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.of([ - 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)), ); } } diff --git a/lib/presentation/screens/event/event_card.dart b/lib/presentation/screens/event/event_card.dart index e727dce..025e421 100644 --- a/lib/presentation/screens/event/event_card.dart +++ b/lib/presentation/screens/event/event_card.dart @@ -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'); + } } diff --git a/lib/presentation/screens/event/event_screen.dart b/lib/presentation/screens/event/event_screen.dart index f04b97e..ebf671e 100644 --- a/lib/presentation/screens/event/event_screen.dart +++ b/lib/presentation/screens/event/event_screen.dart @@ -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>( + 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>( + 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(); - }, - ); - } } diff --git a/lib/presentation/screens/home/home_screen.dart b/lib/presentation/screens/home/home_screen.dart index d1df29d..f1b22f5 100644 --- a/lib/presentation/screens/home/home_screen.dart +++ b/lib/presentation/screens/home/home_screen.dart @@ -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 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 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( - 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 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 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. ); } } diff --git a/lib/presentation/screens/location/location_picker_Screen.dart b/lib/presentation/screens/location/location_picker_Screen.dart new file mode 100644 index 0000000..3e16328 --- /dev/null +++ b/lib/presentation/screens/location/location_picker_Screen.dart @@ -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( + 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); + }, + ), + ); + } +} \ No newline at end of file diff --git a/lib/presentation/screens/login/login_screen.dart b/lib/presentation/screens/login/login_screen.dart index 984daa8..6b66eb3 100644 --- a/lib/presentation/screens/login/login_screen.dart +++ b/lib/presentation/screens/login/login_screen.dart @@ -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 with SingleTickerProviderStateMixin { final _formKey = GlobalKey(); + 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 _buttonScaleAnimation; @@ -41,12 +50,14 @@ class _LoginScreenState extends State 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 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 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 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 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 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 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', diff --git a/lib/presentation/screens/profile/profile_screen.dart b/lib/presentation/screens/profile/profile_screen.dart index 031825f..d0addf4 100644 --- a/lib/presentation/screens/profile/profile_screen.dart +++ b/lib/presentation/screens/profile/profile_screen.dart @@ -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 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), + ); + } } diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index e71a16d..d0e7f79 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,10 @@ #include "generated_plugin_registrant.h" +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); + flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 2e1de87..b29e9ba 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + flutter_secure_storage_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index cccf817..e6a54de 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,10 @@ import FlutterMacOS import Foundation +import flutter_secure_storage_macos +import shared_preferences_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 2856321..2317490 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -49,6 +49,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" + crypto: + dependency: "direct main" + description: + name: crypto + sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27 + url: "https://pub.dev" + source: hosted + version: "3.0.5" csslib: dependency: transitive description: @@ -89,6 +97,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + file: + dependency: transitive + description: + name: file + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" flutter: dependency: "direct main" description: flutter @@ -118,6 +142,54 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.22" + flutter_secure_storage: + dependency: "direct main" + description: + name: flutter_secure_storage + sha256: f2afec1f1762c040a349ea2a588e32f442da5d0db3494a52a929a97c9e550bc5 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + flutter_secure_storage_linux: + dependency: transitive + description: + name: flutter_secure_storage_linux + sha256: "4d91bfc23047422cbcd73ac684bc169859ee766482517c22172c86596bf1464b" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + flutter_secure_storage_macos: + dependency: transitive + description: + name: flutter_secure_storage_macos + sha256: ff0768a6700ea1d9620e03518e2e25eac86a8bd07ca3556e9617bfa5ace4bd00 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + flutter_secure_storage_platform_interface: + dependency: transitive + description: + name: flutter_secure_storage_platform_interface + sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_secure_storage_web: + dependency: transitive + description: + name: flutter_secure_storage_web + sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + flutter_secure_storage_windows: + dependency: transitive + description: + name: flutter_secure_storage_windows + sha256: ca89c8059cf439985aa83c59619b3674c7ef6cc2e86943d169a7369d6a69cab5 + url: "https://pub.dev" + source: hosted + version: "1.1.3" flutter_test: dependency: "direct dev" description: flutter @@ -208,6 +280,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" leak_tracker: dependency: transitive description: @@ -280,6 +360,38 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + url: "https://pub.dev" + source: hosted + version: "3.1.5" plugin_platform_interface: dependency: transitive description: @@ -304,6 +416,62 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" + shared_preferences: + dependency: "direct dev" + description: + name: shared_preferences + sha256: "746e5369a43170c25816cc472ee016d3a66bc13fcf430c0bc41ad7b4b2922051" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "480ba4345773f56acda9abf5f50bd966f581dac5d514e5fc4a18c62976bbba7e" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: c4b35f6cb8f63c147312c054ce7c2254c8066745125264f0c88739c417fc9d9f + url: "https://pub.dev" + source: hosted + version: "2.5.2" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e + url: "https://pub.dev" + source: hosted + version: "2.4.2" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" sky_engine: dependency: transitive description: flutter @@ -397,6 +565,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + url: "https://pub.dev" + source: hosted + version: "1.0.4" sdks: dart: ">=3.5.1 <4.0.0" flutter: ">=3.22.0" diff --git a/pubspec.yaml b/pubspec.yaml index a33fd2d..926b245 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,6 +11,8 @@ dependencies: flutter: sdk: flutter + flutter_secure_storage: ^7.0.1 + crypto: ^3.0.1 google_maps_flutter: ^2.0.10 cupertino_icons: ^1.0.8 @@ -33,6 +35,7 @@ dev_dependencies: flutter_test: sdk: flutter + shared_preferences: ^2.0.13 flutter_lints: ^4.0.0 flutter: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 8b6d468..0c50753 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,9 @@ #include "generated_plugin_registrant.h" +#include void RegisterPlugins(flutter::PluginRegistry* registry) { + FlutterSecureStorageWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index b93c4c3..4fc759c 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + flutter_secure_storage_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST