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

@@ -2,7 +2,8 @@
<application <application
android:label="afterwork" android:label="afterwork"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/ic_launcher"
android:enableOnBackInvokedCallback="true">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"

View File

@@ -5,18 +5,44 @@ import 'package:afterwork/presentation/screens/event/event_screen.dart';
import 'package:afterwork/presentation/screens/story/story_screen.dart'; import 'package:afterwork/presentation/screens/story/story_screen.dart';
import 'package:afterwork/presentation/screens/profile/profile_screen.dart'; import 'package:afterwork/presentation/screens/profile/profile_screen.dart';
import 'package:afterwork/presentation/screens/settings/settings_screen.dart'; import 'package:afterwork/presentation/screens/settings/settings_screen.dart';
import 'package:afterwork/data/datasources/event_remote_data_source.dart';
import '../presentation/reservations/reservations_screen.dart'; import '../presentation/reservations/reservations_screen.dart';
class AppRouter { class AppRouter {
static Route<dynamic> 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<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) { switch (settings.name) {
case '/': case '/':
return MaterialPageRoute(builder: (_) => const LoginScreen()); return MaterialPageRoute(builder: (_) => const LoginScreen());
case '/home': case '/home':
return MaterialPageRoute(builder: (_) => const HomeScreen()); return MaterialPageRoute(
builder: (_) => HomeScreen(
eventRemoteDataSource: eventRemoteDataSource,
userId: userId,
userName: userName,
userLastName: userLastName,
),
);
case '/event': case '/event':
return MaterialPageRoute(builder: (_) => const EventScreen()); return MaterialPageRoute(
builder: (_) => EventScreen(
eventRemoteDataSource: eventRemoteDataSource,
userId: userId,
userName: userName,
userLastName: userLastName,
),
);
case '/story': case '/story':
return MaterialPageRoute(builder: (_) => const StoryScreen()); return MaterialPageRoute(builder: (_) => const StoryScreen());
case '/profile': case '/profile':

View File

@@ -1,6 +1,6 @@
class Urls { 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 login = baseUrl + 'auth/login';
// static const String events = baseUrl + 'events'; static const String eventsUrl = '$baseUrl/events';
// Ajoute d'autres URLs ici // Ajoute d'autres URLs ici
} }

View File

@@ -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<List<EventModel>> 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<dynamic> 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<EventModel> 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<EventModel> 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<EventModel> 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<void> 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<EventModel> 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<void> 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');
}
}
}

View File

@@ -1,6 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'package:afterwork/data/models/user_model.dart';
import 'package:afterwork/core/constants/urls.dart'; import 'package:afterwork/core/constants/urls.dart';
import 'package:afterwork/data/models/user_model.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import '../../core/errors/exceptions.dart'; import '../../core/errors/exceptions.dart';
@@ -10,7 +11,8 @@ class UserRemoteDataSource {
UserRemoteDataSource(this.client); UserRemoteDataSource(this.client);
Future<UserModel> authenticateUser(String email, String password) async { // Authentifier l'utilisateur
Future<UserModel> authenticateUser(String email, String password, String userId) async {
if (email.isEmpty || password.isEmpty) { if (email.isEmpty || password.isEmpty) {
throw Exception('Email ou mot de passe vide'); throw Exception('Email ou mot de passe vide');
} }
@@ -34,8 +36,9 @@ class UserRemoteDataSource {
} }
} }
// Récupérer un utilisateur par ID
Future<UserModel> getUser(String id) async { Future<UserModel> 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) { if (response.statusCode == 200) {
return UserModel.fromJson(json.decode(response.body)); return UserModel.fromJson(json.decode(response.body));
@@ -43,4 +46,45 @@ class UserRemoteDataSource {
throw ServerException(); throw ServerException();
} }
} }
// Créer un nouvel utilisateur
Future<UserModel> 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<UserModel> 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<void> deleteUser(String id) async {
final response = await client.delete(
Uri.parse('${Urls.baseUrl}/users/$id'),
);
if (response.statusCode != 204) {
throw ServerException();
}
}
} }

View File

@@ -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<String, dynamic> json) {
return CreatorModel(
id: json['id'],
nom: json['nom'],
prenoms: json['prenoms'],
);
}
@override
Map<String, dynamic> toJson() {
return {
'id': userId,
'nom': nom,
'prenoms': prenoms,
};
}
}

View File

@@ -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<UserModel> 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<String, dynamic> 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<String, dynamic> 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(),
};
}
}

View File

@@ -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<String, dynamic> json) {
return ParticipantModel(
id: json['id'],
nom: json['nom'],
prenoms: json['prenoms'],
);
}
@override
Map<String, dynamic> toJson() {
return {
'id': userId,
'nom': nom,
'prenoms': prenoms,
};
}
}

