Bon checkpoint + Refactoring

This commit is contained in:
DahoudG
2024-11-08 20:30:23 +00:00
parent 19f6efa995
commit 1e888f41e8
21 changed files with 721 additions and 223 deletions

View File

@@ -5,6 +5,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import '../../state_management/event_bloc.dart';
import '../dialogs/add_event_dialog.dart';
/// Écran principal des événements, affichant une liste d'événements.
class EventScreen extends StatefulWidget {
final String userId;
final String userFirstName;
@@ -63,9 +64,11 @@ class _EventScreenState extends State<EventScreen> {
body: BlocBuilder<EventBloc, EventState>(
builder: (context, state) {
if (state is EventLoading) {
print('[LOG] Chargement en cours des événements...');
return const Center(child: CircularProgressIndicator());
} else if (state is EventLoaded) {
final events = state.events;
print('[LOG] Nombre d\'événements à afficher: ${events.length}');
if (events.isEmpty) {
return const Center(child: Text('Aucun événement disponible.'));
}
@@ -74,6 +77,7 @@ class _EventScreenState extends State<EventScreen> {
itemCount: events.length,
itemBuilder: (context, index) {
final event = events[index];
print('[LOG] Affichage de l\'événement $index : ${event.title}');
return EventCard(
key: ValueKey(event.id),
event: event,
@@ -99,6 +103,7 @@ class _EventScreenState extends State<EventScreen> {
},
);
} else if (state is EventError) {
print('[ERROR] Message d\'erreur: ${state.message}');
return Center(child: Text('Erreur: ${state.message}'));
}
return const Center(child: Text('Aucun événement disponible.'));

View File

@@ -45,11 +45,13 @@ class _FriendsScreenState extends State<FriendsScreen> {
/// 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 &&
// Ajout d'une marge de 200 pixels pour détecter le bas de la liste plus tôt
if (_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent - 200 &&
!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);
debugPrint("[LOG] Scroll : Fin de liste atteinte, chargement de la page suivante.");
provider.fetchFriends(widget.userId, loadMore: true);
}
}
@@ -65,10 +67,12 @@ class _FriendsScreenState extends State<FriendsScreen> {
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);
if (!friendsProvider.isLoading) {
debugPrint("[LOG] Bouton Refresh : demande de rafraîchissement de la liste des amis");
friendsProvider.fetchFriends(widget.userId);
} else {
debugPrint("[LOG] Rafraîchissement en cours, action ignorée.");
}
},
),
],
@@ -110,27 +114,29 @@ class _FriendsScreenState extends State<FriendsScreen> {
mainAxisSpacing: 10,
crossAxisSpacing: 10,
),
itemCount: friendsProvider.friendsList.length,
itemCount: friendsProvider.friendsList.length + (friendsProvider.isLoading && friendsProvider.hasMore ? 1 : 0),
itemBuilder: (context, index) {
if (index >= friendsProvider.friendsList.length) {
return const Center(child: CircularProgressIndicator());
}
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.friendFirstName,
friend.imageUrl ?? '',
);
},
);
},
);
},
),
),

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:logger/logger.dart';
import 'package:provider/provider.dart';
import '../../../assets/animations/friend_expanding_card.dart';
import '../../../data/providers/friends_provider.dart';
@@ -7,10 +8,9 @@ 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 {
final Logger _logger = Logger(); // Logger pour une meilleure traçabilité
@override
Widget build(BuildContext context) {
return Scaffold(
@@ -29,6 +29,7 @@ class FriendsScreenWithProvider extends StatelessWidget {
final friends = friendsProvider.friendsList;
if (friends.isEmpty) {
_logger.i("[LOG] Aucun ami trouvé");
return const Center(
child: Text(
'Aucun ami trouvé',
@@ -51,19 +52,22 @@ class FriendsScreenWithProvider extends StatelessWidget {
child: const Icon(Icons.delete, color: Colors.white),
),
onDismissed: (direction) {
debugPrint("[LOG] Suppression de l'ami avec l'ID : ${friend.friendId}");
_logger.i("[LOG] Suppression de l'ami avec l'ID : ${friend.friendId}");
friendsProvider.removeFriend(friend.friendId);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Ami supprimé : ${friend.friendFirstName}")),
);
},
child: FriendExpandingCard(
name: friend.firstName ?? 'Ami inconnu',
name: friend.friendFirstName ?? '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'}");
_logger.i("[LOG] Envoi d'un message à l'ami : ${friend.friendFirstName ?? 'Ami inconnu'}");
},
onRemoveTap: () {
debugPrint("[LOG] Tentative de suppression de l'ami : ${friend.firstName ?? 'Ami inconnu'}");
_logger.i("[LOG] Tentative de suppression de l'ami : ${friend.friendFirstName ?? 'Ami inconnu'}");
friendsProvider.removeFriend(friend.friendId);
},
),
@@ -79,14 +83,13 @@ class FriendsScreenWithProvider extends StatelessWidget {
);
}
/// 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'}");
_logger.i("[LOG] Navigation vers les détails de l'ami : ${friend.friendFirstName}");
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => FriendDetailScreen(
name: friend.firstName ?? 'Ami inconnu',
name: friend.friendFirstName,
imageUrl: friend.imageUrl ?? '',
friendId: friend.friendId, // Passer l'ID pour récupérer les détails complets
friendId: friend.friendId,
),
));
}

