refactoring
This commit is contained in:
@@ -9,6 +9,7 @@
|
|||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||||
|
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
|
||||||
|
|
||||||
<!-- Permission to vibrate -->
|
<!-- Permission to vibrate -->
|
||||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
description: This file stores settings for Dart & Flutter DevTools.
|
description: This file stores settings for Dart & Flutter DevTools.
|
||||||
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
|
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
|
||||||
extensions:
|
extensions:
|
||||||
|
- provider: true
|
||||||
BIN
flutter_02.png
BIN
flutter_02.png
Binary file not shown.
|
Before Width: | Height: | Size: 364 KiB |
BIN
flutter_03.png
BIN
flutter_03.png
Binary file not shown.
|
Before Width: | Height: | Size: 365 KiB |
BIN
flutter_04.png
BIN
flutter_04.png
Binary file not shown.
|
Before Width: | Height: | Size: 365 KiB |
141
lib/assets/animations/friend_expanding_card.dart
Normal file
141
lib/assets/animations/friend_expanding_card.dart
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// [FriendExpandingCard] est un widget animé qui s'agrandit pour afficher des options supplémentaires.
|
||||||
|
/// Il permet de voir plus de détails, d'envoyer un message ou de supprimer un ami.
|
||||||
|
class FriendExpandingCard extends StatefulWidget {
|
||||||
|
final String name;
|
||||||
|
final String imageUrl;
|
||||||
|
final String description;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
final VoidCallback onMessageTap;
|
||||||
|
final VoidCallback onRemoveTap;
|
||||||
|
|
||||||
|
const FriendExpandingCard({
|
||||||
|
Key? key,
|
||||||
|
required this.name,
|
||||||
|
required this.imageUrl,
|
||||||
|
required this.description,
|
||||||
|
required this.onTap,
|
||||||
|
required this.onMessageTap,
|
||||||
|
required this.onRemoveTap,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_FriendExpandingCardState createState() => _FriendExpandingCardState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FriendExpandingCardState extends State<FriendExpandingCard> {
|
||||||
|
bool _isExpanded = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: widget.onTap,
|
||||||
|
onLongPress: () {
|
||||||
|
setState(() {
|
||||||
|
_isExpanded = !_isExpanded;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: AnimatedContainer(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
height: _isExpanded ? 200 : 100,
|
||||||
|
margin: const EdgeInsets.symmetric(vertical: 10, horizontal: 5),
|
||||||
|
padding: const EdgeInsets.all(10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey.shade900,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
boxShadow: const [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black26,
|
||||||
|
blurRadius: 10,
|
||||||
|
offset: Offset(2, 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Hero(
|
||||||
|
tag: widget.name,
|
||||||
|
child: CircleAvatar(
|
||||||
|
backgroundImage: NetworkImage(widget.imageUrl),
|
||||||
|
radius: 30,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
widget.name,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
AnimatedOpacity(
|
||||||
|
opacity: _isExpanded ? 1.0 : 0.0,
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
child: Text(
|
||||||
|
widget.description,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white70,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (_isExpanded)
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.close, color: Colors.white54),
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_isExpanded = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (_isExpanded) ...[
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: [
|
||||||
|
ElevatedButton.icon(
|
||||||
|
onPressed: widget.onMessageTap,
|
||||||
|
icon: const Icon(Icons.message, color: Colors.white),
|
||||||
|
label: const Text('Message'),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
iconColor: Colors.green,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ElevatedButton.icon(
|
||||||
|
onPressed: widget.onRemoveTap,
|
||||||
|
icon: const Icon(Icons.delete, color: Colors.red),
|
||||||
|
label: const Text('Remove'),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
iconColor: Colors.redAccent,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
]
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
lib/assets/images/default_avatar.png
Normal file
BIN
lib/assets/images/default_avatar.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 254 KiB |
@@ -8,33 +8,43 @@ import 'package:afterwork/presentation/screens/event/event_screen.dart';
|
|||||||
import 'package:afterwork/data/datasources/event_remote_data_source.dart';
|
import 'package:afterwork/data/datasources/event_remote_data_source.dart';
|
||||||
import '../presentation/reservations/reservations_screen.dart';
|
import '../presentation/reservations/reservations_screen.dart';
|
||||||
|
|
||||||
/// Router personnalisé pour gérer la navigation dans l'application.
|
/// [AppRouter] gère la navigation dans l'application.
|
||||||
/// Les logs permettent de tracer chaque navigation dans la console.
|
/// Chaque navigation est loguée pour assurer une traçabilité dans la console.
|
||||||
class AppRouter {
|
class AppRouter {
|
||||||
final EventRemoteDataSource eventRemoteDataSource;
|
final EventRemoteDataSource eventRemoteDataSource;
|
||||||
final String userId;
|
final String userId;
|
||||||
final String userName;
|
final String userName;
|
||||||
final String userLastName;
|
final String userLastName;
|
||||||
|
|
||||||
/// Initialisation des informations utilisateur et source de données
|
/// Constructeur de [AppRouter] initialisant les informations utilisateur
|
||||||
|
/// et la source de données pour les événements.
|
||||||
|
///
|
||||||
|
/// [eventRemoteDataSource] : Source de données pour les événements.
|
||||||
|
/// [userId], [userName], [userLastName] : Informations de l'utilisateur.
|
||||||
AppRouter({
|
AppRouter({
|
||||||
required this.eventRemoteDataSource,
|
required this.eventRemoteDataSource,
|
||||||
required this.userId,
|
required this.userId,
|
||||||
required this.userName,
|
required this.userName,
|
||||||
required this.userLastName,
|
required this.userLastName,
|
||||||
}) {
|
}) {
|
||||||
print("AppRouter initialisé avec les infos utilisateur : $userId, $userName, $userLastName");
|
// Log d'initialisation avec les informations utilisateur
|
||||||
|
debugPrint("[LOG] AppRouter initialisé avec les infos utilisateur : $userId, $userName, $userLastName");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Génération des routes pour l'application
|
/// Génère une route en fonction du [RouteSettings] fourni.
|
||||||
|
///
|
||||||
|
/// Logue chaque navigation en fonction du nom de la route.
|
||||||
Route<dynamic> generateRoute(RouteSettings settings) {
|
Route<dynamic> generateRoute(RouteSettings settings) {
|
||||||
print("Navigation vers la route : ${settings.name}");
|
// Log de la navigation vers la route
|
||||||
|
debugPrint("[LOG] Navigation vers la route : ${settings.name}");
|
||||||
|
|
||||||
switch (settings.name) {
|
switch (settings.name) {
|
||||||
case '/':
|
case '/':
|
||||||
|
debugPrint("[LOG] Chargement de l'écran de connexion");
|
||||||
return MaterialPageRoute(builder: (_) => const LoginScreen());
|
return MaterialPageRoute(builder: (_) => const LoginScreen());
|
||||||
|
|
||||||
case '/home':
|
case '/home':
|
||||||
|
debugPrint("[LOG] Chargement de l'écran d'accueil avec l'ID utilisateur : $userId");
|
||||||
return MaterialPageRoute(
|
return MaterialPageRoute(
|
||||||
builder: (_) => HomeScreen(
|
builder: (_) => HomeScreen(
|
||||||
eventRemoteDataSource: eventRemoteDataSource,
|
eventRemoteDataSource: eventRemoteDataSource,
|
||||||
@@ -46,6 +56,7 @@ class AppRouter {
|
|||||||
);
|
);
|
||||||
|
|
||||||
case '/event':
|
case '/event':
|
||||||
|
debugPrint("[LOG] Chargement de l'écran d'événement pour l'utilisateur : $userId");
|
||||||
return MaterialPageRoute(
|
return MaterialPageRoute(
|
||||||
builder: (_) => EventScreen(
|
builder: (_) => EventScreen(
|
||||||
userId: userId,
|
userId: userId,
|
||||||
@@ -55,21 +66,28 @@ class AppRouter {
|
|||||||
);
|
);
|
||||||
|
|
||||||
case '/story':
|
case '/story':
|
||||||
|
debugPrint("[LOG] Chargement de l'écran des histoires");
|
||||||
return MaterialPageRoute(builder: (_) => const StoryScreen());
|
return MaterialPageRoute(builder: (_) => const StoryScreen());
|
||||||
|
|
||||||
case '/profile':
|
case '/profile':
|
||||||
|
debugPrint("[LOG] Chargement de l'écran du profil");
|
||||||
return MaterialPageRoute(builder: (_) => const ProfileScreen());
|
return MaterialPageRoute(builder: (_) => const ProfileScreen());
|
||||||
|
|
||||||
case '/settings':
|
case '/settings':
|
||||||
|
debugPrint("[LOG] Chargement de l'écran des paramètres");
|
||||||
return MaterialPageRoute(builder: (_) => const SettingsScreen());
|
return MaterialPageRoute(builder: (_) => const SettingsScreen());
|
||||||
|
|
||||||
case '/reservations':
|
case '/reservations':
|
||||||
|
debugPrint("[LOG] Chargement de l'écran des réservations");
|
||||||
return MaterialPageRoute(builder: (_) => const ReservationsScreen());
|
return MaterialPageRoute(builder: (_) => const ReservationsScreen());
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
debugPrint("[ERROR] Route non trouvée : ${settings.name}");
|
||||||
return MaterialPageRoute(
|
return MaterialPageRoute(
|
||||||
builder: (_) => const Scaffold(
|
builder: (_) => const Scaffold(
|
||||||
body: Center(child: Text('Page non trouvée')),
|
body: Center(
|
||||||
|
child: Text('Page non trouvée'),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
class Urls {
|
class Urls {
|
||||||
static const String baseUrl = 'http://192.168.1.145:8085';
|
static const String baseUrl = 'http://192.168.1.11:8085';
|
||||||
|
|
||||||
// Authentication and Users Endpoints
|
// Authentication and Users Endpoints
|
||||||
static const String authenticateUser = '$baseUrl/users/authenticate';
|
static const String authenticateUser = '$baseUrl/users/authenticate';
|
||||||
|
|||||||
@@ -5,136 +5,181 @@ import 'package:http/http.dart' as http;
|
|||||||
import '../../core/errors/exceptions.dart';
|
import '../../core/errors/exceptions.dart';
|
||||||
|
|
||||||
/// Classe pour gérer les opérations API pour les utilisateurs.
|
/// Classe pour gérer les opérations API pour les utilisateurs.
|
||||||
/// Chaque action est loguée pour faciliter la traçabilité et le débogage.
|
/// Toutes les actions sont loguées pour faciliter la traçabilité et le débogage.
|
||||||
class UserRemoteDataSource {
|
class UserRemoteDataSource {
|
||||||
|
// Client HTTP injecté pour réaliser les appels réseau
|
||||||
final http.Client client;
|
final http.Client client;
|
||||||
|
|
||||||
/// Constructeur avec injection du client HTTP
|
/// Constructeur avec injection du client HTTP
|
||||||
UserRemoteDataSource(this.client);
|
UserRemoteDataSource(this.client);
|
||||||
|
|
||||||
/// Authentifie un utilisateur avec l'email et le mot de passe en clair.
|
/// Authentifie un utilisateur avec l'email et le mot de passe.
|
||||||
/// Si l'authentification réussit, retourne un objet `UserModel`.
|
/// Si l'authentification réussit, retourne un objet `UserModel`.
|
||||||
|
/// Les erreurs sont gérées et toutes les actions sont loguées.
|
||||||
Future<UserModel> authenticateUser(String email, String password) async {
|
Future<UserModel> authenticateUser(String email, String password) async {
|
||||||
print("Tentative d'authentification pour l'email : $email");
|
print("[LOG] Tentative d'authentification pour l'email : $email");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Requête POST avec l'email et le mot de passe en clair
|
// Préparation des données d'authentification à envoyer
|
||||||
|
final Map<String, dynamic> body = {
|
||||||
|
'email': email,
|
||||||
|
'motDePasse': password,
|
||||||
|
};
|
||||||
|
|
||||||
|
print("[DEBUG] Données envoyées pour authentification : $body");
|
||||||
|
|
||||||
|
// Envoi de la requête HTTP POST pour authentifier l'utilisateur
|
||||||
final response = await client.post(
|
final response = await client.post(
|
||||||
Uri.parse('${Urls.baseUrl}/users/authenticate'),
|
Uri.parse('${Urls.baseUrl}/users/authenticate'),
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: jsonEncode({
|
body: jsonEncode(body),
|
||||||
'email': email,
|
|
||||||
'motDePasse': password, // Le mot de passe est envoyé en clair pour le moment
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
print("Réponse du serveur pour l'authentification : ${response.statusCode} - ${response.body}");
|
// Log de la réponse reçue du serveur
|
||||||
|
print("[LOG] Réponse du serveur : ${response.statusCode} - ${response.body}");
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
// Si l'authentification réussit, retourne l'utilisateur
|
final userData = jsonDecode(response.body);
|
||||||
return UserModel.fromJson(jsonDecode(response.body));
|
|
||||||
|
if (userData['userId'] != null && userData['userId'].isNotEmpty) {
|
||||||
|
print("[LOG] Utilisateur authentifié avec succès. ID: ${userData['userId']}");
|
||||||
|
return UserModel.fromJson(userData);
|
||||||
|
} else {
|
||||||
|
print("[ERROR] L'ID utilisateur est manquant dans la réponse.");
|
||||||
|
throw Exception("ID utilisateur manquant.");
|
||||||
|
}
|
||||||
} else if (response.statusCode == 401) {
|
} else if (response.statusCode == 401) {
|
||||||
// Gestion des erreurs d'authentification
|
print("[ERROR] Authentification échouée : Mot de passe incorrect.");
|
||||||
throw UnauthorizedException();
|
throw UnauthorizedException("Mot de passe incorrect.");
|
||||||
} else {
|
} else {
|
||||||
throw ServerException();
|
print("[ERROR] Erreur du serveur. Code : ${response.statusCode}");
|
||||||
|
throw ServerExceptionWithMessage("Erreur inattendue : ${response.body}");
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print("Erreur d'authentification : $e");
|
print("[ERROR] Erreur lors de l'authentification : $e");
|
||||||
throw Exception("Erreur lors de l'authentification : $e");
|
throw Exception("Erreur lors de l'authentification : $e");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Récupère un utilisateur par son identifiant et logue les étapes.
|
/// Récupère un utilisateur par son identifiant.
|
||||||
|
/// Les erreurs et les succès sont logués pour un suivi complet.
|
||||||
Future<UserModel> getUser(String id) async {
|
Future<UserModel> getUser(String id) async {
|
||||||
print("Tentative de récupération de l'utilisateur avec l'ID : $id");
|
print("[LOG] Tentative de récupération de l'utilisateur avec l'ID : $id");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Envoi de la requête GET pour obtenir l'utilisateur par son ID
|
||||||
final response = await client.get(Uri.parse('${Urls.baseUrl}/users/$id'));
|
final response = await client.get(Uri.parse('${Urls.baseUrl}/users/$id'));
|
||||||
print("Réponse du serveur pour getUser : ${response.statusCode} - ${response.body}");
|
print("[LOG] Réponse du serveur pour getUser : ${response.statusCode} - ${response.body}");
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
|
// Utilisateur trouvé, retour de l'objet UserModel
|
||||||
return UserModel.fromJson(json.decode(response.body));
|
return UserModel.fromJson(json.decode(response.body));
|
||||||
} else if (response.statusCode == 404) {
|
}
|
||||||
print("Utilisateur non trouvé.");
|
// Gestion du cas où l'utilisateur n'est pas trouvé
|
||||||
|
else if (response.statusCode == 404) {
|
||||||
|
print("[ERROR] Utilisateur non trouvé.");
|
||||||
throw UserNotFoundException();
|
throw UserNotFoundException();
|
||||||
} else {
|
}
|
||||||
|
// Gestion des autres erreurs serveur
|
||||||
|
else {
|
||||||
|
print("[ERROR] Erreur du serveur lors de la récupération de l'utilisateur.");
|
||||||
throw ServerException();
|
throw ServerException();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print("Erreur lors de la récupération de l'utilisateur : $e");
|
print("[ERROR] Erreur lors de la récupération de l'utilisateur : $e");
|
||||||
throw Exception("Erreur lors de la récupération de l'utilisateur : $e");
|
throw Exception("Erreur lors de la récupération de l'utilisateur : $e");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Crée un nouvel utilisateur et logue les détails de la requête.
|
/// Crée un nouvel utilisateur dans le backend.
|
||||||
|
/// Toutes les actions, succès ou erreurs sont logués pour un suivi précis.
|
||||||
Future<UserModel> createUser(UserModel user) async {
|
Future<UserModel> createUser(UserModel user) async {
|
||||||
print("Création d'un nouvel utilisateur : ${user.toJson()}");
|
print("[LOG] Création d'un nouvel utilisateur : ${user.toJson()}");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Envoi de la requête POST pour créer un nouvel utilisateur
|
||||||
final response = await client.post(
|
final response = await client.post(
|
||||||
Uri.parse('${Urls.baseUrl}/users'),
|
Uri.parse('${Urls.baseUrl}/users'),
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: jsonEncode(user.toJson()),
|
body: jsonEncode(user.toJson()), // Conversion du modèle utilisateur en JSON
|
||||||
);
|
);
|
||||||
print("Réponse du serveur pour createUser : ${response.statusCode} - ${response.body}");
|
print("[LOG] Réponse du serveur pour createUser : ${response.statusCode} - ${response.body}");
|
||||||
|
|
||||||
if (response.statusCode == 201) {
|
if (response.statusCode == 201) {
|
||||||
|
// Utilisateur créé avec succès
|
||||||
return UserModel.fromJson(json.decode(response.body));
|
return UserModel.fromJson(json.decode(response.body));
|
||||||
} else if (response.statusCode == 409) {
|
}
|
||||||
// Gestion des conflits (utilisateur déjà existant)
|
// Gestion des conflits (ex: utilisateur déjà existant)
|
||||||
|
else if (response.statusCode == 409) {
|
||||||
|
print("[ERROR] Conflit lors de la création de l'utilisateur : Utilisateur déjà existant.");
|
||||||
throw ConflictException();
|
throw ConflictException();
|
||||||
} else {
|
}
|
||||||
|
// Gestion des autres erreurs serveur
|
||||||
|
else {
|
||||||
|
print("[ERROR] Erreur du serveur lors de la création de l'utilisateur.");
|
||||||
throw ServerException();
|
throw ServerException();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print("Erreur lors de la création de l'utilisateur : $e");
|
print("[ERROR] Erreur lors de la création de l'utilisateur : $e");
|
||||||
throw Exception("Erreur lors de la création de l'utilisateur : $e");
|
throw Exception("Erreur lors de la création de l'utilisateur : $e");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Met à jour un utilisateur existant et logue les étapes.
|
/// Met à jour un utilisateur existant.
|
||||||
|
/// Chaque étape est loguée pour faciliter le débogage.
|
||||||
Future<UserModel> updateUser(UserModel user) async {
|
Future<UserModel> updateUser(UserModel user) async {
|
||||||
print("Mise à jour de l'utilisateur : ${user.toJson()}");
|
print("[LOG] Mise à jour de l'utilisateur : ${user.toJson()}");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Envoi de la requête PUT pour mettre à jour un utilisateur
|
||||||
final response = await client.put(
|
final response = await client.put(
|
||||||
Uri.parse('${Urls.baseUrl}/users/${user.userId}'),
|
Uri.parse('${Urls.baseUrl}/users/${user.userId}'),
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: jsonEncode(user.toJson()),
|
body: jsonEncode(user.toJson()), // Conversion du modèle utilisateur en JSON
|
||||||
);
|
);
|
||||||
print("Réponse du serveur pour updateUser : ${response.statusCode} - ${response.body}");
|
print("[LOG] Réponse du serveur pour updateUser : ${response.statusCode} - ${response.body}");
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
|
// Mise à jour réussie
|
||||||
return UserModel.fromJson(json.decode(response.body));
|
return UserModel.fromJson(json.decode(response.body));
|
||||||
} else if (response.statusCode == 404) {
|
}
|
||||||
// Gestion des cas où l'utilisateur n'est pas trouvé
|
// Gestion du cas où l'utilisateur n'est pas trouvé
|
||||||
|
else if (response.statusCode == 404) {
|
||||||
|
print("[ERROR] Utilisateur non trouvé.");
|
||||||
throw UserNotFoundException();
|
throw UserNotFoundException();
|
||||||
} else {
|
}
|
||||||
|
// Gestion des autres erreurs serveur
|
||||||
|
else {
|
||||||
|
print("[ERROR] Erreur du serveur lors de la mise à jour de l'utilisateur.");
|
||||||
throw ServerException();
|
throw ServerException();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print("Erreur lors de la mise à jour de l'utilisateur : $e");
|
print("[ERROR] Erreur lors de la mise à jour de l'utilisateur : $e");
|
||||||
throw Exception("Erreur lors de la mise à jour de l'utilisateur : $e");
|
throw Exception("Erreur lors de la mise à jour de l'utilisateur : $e");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Supprime un utilisateur et logue chaque étape.
|
/// Supprime un utilisateur par son identifiant.
|
||||||
|
/// Les erreurs et succès sont logués pour garantir un suivi complet.
|
||||||
Future<void> deleteUser(String id) async {
|
Future<void> deleteUser(String id) async {
|
||||||
print("Tentative de suppression de l'utilisateur avec l'ID : $id");
|
print("[LOG] Tentative de suppression de l'utilisateur avec l'ID : $id");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Envoi de la requête DELETE pour supprimer un utilisateur
|
||||||
final response = await client.delete(Uri.parse('${Urls.baseUrl}/users/$id'));
|
final response = await client.delete(Uri.parse('${Urls.baseUrl}/users/$id'));
|
||||||
print("Réponse du serveur pour deleteUser : ${response.statusCode} - ${response.body}");
|
print("[LOG] Réponse du serveur pour deleteUser : ${response.statusCode} - ${response.body}");
|
||||||
|
|
||||||
if (response.statusCode != 204) {
|
// Vérification du succès de la suppression
|
||||||
print("Erreur lors de la suppression de l'utilisateur.");
|
if (response.statusCode == 204) {
|
||||||
|
print("[LOG] Utilisateur supprimé avec succès.");
|
||||||
|
}
|
||||||
|
// Gestion des autres erreurs serveur
|
||||||
|
else {
|
||||||
|
print("[ERROR] Erreur du serveur lors de la suppression de l'utilisateur.");
|
||||||
throw ServerException();
|
throw ServerException();
|
||||||
} else {
|
|
||||||
print("Utilisateur supprimé avec succès.");
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print("Erreur lors de la suppression de l'utilisateur : $e");
|
print("[ERROR] Erreur lors de la suppression de l'utilisateur : $e");
|
||||||
throw Exception("Erreur lors de la suppression de l'utilisateur : $e");
|
throw Exception("Erreur lors de la suppression de l'utilisateur : $e");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
23
lib/data/models/social_post_model.dart
Normal file
23
lib/data/models/social_post_model.dart
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
class SocialPost {
|
||||||
|
final String userName;
|
||||||
|
final String userImage;
|
||||||
|
final String postText;
|
||||||
|
final String postImage;
|
||||||
|
final int likes;
|
||||||
|
final int comments;
|
||||||
|
final int shares;
|
||||||
|
final List<String> badges; // Gamification badges
|
||||||
|
final List<String> tags; // Ajout de tags pour personnalisation des posts
|
||||||
|
|
||||||
|
SocialPost({
|
||||||
|
required this.userName,
|
||||||
|
required this.userImage,
|
||||||
|
required this.postText,
|
||||||
|
required this.postImage,
|
||||||
|
required this.likes,
|
||||||
|
required this.comments,
|
||||||
|
required this.shares,
|
||||||
|
required this.badges,
|
||||||
|
this.tags = const [],
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -20,7 +20,7 @@ class UserModel extends User {
|
|||||||
/// Factory pour créer un `UserModel` à partir d'un JSON reçu depuis l'API.
|
/// Factory pour créer un `UserModel` à partir d'un JSON reçu depuis l'API.
|
||||||
factory UserModel.fromJson(Map<String, dynamic> json) {
|
factory UserModel.fromJson(Map<String, dynamic> json) {
|
||||||
return UserModel(
|
return UserModel(
|
||||||
userId: json['id'] ?? '',
|
userId: json['userId'] ?? '',
|
||||||
nom: json['nom'] ?? 'Inconnu',
|
nom: json['nom'] ?? 'Inconnu',
|
||||||
prenoms: json['prenoms'] ?? 'Inconnu',
|
prenoms: json['prenoms'] ?? 'Inconnu',
|
||||||
email: json['email'] ?? 'inconnu@example.com',
|
email: json['email'] ?? 'inconnu@example.com',
|
||||||
|
|||||||
159
lib/data/providers/friends_provider.dart
Normal file
159
lib/data/providers/friends_provider.dart
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:logger/logger.dart';
|
||||||
|
import '../../domain/entities/friend.dart';
|
||||||
|
import '../../data/repositories/friends_repository_impl.dart';
|
||||||
|
|
||||||
|
/// [FriendsProvider] est un `ChangeNotifier` qui gère la logique de gestion des amis.
|
||||||
|
/// Il utilise [FriendsRepositoryImpl] pour interagir avec l'API et assure la gestion des états,
|
||||||
|
/// comme le chargement, la pagination et les erreurs éventuelles.
|
||||||
|
class FriendsProvider with ChangeNotifier {
|
||||||
|
final FriendsRepositoryImpl friendsRepository;
|
||||||
|
final Logger _logger = Logger(); // Logger pour suivre toutes les actions.
|
||||||
|
|
||||||
|
// Liste privée des amis récupérée depuis l'API
|
||||||
|
List<Friend> _friendsList = [];
|
||||||
|
bool _isLoading = false; // Indique si une opération de chargement est en cours
|
||||||
|
bool _hasMore = true; // Indique s'il reste des amis à charger
|
||||||
|
int _currentPage = 0;
|
||||||
|
final int _friendsPerPage = 10; // Nombre d'amis par page pour la pagination
|
||||||
|
|
||||||
|
/// Constructeur de [FriendsProvider] qui requiert une instance de [FriendsRepositoryImpl].
|
||||||
|
FriendsProvider({required this.friendsRepository});
|
||||||
|
|
||||||
|
// Getters pour accéder aux états depuis l'interface utilisateur
|
||||||
|
bool get isLoading => _isLoading;
|
||||||
|
bool get hasMore => _hasMore;
|
||||||
|
List<Friend> get friendsList => _friendsList;
|
||||||
|
|
||||||
|
/// Récupère la liste paginée des amis pour un utilisateur donné.
|
||||||
|
///
|
||||||
|
/// [userId] : L'identifiant unique de l'utilisateur.
|
||||||
|
/// [loadMore] : Indique s'il s'agit d'une demande de chargement supplémentaire pour la pagination.
|
||||||
|
///
|
||||||
|
/// En cas d'erreur, logue l'exception et gère l'état `isLoading`.
|
||||||
|
Future<void> fetchFriends(String userId, {bool loadMore = false}) async {
|
||||||
|
if (_isLoading) {
|
||||||
|
_logger.w('[LOG] Chargement déjà en cours, annulation de la nouvelle demande.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isLoading = true;
|
||||||
|
notifyListeners();
|
||||||
|
_logger.i('[LOG] Début du chargement des amis.');
|
||||||
|
|
||||||
|
if (!loadMore) {
|
||||||
|
// Réinitialisation de la liste et de la pagination si ce n'est pas un chargement supplémentaire
|
||||||
|
_friendsList = [];
|
||||||
|
_currentPage = 0;
|
||||||
|
_hasMore = true;
|
||||||
|
_logger.i('[LOG] Réinitialisation de la pagination et de la liste des amis.');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
_logger.i('[LOG] Chargement de la page $_currentPage des amis pour l\'utilisateur $userId.');
|
||||||
|
final newFriends = await friendsRepository.fetchFriends(userId, _currentPage, _friendsPerPage);
|
||||||
|
|
||||||
|
if (newFriends.isEmpty) {
|
||||||
|
_hasMore = false;
|
||||||
|
_logger.i('[LOG] Fin de liste atteinte, plus d\'amis à charger.');
|
||||||
|
} else {
|
||||||
|
_friendsList.addAll(newFriends);
|
||||||
|
_currentPage++;
|
||||||
|
_logger.i('[LOG] Amis ajoutés à la liste. Page actuelle : $_currentPage');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
_logger.e('[ERROR] Erreur lors de la récupération des amis : $e');
|
||||||
|
} finally {
|
||||||
|
_isLoading = false;
|
||||||
|
_logger.i('[LOG] Fin du chargement des amis.');
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Supprime un ami dans l'API et met à jour la liste localement.
|
||||||
|
///
|
||||||
|
/// [friendId] : Identifiant unique de l'ami à supprimer.
|
||||||
|
///
|
||||||
|
/// Loggue chaque étape pour assurer un suivi précis de l'opération.
|
||||||
|
Future<void> removeFriend(String friendId) async {
|
||||||
|
try {
|
||||||
|
_logger.i('[LOG] Tentative de suppression de l\'ami avec l\'ID : $friendId');
|
||||||
|
await friendsRepository.removeFriend(friendId);
|
||||||
|
_friendsList.removeWhere((friend) => friend.friendId == friendId);
|
||||||
|
_logger.i('[LOG] Ami supprimé localement avec succès : $friendId');
|
||||||
|
} catch (e) {
|
||||||
|
_logger.e('[ERROR] Erreur lors de la suppression de l\'ami : $e');
|
||||||
|
} finally {
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Récupère les détails d'un ami via l'API.
|
||||||
|
///
|
||||||
|
/// [userId] : L'identifiant de l'utilisateur connecté.
|
||||||
|
/// [friendId] : Identifiant unique de l'ami.
|
||||||
|
///
|
||||||
|
/// Retourne un `Future<Friend?>` contenant les détails ou `null` en cas d'erreur.
|
||||||
|
Future<Friend?> fetchFriendDetails(String userId, String friendId) async {
|
||||||
|
try {
|
||||||
|
_logger.i('[LOG] Tentative de récupération des détails de l\'ami avec l\'ID : $friendId');
|
||||||
|
final friendDetails = await friendsRepository.getFriendDetails(friendId, userId);
|
||||||
|
|
||||||
|
if (friendDetails != null) {
|
||||||
|
_logger.i('[LOG] Détails de l\'ami récupérés avec succès : ${friendDetails.friendId}');
|
||||||
|
} else {
|
||||||
|
_logger.w('[LOG] Détails de l\'ami introuvables pour l\'ID : $friendId');
|
||||||
|
}
|
||||||
|
|
||||||
|
return friendDetails;
|
||||||
|
} catch (e) {
|
||||||
|
_logger.e('[ERROR] Exception lors de la récupération des détails de l\'ami : $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convertit un statut sous forme de chaîne en [FriendStatus].
|
||||||
|
///
|
||||||
|
/// [status] : Le statut sous forme de chaîne.
|
||||||
|
///
|
||||||
|
/// Retourne un [FriendStatus] correspondant, ou `FriendStatus.unknown` si non reconnu.
|
||||||
|
FriendStatus _convertToFriendStatus(String status) {
|
||||||
|
switch (status.toLowerCase()) {
|
||||||
|
case 'pending':
|
||||||
|
return FriendStatus.pending;
|
||||||
|
case 'accepted':
|
||||||
|
return FriendStatus.accepted;
|
||||||
|
case 'blocked':
|
||||||
|
return FriendStatus.blocked;
|
||||||
|
default:
|
||||||
|
return FriendStatus.unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Met à jour le statut d'un ami (par exemple : accepter, bloquer).
|
||||||
|
///
|
||||||
|
/// [friendId] : Identifiant unique de l'ami.
|
||||||
|
/// [status] : Nouveau statut pour l'ami sous forme de chaîne de caractères.
|
||||||
|
///
|
||||||
|
/// Loggue l'action, convertit le statut en `FriendStatus`, et met à jour la liste localement.
|
||||||
|
Future<void> updateFriendStatus(String friendId, String status) async {
|
||||||
|
try {
|
||||||
|
_logger.i('[LOG] Tentative de mise à jour du statut de l\'ami avec l\'ID : $friendId');
|
||||||
|
|
||||||
|
// Conversion du `String` en `FriendStatus` pour l'update locale
|
||||||
|
final friendStatus = _convertToFriendStatus(status);
|
||||||
|
await friendsRepository.updateFriendStatus(friendId, status);
|
||||||
|
|
||||||
|
// Mise à jour locale de la liste pour afficher le changement de statut
|
||||||
|
final friendIndex = _friendsList.indexWhere((friend) => friend.friendId == friendId);
|
||||||
|
if (friendIndex != -1) {
|
||||||
|
_friendsList[friendIndex] = _friendsList[friendIndex].copyWith(status: friendStatus);
|
||||||
|
_logger.i('[LOG] Statut de l\'ami mis à jour localement pour l\'ID : $friendId');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
_logger.e('[ERROR] Erreur lors de la mise à jour du statut de l\'ami : $e');
|
||||||
|
} finally {
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +1,53 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// [UserProvider] est un `ChangeNotifier` qui gère les informations de l'utilisateur.
|
||||||
|
/// Toutes les modifications et actions sont loguées pour assurer une traçabilité complète dans le terminal.
|
||||||
class UserProvider with ChangeNotifier {
|
class UserProvider with ChangeNotifier {
|
||||||
String _userId = '';
|
String _userId = '';
|
||||||
String _userName = '';
|
String _userName = '';
|
||||||
String _userLastName = '';
|
String _userLastName = '';
|
||||||
|
|
||||||
|
/// Getter pour l'ID de l'utilisateur
|
||||||
String get userId => _userId;
|
String get userId => _userId;
|
||||||
|
|
||||||
|
/// Getter pour le nom de l'utilisateur
|
||||||
String get userName => _userName;
|
String get userName => _userName;
|
||||||
|
|
||||||
|
/// Getter pour le prénom de l'utilisateur
|
||||||
String get userLastName => _userLastName;
|
String get userLastName => _userLastName;
|
||||||
|
|
||||||
|
/// Méthode pour définir les informations de l'utilisateur.
|
||||||
|
/// Logue les informations fournies et notifie les listeners des changements.
|
||||||
|
///
|
||||||
|
/// [id] : L'ID de l'utilisateur.
|
||||||
|
/// [name] : Le nom de l'utilisateur.
|
||||||
|
/// [lastName] : Le prénom de l'utilisateur.
|
||||||
void setUser(String id, String name, String lastName) {
|
void setUser(String id, String name, String lastName) {
|
||||||
|
debugPrint("[LOG] Tentative de définition des informations de l'utilisateur : ID = $id, Nom = $name, Prénom = $lastName");
|
||||||
|
|
||||||
_userId = id;
|
_userId = id;
|
||||||
_userName = name;
|
_userName = name;
|
||||||
_userLastName = lastName;
|
_userLastName = lastName;
|
||||||
|
|
||||||
|
debugPrint("[LOG] Informations utilisateur définies : ID = $_userId, Nom = $_userName, Prénom = $_userLastName");
|
||||||
|
|
||||||
|
// Notifie les widgets écoutant ce provider qu'une modification a eu lieu
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Méthode pour réinitialiser les informations de l'utilisateur.
|
||||||
|
/// Les valeurs sont loguées avant et après la réinitialisation.
|
||||||
|
void resetUser() {
|
||||||
|
debugPrint("[LOG] Réinitialisation des informations de l'utilisateur.");
|
||||||
|
debugPrint("[LOG] Valeurs avant réinitialisation : ID = $_userId, Nom = $_userName, Prénom = $_userLastName");
|
||||||
|
|
||||||
|
_userId = '';
|
||||||
|
_userName = '';
|
||||||
|
_userLastName = '';
|
||||||
|
|
||||||
|
debugPrint("[LOG] Informations utilisateur réinitialisées : ID = $_userId, Nom = $_userName, Prénom = $_userLastName");
|
||||||
|
|
||||||
|
// Notifie les widgets écoutant ce provider que l'utilisateur a été réinitialisé
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
48
lib/data/repositories/friends_repository.dart
Normal file
48
lib/data/repositories/friends_repository.dart
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import 'package:afterwork/domain/entities/friend.dart';
|
||||||
|
|
||||||
|
/// Interface [FriendsRepository] définissant les méthodes pour gérer les amis.
|
||||||
|
/// Cette interface permet de séparer la logique métier des appels API et de la gestion des données.
|
||||||
|
/// Elle est implémentée par [FriendsRepositoryImpl], qui contient les détails d'implémentation.
|
||||||
|
abstract class FriendsRepository {
|
||||||
|
|
||||||
|
/// Récupère la liste paginée des amis pour un utilisateur donné via l'API.
|
||||||
|
///
|
||||||
|
/// [userId] : Identifiant unique de l'utilisateur pour lequel récupérer la liste d'amis.
|
||||||
|
/// [currentPage] : Indique la page actuelle pour la pagination.
|
||||||
|
/// [friendsPerPage] : Nombre d'amis à récupérer par page.
|
||||||
|
///
|
||||||
|
/// Retourne une liste d'objets [Friend] correspondant aux amis de l'utilisateur.
|
||||||
|
/// En cas d'échec, une liste vide peut être retournée par l'implémentation.
|
||||||
|
Future<List<Friend>> fetchFriends(String userId, int currentPage, int friendsPerPage);
|
||||||
|
|
||||||
|
/// Envoie une demande pour ajouter un nouvel ami via l'API.
|
||||||
|
///
|
||||||
|
/// [friend] : Objet [Friend] représentant l'ami à ajouter.
|
||||||
|
///
|
||||||
|
/// Retourne un `Future<void>`. En cas d'erreur, l'implémentation peut lancer une exception.
|
||||||
|
Future<void> addFriend(Friend friend);
|
||||||
|
|
||||||
|
/// Supprime un ami existant via l'API.
|
||||||
|
///
|
||||||
|
/// [friendId] : Identifiant unique de l'ami à supprimer.
|
||||||
|
///
|
||||||
|
/// Retourne un `Future<void>`. En cas d'erreur, l'implémentation peut lancer une exception.
|
||||||
|
Future<void> removeFriend(String friendId);
|
||||||
|
|
||||||
|
/// Récupère les détails d'un ami en utilisant son identifiant `friendId`.
|
||||||
|
///
|
||||||
|
/// [userId] : Identifiant unique de l'utilisateur connecté (facultatif selon le contexte).
|
||||||
|
/// [friendId] : Identifiant unique de l'ami pour lequel récupérer les détails.
|
||||||
|
///
|
||||||
|
/// Retourne un `Future<Friend?>` contenant les informations de l'ami si trouvées,
|
||||||
|
/// ou `null` si aucun ami correspondant n'est trouvé ou en cas d'échec.
|
||||||
|
Future<Friend?> getFriendDetails(String friendId, String userId);
|
||||||
|
|
||||||
|
/// Met à jour le statut d'un ami dans le système (par exemple, accepter, bloquer).
|
||||||
|
///
|
||||||
|
/// [friendId] : Identifiant unique de l'ami.
|
||||||
|
/// [status] : Nouveau statut de l'ami sous forme de chaîne de caractères.
|
||||||
|
///
|
||||||
|
/// Retourne un `Future<void>`. En cas d'erreur, l'implémentation peut lancer une exception.
|
||||||
|
Future<void> updateFriendStatus(String friendId, String status);
|
||||||
|
}
|
||||||
175
lib/data/repositories/friends_repository_impl.dart
Normal file
175
lib/data/repositories/friends_repository_impl.dart
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:logger/logger.dart';
|
||||||
|
import '../../core/constants/urls.dart';
|
||||||
|
import '../../domain/entities/friend.dart';
|
||||||
|
import 'friends_repository.dart';
|
||||||
|
|
||||||
|
/// Implémentation de [FriendsRepository] pour gérer les appels API relatifs aux amis.
|
||||||
|
/// Chaque action est loguée pour une traçabilité complète et une gestion des erreurs avancée.
|
||||||
|
class FriendsRepositoryImpl implements FriendsRepository {
|
||||||
|
final http.Client client;
|
||||||
|
final Logger _logger = Logger(); // Logger pour suivre toutes les actions.
|
||||||
|
|
||||||
|
FriendsRepositoryImpl({required this.client});
|
||||||
|
|
||||||
|
/// Récupère la liste paginée des amis pour un utilisateur donné via l'API.
|
||||||
|
///
|
||||||
|
/// [userId] : Identifiant unique de l'utilisateur.
|
||||||
|
/// [page] : Page actuelle pour la pagination.
|
||||||
|
/// [size] : Nombre d'amis par page.
|
||||||
|
///
|
||||||
|
/// Retourne une liste d'objets [Friend] ou une liste vide en cas d'erreur.
|
||||||
|
@override
|
||||||
|
Future<List<Friend>> fetchFriends(String userId, int page, int size) async {
|
||||||
|
try {
|
||||||
|
_logger.i("[LOG] Chargement des amis pour l'utilisateur : $userId, page : $page, taille : $size");
|
||||||
|
|
||||||
|
final uri = Uri.parse('${Urls.baseUrl}/friends/list/$userId?page=$page&size=$size');
|
||||||
|
_logger.d('[LOG] URL appelée : $uri');
|
||||||
|
|
||||||
|
final response = await client.get(uri);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
_logger.i("[LOG] Liste des amis récupérée avec succès.");
|
||||||
|
|
||||||
|
final List<dynamic> friendsJson = json.decode(response.body);
|
||||||
|
_logger.i("[LOG] Nombre d'amis récupérés : ${friendsJson.length}");
|
||||||
|
|
||||||
|
return friendsJson.map((json) => Friend.fromJson(json as Map<String, dynamic>)).toList();
|
||||||
|
} else {
|
||||||
|
_logger.e("[ERROR] Échec de la récupération des amis. Code HTTP : ${response.statusCode}");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
_logger.e("[ERROR] Exception lors de la récupération des amis : $e");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Envoie une demande pour ajouter un nouvel ami via l'API.
|
||||||
|
///
|
||||||
|
/// [friend] : Objet [Friend] représentant l'ami à ajouter.
|
||||||
|
///
|
||||||
|
/// Loggue chaque étape et lève une exception en cas d'erreur.
|
||||||
|
@override
|
||||||
|
Future<void> addFriend(Friend friend) async {
|
||||||
|
try {
|
||||||
|
_logger.i("[LOG] Tentative d'ajout de l'ami : ${friend.firstName} ${friend.lastName}");
|
||||||
|
|
||||||
|
final uri = Uri.parse('${Urls.baseUrl}/friends/send');
|
||||||
|
final response = await client.post(
|
||||||
|
uri,
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: json.encode(friend.toJson()),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
_logger.i("[LOG] Ami ajouté avec succès : ${friend.friendId}");
|
||||||
|
} else {
|
||||||
|
_logger.e("[ERROR] Échec lors de l'ajout de l'ami. Code HTTP : ${response.statusCode}");
|
||||||
|
throw Exception("Erreur lors de l'ajout de l'ami");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
_logger.e("[ERROR] Exception lors de l'ajout de l'ami : $e");
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Supprime un ami existant via l'API.
|
||||||
|
///
|
||||||
|
/// [friendId] : Identifiant unique de l'ami à supprimer.
|
||||||
|
///
|
||||||
|
/// Loggue l'action et lève une exception en cas d'erreur.
|
||||||
|
@override
|
||||||
|
Future<void> removeFriend(String friendId) async {
|
||||||
|
try {
|
||||||
|
_logger.i("[LOG] Tentative de suppression de l'ami avec l'ID : $friendId");
|
||||||
|
|
||||||
|
final uri = Uri.parse('${Urls.baseUrl}/friends/$friendId');
|
||||||
|
final response = await client.delete(uri);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
_logger.i("[LOG] Ami supprimé avec succès : $friendId");
|
||||||
|
} else {
|
||||||
|
_logger.e("[ERROR] Échec lors de la suppression de l'ami. Code HTTP : ${response.statusCode}");
|
||||||
|
throw Exception("Erreur lors de la suppression de l'ami");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
_logger.e("[ERROR] Exception lors de la suppression de l'ami : $e");
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Récupère les détails d'un ami en utilisant son identifiant `friendId`.
|
||||||
|
///
|
||||||
|
/// [friendId] : Identifiant unique de l'ami.
|
||||||
|
/// [userId] : Identifiant unique de l'utilisateur connecté.
|
||||||
|
///
|
||||||
|
/// Retourne un `Future<Friend?>` avec les informations de l'ami ou `null` en cas d'échec.
|
||||||
|
@override
|
||||||
|
Future<Friend?> getFriendDetails(String friendId, String userId) async {
|
||||||
|
try {
|
||||||
|
_logger.i("[LOG] Récupération des détails de l'ami avec ID : $friendId pour l'utilisateur : $userId");
|
||||||
|
|
||||||
|
final uri = Uri.parse('${Urls.baseUrl}/friends/details');
|
||||||
|
_logger.d("[LOG] URL pour les détails de l'ami : $uri");
|
||||||
|
|
||||||
|
final headers = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
};
|
||||||
|
|
||||||
|
final body = jsonEncode({
|
||||||
|
'friendId': friendId,
|
||||||
|
'userId': userId,
|
||||||
|
});
|
||||||
|
|
||||||
|
final response = await client.post(uri, headers: headers, body: body);
|
||||||
|
_logger.d("[LOG] Réponse de l'API : ${response.body}");
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final friendJson = json.decode(response.body);
|
||||||
|
_logger.i("[LOG] Détails de l'ami récupérés : $friendJson");
|
||||||
|
return Friend.fromJson(friendJson);
|
||||||
|
} else {
|
||||||
|
_logger.e("[ERROR] Échec de la récupération des détails. Code HTTP : ${response.statusCode}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
_logger.e("[ERROR] Exception lors de la récupération des détails de l'ami : $e");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Met à jour le statut d'un ami (par exemple, "accepté", "bloqué").
|
||||||
|
///
|
||||||
|
/// [friendId] : Identifiant unique de l'ami.
|
||||||
|
/// [status] : Nouveau statut sous forme de chaîne de caractères.
|
||||||
|
///
|
||||||
|
/// Loggue chaque étape et lève une exception en cas d'échec.
|
||||||
|
@override
|
||||||
|
Future<void> updateFriendStatus(String friendId, String status) async {
|
||||||
|
try {
|
||||||
|
_logger.i("[LOG] Mise à jour du statut de l'ami avec l'ID : $friendId, nouveau statut : $status");
|
||||||
|
|
||||||
|
final uri = Uri.parse('${Urls.baseUrl}/friends/$friendId/status');
|
||||||
|
final response = await client.patch(
|
||||||
|
uri,
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: jsonEncode({'status': status}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
_logger.i("[LOG] Statut de l'ami mis à jour avec succès : $friendId");
|
||||||
|
} else {
|
||||||
|
_logger.e("[ERROR] Erreur lors de la mise à jour du statut. Code HTTP : ${response.statusCode}");
|
||||||
|
throw Exception("Erreur lors de la mise à jour du statut");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
_logger.e("[ERROR] Exception lors de la mise à jour du statut de l'ami : $e");
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,80 +3,96 @@ import 'package:shared_preferences/shared_preferences.dart';
|
|||||||
/// Classe pour gérer les préférences utilisateur à l'aide de SharedPreferences.
|
/// Classe pour gérer les préférences utilisateur à l'aide de SharedPreferences.
|
||||||
/// Permet de stocker et récupérer des informations de manière non sécurisée,
|
/// Permet de stocker et récupérer des informations de manière non sécurisée,
|
||||||
/// contrairement au stockage sécurisé qui est utilisé pour des données sensibles.
|
/// contrairement au stockage sécurisé qui est utilisé pour des données sensibles.
|
||||||
|
/// Chaque action est loguée pour assurer la traçabilité complète dans le terminal.
|
||||||
class PreferencesHelper {
|
class PreferencesHelper {
|
||||||
// Initialisation de SharedPreferences en tant que Future
|
// Initialisation de SharedPreferences en tant que Future
|
||||||
final Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
|
final Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
|
||||||
|
|
||||||
/// Sauvegarde une chaîne de caractères (String) dans les préférences.
|
/// Sauvegarde une chaîne de caractères (String) dans les préférences.
|
||||||
|
/// Les actions sont loguées et les erreurs capturées pour garantir une sauvegarde correcte.
|
||||||
Future<void> setString(String key, String value) async {
|
Future<void> setString(String key, String value) async {
|
||||||
print("Sauvegarde dans les préférences : clé = $key, valeur = $value");
|
print("[LOG] Sauvegarde dans les préférences : clé = $key, valeur = $value");
|
||||||
final prefs = await _prefs;
|
final prefs = await _prefs;
|
||||||
await prefs.setString(key, value);
|
final success = await prefs.setString(key, value);
|
||||||
print("Sauvegarde réussie pour la clé : $key");
|
if (success) {
|
||||||
|
print("[LOG] Sauvegarde réussie pour la clé : $key");
|
||||||
|
} else {
|
||||||
|
print("[ERROR] Échec de la sauvegarde pour la clé : $key");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Récupère une chaîne de caractères depuis les préférences.
|
/// Récupère une chaîne de caractères depuis les préférences.
|
||||||
|
/// Retourne la valeur ou null si aucune donnée n'est trouvée.
|
||||||
Future<String?> getString(String key) async {
|
Future<String?> getString(String key) async {
|
||||||
print("Récupération depuis les préférences pour la clé : $key");
|
print("[LOG] Récupération depuis les préférences pour la clé : $key");
|
||||||
final prefs = await _prefs;
|
final prefs = await _prefs;
|
||||||
final value = prefs.getString(key);
|
final value = prefs.getString(key);
|
||||||
print("Valeur récupérée pour la clé $key : $value");
|
print("[LOG] Valeur récupérée pour la clé $key : $value");
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Supprime une entrée dans les préférences.
|
/// Supprime une entrée dans les préférences.
|
||||||
|
/// Logue chaque étape de la suppression.
|
||||||
Future<void> remove(String key) async {
|
Future<void> remove(String key) async {
|
||||||
print("Suppression dans les préférences pour la clé : $key");
|
print("[LOG] Suppression dans les préférences pour la clé : $key");
|
||||||
final prefs = await _prefs;
|
final prefs = await _prefs;
|
||||||
await prefs.remove(key);
|
final success = await prefs.remove(key);
|
||||||
print("Suppression réussie pour la clé : $key");
|
if (success) {
|
||||||
|
print("[LOG] Suppression réussie pour la clé : $key");
|
||||||
|
} else {
|
||||||
|
print("[ERROR] Échec de la suppression pour la clé : $key");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sauvegarde l'identifiant utilisateur dans les préférences.
|
/// Sauvegarde l'identifiant utilisateur dans les préférences.
|
||||||
|
/// Logue l'action et assure la robustesse de l'opération.
|
||||||
Future<void> saveUserId(String userId) async {
|
Future<void> saveUserId(String userId) async {
|
||||||
print("Sauvegarde de l'userId dans les préférences : $userId");
|
print("[LOG] Sauvegarde de l'userId dans les préférences : $userId");
|
||||||
await setString('user_id', userId);
|
await setString('user_id', userId);
|
||||||
print("Sauvegarde réussie de l'userId.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Récupère l'identifiant utilisateur depuis les préférences.
|
/// Récupère l'identifiant utilisateur depuis les préférences.
|
||||||
|
/// Retourne l'ID ou null en cas d'échec.
|
||||||
Future<String?> getUserId() async {
|
Future<String?> getUserId() async {
|
||||||
print("Récupération de l'userId depuis les préférences.");
|
print("[LOG] Récupération de l'userId depuis les préférences.");
|
||||||
return await getString('user_id');
|
return await getString('user_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sauvegarde le nom d'utilisateur dans les préférences.
|
/// Sauvegarde le nom d'utilisateur dans les préférences.
|
||||||
|
/// Logue l'opération pour assurer un suivi complet.
|
||||||
Future<void> saveUserName(String userName) async {
|
Future<void> saveUserName(String userName) async {
|
||||||
print("Sauvegarde du userName dans les préférences : $userName");
|
print("[LOG] Sauvegarde du userName dans les préférences : $userName");
|
||||||
await setString('user_name', userName);
|
await setString('user_name', userName);
|
||||||
print("Sauvegarde réussie du userName.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Récupère le nom d'utilisateur depuis les préférences.
|
/// Récupère le nom d'utilisateur depuis les préférences.
|
||||||
|
/// Retourne le nom ou null en cas d'échec.
|
||||||
Future<String?> getUserName() async {
|
Future<String?> getUserName() async {
|
||||||
print("Récupération du userName depuis les préférences.");
|
print("[LOG] Récupération du userName depuis les préférences.");
|
||||||
return await getString('user_name');
|
return await getString('user_name');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sauvegarde le prénom de l'utilisateur dans les préférences.
|
/// Sauvegarde le prénom de l'utilisateur dans les préférences.
|
||||||
|
/// Logue l'opération pour assurer un suivi complet.
|
||||||
Future<void> saveUserLastName(String userLastName) async {
|
Future<void> saveUserLastName(String userLastName) async {
|
||||||
print("Sauvegarde du userLastName dans les préférences : $userLastName");
|
print("[LOG] Sauvegarde du userLastName dans les préférences : $userLastName");
|
||||||
await setString('user_last_name', userLastName);
|
await setString('user_last_name', userLastName);
|
||||||
print("Sauvegarde réussie du userLastName.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Récupère le prénom de l'utilisateur depuis les préférences.
|
/// Récupère le prénom de l'utilisateur depuis les préférences.
|
||||||
|
/// Retourne le prénom ou null en cas d'échec.
|
||||||
Future<String?> getUserLastName() async {
|
Future<String?> getUserLastName() async {
|
||||||
print("Récupération du userLastName depuis les préférences.");
|
print("[LOG] Récupération du userLastName depuis les préférences.");
|
||||||
return await getString('user_last_name');
|
return await getString('user_last_name');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Supprime toutes les informations utilisateur dans les préférences.
|
/// Supprime toutes les informations utilisateur dans les préférences.
|
||||||
|
/// Logue chaque étape de la suppression.
|
||||||
Future<void> clearUserInfo() async {
|
Future<void> clearUserInfo() async {
|
||||||
print("Suppression des informations utilisateur (userId, userName, userLastName) des préférences.");
|
print("[LOG] Suppression des informations utilisateur (userId, userName, userLastName) des préférences.");
|
||||||
await remove('user_id');
|
await remove('user_id');
|
||||||
await remove('user_name');
|
await remove('user_name');
|
||||||
await remove('user_last_name');
|
await remove('user_last_name');
|
||||||
print("Suppression réussie des informations utilisateur.");
|
print("[LOG] Suppression réussie des informations utilisateur.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,78 +1,137 @@
|
|||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
|
import 'package:logger/logger.dart';
|
||||||
|
|
||||||
/// Classe pour gérer le stockage sécurisé dans l'application.
|
/// Classe SecureStorage pour gérer les opérations de stockage sécurisé.
|
||||||
/// Utilise FlutterSecureStorage pour stocker, lire et supprimer des données sensibles.
|
/// Toutes les actions sont loguées pour permettre une traçabilité complète dans le terminal.
|
||||||
class SecureStorage {
|
class SecureStorage {
|
||||||
// Instance de FlutterSecureStorage pour gérer le stockage sécurisé
|
// Instance de FlutterSecureStorage pour le stockage sécurisé.
|
||||||
final FlutterSecureStorage _storage = const FlutterSecureStorage();
|
final FlutterSecureStorage _storage = const FlutterSecureStorage();
|
||||||
|
|
||||||
|
// Logger pour suivre et enregistrer les actions dans le terminal.
|
||||||
|
final Logger _logger = Logger();
|
||||||
|
|
||||||
/// Écrit une valeur dans le stockage sécurisé avec la clé spécifiée.
|
/// Écrit une valeur dans le stockage sécurisé avec la clé spécifiée.
|
||||||
|
/// Les actions sont loguées et les erreurs sont capturées pour assurer la robustesse.
|
||||||
Future<void> write(String key, String value) async {
|
Future<void> write(String key, String value) async {
|
||||||
print("Écriture dans le stockage sécurisé : clé = $key, valeur = $value");
|
try {
|
||||||
await _storage.write(key: key, value: value);
|
_logger.i("[LOG] Tentative d'écriture dans le stockage sécurisé : clé = $key, valeur = $value");
|
||||||
print("Écriture réussie pour la clé : $key");
|
await _storage.write(key: key, value: value);
|
||||||
|
_logger.i("[LOG] Écriture réussie pour la clé : $key");
|
||||||
|
} catch (e) {
|
||||||
|
_logger.e("[ERROR] Échec d'écriture pour la clé $key : $e");
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lit une valeur depuis le stockage sécurisé en fonction de la clé spécifiée.
|
/// Lit une valeur depuis le stockage sécurisé en fonction de la clé spécifiée.
|
||||||
|
/// Retourne la valeur ou null en cas d'erreur. Chaque action est loguée.
|
||||||
Future<String?> read(String key) async {
|
Future<String?> read(String key) async {
|
||||||
print("Lecture dans le stockage sécurisé pour la clé : $key");
|
try {
|
||||||
final value = await _storage.read(key: key);
|
_logger.i("[LOG] Lecture de la clé : $key");
|
||||||
print("Valeur lue pour la clé $key : $value");
|
final value = await _storage.read(key: key);
|
||||||
return value;
|
_logger.i("[LOG] Valeur lue pour la clé $key : $value");
|
||||||
|
return value;
|
||||||
|
} catch (e) {
|
||||||
|
_logger.e("[ERROR] Échec de lecture pour la clé $key : $e");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Supprime une entrée dans le stockage sécurisé pour la clé spécifiée.
|
/// Supprime une entrée dans le stockage sécurisé pour la clé spécifiée.
|
||||||
|
/// Logue chaque étape de l'opération de suppression.
|
||||||
Future<void> delete(String key) async {
|
Future<void> delete(String key) async {
|
||||||
print("Suppression dans le stockage sécurisé pour la clé : $key");
|
try {
|
||||||
await _storage.delete(key: key);
|
_logger.i("[LOG] Suppression de la clé : $key");
|
||||||
print("Suppression réussie pour la clé : $key");
|
await _storage.delete(key: key);
|
||||||
|
_logger.i("[LOG] Suppression réussie pour la clé : $key");
|
||||||
|
} catch (e) {
|
||||||
|
_logger.e("[ERROR] Échec de suppression pour la clé $key : $e");
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sauvegarde l'identifiant utilisateur dans le stockage sécurisé.
|
/// Sauvegarde l'identifiant utilisateur dans le stockage sécurisé.
|
||||||
|
/// Logue l'action et assure la robustesse de l'opération.
|
||||||
Future<void> saveUserId(String userId) async {
|
Future<void> saveUserId(String userId) async {
|
||||||
print("Sauvegarde de l'userId dans le stockage sécurisé : $userId");
|
if (userId.isNotEmpty) {
|
||||||
await write('user_id', userId);
|
_logger.i("[LOG] Tentative de sauvegarde de l'userId : $userId");
|
||||||
print("Sauvegarde réussie de l'userId.");
|
await write('user_id', userId);
|
||||||
|
final savedId = await getUserId(); // Récupération immédiate pour vérifier l'enregistrement
|
||||||
|
if (savedId != null && savedId == userId) {
|
||||||
|
_logger.i("[LOG] L'userId a été sauvegardé avec succès et vérifié : $savedId");
|
||||||
|
} else {
|
||||||
|
_logger.e("[ERROR] L'userId n'a pas été correctement sauvegardé.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_logger.e("[ERROR] L'userId est vide, échec de sauvegarde.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Récupère l'identifiant utilisateur depuis le stockage sécurisé.
|
/// Récupère l'identifiant utilisateur depuis le stockage sécurisé.
|
||||||
|
/// Retourne l'ID ou null en cas d'échec.
|
||||||
Future<String?> getUserId() async {
|
Future<String?> getUserId() async {
|
||||||
print("Récupération de l'userId depuis le stockage sécurisé.");
|
_logger.i("[LOG] Récupération de l'userId.");
|
||||||
return await read('user_id');
|
return await read('user_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sauvegarde le nom d'utilisateur dans le stockage sécurisé.
|
/// Sauvegarde le nom d'utilisateur dans le stockage sécurisé.
|
||||||
Future<void> saveUserName(String userName) async {
|
/// Retourne un booléen pour indiquer le succès ou l'échec.
|
||||||
print("Sauvegarde du userName dans le stockage sécurisé : $userName");
|
Future<bool> saveUserName(String userName) async {
|
||||||
await write('user_name', userName);
|
_logger.i("[LOG] Tentative de sauvegarde du userName : $userName");
|
||||||
print("Sauvegarde réussie du userName.");
|
return await _safeWrite('user_name', userName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Récupère le nom d'utilisateur depuis le stockage sécurisé.
|
/// Récupère le nom d'utilisateur depuis le stockage sécurisé.
|
||||||
|
/// Retourne le nom ou null en cas d'échec.
|
||||||
Future<String?> getUserName() async {
|
Future<String?> getUserName() async {
|
||||||
print("Récupération du userName depuis le stockage sécurisé.");
|
_logger.i("[LOG] Tentative de récupération du userName depuis le stockage sécurisé.");
|
||||||
return await read('user_name');
|
return await _safeRead('user_name');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sauvegarde le prénom de l'utilisateur dans le stockage sécurisé.
|
/// Sauvegarde le prénom de l'utilisateur dans le stockage sécurisé.
|
||||||
Future<void> saveUserLastName(String userLastName) async {
|
/// Retourne un booléen pour indiquer le succès ou l'échec.
|
||||||
print("Sauvegarde du userLastName dans le stockage sécurisé : $userLastName");
|
Future<bool> saveUserLastName(String userLastName) async {
|
||||||
await write('user_last_name', userLastName);
|
_logger.i("[LOG] Tentative de sauvegarde du userLastName : $userLastName");
|
||||||
print("Sauvegarde réussie du userLastName.");
|
return await _safeWrite('user_last_name', userLastName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Récupère le prénom de l'utilisateur depuis le stockage sécurisé.
|
/// Récupère le prénom de l'utilisateur depuis le stockage sécurisé.
|
||||||
|
/// Retourne le prénom ou null en cas d'échec.
|
||||||
Future<String?> getUserLastName() async {
|
Future<String?> getUserLastName() async {
|
||||||
print("Récupération du userLastName depuis le stockage sécurisé.");
|
_logger.i("[LOG] Tentative de récupération du userLastName depuis le stockage sécurisé.");
|
||||||
return await read('user_last_name');
|
return await _safeRead('user_last_name');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Supprime toutes les informations utilisateur du stockage sécurisé.
|
/// Supprime toutes les informations utilisateur du stockage sécurisé.
|
||||||
|
/// Logue chaque étape de la suppression.
|
||||||
Future<void> deleteUserInfo() async {
|
Future<void> deleteUserInfo() async {
|
||||||
print("Suppression des informations utilisateur (userId, userName, userLastName).");
|
_logger.i("[LOG] Tentative de suppression de toutes les informations utilisateur.");
|
||||||
await delete('user_id');
|
await delete('user_id');
|
||||||
await delete('user_name');
|
await delete('user_name');
|
||||||
await delete('user_last_name');
|
await delete('user_last_name');
|
||||||
print("Suppression réussie des informations utilisateur.");
|
_logger.i("[LOG] Suppression réussie des informations utilisateur.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Méthode privée pour encapsuler l'écriture sécurisée avec gestion d'erreur.
|
||||||
|
/// Retourne un booléen indiquant le succès ou l'échec de l'opération.
|
||||||
|
Future<bool> _safeWrite(String key, String value) async {
|
||||||
|
try {
|
||||||
|
await write(key, value);
|
||||||
|
return true; // Indique que l'écriture a réussi.
|
||||||
|
} catch (e) {
|
||||||
|
_logger.e("[ERROR] Erreur lors de l'écriture sécurisée : $e");
|
||||||
|
return false; // Indique un échec.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Méthode privée pour encapsuler la lecture sécurisée avec gestion d'erreur.
|
||||||
|
/// Retourne la valeur ou null si la lecture échoue.
|
||||||
|
Future<String?> _safeRead(String key) async {
|
||||||
|
try {
|
||||||
|
return await read(key);
|
||||||
|
} catch (e) {
|
||||||
|
_logger.e("[ERROR] Erreur lors de la lecture sécurisée : $e");
|
||||||
|
return null; // Retourne null en cas d'erreur.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
119
lib/domain/entities/friend.dart
Normal file
119
lib/domain/entities/friend.dart
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:logger/logger.dart';
|
||||||
|
|
||||||
|
/// Enumération représentant les différents statuts possibles d'un ami.
|
||||||
|
/// Utilisée pour éviter les erreurs de chaîne de caractères et faciliter les comparaisons.
|
||||||
|
enum FriendStatus { pending, accepted, blocked, unknown }
|
||||||
|
|
||||||
|
/// Classe [Friend] représentant un ami avec ses informations de base.
|
||||||
|
/// Cette classe est conçue pour être utilisée dans des applications de haut niveau
|
||||||
|
/// avec une gestion robuste des erreurs, des logs avancés, et une immuabilité stricte.
|
||||||
|
///
|
||||||
|
/// Chaque instance de [Friend] est immuable et toute modification doit passer par [copyWith].
|
||||||
|
class Friend extends Equatable {
|
||||||
|
final String friendId; // ID unique de l'ami, requis et non-nullable
|
||||||
|
final String firstName; // Prénom de l'ami, non-nullable pour garantir une intégrité des données
|
||||||
|
final String lastName; // Nom de famille, non-nullable
|
||||||
|
final String? email; // Adresse e-mail, optionnelle mais typiquement présente
|
||||||
|
final String? imageUrl; // URL de l'image de profil, optionnelle
|
||||||
|
final FriendStatus status; // Statut de l'ami, avec une valeur par défaut `unknown`
|
||||||
|
|
||||||
|
/// Logger statique pour suivre toutes les actions et transformations liées à [Friend].
|
||||||
|
static final Logger _logger = Logger();
|
||||||
|
|
||||||
|
/// Constructeur de la classe [Friend].
|
||||||
|
/// Initialisation avec des valeurs spécifiques pour `firstName` et `lastName`.
|
||||||
|
/// La validation des valeurs est incluse pour garantir l'intégrité des données.
|
||||||
|
Friend({
|
||||||
|
required this.friendId,
|
||||||
|
this.firstName = 'Ami inconnu', // Valeur par défaut pour éviter les champs vides
|
||||||
|
this.lastName = '',
|
||||||
|
this.email,
|
||||||
|
this.imageUrl,
|
||||||
|
this.status = FriendStatus.unknown,
|
||||||
|
}) {
|
||||||
|
assert(friendId.isNotEmpty, 'friendId ne doit pas être vide');
|
||||||
|
_logger.i('[LOG] Création d\'un objet Friend : ID = $friendId, Nom = $firstName $lastName');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Méthode factory pour créer un objet [Friend] à partir d'un JSON.
|
||||||
|
/// Inclut une validation approfondie pour vérifier l'intégrité des données.
|
||||||
|
///
|
||||||
|
/// Retourne une instance de [Friend] ou null si les données sont incomplètes.
|
||||||
|
factory Friend.fromJson(Map<String, dynamic> json) {
|
||||||
|
_logger.i('[LOG] Conversion JSON -> Friend : $json');
|
||||||
|
|
||||||
|
if (json['friendId'] == null || (json['friendId'] as String).isEmpty) {
|
||||||
|
_logger.e('[ERROR] friendId manquant ou vide dans le JSON.');
|
||||||
|
throw ArgumentError("friendId est requis pour créer un objet Friend");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Friend(
|
||||||
|
friendId: json['friendId'] as String,
|
||||||
|
firstName: json['friendFirstName'] as String? ?? 'Ami inconnu',
|
||||||
|
lastName: json['friendLastName'] as String? ?? '',
|
||||||
|
email: json['email'] as String?,
|
||||||
|
imageUrl: json['imageUrl'] as String?,
|
||||||
|
status: _parseStatus(json['status'] as String?),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Méthode privée pour parser le champ `status` en type [FriendStatus].
|
||||||
|
/// Retourne [FriendStatus.unknown] si le statut est non reconnu.
|
||||||
|
static FriendStatus _parseStatus(String? status) {
|
||||||
|
switch (status?.toLowerCase()) {
|
||||||
|
case 'pending':
|
||||||
|
return FriendStatus.pending;
|
||||||
|
case 'accepted':
|
||||||
|
return FriendStatus.accepted;
|
||||||
|
case 'blocked':
|
||||||
|
return FriendStatus.blocked;
|
||||||
|
default:
|
||||||
|
return FriendStatus.unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sérialise un objet [Friend] en JSON pour être transmis à l'API.
|
||||||
|
/// Les logs incluent le temps de traitement pour les optimisations de performance.
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final json = {
|
||||||
|
'friendId': friendId,
|
||||||
|
'firstName': firstName,
|
||||||
|
'lastName': lastName,
|
||||||
|
'email': email,
|
||||||
|
'imageUrl': imageUrl,
|
||||||
|
'status': status.name,
|
||||||
|
};
|
||||||
|
_logger.i('[LOG] Conversion Friend -> JSON : $json');
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Méthode [copyWith] pour cloner un objet `Friend` en modifiant certains attributs.
|
||||||
|
/// Facilite la modification immuable des propriétés sans affecter l'instance actuelle.
|
||||||
|
///
|
||||||
|
/// Log chaque copie pour surveiller l'état des données.
|
||||||
|
Friend copyWith({
|
||||||
|
String? friendId,
|
||||||
|
String? firstName,
|
||||||
|
String? lastName,
|
||||||
|
String? email,
|
||||||
|
String? imageUrl,
|
||||||
|
FriendStatus? status,
|
||||||
|
}) {
|
||||||
|
final newFriend = Friend(
|
||||||
|
friendId: friendId ?? this.friendId,
|
||||||
|
firstName: firstName ?? this.firstName,
|
||||||
|
lastName: lastName ?? this.lastName,
|
||||||
|
email: email ?? this.email,
|
||||||
|
imageUrl: imageUrl ?? this.imageUrl,
|
||||||
|
status: status ?? this.status,
|
||||||
|
);
|
||||||
|
_logger.i('[LOG] Création d\'une copie modifiée de Friend : ID = ${newFriend.friendId}');
|
||||||
|
return newFriend;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Propriétés utilisées pour comparer les objets [Friend],
|
||||||
|
/// facilitant l'utilisation dans des listes et des ensembles.
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [friendId, firstName, lastName, email, imageUrl, status];
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:afterwork/config/router.dart';
|
import 'package:afterwork/config/router.dart';
|
||||||
import 'package:afterwork/data/datasources/event_remote_data_source.dart';
|
import 'package:afterwork/data/datasources/event_remote_data_source.dart';
|
||||||
import 'package:afterwork/data/providers/user_provider.dart';
|
import 'package:afterwork/data/providers/user_provider.dart';
|
||||||
|
import 'package:afterwork/data/repositories/friends_repository_impl.dart';
|
||||||
import 'package:afterwork/data/services/preferences_helper.dart';
|
import 'package:afterwork/data/services/preferences_helper.dart';
|
||||||
import 'package:afterwork/data/services/secure_storage.dart';
|
import 'package:afterwork/data/services/secure_storage.dart';
|
||||||
import 'package:afterwork/presentation/state_management/event_bloc.dart';
|
import 'package:afterwork/presentation/state_management/event_bloc.dart';
|
||||||
@@ -9,8 +10,8 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:intl/date_symbol_data_local.dart';
|
import 'package:intl/date_symbol_data_local.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'core/theme/theme_provider.dart';
|
import 'core/theme/theme_provider.dart';
|
||||||
|
import 'data/providers/friends_provider.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
@@ -22,22 +23,30 @@ void main() async {
|
|||||||
final eventRemoteDataSource = EventRemoteDataSource(http.Client());
|
final eventRemoteDataSource = EventRemoteDataSource(http.Client());
|
||||||
final SecureStorage secureStorage = SecureStorage();
|
final SecureStorage secureStorage = SecureStorage();
|
||||||
final PreferencesHelper preferencesHelper = PreferencesHelper();
|
final PreferencesHelper preferencesHelper = PreferencesHelper();
|
||||||
|
final http.Client httpClient = http.Client(); // Nouvelle instance http.Client
|
||||||
|
|
||||||
// Récupération des informations stockées
|
// Récupération des informations stockées avec logs détaillés
|
||||||
String? userId = await secureStorage.getUserId();
|
String? userId = await secureStorage.getUserId();
|
||||||
String? userName = await preferencesHelper.getUserName();
|
String? userName = await preferencesHelper.getUserName();
|
||||||
String? userLastName = await preferencesHelper.getUserLastName();
|
String? userLastName = await preferencesHelper.getUserLastName();
|
||||||
|
|
||||||
// Gestion des valeurs par défaut si nécessaires
|
// Log de la récupération des informations
|
||||||
|
print("[LOG] Récupération des informations utilisateur : userId = $userId, userName = $userName, userLastName = $userLastName");
|
||||||
|
|
||||||
|
// Gestion des valeurs par défaut si les informations ne sont pas trouvées
|
||||||
userId ??= 'default_user_id';
|
userId ??= 'default_user_id';
|
||||||
userName ??= 'Default';
|
userName ??= 'Default';
|
||||||
userLastName ??= 'User';
|
userLastName ??= 'User';
|
||||||
|
|
||||||
|
// Log des valeurs par défaut appliquées
|
||||||
|
print("[LOG] Valeurs par défaut appliquées : userId = $userId, userName = $userName, userLastName = $userLastName");
|
||||||
|
|
||||||
runApp(MyApp(
|
runApp(MyApp(
|
||||||
eventRemoteDataSource: eventRemoteDataSource,
|
eventRemoteDataSource: eventRemoteDataSource,
|
||||||
userId: userId,
|
userId: userId,
|
||||||
userName: userName,
|
userName: userName,
|
||||||
userLastName: userLastName,
|
userLastName: userLastName,
|
||||||
|
httpClient: httpClient, // Passe l'instance client ici
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,6 +55,7 @@ class MyApp extends StatelessWidget {
|
|||||||
final String userId;
|
final String userId;
|
||||||
final String userName;
|
final String userName;
|
||||||
final String userLastName;
|
final String userLastName;
|
||||||
|
final http.Client httpClient; // Ajout de ce paramètre
|
||||||
|
|
||||||
const MyApp({
|
const MyApp({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -53,17 +63,26 @@ class MyApp extends StatelessWidget {
|
|||||||
required this.userId,
|
required this.userId,
|
||||||
required this.userName,
|
required this.userName,
|
||||||
required this.userLastName,
|
required this.userLastName,
|
||||||
|
required this.httpClient, // Spécifier l'argument
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final friendsRepository = FriendsRepositoryImpl(client: httpClient); // Utilisation du client ici
|
||||||
|
|
||||||
|
// Log lors de la construction de l'application
|
||||||
|
print("[LOG] Construction de l'application avec userId : $userId, userName : $userName, userLastName : $userLastName");
|
||||||
|
|
||||||
return MultiProvider(
|
return MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
ChangeNotifierProvider(
|
ChangeNotifierProvider(
|
||||||
create: (_) => UserProvider()..setUser(userId, userName, userLastName),
|
create: (_) => UserProvider()..setUser(userId, userName, userLastName),
|
||||||
),
|
),
|
||||||
ChangeNotifierProvider(
|
ChangeNotifierProvider(
|
||||||
create: (_) => ThemeProvider(), // Fournisseur de thème
|
create: (_) => FriendsProvider(friendsRepository: friendsRepository),
|
||||||
|
),
|
||||||
|
ChangeNotifierProvider(
|
||||||
|
create: (_) => ThemeProvider(),
|
||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (context) => EventBloc(remoteDataSource: eventRemoteDataSource),
|
create: (context) => EventBloc(remoteDataSource: eventRemoteDataSource),
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ class EventCard extends StatelessWidget {
|
|||||||
menuKey: menuKey,
|
menuKey: menuKey,
|
||||||
menuContext: context,
|
menuContext: context,
|
||||||
location: event.location,
|
location: event.location,
|
||||||
|
onClose: () { },
|
||||||
),
|
),
|
||||||
const Divider(color: Colors.white24),
|
const Divider(color: Colors.white24),
|
||||||
Row(
|
Row(
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
|
import 'package:afterwork/presentation/screens/event/event_card.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:afterwork/data/models/event_model.dart';
|
|
||||||
import 'package:afterwork/presentation/screens/event/event_card.dart';
|
|
||||||
import '../dialogs/add_event_dialog.dart';
|
|
||||||
import '../../state_management/event_bloc.dart';
|
import '../../state_management/event_bloc.dart';
|
||||||
|
import '../dialogs/add_event_dialog.dart';
|
||||||
|
|
||||||
class EventScreen extends StatefulWidget {
|
class EventScreen extends StatefulWidget {
|
||||||
final String userId;
|
final String userId;
|
||||||
@@ -42,7 +42,8 @@ class _EventScreenState extends State<EventScreen> {
|
|||||||
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: () {
|
||||||
// Naviguer vers une nouvelle page pour ajouter un événement
|
// Naviguer vers une nouvelle page pour ajouter un événement
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
@@ -95,7 +96,6 @@ class _EventScreenState extends State<EventScreen> {
|
|||||||
},
|
},
|
||||||
status: event.status,
|
status: event.status,
|
||||||
);
|
);
|
||||||
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else if (state is EventError) {
|
} else if (state is EventError) {
|
||||||
|
|||||||
42
lib/presentation/screens/friends/friends_content.dart
Normal file
42
lib/presentation/screens/friends/friends_content.dart
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../../widgets/friend_card.dart';
|
||||||
|
import '../../widgets/friend_detail_screen.dart';
|
||||||
|
|
||||||
|
class FriendsContent extends StatelessWidget {
|
||||||
|
final List<Map<String, String>> friends = [
|
||||||
|
{'name': 'Alice', 'imageUrl': 'https://example.com/image1.jpg'},
|
||||||
|
{'name': 'Bob', 'imageUrl': 'https://example.com/image2.jpg'},
|
||||||
|
{'name': 'Charlie', 'imageUrl': 'https://example.com/image3.jpg'},
|
||||||
|
// Autres amis...
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ListView.builder(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0),
|
||||||
|
itemCount: friends.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final friend = friends[index];
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 10.0),
|
||||||
|
child: FriendCard(
|
||||||
|
name: friend['name']!,
|
||||||
|
imageUrl: friend['imageUrl']!,
|
||||||
|
onTap: () => _navigateToFriendDetail(context, friend),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _navigateToFriendDetail(BuildContext context, Map<String, String> friend) {
|
||||||
|
Navigator.of(context).push(MaterialPageRoute(
|
||||||
|
builder: (context) => FriendDetailScreen(
|
||||||
|
name: friend['name']!,
|
||||||
|
imageUrl: friend['imageUrl']!,
|
||||||
|
friendId: friend['friendId']!,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
142
lib/presentation/screens/friends/friends_screen.dart
Normal file
142
lib/presentation/screens/friends/friends_screen.dart
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import '../../../data/providers/friends_provider.dart';
|
||||||
|
import '../../widgets/friend_detail_screen.dart';
|
||||||
|
import '../../widgets/friends_circle.dart';
|
||||||
|
import '../../widgets/search_friends.dart';
|
||||||
|
|
||||||
|
/// [FriendsScreen] est l'écran principal permettant d'afficher et de gérer la liste des amis.
|
||||||
|
/// Il inclut des fonctionnalités de pagination, de recherche, et de rafraîchissement manuel de la liste.
|
||||||
|
class FriendsScreen extends StatefulWidget {
|
||||||
|
final String userId; // Identifiant de l'utilisateur pour récupérer ses amis
|
||||||
|
|
||||||
|
const FriendsScreen({Key? key, required this.userId}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_FriendsScreenState createState() => _FriendsScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FriendsScreenState extends State<FriendsScreen> {
|
||||||
|
late ScrollController _scrollController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
// Initialisation du contrôleur de défilement pour la gestion de la pagination.
|
||||||
|
_scrollController = ScrollController();
|
||||||
|
_scrollController.addListener(_onScroll);
|
||||||
|
|
||||||
|
// Log pour indiquer le début du chargement des amis
|
||||||
|
debugPrint("[LOG] Initialisation de la page : chargement des amis pour l'utilisateur ${widget.userId}");
|
||||||
|
// Chargement initial de la liste d'amis
|
||||||
|
Provider.of<FriendsProvider>(context, listen: false).fetchFriends(widget.userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
// Nettoyage du contrôleur de défilement pour éviter les fuites de mémoire.
|
||||||
|
_scrollController.removeListener(_onScroll);
|
||||||
|
_scrollController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
debugPrint("[LOG] Dispose : contrôleur de défilement supprimé");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Méthode déclenchée lors du défilement de la liste.
|
||||||
|
/// Vérifie si l'utilisateur a atteint le bas de la liste pour charger plus d'amis.
|
||||||
|
void _onScroll() {
|
||||||
|
final provider = Provider.of<FriendsProvider>(context, listen: false);
|
||||||
|
if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent &&
|
||||||
|
!provider.isLoading && provider.hasMore) {
|
||||||
|
debugPrint("[LOG] Scroll : fin de liste atteinte, chargement de la page suivante");
|
||||||
|
// Charger plus d'amis si on atteint la fin de la liste
|
||||||
|
provider.fetchFriends(widget.userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
// Accès au fournisseur pour gérer les données et les états des amis.
|
||||||
|
final friendsProvider = Provider.of<FriendsProvider>(context, listen: false);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Mes Amis'),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.refresh),
|
||||||
|
onPressed: () {
|
||||||
|
// Log de l'action de rafraîchissement
|
||||||
|
debugPrint("[LOG] Bouton Refresh : demande de rafraîchissement de la liste des amis");
|
||||||
|
// Rafraîchir la liste des amis
|
||||||
|
friendsProvider.fetchFriends(widget.userId);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: SafeArea(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.all(8.0),
|
||||||
|
// Widget pour la recherche d'amis
|
||||||
|
child: SearchFriends(),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
// Construction de la liste d'amis basée sur l'état du FriendsProvider
|
||||||
|
child: Consumer<FriendsProvider>(
|
||||||
|
builder: (context, friendsProvider, child) {
|
||||||
|
// Si le chargement est en cours et qu'il n'y a aucun ami, afficher un indicateur de chargement.
|
||||||
|
if (friendsProvider.isLoading && friendsProvider.friendsList.isEmpty) {
|
||||||
|
debugPrint("[LOG] Chargement : affichage de l'indicateur de progression");
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si la liste est vide après le chargement, afficher un message indiquant qu'aucun ami n'a été trouvé.
|
||||||
|
if (friendsProvider.friendsList.isEmpty) {
|
||||||
|
debugPrint("[LOG] Liste vide : Aucun ami trouvé");
|
||||||
|
return const Center(
|
||||||
|
child: Text('Aucun ami trouvé'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Affichage de la grille des amis
|
||||||
|
debugPrint("[LOG] Affichage de la grille des amis (nombre d'amis : ${friendsProvider.friendsList.length})");
|
||||||
|
|
||||||
|
return GridView.builder(
|
||||||
|
controller: _scrollController,
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
|
crossAxisCount: 3,
|
||||||
|
mainAxisSpacing: 10,
|
||||||
|
crossAxisSpacing: 10,
|
||||||
|
),
|
||||||
|
itemCount: friendsProvider.friendsList.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final friend = friendsProvider.friendsList[index];
|
||||||
|
debugPrint("[LOG] Affichage de l'ami à l'index $index avec ID : ${friend.friendId}");
|
||||||
|
|
||||||
|
return FriendsCircle(
|
||||||
|
friend: friend,
|
||||||
|
onTap: () {
|
||||||
|
// Log pour l'action de visualisation des détails d'un ami
|
||||||
|
debugPrint("[LOG] Détail : Affichage des détails de l'ami ID : ${friend.friendId}");
|
||||||
|
// Naviguer vers l'écran des détails de l'ami
|
||||||
|
FriendDetailScreen.open(
|
||||||
|
context,
|
||||||
|
friend.friendId,
|
||||||
|
friend.firstName ?? 'Ami inconnu',
|
||||||
|
friend.imageUrl ?? '',
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import '../../../assets/animations/friend_expanding_card.dart';
|
||||||
|
import '../../../data/providers/friends_provider.dart';
|
||||||
|
import '../../../domain/entities/friend.dart';
|
||||||
|
import '../../widgets/friend_detail_screen.dart';
|
||||||
|
import '../../widgets/friends_appbar.dart';
|
||||||
|
import '../../widgets/search_friends.dart';
|
||||||
|
|
||||||
|
/// [FriendsScreenWithProvider] est un écran qui affiche la liste des amis.
|
||||||
|
/// Il utilise le provider [FriendsProvider] pour gérer les états et les données.
|
||||||
|
/// Chaque action est loguée pour permettre une traçabilité complète.
|
||||||
|
class FriendsScreenWithProvider extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: Colors.black,
|
||||||
|
appBar: FriendsAppBar(),
|
||||||
|
body: SafeArea(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.all(8.0),
|
||||||
|
child: SearchFriends(),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Consumer<FriendsProvider>(
|
||||||
|
builder: (context, friendsProvider, _) {
|
||||||
|
final friends = friendsProvider.friendsList;
|
||||||
|
|
||||||
|
if (friends.isEmpty) {
|
||||||
|
return const Center(
|
||||||
|
child: Text(
|
||||||
|
'Aucun ami trouvé',
|
||||||
|
style: TextStyle(color: Colors.white70),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ListView.builder(
|
||||||
|
physics: const BouncingScrollPhysics(),
|
||||||
|
itemCount: friends.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final friend = friends[index];
|
||||||
|
return Dismissible(
|
||||||
|
key: Key(friend.friendId),
|
||||||
|
background: Container(
|
||||||
|
color: Colors.redAccent,
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
padding: const EdgeInsets.only(left: 20),
|
||||||
|
child: const Icon(Icons.delete, color: Colors.white),
|
||||||
|
),
|
||||||
|
onDismissed: (direction) {
|
||||||
|
debugPrint("[LOG] Suppression de l'ami avec l'ID : ${friend.friendId}");
|
||||||
|
friendsProvider.removeFriend(friend.friendId);
|
||||||
|
},
|
||||||
|
child: FriendExpandingCard(
|
||||||
|
name: friend.firstName ?? 'Ami inconnu',
|
||||||
|
imageUrl: friend.imageUrl ?? '',
|
||||||
|
description: "Amis depuis ${friend.friendId}",
|
||||||
|
onTap: () => _navigateToFriendDetail(context, friend),
|
||||||
|
onMessageTap: () {
|
||||||
|
debugPrint("[LOG] Envoi d'un message à l'ami : ${friend.firstName ?? 'Ami inconnu'}");
|
||||||
|
},
|
||||||
|
onRemoveTap: () {
|
||||||
|
debugPrint("[LOG] Tentative de suppression de l'ami : ${friend.firstName ?? 'Ami inconnu'}");
|
||||||
|
friendsProvider.removeFriend(friend.friendId);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Navigue vers l'écran des détails de l'utilisateur (ami) récupéré via son `friendId`.
|
||||||
|
void _navigateToFriendDetail(BuildContext context, Friend friend) {
|
||||||
|
debugPrint("[LOG] Navigation vers les détails de l'ami : ${friend.firstName ?? 'Ami inconnu'}");
|
||||||
|
Navigator.of(context).push(MaterialPageRoute(
|
||||||
|
builder: (context) => FriendDetailScreen(
|
||||||
|
name: friend.firstName ?? 'Ami inconnu',
|
||||||
|
imageUrl: friend.imageUrl ?? '',
|
||||||
|
friendId: friend.friendId, // Passer l'ID pour récupérer les détails complets
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,17 +6,17 @@ 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';
|
import 'package:afterwork/data/datasources/event_remote_data_source.dart';
|
||||||
import 'package:afterwork/presentation/screens/notifications/notifications_screen.dart'; // Importez l'écran de notifications
|
import 'package:afterwork/presentation/screens/notifications/notifications_screen.dart'; // Écran de notifications
|
||||||
|
|
||||||
import '../../../core/constants/colors.dart';
|
import '../../../core/constants/colors.dart';
|
||||||
import '../../../core/theme/theme_provider.dart'; // Pour basculer le thème
|
import '../../../core/theme/theme_provider.dart';
|
||||||
|
import '../friends/friends_screen.dart'; // Écran des amis
|
||||||
|
|
||||||
class HomeScreen extends StatefulWidget {
|
class HomeScreen extends StatefulWidget {
|
||||||
final EventRemoteDataSource eventRemoteDataSource;
|
final EventRemoteDataSource eventRemoteDataSource;
|
||||||
final String userId;
|
final String userId;
|
||||||
final String userName;
|
final String userName;
|
||||||
final String userLastName;
|
final String userLastName;
|
||||||
final String userProfileImage; // Ajouter un champ pour l'image de profil de l'utilisateur
|
final String userProfileImage; // Image de profil de l'utilisateur
|
||||||
|
|
||||||
const HomeScreen({
|
const HomeScreen({
|
||||||
Key? key,
|
Key? key,
|
||||||
@@ -69,54 +69,53 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
|
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
|
||||||
return <Widget>[
|
return <Widget>[
|
||||||
SliverAppBar(
|
SliverAppBar(
|
||||||
backgroundColor: AppColors.backgroundColor, // Gère dynamiquement la couleur d'arrière-plan
|
backgroundColor: AppColors.backgroundColor,
|
||||||
floating: true,
|
floating: true,
|
||||||
pinned: true,
|
pinned: true,
|
||||||
snap: true,
|
snap: true,
|
||||||
elevation: 2, // Réduction de l'élévation pour un design plus léger
|
elevation: 2,
|
||||||
leading: Padding(
|
leading: Padding(
|
||||||
padding: const EdgeInsets.all(4.0), // Réduction du padding
|
padding: const EdgeInsets.all(4.0), // Ajustement du padding
|
||||||
child: Image.asset(
|
child: Image.asset(
|
||||||
'lib/assets/images/logo.png',
|
'lib/assets/images/logo.png',
|
||||||
height: 40, // Taille réduite du logo
|
height: 40, // Taille ajustée du logo
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
_buildActionIcon(Icons.add, 'Publier', context),
|
_buildActionIcon(Icons.add, 'Publier', context),
|
||||||
_buildActionIcon(Icons.search, 'Rechercher', context),
|
_buildActionIcon(Icons.search, 'Rechercher', context),
|
||||||
_buildActionIcon(Icons.message, 'Message', context),
|
_buildActionIcon(Icons.message, 'Message', context),
|
||||||
_buildNotificationsIcon(context, 45),
|
_buildNotificationsIcon(context, 5), // Gérer la logique des notifications ici
|
||||||
|
|
||||||
// Ajout du bouton pour basculer entre les thèmes
|
// Bouton pour basculer entre les thèmes
|
||||||
Switch(
|
Switch(
|
||||||
value: themeProvider.isDarkMode,
|
value: themeProvider.isDarkMode,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
themeProvider.toggleTheme(); // Bascule le thème lorsqu'on clique
|
themeProvider.toggleTheme(); // Changer le thème
|
||||||
},
|
},
|
||||||
activeColor: AppColors.accentColor,
|
activeColor: AppColors.accentColor,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
bottom: TabBar(
|
bottom: TabBar(
|
||||||
controller: _tabController,
|
controller: _tabController,
|
||||||
indicatorColor: AppColors.lightPrimary, // Tab active en bleu
|
indicatorColor: AppColors.lightPrimary,
|
||||||
labelStyle: const TextStyle(
|
labelStyle: const TextStyle(
|
||||||
fontSize: 12, // Réduction de la taille du texte des onglets
|
fontSize: 12, // Taille réduite du texte
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
unselectedLabelStyle: const TextStyle(
|
unselectedLabelStyle: const TextStyle(
|
||||||
fontSize: 11, // Réduction pour les onglets non sélectionnés
|
fontSize: 11, // Taille ajustée pour les onglets non sélectionnés
|
||||||
),
|
),
|
||||||
// Changement des couleurs pour les tabs non sélectionnées et sélectionnées
|
labelColor: AppColors.lightPrimary,
|
||||||
labelColor: AppColors.lightPrimary, // Tab active en bleu
|
unselectedLabelColor: AppColors.iconSecondary,
|
||||||
unselectedLabelColor: AppColors.iconSecondary, // Tabs non sélectionnées en blanc
|
|
||||||
|
|
||||||
tabs: [
|
tabs: [
|
||||||
const Tab(icon: Icon(Icons.home, size: 24), text: 'Accueil'),
|
const Tab(icon: Icon(Icons.home, size: 24), text: 'Accueil'),
|
||||||
const Tab(icon: Icon(Icons.event, size: 24), text: 'Événements'),
|
const Tab(icon: Icon(Icons.event, size: 24), text: 'Événements'),
|
||||||
const Tab(icon: Icon(Icons.location_city, size: 24), text: 'Établissements'),
|
const Tab(icon: Icon(Icons.location_city, size: 24), text: 'Établissements'),
|
||||||
const Tab(icon: Icon(Icons.people, size: 24), text: 'Social'),
|
const Tab(icon: Icon(Icons.people, size: 24), text: 'Social'),
|
||||||
const Tab(icon: Icon(Icons.notifications, size: 24), text: 'Notifications'),
|
const Tab(icon: Icon(Icons.people_alt_outlined, size: 24), text: 'Ami(e)s'),
|
||||||
_buildProfileTab(),
|
_buildProfileTab(), // Onglet profil
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -133,7 +132,7 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
),
|
),
|
||||||
const EstablishmentsScreen(),
|
const EstablishmentsScreen(),
|
||||||
const SocialScreen(),
|
const SocialScreen(),
|
||||||
const NotificationsScreen(),
|
FriendsScreen(userId: widget.userId), // Correction ici : passer l'userId
|
||||||
const ProfileScreen(),
|
const ProfileScreen(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -141,27 +140,26 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Widget pour afficher la photo de profil de l'utilisateur dans l'onglet
|
// Widget pour l'affichage de la photo de profil dans l'onglet
|
||||||
Tab _buildProfileTab() {
|
Tab _buildProfileTab() {
|
||||||
return Tab(
|
return Tab(
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: Colors.blue, // Définir la couleur de la bordure ici
|
color: Colors.blue,
|
||||||
width: 2.0,
|
width: 2.0,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: CircleAvatar(
|
child: CircleAvatar(
|
||||||
radius: 16, // Ajustez la taille si nécessaire
|
radius: 16,
|
||||||
backgroundColor: Colors.grey[200], // Couleur de fond pour le cas où l'image ne charge pas
|
backgroundColor: Colors.grey[200], // Couleur de fond par défaut
|
||||||
child: ClipOval(
|
child: ClipOval(
|
||||||
child: FadeInImage.assetNetwork(
|
child: FadeInImage.assetNetwork(
|
||||||
placeholder: 'lib/assets/images/user_placeholder.png', // Chemin de l'image par défaut
|
placeholder: 'lib/assets/images/user_placeholder.png',
|
||||||
image: widget.userProfileImage,
|
image: widget.userProfileImage,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
imageErrorBuilder: (context, error, stackTrace) {
|
imageErrorBuilder: (context, error, stackTrace) {
|
||||||
// Si l'image ne charge pas, afficher une image par défaut
|
|
||||||
return Image.asset('lib/assets/images/profile_picture.png', fit: BoxFit.cover);
|
return Image.asset('lib/assets/images/profile_picture.png', fit: BoxFit.cover);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -171,12 +169,12 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Widget pour afficher l'icône de notifications avec un badge si nécessaire
|
// Icône pour les notifications avec un badge
|
||||||
Widget _buildNotificationsIcon(BuildContext context, int notificationCount) {
|
Widget _buildNotificationsIcon(BuildContext context, int notificationCount) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 6.0),
|
padding: const EdgeInsets.symmetric(horizontal: 6.0),
|
||||||
child: Stack(
|
child: Stack(
|
||||||
clipBehavior: Clip.none, // Permet de positionner le badge en dehors des limites du Stack
|
clipBehavior: Clip.none, // Permet d'afficher le badge en dehors des limites
|
||||||
children: [
|
children: [
|
||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
backgroundColor: AppColors.surface,
|
backgroundColor: AppColors.surface,
|
||||||
@@ -184,7 +182,6 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
child: IconButton(
|
child: IconButton(
|
||||||
icon: const Icon(Icons.notifications, color: AppColors.darkOnPrimary, size: 20),
|
icon: const Icon(Icons.notifications, color: AppColors.darkOnPrimary, size: 20),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// Rediriger vers l'écran des notifications
|
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
@@ -194,7 +191,6 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Affiche le badge si le nombre de notifications est supérieur à 0
|
|
||||||
if (notificationCount > 0)
|
if (notificationCount > 0)
|
||||||
Positioned(
|
Positioned(
|
||||||
right: -6,
|
right: -6,
|
||||||
@@ -202,7 +198,7 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(2),
|
padding: const EdgeInsets.all(2),
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
color: Colors.red, // Couleur du badge
|
color: Colors.red,
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
),
|
),
|
||||||
constraints: const BoxConstraints(
|
constraints: const BoxConstraints(
|
||||||
@@ -210,7 +206,7 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
minHeight: 18,
|
minHeight: 18,
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
notificationCount > 99 ? '99+' : '$notificationCount', // Affiche "99+" si le nombre dépasse 99
|
notificationCount > 99 ? '99+' : '$notificationCount',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
@@ -225,14 +221,15 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Icône d'action générique
|
||||||
Widget _buildActionIcon(IconData iconData, String label, BuildContext context) {
|
Widget _buildActionIcon(IconData iconData, String label, BuildContext context) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 6.0), // Réduction de l'espacement
|
padding: const EdgeInsets.symmetric(horizontal: 6.0),
|
||||||
child: CircleAvatar(
|
child: CircleAvatar(
|
||||||
backgroundColor: AppColors.surface,
|
backgroundColor: AppColors.surface,
|
||||||
radius: 18, // Réduction de la taille des avatars
|
radius: 18,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
icon: Icon(iconData, color: AppColors.darkOnPrimary, size: 20), // Taille réduite de l'icône
|
icon: Icon(iconData, color: AppColors.darkOnPrimary, size: 20),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_onMenuSelected(context, label);
|
_onMenuSelected(context, label);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,13 +10,14 @@ import 'package:fluttertoast/fluttertoast.dart';
|
|||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:loading_icon_button/loading_icon_button.dart';
|
import 'package:loading_icon_button/loading_icon_button.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import '../../../core/errors/exceptions.dart';
|
||||||
import '../../../core/theme/theme_provider.dart';
|
import '../../../core/theme/theme_provider.dart';
|
||||||
import '../../../data/datasources/event_remote_data_source.dart';
|
import '../../../data/datasources/event_remote_data_source.dart';
|
||||||
import '../signup/SignUpScreen.dart';
|
import '../signup/SignUpScreen.dart';
|
||||||
|
|
||||||
/// Écran de connexion pour l'application AfterWork.
|
/// L'écran de connexion où les utilisateurs peuvent s'authentifier.
|
||||||
/// Ce fichier contient des fonctionnalités comme la gestion de la connexion,
|
/// Toutes les actions sont loguées pour permettre un suivi dans le terminal et détecter les erreurs.
|
||||||
/// l'authentification avec mot de passe en clair, la gestion des erreurs et un thème jour/nuit.
|
|
||||||
class LoginScreen extends StatefulWidget {
|
class LoginScreen extends StatefulWidget {
|
||||||
const LoginScreen({super.key});
|
const LoginScreen({super.key});
|
||||||
|
|
||||||
@@ -24,19 +25,21 @@ class LoginScreen extends StatefulWidget {
|
|||||||
_LoginScreenState createState() => _LoginScreenState();
|
_LoginScreenState createState() => _LoginScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _LoginScreenState extends State<LoginScreen> with SingleTickerProviderStateMixin {
|
class _LoginScreenState extends State<LoginScreen>
|
||||||
final _formKey = GlobalKey<FormState>(); // Clé pour valider le formulaire de connexion.
|
with SingleTickerProviderStateMixin {
|
||||||
|
// Clé globale pour la validation du formulaire
|
||||||
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
// Champs utilisateur
|
// Variables pour stocker l'email et le mot de passe saisis par l'utilisateur
|
||||||
String _email = ''; // Email de l'utilisateur
|
String _email = '';
|
||||||
String _password = ''; // Mot de passe de l'utilisateur
|
String _password = '';
|
||||||
|
|
||||||
// États de gestion
|
// États de l'écran
|
||||||
bool _isPasswordVisible = false; // Pour afficher/masquer le mot de passe
|
bool _isPasswordVisible = false; // Pour basculer la visibilité du mot de passe
|
||||||
bool _isSubmitting = false; // Indicateur pour l'état de soumission du formulaire
|
bool _isSubmitting = false; // Indique si la soumission du formulaire est en cours
|
||||||
bool _showErrorMessage = false; // Affichage des erreurs
|
bool _showErrorMessage = false; // Affiche un message d'erreur si nécessaire
|
||||||
|
|
||||||
// Services pour les opérations
|
// Sources de données et services
|
||||||
final UserRemoteDataSource _userRemoteDataSource = UserRemoteDataSource(http.Client());
|
final UserRemoteDataSource _userRemoteDataSource = UserRemoteDataSource(http.Client());
|
||||||
final SecureStorage _secureStorage = SecureStorage();
|
final SecureStorage _secureStorage = SecureStorage();
|
||||||
final PreferencesHelper _preferencesHelper = PreferencesHelper();
|
final PreferencesHelper _preferencesHelper = PreferencesHelper();
|
||||||
@@ -44,7 +47,7 @@ class _LoginScreenState extends State<LoginScreen> with SingleTickerProviderStat
|
|||||||
// Contrôleur pour le bouton de chargement
|
// Contrôleur pour le bouton de chargement
|
||||||
final _btnController = LoadingButtonController();
|
final _btnController = LoadingButtonController();
|
||||||
|
|
||||||
// Contrôleur d'animation pour la transition des écrans
|
// Contrôleur d'animation pour gérer la transition entre les écrans
|
||||||
late AnimationController _animationController;
|
late AnimationController _animationController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -54,25 +57,25 @@ class _LoginScreenState extends State<LoginScreen> with SingleTickerProviderStat
|
|||||||
vsync: this,
|
vsync: this,
|
||||||
duration: const Duration(milliseconds: 500),
|
duration: const Duration(milliseconds: 500),
|
||||||
);
|
);
|
||||||
print("Contrôleur d'animation initialisé.");
|
debugPrint("[LOG] Contrôleur d'animation initialisé.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_animationController.dispose();
|
_animationController.dispose();
|
||||||
print("Ressources d'animation libérées.");
|
debugPrint("[LOG] Ressources d'animation libérées.");
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fonction pour basculer la visibilité du mot de passe
|
/// Bascule la visibilité du mot de passe et logue l'état actuel.
|
||||||
void _togglePasswordVisibility() {
|
void _togglePasswordVisibility() {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isPasswordVisible = !_isPasswordVisible;
|
_isPasswordVisible = !_isPasswordVisible;
|
||||||
});
|
});
|
||||||
print("Visibilité du mot de passe basculée: $_isPasswordVisible");
|
debugPrint("[LOG] Visibilité du mot de passe basculée: $_isPasswordVisible");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fonction pour afficher un toast via FlutterToast
|
/// Affiche un toast avec le message spécifié et logue l'action.
|
||||||
void _showToast(String message) {
|
void _showToast(String message) {
|
||||||
Fluttertoast.showToast(
|
Fluttertoast.showToast(
|
||||||
msg: message,
|
msg: message,
|
||||||
@@ -83,60 +86,79 @@ class _LoginScreenState extends State<LoginScreen> with SingleTickerProviderStat
|
|||||||
textColor: Colors.white,
|
textColor: Colors.white,
|
||||||
fontSize: 16.0,
|
fontSize: 16.0,
|
||||||
);
|
);
|
||||||
|
debugPrint("[LOG] Toast affiché : $message");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fonction soumettre le formulaire
|
/// Soumet le formulaire de connexion et tente d'authentifier l'utilisateur.
|
||||||
|
/// Toutes les étapes et erreurs sont loguées pour une traçabilité complète.
|
||||||
Future<void> _submit() async {
|
Future<void> _submit() async {
|
||||||
print("Tentative de soumission du formulaire de connexion.");
|
debugPrint("[LOG] Tentative de soumission du formulaire de connexion.");
|
||||||
|
|
||||||
if (_formKey.currentState!.validate()) {
|
if (_formKey.currentState!.validate()) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isSubmitting = true;
|
_isSubmitting = true;
|
||||||
_showErrorMessage = false;
|
_showErrorMessage = false;
|
||||||
});
|
});
|
||||||
_formKey.currentState!.save();
|
_formKey.currentState!.save(); // Sauvegarde des données saisies
|
||||||
|
|
||||||
try {
|
try {
|
||||||
_btnController.start();
|
_btnController.start(); // Démarre l'animation de chargement du bouton
|
||||||
|
debugPrint("[LOG] Appel à l'API pour authentifier l'utilisateur.");
|
||||||
|
|
||||||
|
// Appel à l'API pour authentifier l'utilisateur
|
||||||
final UserModel user = await _userRemoteDataSource.authenticateUser(_email, _password);
|
final UserModel user = await _userRemoteDataSource.authenticateUser(_email, _password);
|
||||||
if (user == null) {
|
|
||||||
throw Exception("L'utilisateur n'a pas été trouvé ou l'authentification a échoué.");
|
|
||||||
}
|
|
||||||
|
|
||||||
print("Utilisateur authentifié : ${user.userId}");
|
// Validation de l'ID utilisateur
|
||||||
await _secureStorage.saveUserId(user.userId);
|
if (user.userId.isNotEmpty) {
|
||||||
await _preferencesHelper.saveUserName(user.nom);
|
debugPrint("[LOG] Utilisateur authentifié avec succès. ID: ${user.userId}");
|
||||||
await _preferencesHelper.saveUserLastName(user.prenoms);
|
|
||||||
_showToast("Connexion réussie !");
|
|
||||||
|
|
||||||
// Navigation vers la page d'accueil
|
// Sauvegarde des informations utilisateur
|
||||||
Navigator.pushReplacement(
|
await _secureStorage.saveUserId(user.userId);
|
||||||
context,
|
await _preferencesHelper.saveUserName(user.nom);
|
||||||
MaterialPageRoute(
|
await _preferencesHelper.saveUserLastName(user.prenoms);
|
||||||
builder: (context) => HomeScreen(
|
|
||||||
eventRemoteDataSource: EventRemoteDataSource(http.Client()),
|
_showToast("Connexion réussie !");
|
||||||
userId: user.userId,
|
// Redirection vers l'écran d'accueil
|
||||||
userName: user.nom,
|
Navigator.pushReplacement(
|
||||||
userLastName: user.prenoms,
|
context,
|
||||||
userProfileImage: 'lib/assets/images/profile_picture.png',
|
MaterialPageRoute(
|
||||||
|
builder: (context) => HomeScreen(
|
||||||
|
userId: user.userId,
|
||||||
|
userName: user.nom,
|
||||||
|
userLastName: user.prenoms,
|
||||||
|
userProfileImage: 'lib/assets/images/profile_picture.png',
|
||||||
|
eventRemoteDataSource: EventRemoteDataSource(http.Client()),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
} else {
|
||||||
|
debugPrint("[ERROR] L'ID utilisateur est manquant dans la réponse.");
|
||||||
|
_showToast("Erreur : ID utilisateur manquant.");
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print("Erreur lors de l'authentification : $e");
|
// Gestion des erreurs spécifiques et log de chaque type d'erreur
|
||||||
_btnController.error();
|
if (e is ServerExceptionWithMessage) {
|
||||||
_showToast("Erreur lors de la connexion : ${e.toString()}");
|
debugPrint("[ERROR] Erreur serveur : ${e.message}");
|
||||||
|
_showToast("Erreur serveur : ${e.message}");
|
||||||
|
} else if (e is UnauthorizedException) {
|
||||||
|
debugPrint("[ERROR] Erreur d'authentification : ${e.message}");
|
||||||
|
_showToast("Erreur : ${e.message}");
|
||||||
|
} else {
|
||||||
|
debugPrint("[ERROR] Erreur lors de la connexion : $e");
|
||||||
|
_showToast("Erreur lors de la connexion : ${e.toString()}");
|
||||||
|
}
|
||||||
|
_btnController.error(); // Affiche une erreur sur le bouton
|
||||||
setState(() {
|
setState(() {
|
||||||
_showErrorMessage = true;
|
_showErrorMessage = true;
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
_btnController.reset();
|
_btnController.reset(); // Réinitialise l'état du bouton
|
||||||
setState(() {
|
setState(() {
|
||||||
_isSubmitting = false;
|
_isSubmitting = false; // Réinitialise l'état de chargement
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
print("Échec de validation du formulaire.");
|
debugPrint("[ERROR] Validation du formulaire échouée.");
|
||||||
_btnController.reset();
|
_btnController.reset();
|
||||||
_showToast("Veuillez vérifier les informations saisies.");
|
_showToast("Veuillez vérifier les informations saisies.");
|
||||||
}
|
}
|
||||||
@@ -152,19 +174,21 @@ class _LoginScreenState extends State<LoginScreen> with SingleTickerProviderStat
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: Stack(
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
|
// Arrière-plan animé
|
||||||
AnimatedContainer(
|
AnimatedContainer(
|
||||||
duration: const Duration(seconds: 3),
|
duration: const Duration(seconds: 3),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
colors: [
|
colors: [
|
||||||
theme.colorScheme.primary,
|
theme.colorScheme.primary,
|
||||||
theme.colorScheme.secondary
|
theme.colorScheme.secondary,
|
||||||
],
|
],
|
||||||
begin: Alignment.topLeft,
|
begin: Alignment.topLeft,
|
||||||
end: Alignment.bottomRight,
|
end: Alignment.bottomRight,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
// Spinner de chargement lors de la soumission
|
||||||
if (_isSubmitting)
|
if (_isSubmitting)
|
||||||
const Center(
|
const Center(
|
||||||
child: SpinKitFadingCircle(
|
child: SpinKitFadingCircle(
|
||||||
@@ -172,6 +196,7 @@ class _LoginScreenState extends State<LoginScreen> with SingleTickerProviderStat
|
|||||||
size: 50.0,
|
size: 50.0,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
// Icône de changement de thème
|
||||||
Positioned(
|
Positioned(
|
||||||
top: 40,
|
top: 40,
|
||||||
right: 20,
|
right: 20,
|
||||||
@@ -182,10 +207,11 @@ class _LoginScreenState extends State<LoginScreen> with SingleTickerProviderStat
|
|||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
themeProvider.toggleTheme();
|
themeProvider.toggleTheme();
|
||||||
print("Thème basculé : ${themeProvider.isDarkMode ? 'Sombre' : 'Clair'}");
|
debugPrint("[LOG] Thème basculé : ${themeProvider.isDarkMode ? 'Sombre' : 'Clair'}");
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
// Formulaire de connexion
|
||||||
Center(
|
Center(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
@@ -209,18 +235,18 @@ class _LoginScreenState extends State<LoginScreen> with SingleTickerProviderStat
|
|||||||
icon: Icons.email,
|
icon: Icons.email,
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value == null || value.isEmpty) {
|
if (value == null || value.isEmpty) {
|
||||||
print("Erreur : champ email vide.");
|
debugPrint("[ERROR] Champ email vide.");
|
||||||
return 'Veuillez entrer votre email';
|
return 'Veuillez entrer votre email';
|
||||||
}
|
}
|
||||||
if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value)) {
|
if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value)) {
|
||||||
print("Erreur : email invalide.");
|
debugPrint("[ERROR] Email invalide.");
|
||||||
return 'Veuillez entrer un email valide';
|
return 'Veuillez entrer un email valide';
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
onSaved: (value) {
|
onSaved: (value) {
|
||||||
_email = value!;
|
_email = value!;
|
||||||
print("Email enregistré : $_email");
|
debugPrint("[LOG] Email enregistré : $_email");
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
@@ -230,27 +256,25 @@ class _LoginScreenState extends State<LoginScreen> with SingleTickerProviderStat
|
|||||||
obscureText: !_isPasswordVisible,
|
obscureText: !_isPasswordVisible,
|
||||||
suffixIcon: IconButton(
|
suffixIcon: IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
_isPasswordVisible
|
_isPasswordVisible ? Icons.visibility : Icons.visibility_off,
|
||||||
? Icons.visibility
|
|
||||||
: Icons.visibility_off,
|
|
||||||
color: theme.iconTheme.color,
|
color: theme.iconTheme.color,
|
||||||
),
|
),
|
||||||
onPressed: _togglePasswordVisibility,
|
onPressed: _togglePasswordVisibility,
|
||||||
),
|
),
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value == null || value.isEmpty) {
|
if (value == null || value.isEmpty) {
|
||||||
print("Erreur : champ mot de passe vide.");
|
debugPrint("[ERROR] Champ mot de passe 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 : mot de passe trop court.");
|
debugPrint("[ERROR] Mot de passe 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!;
|
_password = value!;
|
||||||
print("Mot de passe enregistré.");
|
debugPrint("[LOG] Mot de passe enregistré.");
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(height: 30),
|
const SizedBox(height: 30),
|
||||||
@@ -272,28 +296,26 @@ class _LoginScreenState extends State<LoginScreen> with SingleTickerProviderStat
|
|||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
print("Redirection vers la page d'inscription");
|
debugPrint("[LOG] Redirection vers la page d'inscription.");
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => SignUpScreen(),
|
builder: (context) => const SignUpScreen(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
'Pas encore de compte ? Inscrivez-vous',
|
'Pas encore de compte ? Inscrivez-vous',
|
||||||
style: theme.textTheme.bodyMedium!
|
style: theme.textTheme.bodyMedium!.copyWith(color: Colors.white70),
|
||||||
.copyWith(color: Colors.white70),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
print("Mot de passe oublié");
|
debugPrint("[LOG] Mot de passe oublié cliqué.");
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
'Mot de passe oublié ?',
|
'Mot de passe oublié ?',
|
||||||
style: theme.textTheme.bodyMedium!
|
style: theme.textTheme.bodyMedium!.copyWith(color: Colors.white70),
|
||||||
.copyWith(color: Colors.white70),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (_showErrorMessage)
|
if (_showErrorMessage)
|
||||||
@@ -326,9 +348,8 @@ class _LoginScreenState extends State<LoginScreen> with SingleTickerProviderStat
|
|||||||
),
|
),
|
||||||
if (isKeyboardVisible)
|
if (isKeyboardVisible)
|
||||||
Text(
|
Text(
|
||||||
'© 2024 LionsDev',
|
'© 2024',
|
||||||
style: theme.textTheme.bodyMedium!
|
style: theme.textTheme.bodyMedium!.copyWith(color: Colors.white70),
|
||||||
.copyWith(color: Colors.white70),
|
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -339,7 +360,7 @@ class _LoginScreenState extends State<LoginScreen> with SingleTickerProviderStat
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Widget réutilisable pour les champs de texte avec validation et design amélioré
|
/// Méthode pour construire les champs de formulaire avec les styles adaptés.
|
||||||
Widget _buildTextFormField({
|
Widget _buildTextFormField({
|
||||||
required String label,
|
required String label,
|
||||||
required IconData icon,
|
required IconData icon,
|
||||||
|
|||||||
@@ -89,9 +89,6 @@ class _SignUpScreenState extends State<SignUpScreen> {
|
|||||||
|
|
||||||
// Envoi des informations pour créer un nouvel utilisateur
|
// Envoi des informations pour créer un nouvel utilisateur
|
||||||
final createdUser = await _userRemoteDataSource.createUser(user);
|
final createdUser = await _userRemoteDataSource.createUser(user);
|
||||||
if (createdUser == null) {
|
|
||||||
throw Exception("La création du compte a échoué.");
|
|
||||||
}
|
|
||||||
|
|
||||||
print("Utilisateur créé : ${createdUser.userId}");
|
print("Utilisateur créé : ${createdUser.userId}");
|
||||||
|
|
||||||
|
|||||||
101
lib/presentation/screens/social/social_card.dart
Normal file
101
lib/presentation/screens/social/social_card.dart
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import '../../../core/constants/colors.dart';
|
||||||
|
import '../../../data/models/social_post_model.dart';
|
||||||
|
import '../../widgets/social_header_widget.dart';
|
||||||
|
import '../../widgets/social_interaction_row.dart';
|
||||||
|
import '../../widgets/swipe_background.dart'; // Import du widget de swipe
|
||||||
|
|
||||||
|
class SocialCard extends StatelessWidget {
|
||||||
|
final SocialPost post;
|
||||||
|
final VoidCallback onLike;
|
||||||
|
final VoidCallback onComment;
|
||||||
|
final VoidCallback onShare;
|
||||||
|
final VoidCallback onDeletePost;
|
||||||
|
final VoidCallback onEditPost;
|
||||||
|
|
||||||
|
const SocialCard({
|
||||||
|
Key? key,
|
||||||
|
required this.post,
|
||||||
|
required this.onLike,
|
||||||
|
required this.onComment,
|
||||||
|
required this.onShare,
|
||||||
|
required this.onDeletePost,
|
||||||
|
required this.onEditPost,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Dismissible(
|
||||||
|
key: ValueKey(post.postText),
|
||||||
|
direction: DismissDirection.endToStart,
|
||||||
|
onDismissed: (direction) {
|
||||||
|
onDeletePost();
|
||||||
|
},
|
||||||
|
background: SwipeBackground(
|
||||||
|
color: Colors.red,
|
||||||
|
icon: Icons.delete,
|
||||||
|
label: 'Supprimer',
|
||||||
|
),
|
||||||
|
child: Card(
|
||||||
|
color: AppColors.cardColor,
|
||||||
|
margin: const EdgeInsets.symmetric(vertical: 10.0),
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15.0)),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(12.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SocialHeaderWidget(
|
||||||
|
post: post,
|
||||||
|
onEditPost: () {
|
||||||
|
print('Modifier le post');
|
||||||
|
},
|
||||||
|
menuKey: GlobalKey(),
|
||||||
|
menuContext: context,
|
||||||
|
onClosePost: () {
|
||||||
|
print('Close post');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text (
|
||||||
|
post.postText,
|
||||||
|
style: TextStyle(
|
||||||
|
color: AppColors.textSecondary,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
if (post.postImage.isNotEmpty)
|
||||||
|
ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: Image.asset(post.postImage, fit: BoxFit.cover),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Row(
|
||||||
|
children: post.tags
|
||||||
|
.map((tag) => Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 8),
|
||||||
|
child: Text(
|
||||||
|
tag,
|
||||||
|
style: TextStyle(
|
||||||
|
color: AppColors.accentColor,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
SocialInteractionRow(
|
||||||
|
post: post,
|
||||||
|
onLike: onLike,
|
||||||
|
onComment: onComment,
|
||||||
|
onShare: onShare,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
103
lib/presentation/screens/social/social_content.dart
Normal file
103
lib/presentation/screens/social/social_content.dart
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import '../../../data/models/social_post_model.dart';
|
||||||
|
import 'social_card.dart'; // Import de la SocialCard
|
||||||
|
|
||||||
|
class SocialContent extends StatefulWidget {
|
||||||
|
const SocialContent({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_SocialContentState createState() => _SocialContentState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SocialContentState extends State<SocialContent> {
|
||||||
|
final List<SocialPost> _posts = [
|
||||||
|
SocialPost(
|
||||||
|
userName: 'John Doe',
|
||||||
|
userImage: 'lib/assets/images/profile_picture.png',
|
||||||
|
postText: 'Une belle journée au parc avec des amis ! 🌳🌞',
|
||||||
|
postImage: 'lib/assets/images/placeholder.png',
|
||||||
|
likes: 12,
|
||||||
|
comments: 4,
|
||||||
|
badges: ['Explorer', 'Photographe'],
|
||||||
|
tags: ['#Nature', '#FunDay'],
|
||||||
|
shares: 25,
|
||||||
|
),
|
||||||
|
SocialPost(
|
||||||
|
userName: 'Jane Smith',
|
||||||
|
userImage: 'lib/assets/images/profile_picture.png',
|
||||||
|
postText: 'Mon nouveau chat est tellement mignon 🐱',
|
||||||
|
postImage: 'lib/assets/images/placeholder.png',
|
||||||
|
likes: 30,
|
||||||
|
comments: 8,
|
||||||
|
badges: ['Animal Lover', 'Partageur'],
|
||||||
|
tags: ['#Chat', '#Cuteness'],
|
||||||
|
shares: 25,
|
||||||
|
),
|
||||||
|
SocialPost(
|
||||||
|
userName: 'Alice Brown',
|
||||||
|
userImage: 'lib/assets/images/profile_picture.png',
|
||||||
|
postText: 'Café du matin avec une vue magnifique ☕️',
|
||||||
|
postImage: 'lib/assets/images/placeholder.png',
|
||||||
|
likes: 45,
|
||||||
|
comments: 15,
|
||||||
|
badges: ['Gourmet', 'Partageur'],
|
||||||
|
tags: ['#Café', '#MorningVibes'],
|
||||||
|
shares: 25,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ListView.builder(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
itemCount: _posts.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final post = _posts[index];
|
||||||
|
return SocialCard(
|
||||||
|
post: post,
|
||||||
|
onLike: () {
|
||||||
|
setState(() {
|
||||||
|
_posts[index] = SocialPost(
|
||||||
|
userName: post.userName,
|
||||||
|
userImage: post.userImage,
|
||||||
|
postText: post.postText,
|
||||||
|
postImage: post.postImage,
|
||||||
|
likes: post.likes + 1,
|
||||||
|
comments: post.comments,
|
||||||
|
badges: post.badges,
|
||||||
|
tags: post.tags,
|
||||||
|
shares: post.shares + 1,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(content: Text('Like ajouté')),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onComment: () {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(content: Text('Commentaire ajouté')),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onShare: () {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(content: Text('Post partagé')),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onDeletePost: () {
|
||||||
|
setState(() {
|
||||||
|
_posts.removeAt(index);
|
||||||
|
});
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(content: Text('Post supprimé')),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onEditPost: () {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(content: Text('Post modifié')),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +1,25 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'social_content.dart'; // Import du fichier qui contient SocialContent
|
||||||
|
|
||||||
class SocialScreen extends StatelessWidget {
|
class SocialScreen extends StatelessWidget {
|
||||||
const SocialScreen({super.key});
|
const SocialScreen({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Center(
|
return Scaffold(
|
||||||
child: Text(
|
backgroundColor: const Color(0xFF1E1E2C), // Fond noir pour correspondre à un thème sombre
|
||||||
'Social',
|
appBar: AppBar(
|
||||||
style: TextStyle(
|
title: const Text(
|
||||||
color: Colors.white,
|
'Social',
|
||||||
fontSize: 24,
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
color: Colors.white,
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
backgroundColor: Colors.black, // AppBar avec fond noir pour un design cohérent
|
||||||
),
|
),
|
||||||
|
body: SocialContent(), // Appel à SocialContent pour afficher le contenu
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:afterwork/core/utils/date_formatter.dart';
|
import 'package:afterwork/core/utils/date_formatter.dart';
|
||||||
import 'event_status_badge.dart';
|
|
||||||
import 'event_menu.dart';
|
import 'event_menu.dart';
|
||||||
|
|
||||||
class EventHeader extends StatelessWidget {
|
class EventHeader extends StatelessWidget {
|
||||||
@@ -8,9 +7,10 @@ class EventHeader extends StatelessWidget {
|
|||||||
final String userLastName;
|
final String userLastName;
|
||||||
final String? eventDate;
|
final String? eventDate;
|
||||||
final String? imageUrl;
|
final String? imageUrl;
|
||||||
final String location; // Ajout du paramètre "location" pour le lieu de l'événement
|
final String location;
|
||||||
final GlobalKey menuKey;
|
final GlobalKey menuKey;
|
||||||
final BuildContext menuContext;
|
final BuildContext menuContext;
|
||||||
|
final VoidCallback onClose; // Ajout d'un callback pour l'action de fermeture
|
||||||
|
|
||||||
const EventHeader({
|
const EventHeader({
|
||||||
Key? key,
|
Key? key,
|
||||||
@@ -18,9 +18,10 @@ class EventHeader extends StatelessWidget {
|
|||||||
required this.userLastName,
|
required this.userLastName,
|
||||||
this.eventDate,
|
this.eventDate,
|
||||||
this.imageUrl,
|
this.imageUrl,
|
||||||
required this.location, // Initialisation de "location"
|
required this.location,
|
||||||
required this.menuKey,
|
required this.menuKey,
|
||||||
required this.menuContext,
|
required this.menuContext,
|
||||||
|
required this.onClose, // Initialisation du callback de fermeture
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -33,68 +34,85 @@ class EventHeader extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
String formattedDate = date != null ? DateFormatter.formatDate(date) : 'Date inconnue';
|
String formattedDate = date != null ? DateFormatter.formatDate(date) : 'Date inconnue';
|
||||||
|
|
||||||
return Row(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
CircleAvatar(
|
Row(
|
||||||
backgroundColor: Colors.grey.shade800,
|
children: [
|
||||||
backgroundImage: imageUrl != null && imageUrl!.isNotEmpty
|
CircleAvatar(
|
||||||
? NetworkImage(imageUrl!)
|
backgroundColor: Colors.grey.shade800,
|
||||||
: const AssetImage('lib/assets/images/placeholder.png') as ImageProvider,
|
backgroundImage: imageUrl != null && imageUrl!.isNotEmpty
|
||||||
radius: 22,
|
? NetworkImage(imageUrl!)
|
||||||
),
|
: const AssetImage('lib/assets/images/placeholder.png') as ImageProvider,
|
||||||
const SizedBox(width: 8),
|
radius: 22,
|
||||||
Expanded(
|
),
|
||||||
child: Column(
|
const SizedBox(width: 8),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
Expanded(
|
||||||
children: [
|
child: Column(
|
||||||
Text(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
'$userName $userLastName',
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
formattedDate,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.white54,
|
|
||||||
fontSize: 12,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
// Utilisation de Row pour afficher le lieu sur la même ligne
|
|
||||||
Row(
|
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Text(
|
||||||
child: Text(
|
'$userName $userLastName',
|
||||||
location.isNotEmpty ? location : 'Lieu non spécifié',
|
style: const TextStyle(
|
||||||
style: const TextStyle(
|
color: Colors.white,
|
||||||
color: Colors.white60,
|
fontSize: 14,
|
||||||
fontSize: 12,
|
fontWeight: FontWeight.w500,
|
||||||
fontStyle: FontStyle.italic,
|
),
|
||||||
),
|
maxLines: 1,
|
||||||
maxLines: 1,
|
overflow: TextOverflow.ellipsis,
|
||||||
overflow: TextOverflow.ellipsis,
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
formattedDate,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white54,
|
||||||
|
fontSize: 12,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(height: 4),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
location.isNotEmpty ? location : 'Lieu non spécifié',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white60,
|
||||||
|
fontSize: 12,
|
||||||
|
fontStyle: FontStyle.italic,
|
||||||
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
// Ajout des boutons dans le coin supérieur droit
|
||||||
|
Positioned(
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
key: menuKey,
|
||||||
|
icon: const Icon(Icons.more_vert, color: Colors.white54, size: 20),
|
||||||
|
splashRadius: 20,
|
||||||
|
onPressed: () {
|
||||||
|
showEventOptions(menuContext, menuKey);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.close, color: Colors.white54, size: 20),
|
||||||
|
splashRadius: 20,
|
||||||
|
onPressed: onClose, // Appel du callback de fermeture
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
|
||||||
key: menuKey,
|
|
||||||
icon: const Icon(Icons.more_vert, color: Colors.white54, size: 20),
|
|
||||||
splashRadius: 20,
|
|
||||||
onPressed: () {
|
|
||||||
showEventOptions(menuContext, menuKey);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
66
lib/presentation/widgets/friend_card.dart
Normal file
66
lib/presentation/widgets/friend_card.dart
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// [FriendCard] est un widget représentant une carte d'ami.
|
||||||
|
/// Cette carte inclut l'image de l'ami, son nom, et un bouton qui permet
|
||||||
|
/// d'interagir avec cette carte (via le `onTap`).
|
||||||
|
///
|
||||||
|
/// Ce widget est conçu pour être utilisé dans des listes d'amis, comme
|
||||||
|
/// dans la section "Mes Amis" de l'application.
|
||||||
|
class FriendCard extends StatelessWidget {
|
||||||
|
final String name; // Le nom de l'ami
|
||||||
|
final String imageUrl; // URL de l'image de profil de l'ami
|
||||||
|
final VoidCallback onTap; // Fonction callback exécutée lors d'un clic sur la carte
|
||||||
|
|
||||||
|
/// Constructeur de [FriendCard] avec des paramètres obligatoires.
|
||||||
|
const FriendCard({
|
||||||
|
Key? key,
|
||||||
|
required this.name,
|
||||||
|
required this.imageUrl,
|
||||||
|
required this.onTap,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
// Lorsque l'utilisateur clique sur la carte, on déclenche la fonction onTap.
|
||||||
|
debugPrint("[LOG] Carte de l'ami $name cliquée.");
|
||||||
|
onTap(); // Exécuter le callback fourni
|
||||||
|
},
|
||||||
|
child: Card(
|
||||||
|
elevation: 4, // Élévation de la carte pour donner un effet d'ombre
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16.0)), // Bordure arrondie
|
||||||
|
color: Colors.grey.shade800, // Couleur de fond de la carte
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(12.0), // Padding interne pour espacer le contenu
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
// Image de profil de l'ami affichée sous forme de cercle
|
||||||
|
Hero(
|
||||||
|
tag: name, // Le tag Hero permet de créer une transition animée vers un autre écran.
|
||||||
|
child: CircleAvatar(
|
||||||
|
backgroundImage: NetworkImage(imageUrl), // Charger l'image depuis l'URL
|
||||||
|
radius: 30, // Taille de l'avatar
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16), // Espacement entre l'image et le nom
|
||||||
|
// Le nom de l'ami avec un texte en gras et blanc
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
name,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 18, // Taille de la police
|
||||||
|
color: Colors.white, // Couleur du texte
|
||||||
|
fontWeight: FontWeight.bold, // Style en gras
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Icône de flèche indiquant que la carte est cliquable
|
||||||
|
Icon(Icons.chevron_right, color: Colors.white70),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
105
lib/presentation/widgets/friend_detail_screen.dart
Normal file
105
lib/presentation/widgets/friend_detail_screen.dart
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:logger/logger.dart';
|
||||||
|
|
||||||
|
/// [FriendDetailScreen] affiche les détails d'un ami, incluant son nom, son image de profil,
|
||||||
|
/// et une option pour envoyer un message.
|
||||||
|
///
|
||||||
|
/// Utilisé lorsque l'utilisateur clique sur un ami pour voir plus de détails.
|
||||||
|
class FriendDetailScreen extends StatelessWidget {
|
||||||
|
final String name; // Nom de l'ami
|
||||||
|
final String imageUrl; // URL de l'image de profil de l'ami
|
||||||
|
final String friendId; // ID de l'ami pour des actions futures
|
||||||
|
final Logger _logger = Logger(); // Logger pour suivre les actions dans le terminal
|
||||||
|
|
||||||
|
/// Constructeur de la classe [FriendDetailScreen].
|
||||||
|
/// [name], [imageUrl], et [friendId] doivent être fournis.
|
||||||
|
FriendDetailScreen({
|
||||||
|
Key? key,
|
||||||
|
required this.name,
|
||||||
|
required this.imageUrl,
|
||||||
|
required this.friendId,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
/// Méthode statique pour lancer l'écran des détails d'un ami.
|
||||||
|
static void open(BuildContext context, String friendId, String name, String imageUrl) {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (_) => FriendDetailScreen(
|
||||||
|
friendId: friendId,
|
||||||
|
name: name,
|
||||||
|
imageUrl: imageUrl,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
_logger.i('[LOG] Affichage des détails de l\'ami : $name (ID: $friendId)');
|
||||||
|
|
||||||
|
// Utilise `AssetImage` si `imageUrl` est vide ou ne contient pas d'URL valide.
|
||||||
|
final imageProvider = imageUrl.isNotEmpty && Uri.tryParse(imageUrl)?.hasAbsolutePath == true
|
||||||
|
? NetworkImage(imageUrl)
|
||||||
|
: const AssetImage('lib/assets/images/default_avatar.png') as ImageProvider;
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(name), // Titre de l'écran affichant le nom de l'ami
|
||||||
|
backgroundColor: Colors.grey.shade800,
|
||||||
|
),
|
||||||
|
body: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0), // Espacement autour du contenu
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
// Affichage de l'image de l'ami avec animation `Hero`
|
||||||
|
Hero(
|
||||||
|
tag: friendId, // Tag unique pour l'animation Hero basée sur l'ID de l'ami
|
||||||
|
child: CircleAvatar(
|
||||||
|
radius: 50,
|
||||||
|
backgroundImage: imageProvider,
|
||||||
|
backgroundColor: Colors.grey.shade800,
|
||||||
|
onBackgroundImageError: (error, stackTrace) {
|
||||||
|
_logger.e('[ERROR] Erreur lors du chargement de l\'image pour $name (ID: $friendId): $error');
|
||||||
|
},
|
||||||
|
child: imageUrl.isEmpty
|
||||||
|
? const Icon(Icons.person, size: 50, color: Colors.white) // Icône par défaut si aucune image n'est disponible
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16), // Espacement entre l'image et le texte
|
||||||
|
|
||||||
|
// Affichage du nom de l'ami
|
||||||
|
Text(
|
||||||
|
name,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 24, // Taille de la police pour le nom
|
||||||
|
fontWeight: FontWeight.bold, // Texte en gras
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20), // Espacement avant le bouton
|
||||||
|
|
||||||
|
// Bouton pour envoyer un message à l'ami
|
||||||
|
ElevatedButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
_logger.i('[LOG] Envoi d\'un message à $name (ID: $friendId)');
|
||||||
|
// Logique future pour envoyer un message à l'ami
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.message),
|
||||||
|
label: const Text('Envoyer un message'),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.teal, // Couleur de fond du bouton
|
||||||
|
foregroundColor: Colors.white, // Couleur du texte et de l'icône
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
48
lib/presentation/widgets/friends_appbar.dart
Normal file
48
lib/presentation/widgets/friends_appbar.dart
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:logger/logger.dart';
|
||||||
|
|
||||||
|
/// [FriendsAppBar] est une barre d'application personnalisée utilisée dans l'écran des amis.
|
||||||
|
/// Elle permet d'ajouter et de gérer les amis avec des actions spécifiques.
|
||||||
|
/// Toutes les actions sont loguées pour une traçabilité complète.
|
||||||
|
class FriendsAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||||
|
final Logger _logger = Logger(); // Logger pour tracer toutes les actions
|
||||||
|
|
||||||
|
FriendsAppBar({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AppBar(
|
||||||
|
backgroundColor: Colors.grey.shade800, // Couleur de fond de la barre d'application
|
||||||
|
title: const Text(
|
||||||
|
'Mes Amis', // Titre de l'écran
|
||||||
|
style: TextStyle(color: Colors.white), // Couleur du texte
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
Tooltip(
|
||||||
|
message: 'Ajouter un ami', // Améliore l'accessibilité pour l'icône d'ajout
|
||||||
|
child: IconButton(
|
||||||
|
icon: const Icon(Icons.group_add, color: Colors.white), // Icône pour ajouter un nouvel ami
|
||||||
|
onPressed: () {
|
||||||
|
_logger.i("[LOG] Bouton 'Ajouter un ami' pressé.");
|
||||||
|
// Logique à implémenter pour ajouter un nouvel ami
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Tooltip(
|
||||||
|
message: 'Gérer les groupes d\'amis', // Améliore l'accessibilité pour l'icône de gestion des groupes
|
||||||
|
child: IconButton(
|
||||||
|
icon: const Icon(Icons.group, color: Colors.white), // Icône pour gérer les groupes d'amis
|
||||||
|
onPressed: () {
|
||||||
|
_logger.i("[LOG] Bouton 'Gérer les groupes' pressé.");
|
||||||
|
// Logique à implémenter pour gérer les groupes d'amis
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Définit la taille préférée de la barre d'application.
|
||||||
|
@override
|
||||||
|
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
|
||||||
|
}
|
||||||
73
lib/presentation/widgets/friends_circle.dart
Normal file
73
lib/presentation/widgets/friends_circle.dart
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:logger/logger.dart';
|
||||||
|
import '../../domain/entities/friend.dart';
|
||||||
|
|
||||||
|
/// [FriendsCircle] est un widget qui affiche un ami sous forme d'avatar circulaire avec son nom.
|
||||||
|
/// L'avatar est cliquable, permettant à l'utilisateur d'accéder aux détails de l'ami
|
||||||
|
/// ou de déclencher d'autres actions liées.
|
||||||
|
class FriendsCircle extends StatelessWidget {
|
||||||
|
final Friend friend; // Représente l'entité Friend à afficher (nom et image).
|
||||||
|
final VoidCallback onTap; // Fonction callback exécutée lorsque l'utilisateur clique sur l'avatar.
|
||||||
|
|
||||||
|
// Logger pour tracer les actions dans le terminal
|
||||||
|
final Logger _logger = Logger();
|
||||||
|
|
||||||
|
/// Constructeur pour [FriendsCircle], prenant en entrée un ami et une fonction de callback.
|
||||||
|
FriendsCircle({
|
||||||
|
Key? key,
|
||||||
|
required this.friend, // L'ami à afficher (doit inclure friendId, name, imageUrl).
|
||||||
|
required this.onTap, // Action à exécuter lors du clic.
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
// Combine firstName et lastName ou utilise "Ami inconnu" par défaut.
|
||||||
|
String displayName = [friend.firstName, friend.lastName]
|
||||||
|
.where((namePart) => namePart != null && namePart.isNotEmpty)
|
||||||
|
.join(" ")
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
if (displayName.isEmpty) {
|
||||||
|
displayName = 'Ami inconnu';
|
||||||
|
}
|
||||||
|
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
_logger.i('[LOG] Avatar de ${displayName.trim()} cliqué');
|
||||||
|
onTap(); // Exécute l'action de clic définie par l'utilisateur
|
||||||
|
},
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center, // Centre verticalement les éléments de la colonne.
|
||||||
|
children: [
|
||||||
|
Hero(
|
||||||
|
tag: friend.friendId, // Tag unique pour l'animation Hero basé sur l'ID de l'ami.
|
||||||
|
child: CircleAvatar(
|
||||||
|
radius: 40,
|
||||||
|
backgroundImage: friend.imageUrl != null && friend.imageUrl!.isNotEmpty
|
||||||
|
? NetworkImage(friend.imageUrl!) // Utilise NetworkImage si l'URL est valide
|
||||||
|
: AssetImage('lib/assets/images/default_avatar.png') as ImageProvider, // Utilise AssetImage pour l'avatar par défaut
|
||||||
|
onBackgroundImageError: (error, stackTrace) {
|
||||||
|
_logger.e('[ERROR] Erreur lors du chargement de l\'image pour ${displayName.trim()} : $error');
|
||||||
|
},
|
||||||
|
backgroundColor: Colors.grey.shade800, // Fond si l'image ne charge pas.
|
||||||
|
child: friend.imageUrl == null || friend.imageUrl!.isEmpty
|
||||||
|
? const Icon(Icons.person, size: 40, color: Colors.white) // Icône de remplacement si aucune image n'est disponible
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8), // Ajoute un espace entre l'image et le texte.
|
||||||
|
Text(
|
||||||
|
displayName, // Affiche le nom de l'ami sous l'avatar ou une valeur par défaut.
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
38
lib/presentation/widgets/search_friends.dart
Normal file
38
lib/presentation/widgets/search_friends.dart
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// [SearchFriends] est un widget permettant à l'utilisateur de rechercher des amis.
|
||||||
|
/// Il inclut un champ de texte stylisé pour saisir la requête de recherche.
|
||||||
|
/// Chaque modification du texte dans le champ génère un log dans le terminal pour suivre en temps réel l'activité.
|
||||||
|
class SearchFriends extends StatelessWidget {
|
||||||
|
const SearchFriends({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return TextField(
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white, // Le texte saisi est de couleur blanche.
|
||||||
|
),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'Rechercher un ami...', // Indication textuelle pour aider l'utilisateur.
|
||||||
|
hintStyle: const TextStyle(
|
||||||
|
color: Colors.white54, // Style de l'indicateur avec une couleur plus claire.
|
||||||
|
),
|
||||||
|
filled: true,
|
||||||
|
fillColor: Colors.grey.shade800, // Couleur de fond du champ de recherche.
|
||||||
|
prefixIcon: const Icon(
|
||||||
|
Icons.search, // Icône de loupe pour indiquer la recherche.
|
||||||
|
color: Colors.white54, // Couleur de l'icône de recherche.
|
||||||
|
),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(30.0), // Bordure arrondie pour un style moderne.
|
||||||
|
borderSide: BorderSide.none, // Aucune bordure visible pour un look propre.
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onChanged: (value) {
|
||||||
|
// Fonction appelée chaque fois que l'utilisateur modifie le texte dans le champ de recherche.
|
||||||
|
debugPrint('[LOG] Recherche d\'amis : $value'); // Log de chaque saisie.
|
||||||
|
// Vous pouvez ajouter ici la logique de filtrage de la liste des amis en fonction de la recherche.
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
49
lib/presentation/widgets/social_badge_widget.dart
Normal file
49
lib/presentation/widgets/social_badge_widget.dart
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import '../../../core/constants/colors.dart';
|
||||||
|
|
||||||
|
class BadgeWidget extends StatelessWidget {
|
||||||
|
final String badge;
|
||||||
|
final IconData? icon; // Optionnel : ajouter une icône au badge
|
||||||
|
|
||||||
|
const BadgeWidget({
|
||||||
|
Key? key,
|
||||||
|
required this.badge,
|
||||||
|
this.icon,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.accentColor.withOpacity(0.2),
|
||||||
|
borderRadius: BorderRadius.circular(12.0),
|
||||||
|
border: Border.all(
|
||||||
|
color: AppColors.accentColor,
|
||||||
|
width: 1.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
if (icon != null) ...[
|
||||||
|
Icon(
|
||||||
|
icon,
|
||||||
|
color: AppColors.accentColor,
|
||||||
|
size: 16.0,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 5),
|
||||||
|
],
|
||||||
|
Text(
|
||||||
|
badge,
|
||||||
|
style: TextStyle(
|
||||||
|
color: AppColors.accentColor,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
119
lib/presentation/widgets/social_header_widget.dart
Normal file
119
lib/presentation/widgets/social_header_widget.dart
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import '../../../core/constants/colors.dart';
|
||||||
|
import '../../../data/models/social_post_model.dart';
|
||||||
|
import 'social_badge_widget.dart'; // Import du widget BadgeWidget
|
||||||
|
|
||||||
|
class SocialHeaderWidget extends StatelessWidget {
|
||||||
|
final SocialPost post;
|
||||||
|
final VoidCallback onEditPost;
|
||||||
|
final VoidCallback onClosePost; // Ajout du callback pour la fermeture du post
|
||||||
|
final GlobalKey menuKey;
|
||||||
|
final BuildContext menuContext;
|
||||||
|
|
||||||
|
const SocialHeaderWidget({
|
||||||
|
Key? key,
|
||||||
|
required this.post,
|
||||||
|
required this.onEditPost,
|
||||||
|
required this.onClosePost, // Initialisation du callback de fermeture
|
||||||
|
required this.menuKey,
|
||||||
|
required this.menuContext,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
CircleAvatar(
|
||||||
|
backgroundColor: Colors.grey.shade800,
|
||||||
|
backgroundImage: post.userImage.isNotEmpty
|
||||||
|
? AssetImage(post.userImage)
|
||||||
|
: const AssetImage('lib/assets/images/placeholder.png'),
|
||||||
|
radius: 22,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
post.userName,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Wrap(
|
||||||
|
spacing: 6,
|
||||||
|
children: post.badges
|
||||||
|
.map((badge) => BadgeWidget(badge: badge))
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
// Ajout des boutons dans le coin supérieur droit
|
||||||
|
Positioned(
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min, // Réduit la taille du Row au minimum
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
key: menuKey,
|
||||||
|
icon: const Icon(Icons.more_vert, color: Colors.white54, size: 20),
|
||||||
|
splashRadius: 20,
|
||||||
|
onPressed: () {
|
||||||
|
_showOptionsMenu(menuContext, menuKey);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4), // Espacement entre les boutons
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.close, color: Colors.white54, size: 20),
|
||||||
|
splashRadius: 20,
|
||||||
|
onPressed: onClosePost, // Appel du callback de fermeture
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showOptionsMenu(BuildContext context, GlobalKey menuKey) {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return Container(
|
||||||
|
color: AppColors.backgroundColor,
|
||||||
|
child: Wrap(
|
||||||
|
children: <Widget>[
|
||||||
|
ListTile(
|
||||||
|
leading: Icon(Icons.edit, color: AppColors.iconPrimary),
|
||||||
|
title: const Text('Modifier'),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
onEditPost();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: Icon(Icons.delete, color: AppColors.iconPrimary),
|
||||||
|
title: const Text('Supprimer'),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
48
lib/presentation/widgets/social_interaction_row.dart
Normal file
48
lib/presentation/widgets/social_interaction_row.dart
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import '../../../core/constants/colors.dart';
|
||||||
|
import '../../../data/models/social_post_model.dart';
|
||||||
|
|
||||||
|
class SocialInteractionRow extends StatelessWidget {
|
||||||
|
final SocialPost post;
|
||||||
|
final VoidCallback onLike;
|
||||||
|
final VoidCallback onComment;
|
||||||
|
final VoidCallback onShare;
|
||||||
|
|
||||||
|
const SocialInteractionRow({
|
||||||
|
Key? key,
|
||||||
|
required this.post,
|
||||||
|
required this.onLike,
|
||||||
|
required this.onComment,
|
||||||
|
required this.onShare,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: _buildIconButton(Icons.thumb_up_alt_outlined, 'J’aime', post.likes, onLike),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: _buildIconButton(Icons.comment_outlined, 'Commentaires', post.comments, onComment),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: _buildIconButton(Icons.share_outlined, 'Partages', post.shares, onShare),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildIconButton(IconData icon, String label, int count, VoidCallback onPressed) {
|
||||||
|
return TextButton.icon(
|
||||||
|
onPressed: onPressed,
|
||||||
|
icon: Icon(icon, color: AppColors.accentColor, size: 18),
|
||||||
|
label: Text(
|
||||||
|
'$label ($count)',
|
||||||
|
style: const TextStyle(color: Colors.white70, fontSize: 12),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:afterwork/core/constants/colors.dart';
|
|
||||||
|
|
||||||
class SubmitButton extends StatelessWidget {
|
class SubmitButton extends StatelessWidget {
|
||||||
final VoidCallback onPressed;
|
final VoidCallback onPressed;
|
||||||
|
|||||||
134
pubspec.lock
134
pubspec.lock
@@ -13,18 +13,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: args
|
name: args
|
||||||
sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
|
sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.0"
|
version: "2.6.0"
|
||||||
asn1lib:
|
asn1lib:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: asn1lib
|
name: asn1lib
|
||||||
sha256: "6b151826fcc95ff246cd219a0bf4c753ea14f4081ad71c61939becf3aba27f70"
|
sha256: "4bae5ae63e6d6dd17c4aac8086f3dec26c0236f6a0f03416c6c19d830c367cf5"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.5.5"
|
version: "1.5.8"
|
||||||
async:
|
async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -93,10 +93,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: cli_util
|
name: cli_util
|
||||||
sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19
|
sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.1"
|
version: "0.4.2"
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -125,10 +125,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: convert
|
name: convert
|
||||||
sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
|
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.1"
|
version: "3.1.2"
|
||||||
cross_file:
|
cross_file:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -141,18 +141,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: crypto
|
name: crypto
|
||||||
sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27
|
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.5"
|
version: "3.0.6"
|
||||||
csslib:
|
csslib:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: csslib
|
name: csslib
|
||||||
sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb"
|
sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
version: "1.0.2"
|
||||||
cupertino_icons:
|
cupertino_icons:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -205,26 +205,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: file
|
name: file
|
||||||
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
|
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.0.0"
|
version: "7.0.1"
|
||||||
file_selector_linux:
|
file_selector_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: file_selector_linux
|
name: file_selector_linux
|
||||||
sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492"
|
sha256: "712ce7fab537ba532c8febdb1a8f167b32441e74acd68c3ccb2e36dcb52c4ab2"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.9.2+1"
|
version: "0.9.3"
|
||||||
file_selector_macos:
|
file_selector_macos:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: file_selector_macos
|
name: file_selector_macos
|
||||||
sha256: cb284e267f8e2a45a904b5c094d2ba51d0aabfc20b1538ab786d9ef7dc2bf75c
|
sha256: "271ab9986df0c135d45c3cdb6bd0faa5db6f4976d3e4b437cf7d0f258d941bfc"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.9.4+1"
|
version: "0.9.4+2"
|
||||||
file_selector_platform_interface:
|
file_selector_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -237,10 +237,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: file_selector_windows
|
name: file_selector_windows
|
||||||
sha256: "2ad726953f6e8affbc4df8dc78b77c3b4a060967a291e528ef72ae846c60fb69"
|
sha256: "8f5d2f6590d51ecd9179ba39c64f722edc15226cc93dcc8698466ad36a4a85a4"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.9.3+2"
|
version: "0.9.3+3"
|
||||||
flare_flutter:
|
flare_flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -282,10 +282,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flutter_plugin_android_lifecycle
|
name: flutter_plugin_android_lifecycle
|
||||||
sha256: "9ee02950848f61c4129af3d6ec84a1cfc0e47931abc746b03e7a3bc3e8ff6eda"
|
sha256: "9b78450b89f059e96c9ebb355fa6b3df1d6b330436e0b885fb49594c41721398"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.22"
|
version: "2.0.23"
|
||||||
flutter_secure_storage:
|
flutter_secure_storage:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -380,10 +380,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: google_maps
|
name: google_maps
|
||||||
sha256: "463b38e5a92a05cde41220a11fd5eef3847031fef3e8cf295ac76ec453246907"
|
sha256: "4d6e199c561ca06792c964fa24b2bac7197bf4b401c2e1d23e345e5f9939f531"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.0.0"
|
version: "8.1.1"
|
||||||
google_maps_flutter:
|
google_maps_flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -396,26 +396,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: google_maps_flutter_android
|
name: google_maps_flutter_android
|
||||||
sha256: "10cf27bee8c560f8e69992b3a0f27ddf1d7acbea622ddb13ef3f587848a73f26"
|
sha256: bccf64ccbb2ea672dc62a61177b315a340af86b0228564484b023657544a3fd5
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.14.7"
|
version: "2.14.11"
|
||||||
google_maps_flutter_ios:
|
google_maps_flutter_ios:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: google_maps_flutter_ios
|
name: google_maps_flutter_ios
|
||||||
sha256: "3a484846fc56f15e47e3de1f5ea80a7ff2b31721d2faa88f390f3b3cf580c953"
|
sha256: "753ebf6a2bc24c5eba8e714c901345d858abd9694b1f878c43614fd3f06b8060"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.13.0"
|
version: "2.13.1"
|
||||||
google_maps_flutter_platform_interface:
|
google_maps_flutter_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: google_maps_flutter_platform_interface
|
name: google_maps_flutter_platform_interface
|
||||||
sha256: "099874463dc4c9bff04fe4b2b8cf7284d2455c2deead8f9a59a87e1b9f028c69"
|
sha256: a951981c22d790848efb9f114f81794945bc5c06bc566238a419a92f110af6cb
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.9.2"
|
version: "2.9.5"
|
||||||
google_maps_flutter_web:
|
google_maps_flutter_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -428,10 +428,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: html
|
name: html
|
||||||
sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a"
|
sha256: "1fc58edeaec4307368c60d59b7e15b9d658b57d7f3125098b6294153c75337ec"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.15.4"
|
version: "0.15.5"
|
||||||
http:
|
http:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -452,10 +452,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image
|
name: image
|
||||||
sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8"
|
sha256: f31d52537dc417fdcde36088fdf11d191026fd5e4fae742491ebd40e5a8bea7d
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.2.0"
|
version: "4.3.0"
|
||||||
image_picker:
|
image_picker:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -468,10 +468,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_android
|
name: image_picker_android
|
||||||
sha256: c0a6763d50b354793d0192afd0a12560b823147d3ded7c6b77daf658fa05cc85
|
sha256: "8faba09ba361d4b246dc0a17cb4289b3324c2b9f6db7b3d457ee69106a86bd32"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.12+13"
|
version: "0.8.12+17"
|
||||||
image_picker_for_web:
|
image_picker_for_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -484,10 +484,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_ios
|
name: image_picker_ios
|
||||||
sha256: "6703696ad49f5c3c8356d576d7ace84d1faf459afb07accbb0fae780753ff447"
|
sha256: "4f0568120c6fcc0aaa04511cb9f9f4d29fc3d0139884b1d06be88dcec7641d6b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.12"
|
version: "0.8.12+1"
|
||||||
image_picker_linux:
|
image_picker_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -572,10 +572,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: loading_icon_button
|
name: loading_icon_button
|
||||||
sha256: "5ef8c82796c19b96a5995457410037cbe05cb4840af766e50330b2d108dacdfd"
|
sha256: "682c03eaaeb6d00e3cce318ec365c0190eeacbc56ff1b6694a4561ed1e3b7e02"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.0.6"
|
version: "0.0.7"
|
||||||
logger:
|
logger:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -609,7 +609,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.4"
|
version: "1.0.4"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
|
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
|
||||||
@@ -644,18 +644,18 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: path_provider
|
name: path_provider
|
||||||
sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378
|
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.4"
|
version: "2.1.5"
|
||||||
path_provider_android:
|
path_provider_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_android
|
name: path_provider_android
|
||||||
sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7"
|
sha256: c464428172cb986b758c6d1724c603097febb8fb855aa265aeecc9280c294d4a
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.10"
|
version: "2.2.12"
|
||||||
path_provider_foundation:
|
path_provider_foundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -740,10 +740,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: platform
|
name: platform
|
||||||
sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
|
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.5"
|
version: "3.1.6"
|
||||||
plugin_platform_interface:
|
plugin_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -796,18 +796,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_android
|
name: shared_preferences_android
|
||||||
sha256: "480ba4345773f56acda9abf5f50bd966f581dac5d514e5fc4a18c62976bbba7e"
|
sha256: "3b9febd815c9ca29c9e3520d50ec32f49157711e143b7a4ca039eb87e8ade5ab"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.2"
|
version: "2.3.3"
|
||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_foundation
|
name: shared_preferences_foundation
|
||||||
sha256: c4b35f6cb8f63c147312c054ce7c2254c8066745125264f0c88739c417fc9d9f
|
sha256: "07e050c7cd39bad516f8d64c455f04508d09df104be326d8c02551590a0d513d"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.2"
|
version: "2.5.3"
|
||||||
shared_preferences_linux:
|
shared_preferences_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -905,10 +905,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: typed_data
|
name: typed_data
|
||||||
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
|
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.2"
|
version: "1.4.0"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -921,42 +921,42 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: video_player
|
name: video_player
|
||||||
sha256: e30df0d226c4ef82e2c150ebf6834b3522cf3f654d8e2f9419d376cdc071425d
|
sha256: "4a8c3492d734f7c39c2588a3206707a05ee80cef52e8c7f3b2078d430c84bc17"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.9.1"
|
version: "2.9.2"
|
||||||
video_player_android:
|
video_player_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: video_player_android
|
name: video_player_android
|
||||||
sha256: "38d8fe136c427abdce68b5e8c3c08ea29d7a794b453c7a51b12ecfad4aad9437"
|
sha256: "391e092ba4abe2f93b3e625bd6b6a6ec7d7414279462c1c0ee42b5ab8d0a0898"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.7.3"
|
version: "2.7.16"
|
||||||
video_player_avfoundation:
|
video_player_avfoundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: video_player_avfoundation
|
name: video_player_avfoundation
|
||||||
sha256: d1e9a824f2b324000dc8fb2dcb2a3285b6c1c7c487521c63306cc5b394f68a7c
|
sha256: cd5ab8a8bc0eab65ab0cea40304097edc46da574c8c1ecdee96f28cd8ef3792f
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.6.1"
|
version: "2.6.2"
|
||||||
video_player_platform_interface:
|
video_player_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: video_player_platform_interface
|
name: video_player_platform_interface
|
||||||
sha256: "236454725fafcacf98f0f39af0d7c7ab2ce84762e3b63f2cbb3ef9a7e0550bc6"
|
sha256: "229d7642ccd9f3dc4aba169609dd6b5f3f443bb4cc15b82f7785fcada5af9bbb"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.2.2"
|
version: "6.2.3"
|
||||||
video_player_web:
|
video_player_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: video_player_web
|
name: video_player_web
|
||||||
sha256: "6dcdd298136523eaf7dfc31abaf0dfba9aa8a8dbc96670e87e9d42b6f2caf774"
|
sha256: "881b375a934d8ebf868c7fb1423b2bfaa393a0a265fa3f733079a86536064a10"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.2"
|
version: "2.3.3"
|
||||||
vm_service:
|
vm_service:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -969,18 +969,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: web
|
name: web
|
||||||
sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062
|
sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
version: "1.1.0"
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: xdg_directories
|
name: xdg_directories
|
||||||
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
|
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.4"
|
version: "1.1.0"
|
||||||
xml:
|
xml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ dependencies:
|
|||||||
# Functional programming utilities
|
# Functional programming utilities
|
||||||
dartz: ^0.10.1
|
dartz: ^0.10.1
|
||||||
|
|
||||||
|
meta: any
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
rxdart: ^0.28.0
|
rxdart: ^0.28.0
|
||||||
|
|
||||||
@@ -82,6 +83,7 @@ flutter:
|
|||||||
- lib/assets/images/story_placeholder.png
|
- lib/assets/images/story_placeholder.png
|
||||||
- lib/assets/images/user_placeholder.png
|
- lib/assets/images/user_placeholder.png
|
||||||
- lib/assets/videos/test.mp4
|
- lib/assets/videos/test.mp4
|
||||||
|
- lib/assets/images/default_avatar.png
|
||||||
|
|
||||||
fonts:
|
fonts:
|
||||||
- family: Montserrat
|
- family: Montserrat
|
||||||
|
|||||||
Reference in New Issue
Block a user