Photo de profile OK, modif nom, prénom, tel OK
This commit is contained in:
		| @@ -64,12 +64,14 @@ class _LoginPageState extends State<LoginPage> { | ||||
|         // Maintenant que toutes les données sont chargées, naviguer vers CalendarPage. | ||||
|         Navigator.of(context).pushReplacementNamed('/calendar'); | ||||
|       } else { | ||||
|         if (!mounted) return; | ||||
|         setState(() { | ||||
|           _errorMessage = "Aucune donnée utilisateur trouvée."; | ||||
|           _isLoading = false; | ||||
|         }); | ||||
|       } | ||||
|     } on FirebaseAuthException catch (e) { | ||||
|       if (!mounted) return; | ||||
|       setState(() { | ||||
|         _isLoading = false; | ||||
|         if (e.code == 'user-not-found' || e.code == 'wrong-password') { | ||||
| @@ -81,6 +83,7 @@ class _LoginPageState extends State<LoginPage> { | ||||
|         } | ||||
|       }); | ||||
|     } catch (e) { | ||||
|       if (!mounted) return; | ||||
|       setState(() { | ||||
|         _errorMessage = | ||||
|             "Erreur lors de la récupération des données utilisateur."; | ||||
| @@ -90,6 +93,7 @@ class _LoginPageState extends State<LoginPage> { | ||||
|   } | ||||
|  | ||||
|   void _togglePasswordVisibility() { | ||||
|     if (!mounted) return; | ||||
|     setState(() { | ||||
|       _obscurePassword = !_obscurePassword; | ||||
|     }); | ||||
|   | ||||
| @@ -1,21 +1,187 @@ | ||||
| import 'package:em2rp/providers/user_provider.dart'; | ||||
| import 'package:em2rp/utils/firebase_storage_manager.dart'; | ||||
| import 'package:em2rp/views/widgets/image/profile_picture.dart'; | ||||
| import 'package:em2rp/views/widgets/nav/main_drawer.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:firebase_auth/firebase_auth.dart'; | ||||
| import 'package:cloud_firestore/cloud_firestore.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| import 'package:image_picker/image_picker.dart'; | ||||
|  | ||||
| class MyAccountPage extends StatelessWidget { | ||||
| class MyAccountPage extends StatefulWidget { | ||||
|   const MyAccountPage({super.key}); | ||||
|   @override | ||||
|   _MyAccountPageState createState() => _MyAccountPageState(); | ||||
| } | ||||
|  | ||||
| class _MyAccountPageState extends State<MyAccountPage> { | ||||
|   final User? user = FirebaseAuth.instance.currentUser; | ||||
|   final TextEditingController _firstNameController = TextEditingController(); | ||||
|   final TextEditingController _lastNameController = TextEditingController(); | ||||
|   final TextEditingController _phoneController = TextEditingController(); | ||||
|   String? profilePhotoUrl; | ||||
|   bool _isHoveringProfilePic = false; | ||||
|   final FirebaseStorageManager _storageManager = | ||||
|       FirebaseStorageManager(); // Instance of FirebaseStorageManager | ||||
|  | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|     _loadUserData(); | ||||
|   } | ||||
|  | ||||
|   Future<void> _loadUserData() async { | ||||
|     if (user != null) { | ||||
|       DocumentSnapshot userData = await FirebaseFirestore.instance | ||||
|           .collection('users') | ||||
|           .doc(user!.uid) | ||||
|           .get(); | ||||
|  | ||||
|       if (userData.exists) { | ||||
|         if (!mounted) return; | ||||
|         setState(() { | ||||
|           _firstNameController.text = userData['firstName'] ?? ''; | ||||
|           _lastNameController.text = userData['lastName'] ?? ''; | ||||
|           _phoneController.text = userData['phone'] ?? ''; | ||||
|         }); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   Future<void> _updateUserData() async { | ||||
|     if (user != null) { | ||||
|       await FirebaseFirestore.instance.collection('users').doc(user!.uid).set({ | ||||
|         'firstName': _firstNameController.text, | ||||
|         'lastName': _lastNameController.text, | ||||
|         'phone': _phoneController.text, | ||||
|       }, SetOptions(merge: true)); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   Future<void> _changeProfilePicture() async { | ||||
|     final ImagePicker picker = ImagePicker(); | ||||
|     final XFile? image = await picker.pickImage( | ||||
|       source: ImageSource.gallery, | ||||
|     ); // You can also use ImageSource.camera | ||||
|  | ||||
|     if (image != null) { | ||||
|       // Call FirebaseStorageManager to send the profile picture | ||||
|       String? newProfilePhotoUrl = await _storageManager.sendProfilePicture( | ||||
|         imageFile: image, | ||||
|         uid: user!.uid, | ||||
|       ); | ||||
|       if (newProfilePhotoUrl != null) { | ||||
|         if (!mounted) return; | ||||
|         setState(() { | ||||
|           profilePhotoUrl = | ||||
|               newProfilePhotoUrl; // Update the profilePhotoUrl to refresh the UI | ||||
|         }); | ||||
|         // Optionally, update the UserProvider if you are using it to manage user data globally | ||||
|         // Provider.of<UserProvider>(context, listen: false).setUserProfilePhotoUrl(newProfilePhotoUrl); | ||||
|         ScaffoldMessenger.of(context).showSnackBar( | ||||
|           const SnackBar( | ||||
|             content: Text('Photo de profil mise à jour avec succès!'), | ||||
|           ), | ||||
|         ); | ||||
|       } else { | ||||
|         ScaffoldMessenger.of(context).showSnackBar( | ||||
|           const SnackBar( | ||||
|             content: Text( | ||||
|               'Erreur lors de la mise à jour de la photo de profil.', | ||||
|             ), | ||||
|           ), | ||||
|         ); | ||||
|       } | ||||
|     } else { | ||||
|       // User cancelled image picking | ||||
|       ScaffoldMessenger.of(context).showSnackBar( | ||||
|         const SnackBar(content: Text('Sélection de photo annulée.')), | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final userProvider = Provider.of<UserProvider>(context); | ||||
|     final userProvider = Provider.of<UserProvider>( | ||||
|       context, | ||||
|     ); // Get UserProvider instance | ||||
|  | ||||
|     return Scaffold( | ||||
|       appBar: AppBar(title: const Text('Mon Compte')), | ||||
|       drawer: | ||||
|           MainDrawer(currentPage: '/my_account', userProvider: userProvider), | ||||
|       body: const Center( | ||||
|         child: Text('Page Mon Compte', style: TextStyle(fontSize: 24)), | ||||
|       appBar: AppBar(title: const Text('Gestion du compte')), | ||||
|       drawer: MainDrawer( | ||||
|         currentPage: '/my_account', | ||||
|         userProvider: userProvider, | ||||
|       ), // Pass UserProvider to MainDrawer | ||||
|       body: Padding( | ||||
|         padding: const EdgeInsets.all(16.0), | ||||
|         child: Column( | ||||
|           crossAxisAlignment: CrossAxisAlignment.center, | ||||
|           children: [ | ||||
|             MouseRegion( | ||||
|               onEnter: (_) => setState(() => _isHoveringProfilePic = true), | ||||
|               onExit: (_) => setState(() => _isHoveringProfilePic = false), | ||||
|               cursor: SystemMouseCursors.click, | ||||
|               child: GestureDetector( | ||||
|                 onTap: | ||||
|                     _changeProfilePicture, // Call _changeProfilePicture on tap | ||||
|                 child: Stack( | ||||
|                   alignment: Alignment.center, | ||||
|                   children: [ | ||||
|                     ProfilePictureWidget(userId: user!.uid, radius: 45), | ||||
|                     if (_isHoveringProfilePic) | ||||
|                       Container( | ||||
|                         width: 140, | ||||
|                         height: 140, | ||||
|                         decoration: BoxDecoration( | ||||
|                           color: Colors.black54, | ||||
|                           shape: BoxShape.circle, | ||||
|                         ), | ||||
|                         child: const Center( | ||||
|                           child: Icon( | ||||
|                             Icons.edit, | ||||
|                             color: Colors.white, | ||||
|                             size: 30, | ||||
|                           ), | ||||
|                         ), | ||||
|                       ), | ||||
|                   ], | ||||
|                 ), | ||||
|               ), | ||||
|             ), | ||||
|             const SizedBox(height: 20), | ||||
|             _buildTextField('Prénom', _firstNameController), | ||||
|             _buildTextField('Nom', _lastNameController), | ||||
|             _buildTextField('Numéro de téléphone', _phoneController), | ||||
|             _buildTextField( | ||||
|               'Email', | ||||
|               TextEditingController(text: user?.email ?? ''), | ||||
|               enabled: false, | ||||
|             ), | ||||
|             const SizedBox(height: 20), | ||||
|             ElevatedButton( | ||||
|               onPressed: _updateUserData, | ||||
|               child: const Text('Enregistrer'), | ||||
|             ), | ||||
|           ], | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   Widget _buildTextField( | ||||
|     String label, | ||||
|     TextEditingController controller, { | ||||
|     bool enabled = true, | ||||
|   }) { | ||||
|     return Padding( | ||||
|       padding: const EdgeInsets.symmetric(vertical: 8.0), | ||||
|       child: TextField( | ||||
|         controller: controller, | ||||
|         enabled: enabled, | ||||
|         decoration: InputDecoration( | ||||
|           labelText: label, | ||||
|           border: OutlineInputBorder(), | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
|   | ||||
							
								
								
									
										102
									
								
								em2rp/lib/views/widgets/image/profile_picture.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								em2rp/lib/views/widgets/image/profile_picture.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,102 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:cloud_firestore/cloud_firestore.dart'; | ||||
|  | ||||
| class ProfilePictureWidget extends StatelessWidget { | ||||
|   final String userId; | ||||
|   final double radius; | ||||
|   final String? defaultImageUrl; // URL de l'image par défaut (optionnel) | ||||
|  | ||||
|   const ProfilePictureWidget({ | ||||
|     super.key, | ||||
|     required this.userId, | ||||
|     this.radius = 25, | ||||
|     this.defaultImageUrl, | ||||
|   }); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return FutureBuilder<DocumentSnapshot>( | ||||
|       future: FirebaseFirestore.instance.collection('users').doc(userId).get(), | ||||
|       builder: (context, snapshot) { | ||||
|         if (snapshot.connectionState == ConnectionState.waiting) { | ||||
|           return _buildLoadingAvatar( | ||||
|               radius); // Afficher un avatar de chargement | ||||
|         } else if (snapshot.hasError) { | ||||
|           print("Erreur FutureBuilder ProfilePictureWidget: ${snapshot.error}"); | ||||
|           return _buildDefaultAvatar(radius, | ||||
|               defaultImageUrl); // Afficher avatar par défaut en cas d'erreur Firestore | ||||
|         } else if (snapshot.data != null && snapshot.data!.exists) { | ||||
|           final userData = snapshot.data!; | ||||
|           final profilePhotoUrl = userData['profilePhotoUrl'] as String?; | ||||
|  | ||||
|           if (profilePhotoUrl != null && profilePhotoUrl.isNotEmpty) { | ||||
|             return CircleAvatar( | ||||
|               radius: radius, | ||||
|               // Utilisation de Image.network directement dans backgroundImage | ||||
|               backgroundImage: Image.network( | ||||
|                 profilePhotoUrl, | ||||
|                 errorBuilder: (context, error, stackTrace) { | ||||
|                   print( | ||||
|                       "Erreur de chargement Image.network pour l'URL: $profilePhotoUrl, Erreur: $error"); | ||||
|                   // En cas d'erreur de chargement de l'image réseau, afficher l'avatar par défaut | ||||
|                   return _buildDefaultAvatar(radius, defaultImageUrl); | ||||
|                 }, | ||||
|               ).image, // .image pour obtenir un ImageProvider pour backgroundImage | ||||
|             ); | ||||
|           } else { | ||||
|             return _buildDefaultAvatar(radius, | ||||
|                 defaultImageUrl); // Pas d'URL dans Firestore, afficher avatar par défaut | ||||
|           } | ||||
|         } else { | ||||
|           return _buildDefaultAvatar(radius, | ||||
|               defaultImageUrl); // Document utilisateur non trouvé ou n'existe pas | ||||
|         } | ||||
|       }, | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   // Widget utilitaire pour construire un CircleAvatar de chargement | ||||
|   Widget _buildLoadingAvatar(double radius) { | ||||
|     return CircleAvatar( | ||||
|       radius: radius, | ||||
|       backgroundColor: | ||||
|           Colors.grey[300], // Couleur de fond pendant le chargement | ||||
|       child: SizedBox( | ||||
|         width: radius * 0.8, // Ajuster la taille du loader | ||||
|         height: radius * 0.8, | ||||
|         child: CircularProgressIndicator( | ||||
|             strokeWidth: 2), // Indicateur de chargement | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   // Widget utilitaire pour construire un CircleAvatar par défaut (avec icône ou image par défaut) | ||||
|   Widget _buildDefaultAvatar(double radius, String? defaultImageUrl) { | ||||
|     if (defaultImageUrl != null && defaultImageUrl.isNotEmpty) { | ||||
|       return CircleAvatar( | ||||
|         radius: radius, | ||||
|         // Utilisation de Image.network pour l'image par défaut, avec gestion d'erreur similaire | ||||
|         backgroundImage: Image.network( | ||||
|           defaultImageUrl, | ||||
|           errorBuilder: (context, error, stackTrace) { | ||||
|             print( | ||||
|                 "Erreur de chargement Image.network pour l'URL par défaut: $defaultImageUrl, Erreur: $error"); | ||||
|             return _buildIconAvatar( | ||||
|                 radius); // Si l'image par défaut ne charge pas, afficher l'icône | ||||
|           }, | ||||
|         ).image, // .image pour ImageProvider | ||||
|       ); | ||||
|     } else { | ||||
|       return _buildIconAvatar( | ||||
|           radius); // Si pas d'URL par défaut fournie, afficher l'icône | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // Widget utilitaire pour construire un CircleAvatar avec une icône par défaut | ||||
|   Widget _buildIconAvatar(double radius) { | ||||
|     return CircleAvatar( | ||||
|       radius: radius, | ||||
|       child: Icon(Icons.account_circle, size: radius * 1.5), // Icône par défaut | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @@ -4,6 +4,7 @@ import 'package:em2rp/views/calendar_page.dart'; | ||||
| import 'package:em2rp/views/my_account_page.dart'; | ||||
| import 'package:em2rp/views/user_management_page.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:em2rp/views/widgets/image/profile_picture.dart'; | ||||
|  | ||||
| class MainDrawer extends StatelessWidget { | ||||
|   final String currentPage; | ||||
| @@ -38,12 +39,9 @@ class MainDrawer extends StatelessWidget { | ||||
|                 crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                 mainAxisAlignment: MainAxisAlignment.end, | ||||
|                 children: [ | ||||
|                   CircleAvatar( | ||||
|                     radius: 25, | ||||
|                     backgroundImage: NetworkImage( | ||||
|                       userProvider.profilePhotoUrl ?? | ||||
|                           'https://firebasestorage.googleapis.com/v0/b/em2rp-951dc/o/EM2_NsurB.jpg?alt=media&token=530479c3-5f8c-413b-86a2-53ec4a4ed734', //TODO: Remplacer par logo EM2 | ||||
|                     ), | ||||
|                   ProfilePictureWidget( | ||||
|                     userId: userProvider.uid!, | ||||
|                     radius: 30, | ||||
|                   ), | ||||
|                   const SizedBox(height: 8), | ||||
|                   Text( | ||||
|   | ||||
		Reference in New Issue
	
	Block a user