View File

@@ -1,14 +1,14 @@
import 'package:afterwork/domain/entities/user.dart'; import 'package:afterwork/domain/entities/user.dart';
class UserModel extends User { class UserModel extends User {
const UserModel({ const UserModel({
required String id, required String userId, // Utilisez `id` pour correspondre à l'entité User
required String nom, required String nom,
required String prenoms, required String prenoms,
required String email, required String email,
required String motDePasse, required String motDePasse,
}) : super( }) : super(
id: id, userId: userId,
nom: nom, nom: nom,
prenoms: prenoms, prenoms: prenoms,
email: email, email: email,
@@ -17,7 +17,7 @@ class UserModel extends User {
factory UserModel.fromJson(Map<String, dynamic> json) { factory UserModel.fromJson(Map<String, dynamic> json) {
return UserModel( return UserModel(
id: json['id'] ?? '', userId: json['id'] ?? '',
nom: json['nom'] ?? '', nom: json['nom'] ?? '',
prenoms: json['prenoms'] ?? '', prenoms: json['prenoms'] ?? '',
email: json['email'] ?? '', email: json['email'] ?? '',
@@ -27,7 +27,7 @@ class UserModel extends User {
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { return {
'id': id, 'id': userId, // Utilisez `id` pour correspondre à l'entité User
'nom': nom, 'nom': nom,
'prenoms': prenoms, 'prenoms': prenoms,
'email': email, 'email': email,

View File

@@ -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();
}
}

View File

@@ -14,8 +14,8 @@ class UserRepositoryImpl implements UserRepository {
return userModel; // Retourne un UserModel qui est un sous-type de User return userModel; // Retourne un UserModel qui est un sous-type de User
} }
Future<User> authenticateUser(String email, String password) async { Future<User> authenticateUser(String email, String password, String userId) async {
UserModel userModel = await remoteDataSource.authenticateUser(email, password); UserModel userModel = await remoteDataSource.authenticateUser(email, password, userId);
return userModel; // Retourne un UserModel qui est un sous-type de User return userModel; // Retourne un UserModel qui est un sous-type de User
} }
} }

View File

@@ -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
}

View File

@@ -0,0 +1,50 @@
import 'package:shared_preferences/shared_preferences.dart';
class PreferencesHelper {
final Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
Future<void> setString(String key, String value) async {
final prefs = await _prefs;
await prefs.setString(key, value);
}
Future<String?> getString(String key) async {
final prefs = await _prefs;
return prefs.getString(key);
}
Future<void> remove(String key) async {
final prefs = await _prefs;
await prefs.remove(key);
}
Future<void> saveUserId(String userId) async {
await setString('user_id', userId);
}
Future<String?> getUserId() async {
return await getString('user_id');
}
Future<void> saveUserName(String userName) async {
await setString('user_name', userName);
}
Future<String?> getUserName() async {
return await getString('user_name');
}
Future<void> saveUserLastName(String userLastName) async {
await setString('user_last_name', userLastName);
}
Future<String?> getUserLastName() async {
return await getString('user_last_name');
}
Future<void> clearUserInfo() async {
await remove('user_id');
await remove('user_name');
await remove('user_last_name');
}
}

View File

@@ -0,0 +1,47 @@
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class SecureStorage {
final FlutterSecureStorage _storage = const FlutterSecureStorage();
Future<void> write(String key, String value) async {
await _storage.write(key: key, value: value);
}
Future<String?> read(String key) async {
return await _storage.read(key: key);
}
Future<void> delete(String key) async {
await _storage.delete(key: key);
}
Future<void> saveUserId(String userId) async {
await write('user_id', userId);
}
Future<String?> getUserId() async {
return await read('user_id');
}
Future<void> saveUserName(String userName) async {
await write('user_name', userName);
}
Future<String?> getUserName() async {
return await read('user_name');
}
Future<void> saveUserLastName(String userLastName) async {
await write('user_last_name', userLastName);
}
Future<String?> getUserLastName() async {
return await read('user_last_name');
}
Future<void> deleteUserInfo() async {
await delete('user_id');
await delete('user_name');
await delete('user_last_name');
}
}

View File

@@ -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<String, dynamic> 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<String, dynamic> 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<EventModel> fromJsonList(List<dynamic> jsonList) {
return jsonList.map((json) => EventModel.fromJson(json)).toList();
}
// Convertir une liste d'EventModel en JSON
static List<Map<String, dynamic>> toJsonList(List<EventModel> events) {
return events.map((event) => event.toJson()).toList();
}
}

View File

@@ -1,14 +1,14 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
class User extends Equatable { class User extends Equatable {
final String id; final String userId;
final String nom; final String nom;
final String prenoms; final String prenoms;
final String email; final String email;
final String motDePasse; final String motDePasse;
const User({ const User({
required this.id, required this.userId,
required this.nom, required this.nom,
required this.prenoms, required this.prenoms,
required this.email, required this.email,
@@ -16,5 +16,5 @@ class User extends Equatable {
}); });
@override @override
List<Object?> get props => [id, nom, prenoms, email, motDePasse]; List<Object?> get props => [userId, nom, prenoms, email, motDePasse];
} }

View File

@@ -1,23 +1,74 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'config/router.dart'; import 'package:afterwork/config/router.dart';
import 'core/theme/app_theme.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() { void main() async {
runApp(const AfterWorkApp()); 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 { class MyApp extends StatelessWidget {
const AfterWorkApp({super.key}); 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MultiProvider(
title: 'AfterWork', providers: [
theme: AppTheme.lightTheme, ChangeNotifierProvider(
darkTheme: AppTheme.darkTheme, // Ajout du thème sombre create: (_) => UserProvider()..setUser(userId, userName, userLastName),
themeMode: ThemeMode.system, // Choix automatique du thème en fonction du système ),
onGenerateRoute: AppRouter.generateRoute, // Ajouter d'autres providers ici si nécessaire
initialRoute: '/', ],
child: MaterialApp(
title: 'AfterWork',
theme: ThemeData(
primarySwatch: Colors.blue,
),
onGenerateRoute: AppRouter(
eventRemoteDataSource: eventRemoteDataSource,
userId: userId,
userName: userName,
userLastName: userLastName,
).generateRoute,
initialRoute: '/',
),
); );
} }
} }

View File

@@ -1,8 +1,26 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:http/http.dart' as http;
import '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 { 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 @override
_AddEventDialogState createState() => _AddEventDialogState(); _AddEventDialogState createState() => _AddEventDialogState();
@@ -14,10 +32,10 @@ class _AddEventDialogState extends State<AddEventDialog> {
String _description = ''; String _description = '';
DateTime? _selectedDate; DateTime? _selectedDate;
String? _imagePath; String? _imagePath;
String _location = ''; String _location = 'Abidjan'; // Par défaut à Cocody, Abidjan
String _category = ''; String _category = '';
String _link = ''; String _link = '';
LatLng? _selectedLatLng; LatLng? _selectedLatLng = const LatLng(5.348722, -3.985038); // Par défaut à Cocody, Abidjan
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -26,29 +44,31 @@ class _AddEventDialogState extends State<AddEventDialog> {
borderRadius: BorderRadius.circular(15.0), borderRadius: BorderRadius.circular(15.0),
), ),
backgroundColor: const Color(0xFF2C2C3E), backgroundColor: const Color(0xFF2C2C3E),
child: Padding( child: SingleChildScrollView(
padding: const EdgeInsets.all(16.0), child: Padding(
child: Form( padding: const EdgeInsets.all(16.0),
key: _formKey, child: Form(
child: Column( key: _formKey,
mainAxisSize: MainAxisSize.min, child: Column(
children: [ mainAxisSize: MainAxisSize.min,
_buildTitleField(), children: [
const SizedBox(height: 10), _buildTitleField(),
_buildDescriptionField(), const SizedBox(height: 10),
const SizedBox(height: 10), _buildDescriptionField(),
_buildDatePicker(), const SizedBox(height: 10),
const SizedBox(height: 10), _buildDatePicker(),
_buildLocationField(context), const SizedBox(height: 10),
const SizedBox(height: 10), _buildLocationField(context),
_buildCategoryField(), const SizedBox(height: 10),
const SizedBox(height: 10), _buildCategoryField(),
_buildImagePicker(), const SizedBox(height: 10),
const SizedBox(height: 10), _buildImagePicker(),
_buildLinkField(), const SizedBox(height: 10),
const SizedBox(height: 20), _buildLinkField(),
_buildSubmitButton(), const SizedBox(height: 20),
], _buildSubmitButton(),
],
),
), ),
), ),
), ),
@@ -71,12 +91,14 @@ class _AddEventDialogState extends State<AddEventDialog> {
style: const TextStyle(color: Colors.white), style: const TextStyle(color: Colors.white),
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
print('Erreur: Titre est vide');
return 'Veuillez entrer un titre'; return 'Veuillez entrer un titre';
} }
return null; return null;
}, },
onSaved: (value) { onSaved: (value) {
_title = value ?? ''; _title = value ?? '';
print('Titre sauvegardé: $_title');
}, },
); );
} }
@@ -98,12 +120,14 @@ class _AddEventDialogState extends State<AddEventDialog> {
maxLines: 3, maxLines: 3,
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
print('Erreur: Description est vide');
return 'Veuillez entrer une description'; return 'Veuillez entrer une description';
} }
return null; return null;
}, },
onSaved: (value) { onSaved: (value) {
_description = value ?? ''; _description = value ?? '';
print('Description sauvegardée: $_description');
}, },
); );
} }
@@ -120,7 +144,10 @@ class _AddEventDialogState extends State<AddEventDialog> {
if (picked != null && picked != _selectedDate) { if (picked != null && picked != _selectedDate) {
setState(() { setState(() {
_selectedDate = picked; _selectedDate = picked;
print('Date sélectionnée: $_selectedDate');
}); });
} else {
print('Date non sélectionnée ou égale à la précédente');
} }
}, },
child: Container( child: Container(
@@ -158,7 +185,10 @@ class _AddEventDialogState extends State<AddEventDialog> {
setState(() { setState(() {
_selectedLatLng = pickedLocation; _selectedLatLng = pickedLocation;
_location = '${pickedLocation.latitude}, ${pickedLocation.longitude}'; _location = '${pickedLocation.latitude}, ${pickedLocation.longitude}';
print('Localisation sélectionnée: $_location');
}); });
} else {
print('Localisation non sélectionnée');
} }
}, },
child: Container( child: Container(
@@ -199,6 +229,7 @@ class _AddEventDialogState extends State<AddEventDialog> {
style: const TextStyle(color: Colors.white), style: const TextStyle(color: Colors.white),
onSaved: (value) { onSaved: (value) {
_category = value ?? ''; _category = value ?? '';
print('Catégorie sauvegardée: $_category');
}, },
); );
} }
@@ -207,6 +238,7 @@ class _AddEventDialogState extends State<AddEventDialog> {
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
// Logique pour sélectionner une image // Logique pour sélectionner une image
print('Image Picker activé');
}, },
child: Container( child: Container(
padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 16.0), padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 16.0),
@@ -218,7 +250,9 @@ class _AddEventDialogState extends State<AddEventDialog> {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( 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), style: const TextStyle(color: Colors.white70),
), ),
const Icon(Icons.image, 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), style: const TextStyle(color: Colors.white),
onSaved: (value) { onSaved: (value) {
_link = value ?? ''; _link = value ?? '';
print('Lien sauvegardé: $_link');
}, },
); );
} }
Widget _buildSubmitButton() { Widget _buildSubmitButton() {
return ElevatedButton( return ElevatedButton(
onPressed: () { onPressed: () async {
if (_formKey.currentState!.validate()) { if (_formKey.currentState!.validate()) {
_formKey.currentState!.save(); _formKey.currentState!.save();
// Logique pour soumettre les données print('Formulaire validé');
Navigator.of(context).pop();
// 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( style: ElevatedButton.styleFrom(
@@ -265,40 +351,8 @@ class _AddEventDialogState extends State<AddEventDialog> {
padding: const EdgeInsets.symmetric(vertical: 12.0), padding: const EdgeInsets.symmetric(vertical: 12.0),
minimumSize: const Size(double.infinity, 40), minimumSize: const Size(double.infinity, 40),
), ),
child: const Text('Ajouter l\'événement', style: TextStyle(color: Colors.white)), 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);
},
),
); );
} }
} }

