Files
afterwork/lib/presentation/screens/profile/profile_screen.dart
dahoud 92612abbd7 fix(chat): Correction race condition + Implémentation TODOs
## Corrections Critiques

### Race Condition - Statuts de Messages
- Fix : Les icônes de statut (✓, ✓✓, ✓✓ bleu) ne s'affichaient pas
- Cause : WebSocket delivery confirmations arrivaient avant messages locaux
- Solution : Pattern Optimistic UI dans chat_bloc.dart
  - Création message temporaire immédiate
  - Ajout à la liste AVANT requête HTTP
  - Remplacement par message serveur à la réponse
- Fichier : lib/presentation/state_management/chat_bloc.dart

## Implémentation TODOs (13/21)

### Social (social_header_widget.dart)
-  Copier lien du post dans presse-papiers
-  Partage natif via Share.share()
-  Dialogue de signalement avec 5 raisons

### Partage (share_post_dialog.dart)
-  Interface sélection d'amis avec checkboxes
-  Partage externe via Share API

### Média (media_upload_service.dart)
-  Parsing JSON réponse backend
-  Méthode deleteMedia() pour suppression
-  Génération miniature vidéo

### Posts (create_post_dialog.dart, edit_post_dialog.dart)
-  Extraction URL depuis uploads
-  Documentation chargement médias

### Chat (conversations_screen.dart)
-  Navigation vers notifications
-  ConversationSearchDelegate pour recherche

## Nouveaux Fichiers

### Configuration
- build-prod.ps1 : Script build production avec dart-define
- lib/core/constants/env_config.dart : Gestion environnements

### Documentation
- TODOS_IMPLEMENTED.md : Documentation complète TODOs

## Améliorations

### Architecture
- Refactoring injection de dépendances
- Amélioration routing et navigation
- Optimisation providers (UserProvider, FriendsProvider)

### UI/UX
- Amélioration thème et couleurs
- Optimisation animations
- Meilleure gestion erreurs

### Services
- Configuration API avec env_config
- Amélioration datasources (events, users)
- Optimisation modèles de données
2026-01-10 10:43:17 +00:00

