import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:travel_mate/components/widgets/user_state_widget.dart'; import 'package:travel_mate/services/error_service.dart'; import 'package:image_picker/image_picker.dart'; import 'package:firebase_storage/firebase_storage.dart'; import 'dart:io'; import '../../../blocs/user/user_bloc.dart'; import '../../../blocs/user/user_state.dart' as user_state; import '../../../blocs/user/user_event.dart' as user_event; import '../../../services/auth_service.dart'; class ProfileContent extends StatelessWidget { ProfileContent({super.key}); final _errorService = ErrorService(); @override Widget build(BuildContext context) { return UserStateWrapper( builder: (context, user) { final isEmailAuth = user.authMethod == 'email' || user.authMethod == null; return SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ // En-tête avec photo de profil Container( padding: EdgeInsets.symmetric(vertical: 24, horizontal: 16), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ // Photo de profil Container( decoration: BoxDecoration( shape: BoxShape.circle, boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.1), blurRadius: 8, offset: Offset(0, 2), ) ], ), child: user.profilePictureUrl != null && user.profilePictureUrl!.isNotEmpty ? CircleAvatar( radius: 50, backgroundImage: NetworkImage( user.profilePictureUrl!, // Force le rechargement avec un paramètre de cache headers: { 'pragma': 'no-cache', 'cache-control': 'no-cache', }, ), ) : CircleAvatar( radius: 50, backgroundColor: Theme.of(context).colorScheme.primary, child: Text( user.prenom.isNotEmpty ? user.prenom[0].toUpperCase() : 'U', style: TextStyle( fontSize: 32, fontWeight: FontWeight.bold, color: Colors.white, ), ), ), ), SizedBox(height: 16), // Nom complet Text( '${user.prenom} ${user.nom ?? ''}', style: TextStyle( fontSize: 22, fontWeight: FontWeight.bold, ), textAlign: TextAlign.center, ), SizedBox(height: 4), // Email Text( user.email, style: TextStyle( fontSize: 14, color: Colors.grey[600], ), textAlign: TextAlign.center, ), SizedBox(height: 12), // Badge de méthode de connexion Container( padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: _getAuthMethodColor(user.authMethod, context), borderRadius: BorderRadius.circular(12), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ if (_getAuthMethodIcon(user.authMethod).isNotEmpty) Image.asset( _getAuthMethodIcon(user.authMethod), height: 16, width: 16, ), if (_getAuthMethodIcon(user.authMethod).isNotEmpty) SizedBox(width: 8), Text( _getAuthMethodLabel(user.authMethod), style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: _getAuthMethodTextColor(user.authMethod, context), ), ), ], ), ), ], ), ), // Section Informations personnelles Padding( padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Row( children: [ Text( 'Informations personnelles', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Colors.black87, ), ), ], ), ), // Tuiles d'information _buildInfoTile( icon: Icons.person_outline, label: 'Nom complet', value: '${user.prenom} ${user.nom ?? ''}', context: context, ), _buildInfoTile( icon: Icons.email_outlined, label: 'Adresse e-mail', value: user.email, context: context, ), _buildInfoTile( icon: Icons.phone_outlined, label: 'Téléphone', value: user.phoneNumber ?? 'Non défini', context: context, ), // Option de changement de mot de passe seulement pour email if (isEmailAuth) _buildActionTile( icon: Icons.lock_outlined, label: 'Changer de mot de passe', context: context, onTap: () { _showChangePasswordDialog(context, user); }, ), SizedBox(height: 8), // Option pour modifier le profil _buildActionTile( icon: Icons.edit_outlined, label: 'Modifier le profil', context: context, onTap: () { _showEditProfileDialog(context, user); }, ), SizedBox(height: 8), // Option pour supprimer le compte _buildActionTile( icon: Icons.delete_outline, label: 'Supprimer le compte', context: context, isDestructive: true, onTap: () { _showDeleteAccountDialog(context, user); }, ), SizedBox(height: 24), ], ), ); }, ); } Widget _buildInfoTile({ required IconData icon, required String label, required String value, required BuildContext context, }) { final isDarkMode = Theme.of(context).brightness == Brightness.dark; return Container( margin: EdgeInsets.symmetric(horizontal: 16, vertical: 8), padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( color: isDarkMode ? Colors.grey[850] : Colors.grey[100], borderRadius: BorderRadius.circular(8), ), child: Row( children: [ Container( padding: EdgeInsets.all(8), decoration: BoxDecoration( color: isDarkMode ? Colors.grey[800] : Colors.grey[200], borderRadius: BorderRadius.circular(6), ), child: Icon( icon, size: 20, color: Theme.of(context).colorScheme.primary, ), ), SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: TextStyle( fontSize: 12, color: Colors.grey[600], fontWeight: FontWeight.w500, ), ), SizedBox(height: 2), Text( value, style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: isDarkMode ? Colors.white : Colors.black87, ), overflow: TextOverflow.ellipsis, ), ], ), ), ], ), ); } Widget _buildActionTile({ required IconData icon, required String label, required BuildContext context, required VoidCallback onTap, bool isDestructive = false, }) { final isDarkMode = Theme.of(context).brightness == Brightness.dark; return GestureDetector( onTap: onTap, child: Container( margin: EdgeInsets.symmetric(horizontal: 16, vertical: 8), padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( color: isDarkMode ? Colors.grey[850] : Colors.grey[100], borderRadius: BorderRadius.circular(8), ), child: Row( children: [ Container( padding: EdgeInsets.all(8), decoration: BoxDecoration( color: isDarkMode ? Colors.grey[800] : Colors.grey[200], borderRadius: BorderRadius.circular(6), ), child: Icon( icon, size: 20, color: isDestructive ? Colors.red : Theme.of(context).colorScheme.primary, ), ), SizedBox(width: 16), Expanded( child: Text( label, style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: isDestructive ? Colors.red : (isDarkMode ? Colors.white : Colors.black87), ), ), ), Icon( Icons.arrow_forward_ios, size: 16, color: Colors.grey[400], ), ], ), ), ); } String _getAuthMethodLabel(String? authMethod) { switch (authMethod) { case 'apple': return 'Connecté avec Apple'; case 'google': return 'Connecté avec Google'; default: return 'Connecté avec Email'; } } String _getAuthMethodIcon(String? authMethod) { switch (authMethod) { case 'apple': return 'assets/icons/apple_white.png'; case 'google': return 'assets/icons/google.png'; default: return ''; } } Color _getAuthMethodColor(String? authMethod, BuildContext context) { final isDarkMode = Theme.of(context).brightness == Brightness.dark; switch (authMethod) { case 'apple': return isDarkMode ? Colors.white : Colors.black87; case 'google': return isDarkMode ? Colors.white : Colors.black87; default: return isDarkMode ? Colors.white : Colors.blue; } } Color _getAuthMethodTextColor(String? authMethod, BuildContext context) { final isDarkMode = Theme.of(context).brightness == Brightness.dark; switch (authMethod) { case 'apple': return isDarkMode ? Colors.black87 : Colors.white; case 'google': return isDarkMode ? Colors.black87 : Colors.white; default: return isDarkMode ? Colors.black87 : Colors.white; } } void _showEditProfileDialog(BuildContext context, user_state.UserModel user) { final nomController = TextEditingController(text: user.nom); final prenomController = TextEditingController(text: user.prenom); final phoneController = TextEditingController(text: user.phoneNumber ?? ''); showDialog( context: context, builder: (BuildContext dialogContext) { return StatefulBuilder( builder: (context, setState) { return AlertDialog( title: Text('Modifier le profil'), content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ // Photo de profil avec option de changement Center( child: Stack( children: [ CircleAvatar( radius: 50, backgroundColor: Theme.of(context).colorScheme.primary, child: Text( prenomController.text.isNotEmpty ? prenomController.text[0].toUpperCase() : 'U', style: TextStyle( fontSize: 32, fontWeight: FontWeight.bold, color: Colors.white, ), ), ), Positioned( bottom: 0, right: 0, child: Container( decoration: BoxDecoration( shape: BoxShape.circle, color: Theme.of(context).colorScheme.primary, ), child: IconButton( icon: Icon(Icons.camera_alt, color: Colors.white, size: 20), onPressed: () { _showPhotoPickerDialog(dialogContext); }, ), ), ), ], ), ), SizedBox(height: 24), // Champ Prénom TextField( controller: prenomController, decoration: InputDecoration( labelText: 'Prénom', border: OutlineInputBorder(), prefixIcon: Icon(Icons.person_outline), ), onChanged: (_) { setState(() {}); }, ), SizedBox(height: 16), // Champ Nom TextField( controller: nomController, decoration: InputDecoration( labelText: 'Nom', border: OutlineInputBorder(), prefixIcon: Icon(Icons.person_outline), ), ), SizedBox(height: 16), // Champ Téléphone TextField( controller: phoneController, decoration: InputDecoration( labelText: 'Numéro de téléphone', border: OutlineInputBorder(), prefixIcon: Icon(Icons.phone_outlined), hintText: '+33 6 12 34 56 78', ), keyboardType: TextInputType.phone, ), ], ), ), actions: [ TextButton( onPressed: () => Navigator.of(dialogContext).pop(), child: Text('Annuler'), ), TextButton( onPressed: () { if (prenomController.text.trim().isNotEmpty) { context.read().add( user_event.UserUpdated({ 'prenom': prenomController.text.trim(), 'nom': nomController.text.trim(), 'phoneNumber': phoneController.text.trim(), }), ); Navigator.of(dialogContext).pop(); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Profil mis à jour !'), backgroundColor: Colors.green, ), ); } }, child: Text('Sauvegarder'), ), ], ); }, ); }, ); } void _showPhotoPickerDialog(BuildContext context) { // Récupérer les références AVANT que le modal ne se ferme final userBloc = context.read(); showModalBottomSheet( context: context, builder: (BuildContext sheetContext) { return Container( child: Wrap( children: [ ListTile( leading: Icon(Icons.photo_library), title: Text('Galerie'), onTap: () { Navigator.pop(sheetContext); _pickImageFromGallery(context, userBloc); }, ), ListTile( leading: Icon(Icons.camera_alt), title: Text('Caméra'), onTap: () { Navigator.pop(sheetContext); _pickImageFromCamera(context, userBloc); }, ), ListTile( leading: Icon(Icons.close), title: Text('Annuler'), onTap: () => Navigator.pop(sheetContext), ), ], ), ); }, ); } Future _pickImageFromGallery(BuildContext context, UserBloc userBloc) async { try { final ImagePicker picker = ImagePicker(); final XFile? image = await picker.pickImage(source: ImageSource.gallery); if (image != null) { await _uploadProfilePicture(context, image.path, userBloc); } } catch (e) { _errorService.showError(message: 'Erreur lors de la sélection de l\'image'); } } Future _pickImageFromCamera(BuildContext context, UserBloc userBloc) async { try { final ImagePicker picker = ImagePicker(); final XFile? image = await picker.pickImage(source: ImageSource.camera); if (image != null) { await _uploadProfilePicture(context, image.path, userBloc); } } catch (e) { _errorService.showError(message: 'Erreur lors de la prise de photo'); } } Future _uploadProfilePicture(BuildContext context, String imagePath, UserBloc userBloc) async { try { final File imageFile = File(imagePath); // Vérifier que le fichier existe if (!await imageFile.exists()) { _errorService.showError(message: 'Le fichier image n\'existe pas'); return; } print('DEBUG: Taille du fichier: ${imageFile.lengthSync()} bytes'); final userState = userBloc.state; if (userState is! user_state.UserLoaded) { _errorService.showError(message: 'Utilisateur non connecté'); return; } final user = userState.user; // Créer un nom unique pour la photo final String fileName = 'profile_${user.id}_${DateTime.now().millisecondsSinceEpoch}.jpg'; final Reference storageRef = FirebaseStorage.instance .ref() .child('profile_pictures') .child(fileName); print('DEBUG: Chemin Storage: ${storageRef.fullPath}'); print('DEBUG: Upload en cours pour $fileName'); // Uploader l'image avec gestion d'erreur détaillée try { final uploadTask = storageRef.putFile(imageFile); // Écouter la progression uploadTask.snapshotEvents.listen((TaskSnapshot snapshot) { print('DEBUG: Progression: ${snapshot.bytesTransferred}/${snapshot.totalBytes}'); }); final snapshot = await uploadTask; print('DEBUG: Upload terminé. État: ${snapshot.state}'); } on FirebaseException catch (e) { print('DEBUG: FirebaseException lors de l\'upload: ${e.code} - ${e.message}'); if (context.mounted) { _errorService.showError( message: 'Erreur Firebase: ${e.code}\n${e.message}', ); } return; } print('DEBUG: Upload terminé, récupération de l\'URL'); // Récupérer l'URL final String downloadUrl = await storageRef.getDownloadURL(); print('DEBUG: URL obtenue: $downloadUrl'); // Mettre à jour le profil avec l'URL en utilisant la référence sauvegardée du BLoC print('DEBUG: Envoi de UserUpdated event au BLoC'); userBloc.add( user_event.UserUpdated({ 'profilePictureUrl': downloadUrl, }), ); // Attendre un peu que Firestore se mette à jour await Future.delayed(Duration(milliseconds: 500)); if (context.mounted) { print('DEBUG: Affichage du succès'); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Photo de profil mise à jour !'), backgroundColor: Colors.green, ), ); } } catch (e, stackTrace) { print('DEBUG: Erreur lors de l\'upload: $e'); print('DEBUG: Stack trace: $stackTrace'); _errorService.logError( 'ProfileContent - _uploadProfilePicture', 'Erreur lors de l\'upload de la photo: $e\n$stackTrace', ); if (context.mounted) { _errorService.showError(message: 'Erreur: ${e.toString()}'); } } } void _showChangePasswordDialog( BuildContext context, user_state.UserModel user, ) { final currentPasswordController = TextEditingController(); final newPasswordController = TextEditingController(); final confirmPasswordController = TextEditingController(); final authService = AuthService(); showDialog( context: context, builder: (BuildContext dialogContext) { return AlertDialog( title: Text('Changer le mot de passe'), content: Column( mainAxisSize: MainAxisSize.min, children: [ TextField( controller: currentPasswordController, obscureText: true, decoration: InputDecoration(labelText: 'Mot de passe actuel'), ), SizedBox(height: 16), TextField( controller: newPasswordController, obscureText: true, decoration: InputDecoration(labelText: 'Nouveau mot de passe'), ), SizedBox(height: 16), TextField( controller: confirmPasswordController, obscureText: true, decoration: InputDecoration( labelText: 'Confirmer le mot de passe', ), ), ], ), actions: [ TextButton( onPressed: () => Navigator.of(dialogContext).pop(), child: Text('Annuler'), ), TextButton( onPressed: () async { if (currentPasswordController.text.isEmpty || newPasswordController.text.isEmpty || confirmPasswordController.text.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Tous les champs sont requis'), backgroundColor: Colors.red, ), ); return; } if (newPasswordController.text != confirmPasswordController.text) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Les mots de passe ne correspondent pas'), backgroundColor: Colors.red, ), ); return; } try { await authService.resetPasswordFromCurrentPassword( currentPassword: currentPasswordController.text, newPassword: newPasswordController.text, email: user.email, ); Navigator.of(dialogContext).pop(); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Mot de passe changé !'), backgroundColor: Colors.green, ), ); } catch (e) { _errorService.showError( message: 'Erreur: Mot de passe actuel incorrect', ); } }, child: Text('Changer'), ), ], ); }, ); } void _showDeleteAccountDialog( BuildContext context, user_state.UserModel user, ) { final passwordController = TextEditingController(); final authService = AuthService(); showDialog( context: context, builder: (BuildContext dialogContext) { return AlertDialog( title: Text('Supprimer le compte'), content: Column( mainAxisSize: MainAxisSize.min, children: [ Text( 'Êtes-vous sûr de vouloir supprimer votre compte ? Cette action est irréversible.', ), SizedBox(height: 16), TextField( controller: passwordController, obscureText: true, decoration: InputDecoration( labelText: 'Confirmez votre mot de passe', border: OutlineInputBorder(), ), ), ], ), actions: [ TextButton( onPressed: () => Navigator.of(dialogContext).pop(), child: Text('Annuler'), ), TextButton( onPressed: () async { try { await authService.deleteAccount( password: passwordController.text, email: user.email, ); Navigator.of(dialogContext).pop(); context.read().add(user_event.UserLoggedOut()); Navigator.pushNamedAndRemoveUntil( context, '/login', (route) => false, ); } catch (e) { _errorService.showError( message: 'Erreur: Mot de passe incorrect', ); } }, style: TextButton.styleFrom(foregroundColor: Colors.red), child: Text('Supprimer'), ), ], ); }, ); } }