View File

@@ -11,7 +11,6 @@ import '../../widgets/statistics_section_card.dart';
import '../../widgets/support_section_card.dart';
import '../../widgets/user_info_card.dart';
class ProfileScreen extends StatelessWidget {
const ProfileScreen({super.key});

View File

@@ -65,10 +65,14 @@ class EventBloc extends Bloc<EventEvent, EventState> {
// Gestion du chargement des événements
Future<void> _onLoadEvents(LoadEvents event, Emitter<EventState> emit) async {
emit(EventLoading());
print('[LOG] Début du chargement des événements pour l\'utilisateur ${event.userId}');
try {
final events = await remoteDataSource.getAllEvents();
final events = await remoteDataSource.getEventsCreatedByUserAndFriends(event.userId);
print('[LOG] Événements chargés: ${events.length} éléments récupérés.');
emit(EventLoaded(events));
} catch (e) {
print('[ERROR] Erreur lors du chargement des événements: $e');
emit(EventError('Erreur lors du chargement des événements.'));
}
}

View File

@@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import '../../../../core/constants/colors.dart';
/// [AccountDeletionCard] est un widget permettant à l'utilisateur de supprimer son compte.
/// Il affiche une confirmation avant d'effectuer l'action de suppression.
class AccountDeletionCard extends StatelessWidget {
final BuildContext context;
@@ -23,6 +25,7 @@ class AccountDeletionCard extends StatelessWidget {
);
}
/// Affiche un dialogue de confirmation pour la suppression du compte.
void _showDeleteConfirmationDialog() {
showDialog(
context: context,
@@ -39,14 +42,17 @@ class AccountDeletionCard extends StatelessWidget {
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
onPressed: () {
debugPrint("[LOG] Suppression du compte annulée.");
Navigator.of(context).pop();
},
child: Text('Annuler', style: TextStyle(color: AppColors.accentColor)),
),
TextButton(
onPressed: () {
print("[LOG] Suppression du compte confirmée.");
debugPrint("[LOG] Suppression du compte confirmée.");
Navigator.of(context).pop();
// Logique de suppression du compte
// Logique de suppression du compte ici.
},
child: const Text(
'Supprimer',

View File

@@ -1,35 +1,98 @@
import 'package:flutter/material.dart';
import 'custom_list_tile.dart';
import '../../../../core/constants/colors.dart';
/// [EditOptionsCard] permet à l'utilisateur d'accéder aux options d'édition du profil,
/// incluant la modification du profil, la photo et le mot de passe.
/// Les interactions sont entièrement loguées pour une traçabilité complète.
class EditOptionsCard extends StatelessWidget {
const EditOptionsCard({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
debugPrint("[LOG] Initialisation de EditOptionsCard");
return Card(
color: AppColors.cardColor,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
elevation: 2,
color: AppColors.cardColor.withOpacity(0.95),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
elevation: 4,
shadowColor: AppColors.darkPrimary.withOpacity(0.3),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CustomListTile(
_buildOption(
context,
icon: Icons.edit,
label: 'Éditer le profil',
onTap: () => print("[LOG] Édition du profil."),
logMessage: "Édition du profil",
onTap: () => debugPrint("[LOG] Édition du profil activée."),
),
CustomListTile(
_buildDivider(),
_buildOption(
context,
icon: Icons.camera_alt,
label: 'Changer la photo de profil',
onTap: () => print("[LOG] Changement de la photo de profil."),
logMessage: "Changement de la photo de profil",
onTap: () =>
debugPrint("[LOG] Changement de la photo de profil activé."),
),
CustomListTile(
_buildDivider(),
_buildOption(
context,
icon: Icons.lock,
label: 'Changer le mot de passe',
onTap: () => print("[LOG] Changement du mot de passe."),
logMessage: "Changement du mot de passe",
onTap: () => debugPrint("[LOG] Changement du mot de passe activé."),
),
],
),
);
}
/// Construit chaque option de la carte avec une animation de feedback visuel.
Widget _buildOption(
BuildContext context, {
required IconData icon,
required String label,
required String logMessage,
required VoidCallback onTap,
}) {
return InkWell(
onTap: () {
debugPrint("[LOG] $logMessage");
onTap();
},
splashColor: AppColors.accentColor.withOpacity(0.3),
highlightColor: AppColors.accentColor.withOpacity(0.1),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
child: Row(
children: [
Icon(icon, color: AppColors.accentColor),
const SizedBox(width: 16),
Text(
label,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.white,
),
),
const Spacer(),
const Icon(Icons.arrow_forward_ios, color: Colors.white, size: 16),
],
),
),
);
}
/// Construit un séparateur entre les options pour une meilleure structure visuelle.
Widget _buildDivider() {
return Divider(
color: Colors.white.withOpacity(0.2),
height: 1,
indent: 16,
endIndent: 16,
);
}
}

View File

@@ -1,7 +1,9 @@
import 'package:flutter/material.dart';
import '../../../../core/constants/colors.dart';
class ExpandableSectionCard extends StatelessWidget {
/// [ExpandableSectionCard] est une carte qui peut s'étendre pour révéler des éléments enfants.
/// Ce composant inclut des animations d'extension, des logs pour chaque action et une expérience utilisateur optimisée.
class ExpandableSectionCard extends StatefulWidget {
final String title;
final IconData icon;
final List<Widget> children;
@@ -13,25 +15,73 @@ class ExpandableSectionCard extends StatelessWidget {
required this.children,
}) : super(key: key);
@override
_ExpandableSectionCardState createState() => _ExpandableSectionCardState();
}
class _ExpandableSectionCardState extends State<ExpandableSectionCard> with SingleTickerProviderStateMixin {
bool _isExpanded = false;
late AnimationController _controller;
late Animation<double> _iconRotation;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 300),
);
_iconRotation = Tween<double>(begin: 0, end: 0.5).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeOut),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _toggleExpansion() {
setState(() {
_isExpanded = !_isExpanded;
_isExpanded ? _controller.forward() : _controller.reverse();
debugPrint("[LOG] ${_isExpanded ? 'Ouverture' : 'Fermeture'} de l'ExpandableSectionCard : ${widget.title}");
});
}
@override
Widget build(BuildContext context) {
return Card(
color: AppColors.cardColor,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
elevation: 2,
child: ExpansionTile(
title: Text(
title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white,
elevation: 3,
child: Column(
children: [
ListTile(
leading: Icon(widget.icon, color: AppColors.accentColor),
title: Text(
widget.title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
trailing: RotationTransition(
turns: _iconRotation,
child: Icon(Icons.expand_more, color: AppColors.accentColor),
),
onTap: _toggleExpansion,
),
),
leading: Icon(icon, color: AppColors.accentColor),
iconColor: AppColors.accentColor,
collapsedIconColor: AppColors.accentColor,
children: children,
// Contenu de l'expansion
AnimatedCrossFade(
duration: const Duration(milliseconds: 300),
firstChild: Container(),
secondChild: Column(children: widget.children),
crossFadeState: _isExpanded ? CrossFadeState.showSecond : CrossFadeState.showFirst,
),
],
),
);
}

View File

@@ -22,8 +22,8 @@ class FriendsCircle extends StatelessWidget {
@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.isNotEmpty)
String displayName = [friend.friendFirstName, friend.friendLastName]
.where((namePart) => namePart != null && namePart.isNotEmpty)
.join(" ")
.trim();
@@ -44,8 +44,10 @@ class FriendsCircle extends StatelessWidget {
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
? (friend.imageUrl!.startsWith('http') // Vérifie si l'image est une URL réseau
? NetworkImage(friend.imageUrl!)
: AssetImage(friend.imageUrl!) as ImageProvider) // Utilise AssetImage si c'est une ressource locale
: const AssetImage('lib/assets/images/default_avatar.png'), // 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');
},
@@ -71,3 +73,4 @@ class FriendsCircle extends StatelessWidget {
);
}
}

View File

@@ -4,8 +4,9 @@ import '../../../../core/constants/colors.dart';
import '../../../../data/providers/user_provider.dart';
import '../../../../domain/entities/user.dart';
/// [ProfileHeader] est un widget qui affiche l'en-tête du profil utilisateur.
/// Comprend le nom de l'utilisateur, une image de fond, et un bouton de déconnexion avec confirmation.
/// [ProfileHeader] : Un widget d'en-tête de profil utilisateur visuellement amélioré
/// avec un gradient élégant, des animations, et un bouton de déconnexion stylisé.
/// Entièrement logué pour une traçabilité complète.
class ProfileHeader extends StatelessWidget {
final User user;
@@ -13,97 +14,157 @@ class ProfileHeader extends StatelessWidget {
@override
Widget build(BuildContext context) {
debugPrint("[LOG] Initialisation de ProfileHeader pour l'utilisateur : ${user.userFirstName} ${user.userLastName}");
return SliverAppBar(
expandedHeight: 200.0,
expandedHeight: 250.0,
floating: false,
pinned: true,
elevation: 0,
backgroundColor: AppColors.darkPrimary,
flexibleSpace: FlexibleSpaceBar(
title: Text(
flexibleSpace: _buildFlexibleSpaceBar(user),
actions: [_buildLogoutButton(context)],
);
}
/// Construit un FlexibleSpaceBar avec un gradient et des animations.
/// Affiche le nom de l'utilisateur et l'image de profil avec un effet visuel enrichi.
Widget _buildFlexibleSpaceBar(User user) {
debugPrint("[LOG] Construction de FlexibleSpaceBar avec nom et image de profil.");
return FlexibleSpaceBar(
centerTitle: true,
title: Container(
padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 2.0),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.6),
borderRadius: BorderRadius.circular(12.0),
),
child: Text(
'Profil de ${user.userFirstName}',
style: TextStyle(
color: AppColors.accentColor,
fontSize: 20.0,
fontSize: 18.0,
fontWeight: FontWeight.bold,
),
),
background: Image.network(
user.profileImageUrl,
),
background: _buildProfileImageWithGradient(user.profileImageUrl),
);
}
/// Construit l'image de profil avec un overlay en gradient.
/// En cas d'erreur de chargement, affiche une image par défaut.
Widget _buildProfileImageWithGradient(String profileImageUrl) {
debugPrint("[LOG] Chargement de l'image de profil avec overlay de gradient.");
return Stack(
fit: StackFit.expand,
children: [
Image.network(
profileImageUrl,
fit: BoxFit.cover,
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return Center(child: CircularProgressIndicator(color: AppColors.accentColor));
},
errorBuilder: (context, error, stackTrace) {
// Log en cas d'erreur de chargement de l'image
print("[ERROR] Erreur lors du chargement de l'image de profil : $error");
return Image.asset('lib/assets/images/default_avatar.png', fit: BoxFit.cover);
debugPrint("[ERROR] Erreur lors du chargement de l'image de profil : $error");
return Image.asset(
'lib/assets/images/default_avatar.png',
fit: BoxFit.cover,
);
},
),
),
actions: [
IconButton(
icon: const Icon(Icons.logout, color: Colors.white),
onPressed: () {
print("[LOG] Bouton de déconnexion cliqué."); // Log du clic du bouton de déconnexion
_showLogoutConfirmationDialog(context); // Affiche le dialogue de confirmation
},
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.transparent, AppColors.darkPrimary.withOpacity(0.8)],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
),
],
);
}
/// Construit un bouton de déconnexion stylisé avec animation.
/// Log chaque interaction pour assurer une traçabilité complète.
Widget _buildLogoutButton(BuildContext context) {
return IconButton(
icon: const Icon(Icons.logout, color: Colors.white),
splashRadius: 20,
onPressed: () {
debugPrint("[LOG] Clic sur le bouton de déconnexion.");
_showLogoutConfirmationDialog(context);
},
tooltip: 'Déconnexion',
);
}
/// Affiche une boîte de dialogue de confirmation pour la déconnexion.
/// Log chaque action et résultat dans le terminal.
/// Log chaque action et résultat pour une visibilité dans le terminal.
void _showLogoutConfirmationDialog(BuildContext context) {
showDialog(
context: context,
builder: (BuildContext context) {
// Log affichage du dialogue
print("[LOG] Affichage de la boîte de dialogue de confirmation de déconnexion.");
debugPrint("[LOG] Affichage de la boîte de dialogue de confirmation de déconnexion.");
return AlertDialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
backgroundColor: AppColors.backgroundColor,
title: const Text(
'Confirmer la déconnexion',
style: TextStyle(color: Colors.white),
style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.w600),
),
content: const Text(
'Voulez-vous vraiment vous déconnecter ?',
style: TextStyle(color: Colors.white70),
style: TextStyle(color: Colors.white70, fontSize: 16),
),
actions: [
// Bouton d'annulation de la déconnexion
TextButton(
onPressed: () {
print("[LOG] Déconnexion annulée par l'utilisateur.");
Navigator.of(context).pop(); // Ferme le dialogue sans déconnecter
},
child: Text(
'Annuler',
style: TextStyle(color: AppColors.accentColor),
),
),
// Bouton de confirmation de la déconnexion
TextButton(
onPressed: () {
print("[LOG] Déconnexion confirmée."); // Log de la confirmation
Provider.of<UserProvider>(context, listen: false).resetUser(); // Réinitialise les infos utilisateur
print("[LOG] Informations utilisateur réinitialisées dans UserProvider.");
Navigator.of(context).pop(); // Ferme la boîte de dialogue
print("[LOG] Boîte de dialogue de confirmation fermée.");
Navigator.of(context).pushReplacementNamed('/'); // Redirige vers l'écran de connexion
print("[LOG] Redirection vers l'écran de connexion.");
},
child: const Text(
'Déconnexion',
style: TextStyle(color: Colors.redAccent),
),
),
_buildCancelButton(context),
_buildConfirmButton(context),
],
);
},
).then((_) {
// Log lorsque le dialogue est fermé pour toute raison (confirmation ou annulation)
print("[LOG] La boîte de dialogue de confirmation de déconnexion est fermée.");
debugPrint("[LOG] Fermeture de la boîte de dialogue de déconnexion.");
});
}
/// Construit le bouton pour annuler la déconnexion avec log.
Widget _buildCancelButton(BuildContext context) {
return TextButton(
onPressed: () {
debugPrint("[LOG] L'utilisateur a annulé la déconnexion.");
Navigator.of(context).pop();
},
child: Text(
'Annuler',
style: TextStyle(color: AppColors.accentColor, fontWeight: FontWeight.bold),
),
);
}
/// Construit le bouton pour confirmer la déconnexion, avec log et réinitialisation des données utilisateur.
Widget _buildConfirmButton(BuildContext context) {
return TextButton(
onPressed: () {
debugPrint("[LOG] L'utilisateur a confirmé la déconnexion.");
// Réinitialisation des informations de l'utilisateur
Provider.of<UserProvider>(context, listen: false).resetUser();
debugPrint("[LOG] Informations utilisateur réinitialisées dans UserProvider.");
Navigator.of(context).pop();
Navigator.of(context).pushReplacementNamed('/'); // Redirection vers l'écran de connexion
debugPrint("[LOG] Redirection vers l'écran de connexion.");
},
child: const Text(
'Déconnexion',
style: TextStyle(color: Colors.redAccent, fontWeight: FontWeight.bold),
),
);
}
}

View File

@@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import '../../../../core/constants/colors.dart';
/// [StatTile] affiche une statistique utilisateur avec une icône, un label et une valeur.
/// Ce composant inclut des animations et une traçabilité des interactions.
class StatTile extends StatelessWidget {
final IconData icon;
final String label;
@@ -15,17 +17,40 @@ class StatTile extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListTile(
leading: Icon(icon, color: AppColors.accentColor),
title: Text(label, style: const TextStyle(color: Colors.white)),
trailing: Text(
value,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
debugPrint("[LOG] Initialisation de StatTile pour la statistique : $label");
return TweenAnimationBuilder<double>(
duration: const Duration(milliseconds: 500),
tween: Tween<double>(begin: 0.9, end: 1.0),
curve: Curves.easeOutBack,
builder: (context, scale, child) {
return Transform.scale(
scale: scale,
child: ListTile(
leading: Icon(
icon,
color: AppColors.accentColor,
size: 28,
),
title: Text(
label,
style: const TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
trailing: Text(
value,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
),
);
},
);
}
}

View File

@@ -3,6 +3,8 @@ import '../../../../core/constants/colors.dart';
import '../../../../domain/entities/user.dart';
import 'stat_tile.dart';
/// [StatisticsSectionCard] affiche les statistiques principales de l'utilisateur avec des animations.
/// Ce composant est optimisé pour une expérience interactive et une traçabilité complète des actions via les logs.
class StatisticsSectionCard extends StatelessWidget {
final User user;
@@ -10,10 +12,13 @@ class StatisticsSectionCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
debugPrint("[LOG] Initialisation de StatisticsSectionCard pour l'utilisateur : ${user.userFirstName} ${user.userLastName}");
return Card(
color: AppColors.cardColor,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
elevation: 2,
color: AppColors.cardColor.withOpacity(0.95),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
elevation: 5,
shadowColor: AppColors.darkPrimary.withOpacity(0.4),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
@@ -28,13 +33,73 @@ class StatisticsSectionCard extends StatelessWidget {
),
),
const SizedBox(height: 10),
StatTile(icon: Icons.event, label: 'Événements Participés', value: '${user.eventsCount}'),
StatTile(icon: Icons.place, label: 'Établissements Visités', value: '${user.visitedPlacesCount}'),
StatTile(icon: Icons.post_add, label: 'Publications', value: '${user.postsCount}'),
StatTile(icon: Icons.group, label: 'Amis/Followers', value: '${user.friendsCount}'),
// Liste des statistiques avec animations
_buildAnimatedStatTile(
icon: Icons.event,
label: 'Événements Participés',
value: '${user.eventsCount}',
logMessage: "Affichage des événements participés : ${user.eventsCount}",
),
_buildDivider(),
_buildAnimatedStatTile(
icon: Icons.place,
label: 'Établissements Visités',
value: '${user.visitedPlacesCount}',
logMessage: "Affichage des établissements visités : ${user.visitedPlacesCount}",
),
_buildDivider(),
_buildAnimatedStatTile(
icon: Icons.post_add,
label: 'Publications',
value: '${user.postsCount}',
logMessage: "Affichage des publications : ${user.postsCount}",
),
_buildDivider(),
_buildAnimatedStatTile(
icon: Icons.group,
label: 'Amis/Followers',
value: '${user.friendsCount}',
logMessage: "Affichage des amis/followers : ${user.friendsCount}",
),
],
),
),
);
}
/// Construit chaque `StatTile` avec une animation de transition en fondu et logue chaque statistique.
Widget _buildAnimatedStatTile({
required IconData icon,
required String label,
required String value,
required String logMessage,
}) {
debugPrint("[LOG] $logMessage");
return TweenAnimationBuilder<double>(
duration: const Duration(milliseconds: 500),
tween: Tween<double>(begin: 0, end: 1),
curve: Curves.easeOut,
builder: (context, opacity, child) {
return Opacity(
opacity: opacity,
child: StatTile(
icon: icon,
label: label,
value: value,
),
);
},
);
}
/// Construit un séparateur visuel entre chaque statistique.
Widget _buildDivider() {
return Divider(
color: Colors.white.withOpacity(0.2),
height: 1,
indent: 16,
endIndent: 16,
);
}
}

View File

@@ -1,46 +1,107 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../../../../core/constants/colors.dart';
import 'custom_list_tile.dart';
/// [SupportSectionCard] affiche les options de support et assistance.
/// Inclut des animations, du retour haptique, et des logs détaillés pour chaque action.
class SupportSectionCard extends StatelessWidget {
const SupportSectionCard({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
debugPrint("[LOG] Initialisation de SupportSectionCard.");
return Card(
color: AppColors.cardColor,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
elevation: 2,
child: Column(
children: [
const Padding(
padding: EdgeInsets.all(16.0),
child: Text(
color: AppColors.cardColor.withOpacity(0.95),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
elevation: 6,
shadowColor: AppColors.darkPrimary.withOpacity(0.4),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Support et Assistance',
style: TextStyle(
fontSize: 20,
fontSize: 22,
fontWeight: FontWeight.bold,
color: Colors.white,
letterSpacing: 1.1,
),
),
),
CustomListTile(
icon: Icons.help,
label: 'Support et Assistance',
onTap: () => print("[LOG] Accès au Support et Assistance."),
),
CustomListTile(
icon: Icons.article,
label: 'Conditions d\'utilisation',
onTap: () => print("[LOG] Accès aux conditions d'utilisation."),
),
CustomListTile(
icon: Icons.privacy_tip,
label: 'Politique de confidentialité',
onTap: () => print("[LOG] Accès à la politique de confidentialité."),
),
],
const SizedBox(height: 10),
_buildOption(
context,
icon: Icons.help_outline,
label: 'Support et Assistance',
logMessage: "Accès au Support et Assistance.",
),
_buildDivider(),
_buildOption(
context,
icon: Icons.article_outlined,
label: 'Conditions d\'utilisation',
logMessage: "Accès aux conditions d'utilisation.",
),
_buildDivider(),
_buildOption(
context,
icon: Icons.privacy_tip_outlined,
label: 'Politique de confidentialité',
logMessage: "Accès à la politique de confidentialité.",
),
],
),
),
);
}
/// Construit chaque option de support avec une animation de feedback visuel.
Widget _buildOption(
BuildContext context, {
required IconData icon,
required String label,
required String logMessage,
}) {
return InkWell(
onTap: () {
HapticFeedback.lightImpact(); // Retour haptique léger
debugPrint("[LOG] $logMessage");
// Ajout de la navigation ou de l'action ici.
},
splashColor: AppColors.accentColor.withOpacity(0.3),
highlightColor: AppColors.cardColor.withOpacity(0.1),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 12.0),
child: Row(
children: [
Icon(icon, color: AppColors.accentColor, size: 28),
const SizedBox(width: 15),
Expanded(
child: Text(
label,
style: const TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
),
const Icon(Icons.chevron_right, color: Colors.white70),
],
),
),
);
}
/// Construit un séparateur entre les options pour une meilleure structure visuelle.
Widget _buildDivider() {
return Divider(
color: Colors.white.withOpacity(0.2),
height: 1,
indent: 16,
endIndent: 16,
);
}
}

View File

@@ -1,7 +1,11 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../core/constants/colors.dart';
import '../../../../domain/entities/user.dart';
import '../../data/providers/user_provider.dart';
/// [UserInfoCard] affiche les informations essentielles de l'utilisateur de manière concise.
/// Conçu pour minimiser les répétitions tout en garantissant une expérience utilisateur fluide.
class UserInfoCard extends StatelessWidget {
final User user;
@@ -9,38 +13,64 @@ class UserInfoCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
debugPrint("[LOG] Initialisation de UserInfoCard pour l'utilisateur : ${user.userFirstName} ${user.userLastName}");
return Card(
color: AppColors.cardColor,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
elevation: 2,
color: AppColors.cardColor.withOpacity(0.9),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
elevation: 5,
shadowColor: AppColors.darkPrimary.withOpacity(0.4),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircleAvatar(
radius: 50,
backgroundImage: NetworkImage(user.profileImageUrl),
backgroundColor: Colors.transparent,
TweenAnimationBuilder<double>(
duration: const Duration(milliseconds: 600),
tween: Tween<double>(begin: 0, end: 1),
curve: Curves.elasticOut,
builder: (context, scale, child) {
return Transform.scale(
scale: scale,
child: CircleAvatar(
radius: 50,
backgroundImage: NetworkImage(user.profileImageUrl),
backgroundColor: Colors.transparent,
onBackgroundImageError: (error, stackTrace) {
debugPrint("[ERROR] Erreur de chargement de l'image de profil : $error");
},
child: child,
),
);
},
child: Icon(Icons.person, size: 50, color: Colors.grey.shade300),
),
const SizedBox(height: 10),
Text(
'${user.userFirstName} ${user.userLastName}',
style: const TextStyle(
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white,
color: AppColors.accentColor,
letterSpacing: 1.2,
),
),
const SizedBox(height: 5),
Text(
user.email,
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
decoration: TextDecoration.underline,
if (!context.select((UserProvider provider) => provider.isEmailDisplayedElsewhere)) // Afficher seulement si non affiché ailleurs
GestureDetector(
onTap: () {
debugPrint("[LOG] Clic sur l'email de l'utilisateur : ${user.email}");
},
child: Text(
user.email,
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade300,
decoration: TextDecoration.underline,
decorationColor: AppColors.accentColor.withOpacity(0.5),
),
),
),
),
],
),
),