460 lines
16 KiB
Dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../../core/constants/design_system.dart';
import '../../../core/theme/theme_provider.dart';
import '../../../core/utils/page_transitions.dart';
import '../../../data/providers/user_provider.dart';
import '../../../data/services/secure_storage.dart';
import '../../widgets/animated_widgets.dart';
import '../../widgets/custom_snackbar.dart';
import '../notifications/notifications_screen.dart';
import '../settings/settings_screen.dart';
import 'edit_profile_screen.dart';
/// Écran de profil moderne et épuré.
class ProfileScreen extends StatelessWidget {
const ProfileScreen({super.key});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final userProvider = Provider.of<UserProvider>(context);
final user = userProvider.user;
final themeProvider = Provider.of<ThemeProvider>(context);
return Scaffold(
body: CustomScrollView(
slivers: [
// AppBar avec image de fond et avatar
SliverAppBar(
expandedHeight: 200,
pinned: true,
flexibleSpace: FlexibleSpaceBar(
background: Stack(
fit: StackFit.expand,
children: [
// Fond dégradé
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
theme.colorScheme.primary,
theme.colorScheme.primary.withOpacity(0.7),
],
),
),
),
// Avatar centré
Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: const EdgeInsets.only(bottom: 16),
child: Hero(
tag: 'user_profile_avatar_${user.userId}',
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: theme.scaffoldBackgroundColor,
width: 4,
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: CircleAvatar(
radius: 50,
backgroundImage: user.profileImageUrl != null &&
user.profileImageUrl!.isNotEmpty
? NetworkImage(user.profileImageUrl!)
: null,
child: user.profileImageUrl == null ||
user.profileImageUrl!.isEmpty
? Icon(
Icons.person_rounded,
size: 50,
color: theme.colorScheme.onPrimary,
)
: null,
),
),
),
),
),
],
),
),
),
// Contenu
SliverToBoxAdapter(
child: Column(
children: [
const SizedBox(height: DesignSystem.spacingLg),
// Nom et email
Text(
'${user.userFirstName} ${user.userLastName}',
style: theme.textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: DesignSystem.spacingSm),
Text(
user.email,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurface.withOpacity(0.6),
),
),
const SizedBox(height: DesignSystem.spacingXl),
// Bouton Éditer le profil
Padding(
padding: DesignSystem.paddingHorizontal(DesignSystem.spacingXl),
child: SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
onPressed: () {
context.pushSlideRight(EditProfileScreen(user: user));
},
icon: const Icon(Icons.edit_rounded, size: 18),
label: const Text('Éditer le profil'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(DesignSystem.radiusMd),
),
),
),
),
),
const SizedBox(height: DesignSystem.spacingXl),
// Statistiques
_buildStatsRow(theme, user),
const SizedBox(height: DesignSystem.spacingXl),
// Sections
Padding(
padding: DesignSystem.paddingHorizontal(DesignSystem.spacingLg),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Section Historique
_buildSectionHeader(theme, 'Historique'),
const SizedBox(height: DesignSystem.spacingSm),
_buildListTile(
context,
theme,
Icons.event_note_rounded,
'Historique des Événements',
() => context.showInfo('Historique des événements à venir'),
),
_buildListTile(
context,
theme,
Icons.article_rounded,
'Historique des Publications',
() => context.showInfo('Historique des publications à venir'),
),
_buildListTile(
context,
theme,
Icons.bookmark_rounded,
'Historique de Réservations',
() => context.showInfo('Historique des réservations à venir'),
),
const SizedBox(height: DesignSystem.spacingXl),
// Section Paramètres
_buildSectionHeader(theme, 'Paramètres'),
const SizedBox(height: DesignSystem.spacingSm),
_buildListTile(
context,
theme,
Icons.privacy_tip_rounded,
'Confidentialité',
() => context.showInfo('Paramètres de confidentialité à venir'),
),
_buildListTile(
context,
theme,
Icons.notifications_rounded,
'Notifications',
() => context.pushFadeScale(const NotificationsScreen()),
),
_buildListTile(
context,
theme,
Icons.language_rounded,
'Langue',
() => context.showInfo('Sélection de langue à venir'),
),
_buildSwitchTile(
context,
theme,
themeProvider.isDarkMode ? Icons.light_mode_rounded : Icons.dark_mode_rounded,
'Mode sombre',
themeProvider.isDarkMode,
(value) => themeProvider.toggleTheme(),
),
_buildListTile(
context,
theme,
Icons.settings_rounded,
'Paramètres avancés',
() => context.pushSlideRight(const SettingsScreen()),
),
const SizedBox(height: DesignSystem.spacingXl),
// Section Aide
_buildSectionHeader(theme, 'Aide & Support'),
const SizedBox(height: DesignSystem.spacingSm),
_buildListTile(
context,
theme,
Icons.help_rounded,
'Centre d\'aide',
() => context.showInfo('Centre d\'aide à venir'),
),
_buildListTile(
context,
theme,
Icons.feedback_rounded,
'Envoyer un feedback',
() => context.showInfo('Formulaire de feedback à venir'),
),
_buildListTile(
context,
theme,
Icons.info_rounded,
'À propos',
() => context.showInfo('À propos de l\'application'),
),
const SizedBox(height: DesignSystem.spacingXl),
// Bouton Déconnexion
SizedBox(
width: double.infinity,
child: OutlinedButton.icon(
onPressed: () {
_showLogoutDialog(context);
},
icon: const Icon(Icons.logout_rounded, size: 18),
label: const Text('Se déconnecter'),
style: OutlinedButton.styleFrom(
foregroundColor: theme.colorScheme.error,
side: BorderSide(color: theme.colorScheme.error),
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(DesignSystem.radiusMd),
),
),
),
),
const SizedBox(height: DesignSystem.spacingXl),
],
),
),
],
),
),
],
),
);
}
Widget _buildStatsRow(ThemeData theme, dynamic user) {
return Padding(
padding: DesignSystem.paddingHorizontal(DesignSystem.spacingXl),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildStatItem(theme, '0', 'Publications'),
Container(
height: 40,
width: 1,
color: theme.dividerColor,
),
_buildStatItem(theme, '0', 'Amis'),
Container(
height: 40,
width: 1,
color: theme.dividerColor,
),
_buildStatItem(theme, '0', 'Événements'),
],
),
);
}
Widget _buildStatItem(ThemeData theme, String value, String label) {
return Column(
children: [
Text(
value,
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
label,
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurface.withOpacity(0.6),
fontSize: 12,
),
),
],
);
}
Widget _buildSectionHeader(ThemeData theme, String title) {
return Text(
title,
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
fontSize: 15,
),
);
}
Widget _buildListTile(
BuildContext context,
ThemeData theme,
IconData icon,
String title,
VoidCallback onTap,
) {
return AnimatedCard(
margin: const EdgeInsets.only(bottom: DesignSystem.spacingSm),
borderRadius: DesignSystem.borderRadiusMd,
elevation: 0.5,
hoverElevation: 2,
padding: const EdgeInsets.symmetric(
horizontal: DesignSystem.spacingLg,
vertical: DesignSystem.spacingMd,
),
onTap: onTap,
child: Row(
children: [
Icon(
icon,
size: 22,
color: theme.colorScheme.primary,
),
const SizedBox(width: DesignSystem.spacingLg),
Expanded(
child: Text(
title,
style: theme.textTheme.bodyMedium?.copyWith(
fontSize: 14,
),
),
),
Icon(
Icons.chevron_right_rounded,
size: 20,
color: theme.colorScheme.onSurface.withOpacity(0.3),
),
],
),
);
}
Widget _buildSwitchTile(
BuildContext context,
ThemeData theme,
IconData icon,
String title,
bool value,
ValueChanged<bool> onChanged,
) {
return AnimatedCard(
margin: const EdgeInsets.only(bottom: DesignSystem.spacingSm),
borderRadius: DesignSystem.borderRadiusMd,
elevation: 0.5,
hoverElevation: 1,
padding: const EdgeInsets.symmetric(
horizontal: DesignSystem.spacingLg,
vertical: DesignSystem.spacingSm,
),
child: Row(
children: [
Icon(
icon,
size: 22,
color: theme.colorScheme.primary,
),
const SizedBox(width: DesignSystem.spacingLg),
Expanded(
child: Text(
title,
style: theme.textTheme.bodyMedium?.copyWith(
fontSize: 14,
),
),
),
Switch(
value: value,
onChanged: onChanged,
),
],
),
);
}
void _showLogoutDialog(BuildContext context) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Déconnexion'),
content: const Text('Êtes-vous sûr de vouloir vous déconnecter ?'),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(DesignSystem.radiusMd),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Annuler'),
),
FilledButton(
onPressed: () async {
// Fermer le dialogue
Navigator.pop(context);
// Supprimer les informations de l'utilisateur du stockage sécurisé
final secureStorage = SecureStorage();
await secureStorage.deleteUserInfo();
// Naviguer vers l'écran de connexion et supprimer toute la pile de navigation
if (context.mounted) {
Navigator.of(context).pushNamedAndRemoveUntil('/', (route) => false);
context.showSuccess('Déconnexion effectuée');
}
},
style: FilledButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.error,
),
child: const Text('Déconnecter'),
),
],
),
);
}
}