View File

@@ -1,6 +1,13 @@
import 'package:flutter/material.dart'; 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 { class EventCard extends StatelessWidget {
final String eventId;
final EventRemoteDataSource eventRemoteDataSource;
final String userId;
final String userName;
final String userLastName;
final String profileImage; final String profileImage;
final String name; final String name;
final String datePosted; final String datePosted;
@@ -19,6 +26,11 @@ class EventCard extends StatelessWidget {
const EventCard({ const EventCard({
Key? key, Key? key,
required this.eventId,
required this.eventRemoteDataSource,
required this.userId,
required this.userName,
required this.userLastName,
required this.profileImage, required this.profileImage,
required this.name, required this.name,
required this.datePosted, required this.datePosted,
@@ -33,7 +45,7 @@ class EventCard extends StatelessWidget {
required this.onShare, required this.onShare,
required this.onParticipate, required this.onParticipate,
required this.onCloseEvent, required this.onCloseEvent,
required this.onMoreOptions, required String assetImage, required this.onMoreOptions,
}) : super(key: key); }) : super(key: key);
@override @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() { Widget _buildHeader() {
return Row( return Row(
children: [ children: [
@@ -94,16 +107,17 @@ class EventCard extends StatelessWidget {
), ),
IconButton( IconButton(
icon: const Icon(Icons.more_vert, color: Colors.white), icon: const Icon(Icons.more_vert, color: Colors.white),
onPressed: onMoreOptions, onPressed: _onMoreOptions,
), ),
IconButton( IconButton(
icon: const Icon(Icons.close, color: Colors.white), icon: const Icon(Icons.close, color: Colors.white),
onPressed: onCloseEvent, onPressed: _onCloseEvent,
), ),
], ],
); );
} }
/// Afficher les détails de l'événement.
Widget _buildEventDetails() { Widget _buildEventDetails() {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -125,6 +139,7 @@ class EventCard extends StatelessWidget {
); );
} }
/// Afficher l'image de l'événement.
Widget _buildEventImage() { Widget _buildEventImage() {
return ClipRRect( return ClipRRect(
borderRadius: BorderRadius.circular(10.0), borderRadius: BorderRadius.circular(10.0),
@@ -134,6 +149,7 @@ class EventCard extends StatelessWidget {
width: double.infinity, width: double.infinity,
fit: BoxFit.cover, fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) { errorBuilder: (context, error, stackTrace) {
print('Erreur de chargement de l\'image: $error');
return Image.asset( return Image.asset(
'lib/assets/images/placeholder.png', 'lib/assets/images/placeholder.png',
height: 180, height: 180,
@@ -145,6 +161,7 @@ class EventCard extends StatelessWidget {
); );
} }
/// Afficher les icônes d'interaction (réagir, commenter, partager).
Widget _buildInteractionRow() { Widget _buildInteractionRow() {
return Padding( return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0), padding: const EdgeInsets.symmetric(vertical: 8.0),
@@ -174,6 +191,7 @@ class EventCard extends StatelessWidget {
); );
} }
/// Bouton d'interaction personnalisé.
Widget _buildIconButton({ Widget _buildIconButton({
required IconData icon, required IconData icon,
required String label, required String label,
@@ -192,9 +210,10 @@ class EventCard extends StatelessWidget {
); );
} }
/// Bouton pour participer à l'événement.
Widget _buildParticipateButton() { Widget _buildParticipateButton() {
return ElevatedButton( return ElevatedButton(
onPressed: onParticipate, onPressed: _onParticipate,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF1DBF73), backgroundColor: const Color(0xFF1DBF73),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
@@ -206,4 +225,54 @@ class EventCard extends StatelessWidget {
child: const Text('Participer', style: TextStyle(color: Colors.white)), 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 '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 'event_card.dart';
import '../dialogs/add_event_dialog.dart';
/// Écran principal pour afficher les événements.
class EventScreen extends StatelessWidget { 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( 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), backgroundColor: const Color(0xFF1E1E2C),
actions: [ actions: [
IconButton( IconButton(
icon: const Icon(Icons.add_circle_outline, size: 28, color: Color(0xFF1DBF73)), icon: const Icon(Icons.add_circle_outline, size: 28, color: Color(0xFF1DBF73)),
onPressed: () { onPressed: () async {
_showAddEventDialog(context); 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( body: FutureBuilder<List<EventModel>>(
padding: const EdgeInsets.all(16.0), future: eventRemoteDataSource.getAllEvents(),
itemCount: 10, builder: (context, snapshot) {
itemBuilder: (context, index) { if (snapshot.connectionState == ConnectionState.waiting) {
return EventCard( return const Center(child: CircularProgressIndicator());
profileImage: 'lib/assets/images/profile_picture.png', } else if (snapshot.hasError) {
name: 'Nom Prénom', print('Erreur lors de la récupération des événements: ${snapshot.error}');
datePosted: 'Posté le 24/08/2024', return Center(child: Text('Erreur: ${snapshot.error}'));
eventTitle: 'Titre de l\'événement', } else if (!snapshot.hasData || snapshot.data!.isEmpty) {
eventDescription: 'Description détaillée de l\'événement...', return const Center(child: Text('Aucun événement trouvé.'));
eventImageUrl: 'lib/assets/images/profile_picture.png', }
reactionsCount: 120,
commentsCount: 45, final events = snapshot.data!;
sharesCount: 30, print('Nombre d\'événements récupérés: ${events.length}');
onReact: () {
// Logique pour réagir à l'événement 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), 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/social/social_screen.dart';
import 'package:afterwork/presentation/screens/establishments/establishments_screen.dart'; import 'package:afterwork/presentation/screens/establishments/establishments_screen.dart';
import 'package:afterwork/presentation/screens/home/home_content.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 { 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 @override
_HomeScreenState createState() => _HomeScreenState(); _HomeScreenState createState() => _HomeScreenState();
@@ -18,25 +35,32 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
@override @override
void initState() { void initState() {
super.initState(); super.initState();
// Initialisation du TabController avec 5 onglets.
_tabController = TabController(length: 5, vsync: this); _tabController = TabController(length: 5, vsync: this);
debugPrint('HomeScreen initialisé avec userId: ${widget.userId}, userName: ${widget.userName}, userLastName: ${widget.userLastName}');
} }
@override @override
void dispose() { void dispose() {
// Nettoyage du TabController pour éviter les fuites de mémoire.
_tabController.dispose(); _tabController.dispose();
super.dispose(); super.dispose();
debugPrint('HomeScreen dispose appelé');
} }
/// Gestion des sélections dans le menu contextuel de l'AppBar.
void _onMenuSelected(BuildContext context, String option) { void _onMenuSelected(BuildContext context, String option) {
// Implémente la logique pour chaque option ici
switch (option) { switch (option) {
case 'Publier': case 'Publier':
// Redirige vers la page de publication debugPrint('Option "Publier" sélectionnée');
// Rediriger vers la page de publication.
break; break;
case 'Story': 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; break;
default: default:
debugPrint('Option inconnue sélectionnée: $option');
break; break;
} }
} }
@@ -45,22 +69,25 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
backgroundColor: Colors.black, // Fond noir pour l'AppBar backgroundColor: Colors.black,
elevation: 0, // Enlève l'ombre sous l'AppBar elevation: 0,
leading: Padding( leading: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Image.asset( 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, height: 40,
), ),
), ),
actions: [ actions: [
// Bouton + // Bouton pour ajouter du contenu (Publier, Story).
CircleAvatar( CircleAvatar(
backgroundColor: Colors.white, // Cercle blanc backgroundColor: Colors.white,
radius: 18, // Réduit la taille du bouton radius: 18,
child: PopupMenuButton<String>( child: PopupMenuButton<String>(
onSelected: (value) => _onMenuSelected(context, value), onSelected: (value) {
_onMenuSelected(context, value);
debugPrint('Menu contextuel sélectionné: $value');
},
itemBuilder: (context) => [ itemBuilder: (context) => [
const PopupMenuItem( const PopupMenuItem(
value: 'Publier', value: 'Publier',
@@ -71,39 +98,48 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
child: Text('Story'), child: Text('Story'),
), ),
], ],
icon: const Icon(Icons.add, color: Colors.blueAccent, size: 20), // Icône bleue légèrement plus petite icon: const Icon(Icons.add, color: Colors.blueAccent, size: 20),
color: Colors.white, // Menu contextuel en blanc color: Colors.white,
), ),
), ),
const SizedBox(width: 8), // Réduit l'espacement entre les boutons const SizedBox(width: 8), // Espacement entre les boutons.
// Bouton Recherche
// Bouton Recherche.
CircleAvatar( CircleAvatar(
backgroundColor: Colors.white, backgroundColor: Colors.white,
radius: 18, // Réduit la taille du bouton radius: 18,
child: IconButton( 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: () { 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 const SizedBox(width: 8), // Espacement entre les boutons.
// Bouton Messagerie
// Bouton Messagerie.
CircleAvatar( CircleAvatar(
backgroundColor: Colors.white, backgroundColor: Colors.white,
radius: 18, // Réduit la taille du bouton radius: 18,
child: IconButton( 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: () { 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( bottom: TabBar(
controller: _tabController, controller: _tabController,
indicatorColor: Colors.blueAccent, 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 [ tabs: const [
Tab(icon: Icon(Icons.home), text: 'Accueil'), Tab(icon: Icon(Icons.home), text: 'Accueil'),
Tab(icon: Icon(Icons.event), text: 'Événements'), Tab(icon: Icon(Icons.event), text: 'Événements'),
@@ -115,15 +151,20 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
), ),
body: TabBarView( body: TabBarView(
controller: _tabController, controller: _tabController,
children: const [ children: [
HomeContentScreen(), // Contenu de l'accueil const HomeContentScreen(), // Contenu de l'accueil.
EventScreen(), // Écran des événements EventScreen(
EstablishmentsScreen(), // Écran des établissements eventRemoteDataSource: widget.eventRemoteDataSource,
SocialScreen(), // Écran social userId: widget.userId,
ProfileScreen(), // Écran du profil 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:flutter/material.dart';
import 'package:afterwork/data/datasources/user_remote_data_source.dart'; import 'package:afterwork/data/datasources/user_remote_data_source.dart';
import 'package:afterwork/data/models/user_model.dart'; import 'package:afterwork/data/models/user_model.dart';
import 'package:afterwork/presentation/screens/home/home_screen.dart'; import 'package:afterwork/presentation/screens/home/home_screen.dart';
import 'package:http/http.dart' as http; 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 { class LoginScreen extends StatefulWidget {
const LoginScreen({super.key}); const LoginScreen({super.key});
@@ -13,12 +19,15 @@ class LoginScreen extends StatefulWidget {
class _LoginScreenState extends State<LoginScreen> with SingleTickerProviderStateMixin { class _LoginScreenState extends State<LoginScreen> with SingleTickerProviderStateMixin {
final _formKey = GlobalKey<FormState>(); final _formKey = GlobalKey<FormState>();
String _userId = '';
String _email = ''; String _email = '';
String _password = ''; String _password = '';
bool _isPasswordVisible = false; bool _isPasswordVisible = false;
bool _isSubmitting = false; bool _isSubmitting = false;
final UserRemoteDataSource _userRemoteDataSource = UserRemoteDataSource(http.Client()); final UserRemoteDataSource _userRemoteDataSource = UserRemoteDataSource(http.Client());
final SecureStorage _secureStorage = SecureStorage();
final PreferencesHelper _preferencesHelper = PreferencesHelper();
late AnimationController _controller; late AnimationController _controller;
late Animation<double> _buttonScaleAnimation; late Animation<double> _buttonScaleAnimation;
@@ -41,12 +50,14 @@ class _LoginScreenState extends State<LoginScreen> with SingleTickerProviderStat
super.dispose(); super.dispose();
} }
/// Afficher/Masquer le mot de passe
void _togglePasswordVisibility() { void _togglePasswordVisibility() {
setState(() { setState(() {
_isPasswordVisible = !_isPasswordVisible; _isPasswordVisible = !_isPasswordVisible;
}); });
} }
/// Soumission du formulaire d'authentification
void _submit() async { void _submit() async {
if (_formKey.currentState!.validate()) { if (_formKey.currentState!.validate()) {
setState(() { setState(() {
@@ -54,25 +65,65 @@ class _LoginScreenState extends State<LoginScreen> with SingleTickerProviderStat
}); });
_formKey.currentState!.save(); _formKey.currentState!.save();
try { print("===== DEBUT DE LA SOUMISSION DU FORMULAIRE =====");
UserModel user = await _userRemoteDataSource.authenticateUser(_email, _password); print("Email: $_email");
print('Connexion réussie : ${user.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( Navigator.pushReplacement(
context, 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) { } catch (e) {
print('Erreur lors de la connexion: $e'); print('Erreur lors de la connexion: $e');
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(e.toString())), SnackBar(content: Text('Erreur : ${e.toString()}')),
); );
} finally { } finally {
print('Fin du processus d\'authentification'); // Débogage
setState(() { setState(() {
_isSubmitting = false; _isSubmitting = false;
}); });
} }
} else {
print("===== FORMULAIRE NON VALIDE =====");
} }
} }
@@ -102,7 +153,7 @@ class _LoginScreenState extends State<LoginScreen> with SingleTickerProviderStat
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
// Logo avec légère animation // Logo animé
AnimatedBuilder( AnimatedBuilder(
animation: _controller, animation: _controller,
builder: (context, child) { builder: (context, child) {
@@ -154,15 +205,18 @@ class _LoginScreenState extends State<LoginScreen> with SingleTickerProviderStat
style: const TextStyle(color: Colors.white), style: const TextStyle(color: Colors.white),
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
print("Erreur: Le champ email est vide");
return 'Veuillez entrer votre email'; return 'Veuillez entrer votre email';
} }
if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value)) { if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value)) {
print("Erreur: Le format de l'email est invalide");
return 'Veuillez entrer un email valide'; return 'Veuillez entrer un email valide';
} }
return null; return null;
}, },
onSaved: (value) { 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), const SizedBox(height: 20),
@@ -180,9 +234,8 @@ class _LoginScreenState extends State<LoginScreen> with SingleTickerProviderStat
prefixIcon: const Icon(Icons.lock, color: Colors.white), prefixIcon: const Icon(Icons.lock, color: Colors.white),
suffixIcon: IconButton( suffixIcon: IconButton(
icon: Icon( icon: Icon(
_isPasswordVisible ? Icons.visibility : Icons.visibility_off, _isPasswordVisible ? Icons.visibility : Icons.visibility_off,
color: Colors.white, color: Colors.white),
),
onPressed: _togglePasswordVisibility, onPressed: _togglePasswordVisibility,
), ),
), ),
@@ -190,15 +243,18 @@ class _LoginScreenState extends State<LoginScreen> with SingleTickerProviderStat
style: const TextStyle(color: Colors.white), style: const TextStyle(color: Colors.white),
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
print("Erreur: Le champ mot de passe est vide");
return 'Veuillez entrer votre mot de passe'; return 'Veuillez entrer votre mot de passe';
} }
if (value.length < 6) { 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 'Le mot de passe doit comporter au moins 6 caractères';
} }
return null; return null;
}, },
onSaved: (value) { 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), const SizedBox(height: 20),
@@ -225,6 +281,7 @@ class _LoginScreenState extends State<LoginScreen> with SingleTickerProviderStat
TextButton( TextButton(
onPressed: () { onPressed: () {
// Naviguer vers la page d'inscription // Naviguer vers la page d'inscription
print("Redirection vers la page d'inscription");
}, },
child: const Text( child: const Text(
'Pas encore de compte ? Inscrivez-vous', 'Pas encore de compte ? Inscrivez-vous',

View File

@@ -7,274 +7,388 @@ class ProfileScreen extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Profil'), title: const Text('Profil',
backgroundColor: Colors.black, style: TextStyle(
color: Color(0xFF1DBF73), // Définit la couleur verte du texte
),
),
backgroundColor: const Color(0xFF1E1E2C),
actions: [ actions: [
IconButton( IconButton(
icon: const Icon(Icons.settings), icon: const Icon(Icons.settings, color: Colors.white),
onPressed: () { onPressed: () {
// Naviguer vers la page des paramètres // Naviguer vers la page des paramètres
}, },
), ),
], ],
), ),
body: CustomScrollView( body: ListView(
slivers: [ padding: const EdgeInsets.all(16.0),
SliverList( children: [
delegate: SliverChildListDelegate([ _buildUserInfoCard(),
// Informations de l'Utilisateur const SizedBox(height: 20),
_buildUserInfoSection(context), _buildEditOptionsCard(),
const Divider(), const SizedBox(height: 20),
// Options de Modification _buildStatisticsSectionCard(),
_buildEditOptions(context), const SizedBox(height: 20),
const Divider(), _buildExpandableSectionCard(
// Statistiques Personnelles title: 'Historique',
_buildStatisticsSection(context), icon: Icons.history,
const Divider(), children: [
// Historique _buildAnimatedListTile(
_buildHistorySection(context), icon: Icons.event_note,
const Divider(), label: 'Historique des Événements',
// Préférences et Paramètres onTap: () {
_buildPreferencesSection(context), // Naviguer vers l'historique des événements
const Divider(), },
// Autres Fonctions ),
_buildSupportSection(context), _buildAnimatedListTile(
const Divider(), icon: Icons.history,
// Suppression de Compte label: 'Historique des Publications',
_buildAccountDeletionSection(context), 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) { Widget _buildStatisticsSectionCard() {
return const Column( return Card(
children: [ color: const Color(0xFF292B37),
CircleAvatar( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
radius: 50, child: Padding(
backgroundImage: AssetImage('lib/assets/images/profile_picture.png'), //Photo de profil 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),
);
}
} }

View File

@@ -6,6 +6,10 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) { 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);
} }

View File

@@ -3,6 +3,7 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
flutter_secure_storage_linux
) )
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST

View File

@@ -5,6 +5,10 @@
import FlutterMacOS import FlutterMacOS
import Foundation import Foundation
import flutter_secure_storage_macos
import shared_preferences_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
} }

View File

@@ -49,6 +49,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.18.0" version: "1.18.0"
crypto:
dependency: "direct main"
description:
name: crypto
sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27
url: "https://pub.dev"
source: hosted
version: "3.0.5"
csslib: csslib:
dependency: transitive dependency: transitive
description: description:
@@ -89,6 +97,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.1" 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: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@@ -118,6 +142,54 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.22" 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: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@@ -208,6 +280,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.2" version: "4.0.2"
js:
dependency: transitive
description:
name: js
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
url: "https://pub.dev"
source: hosted
version: "0.6.7"
leak_tracker: leak_tracker:
dependency: transitive dependency: transitive
description: description:
@@ -280,6 +360,38 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.0" 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: plugin_platform_interface:
dependency: transitive dependency: transitive
description: description:
@@ -304,6 +416,62 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.0" 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: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@@ -397,6 +565,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.0" 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: sdks:
dart: ">=3.5.1 <4.0.0" dart: ">=3.5.1 <4.0.0"
flutter: ">=3.22.0" flutter: ">=3.22.0"

View File

@@ -11,6 +11,8 @@ dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
flutter_secure_storage: ^7.0.1
crypto: ^3.0.1
google_maps_flutter: ^2.0.10 google_maps_flutter: ^2.0.10
cupertino_icons: ^1.0.8 cupertino_icons: ^1.0.8
@@ -33,6 +35,7 @@ dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
shared_preferences: ^2.0.13
flutter_lints: ^4.0.0 flutter_lints: ^4.0.0
flutter: flutter:

View File

@@ -6,6 +6,9 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
} }

View File

@@ -3,6 +3,7 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
flutter_secure_storage_windows
) )
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST