Refactoring + Checkpoint

This commit is contained in:
DahoudG
2024-11-17 23:00:18 +00:00
parent 1e888f41e8
commit 77ab8a02a2
56 changed files with 1904 additions and 790 deletions

View File

@@ -0,0 +1,67 @@
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;
const AccountDeletionCard({Key? key, required this.context}) : super(key: key);
@override
Widget build(BuildContext context) {
return Card(
color: AppColors.cardColor,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
elevation: 2,
child: ListTile(
leading: const Icon(Icons.delete, color: Colors.redAccent),
title: const Text(
'Supprimer le compte',
style: TextStyle(color: Colors.redAccent, fontWeight: FontWeight.bold),
),
onTap: () => _showDeleteConfirmationDialog(),
),
);
}
/// Affiche un dialogue de confirmation pour la suppression du compte.
void _showDeleteConfirmationDialog() {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
backgroundColor: AppColors.backgroundColor,
title: const Text(
'Confirmer la suppression',
style: TextStyle(color: Colors.white),
),
content: const Text(
'Êtes-vous sûr de vouloir supprimer votre compte ? Cette action est irréversible.',
style: TextStyle(color: Colors.white70),
),
actions: [
TextButton(
onPressed: () {
debugPrint("[LOG] Suppression du compte annulée.");
Navigator.of(context).pop();
},
child: Text('Annuler', style: TextStyle(color: AppColors.accentColor)),
),
TextButton(
onPressed: () {
debugPrint("[LOG] Suppression du compte confirmée.");
Navigator.of(context).pop();
// Logique de suppression du compte ici.
},
child: const Text(
'Supprimer',
style: TextStyle(color: Colors.redAccent),
),
),
],
);
},
);
}
}

View File

@@ -0,0 +1,98 @@
import 'package:flutter/material.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.withOpacity(0.95),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
elevation: 4,
shadowColor: AppColors.darkPrimary.withOpacity(0.3),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_buildOption(
context,
icon: Icons.edit,
label: 'Éditer le profil',
logMessage: "Édition du profil",
onTap: () => debugPrint("[LOG] Édition du profil activée."),
),
_buildDivider(),
_buildOption(
context,
icon: Icons.camera_alt,
label: 'Changer la photo de profil',
logMessage: "Changement de la photo de profil",
onTap: () =>
debugPrint("[LOG] Changement de la photo de profil activé."),
),
_buildDivider(),
_buildOption(
context,
icon: Icons.lock,
label: 'Changer le 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

@@ -0,0 +1,88 @@
import 'package:flutter/material.dart';
import '../../../../../core/constants/colors.dart';
/// [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;
const ExpandableSectionCard({
Key? key,
required this.title,
required this.icon,
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: 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,
),
// 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

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

View File

@@ -0,0 +1,105 @@
import 'package:flutter/material.dart';
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;
const StatisticsSectionCard({Key? key, required this.user}) : super(key: key);
@override
Widget build(BuildContext context) {
debugPrint("[LOG] Initialisation de StatisticsSectionCard pour l'utilisateur : ${user.userFirstName} ${user.userLastName}");
return Card(
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(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Statistiques Personnelles',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
const SizedBox(height: 10),
// 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

@@ -0,0 +1,107 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../../../../core/constants/colors.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.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: 22,
fontWeight: FontWeight.bold,
color: Colors.white,
letterSpacing: 1.1,
),
),
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

@@ -0,0 +1,79 @@
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;
const UserInfoCard({Key? key, required this.user}) : super(key: key);
@override
Widget build(BuildContext context) {
debugPrint("[LOG] Initialisation de UserInfoCard pour l'utilisateur : ${user.userFirstName} ${user.userLastName}");
return Card(
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: [
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: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: AppColors.accentColor,
letterSpacing: 1.2,
),
),
const SizedBox(height: 5),
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),
),
),
),
],
),
),
);
}
}