diff --git a/em2rp/lib/main.dart b/em2rp/lib/main.dart index ff81bd0..1098ca2 100644 --- a/em2rp/lib/main.dart +++ b/em2rp/lib/main.dart @@ -1,4 +1,5 @@ import 'package:em2rp/utils/auth_guard_widget.dart'; +import 'package:em2rp/view_model/user_management_view_model.dart'; import 'package:em2rp/views/calendar_page.dart'; import 'package:em2rp/views/login_page.dart'; import 'package:firebase_auth/firebase_auth.dart'; @@ -10,6 +11,8 @@ import 'views/my_account_page.dart'; import 'views/user_management_page.dart'; import 'package:provider/provider.dart'; import 'providers/user_provider.dart'; +import 'providers/local_auth_provider.dart'; // Ajout de l'AuthProvider +import 'services/user_service.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -19,9 +22,25 @@ void main() async { await FirebaseAuth.instance.setPersistence(Persistence.LOCAL); runApp( - ChangeNotifierProvider( - // Wrap MyApp with ChangeNotifierProvider - create: (context) => UserProvider(), // Create UserProvider instance + MultiProvider( + providers: [ + // Injection du service UserService + Provider(create: (_) => UserService()), + + // AuthProvider pour la gestion de l'authentification + ChangeNotifierProvider( + create: (context) => LocalAuthProvider()), + + // UserProvider déjà existant + ChangeNotifierProvider( + create: (context) => UserProvider()), + + // Injection des ViewModels en utilisant UserService et AuthProvider + ChangeNotifierProvider( + create: (context) => + UserManagementViewModel(context.read()), + ), + ], child: const MyApp(), ), ); @@ -43,43 +62,27 @@ class MyApp extends StatelessWidget { textTheme: const TextTheme( bodyMedium: TextStyle(color: AppColors.noir), ), - // Personnalisation de l'InputDecorationTheme pour les text fields inputDecorationTheme: InputDecorationTheme( focusedBorder: OutlineInputBorder( - // Bordure lorsqu'il est focus - borderSide: - BorderSide(color: AppColors.noir), // Couleur rouge quand focus + borderSide: BorderSide(color: AppColors.noir), ), enabledBorder: OutlineInputBorder( - // Bordure par défaut (non focus) - borderSide: - BorderSide(color: AppColors.gris), // Couleur grise par défaut + borderSide: BorderSide(color: AppColors.gris), ), - labelStyle: TextStyle(color: AppColors.noir), // Couleur du label - hintStyle: TextStyle(color: AppColors.gris), // Couleur du hint text - // Tu peux personnaliser d'autres propriétés ici : - // fillColor, filled, iconColor, prefixStyle, suffixStyle, etc. + labelStyle: TextStyle(color: AppColors.noir), + hintStyle: TextStyle(color: AppColors.gris), ), elevatedButtonTheme: ElevatedButtonThemeData( style: ElevatedButton.styleFrom( - foregroundColor: - AppColors.blanc, // Couleur du texte du bouton (ici blanc) - backgroundColor: AppColors - .noir, // Couleur de fond du bouton (si tu veux aussi changer le fond) - // Autres styles possibles pour les boutons : - // textStyle, padding, shape, elevation, etc. + foregroundColor: AppColors.blanc, + backgroundColor: AppColors.noir, ), ), ), routes: { - //Pages accessibles à tous '/login': (context) => const LoginPage(), - - //Pages réservées aux UTILISATEURS CONNECTÉS '/calendar': (context) => const AuthGuard(child: CalendarPage()), '/my_account': (context) => const AuthGuard(child: MyAccountPage()), - - //Pages réservées aux ADMIN '/user_management': (context) => const AuthGuard(requiredRole: "ADMIN", child: UserManagementPage()), }, diff --git a/em2rp/lib/models/user_model.dart b/em2rp/lib/models/user_model.dart new file mode 100644 index 0000000..633456b --- /dev/null +++ b/em2rp/lib/models/user_model.dart @@ -0,0 +1,44 @@ +class UserModel { + final String uid; + final String firstName; + final String lastName; + final String role; + final String profilePhotoUrl; + final String email; + final String phoneNumber; + + UserModel({ + required this.uid, + required this.firstName, + required this.lastName, + required this.role, + required this.profilePhotoUrl, + required this.email, + required this.phoneNumber, + }); + + // Convertit une Map (Firestore) en UserModel + factory UserModel.fromMap(Map data, String uid) { + return UserModel( + uid: uid, + firstName: data['firstName'] ?? '', + lastName: data['lastName'] ?? '', + role: data['role'] ?? 'USER', + profilePhotoUrl: data['profilePhotoUrl'] ?? '', + email: data['email'] ?? '', + phoneNumber: data['phoneNumber'] ?? '', + ); + } + + // Convertit un UserModel en Map pour Firestore + Map toMap() { + return { + 'firstName': firstName, + 'lastName': lastName, + 'role': role, + 'profilePhotoUrl': profilePhotoUrl, + 'email': email, + 'phoneNumber': phoneNumber, + }; + } +} diff --git a/em2rp/lib/providers/local_auth_provider.dart b/em2rp/lib/providers/local_auth_provider.dart new file mode 100644 index 0000000..13b0175 --- /dev/null +++ b/em2rp/lib/providers/local_auth_provider.dart @@ -0,0 +1,46 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/material.dart'; +import '../models/user_model.dart'; + +class LocalAuthProvider with ChangeNotifier { + UserModel? _currentUser; + + UserModel? get currentUser => _currentUser; + String? get role => _currentUser?.role; + + void setUser(UserModel user) { + _currentUser = user; + notifyListeners(); + } + + void clearUser() { + _currentUser = null; + notifyListeners(); + } + + Future signInWithEmailAndPassword( + String email, String password) async { + try { + UserCredential userCredential = await FirebaseAuth.instance + .signInWithEmailAndPassword(email: email, password: password); + + DocumentSnapshot userDoc = await FirebaseFirestore.instance + .collection('users') + .doc(userCredential.user!.uid) + .get(); + + if (userDoc.exists) { + setUser(UserModel.fromMap( + userDoc.data() as Map, userDoc.id)); + } else { + throw FirebaseAuthException( + code: 'user-not-found', + message: "Aucune donnée utilisateur trouvée."); + } + return userCredential; + } on FirebaseAuthException catch (e) { + throw FirebaseAuthException(code: e.code, message: e.message); + } + } +} diff --git a/em2rp/lib/providers/user_provider.dart b/em2rp/lib/providers/user_provider.dart index ae8565e..70cbaec 100644 --- a/em2rp/lib/providers/user_provider.dart +++ b/em2rp/lib/providers/user_provider.dart @@ -21,7 +21,7 @@ class UserProvider extends ChangeNotifier { _uid = uid; _firstName = userData['firstName']; _lastName = userData['lastName']; - _role = userData['role'] ?? 'USER'; // Default role if not provided + _role = userData['role'] ?? 'USER'; if (userData['profilePhotoUrl'] != "") { _profilePictureUrl = userData['profilePhotoUrl']; } diff --git a/em2rp/lib/services/user_service.dart b/em2rp/lib/services/user_service.dart new file mode 100644 index 0000000..6fe5e50 --- /dev/null +++ b/em2rp/lib/services/user_service.dart @@ -0,0 +1,44 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import '../models/user_model.dart'; + +class UserService { + final FirebaseFirestore _firestore = FirebaseFirestore.instance; + + Future> fetchUsers() async { + try { + final snapshot = await _firestore.collection('users').get(); + return snapshot.docs + .map((doc) => UserModel.fromMap(doc.data(), doc.id)) + .toList(); + } catch (e) { + print("Erreur: $e"); + return []; + } + } + + Future updateUser(UserModel user) async { + try { + await _firestore.collection('users').doc(user.uid).update(user.toMap()); + } catch (e) { + print("Erreur mise à jour: $e"); + } + } + + Future deleteUser(String uid) async { + try { + await _firestore.collection('users').doc(uid).delete(); + } catch (e) { + print("Erreur suppression: $e"); + } + } + + Future resetPassword(String email) async { + try { + await FirebaseAuth.instance.sendPasswordResetEmail(email: email); + print("Email de réinitialisation envoyé à $email"); + } catch (e) { + print("Erreur reset password: $e"); + } + } +} diff --git a/em2rp/lib/utils/auth_guard_widget.dart b/em2rp/lib/utils/auth_guard_widget.dart index b483330..82020a7 100644 --- a/em2rp/lib/utils/auth_guard_widget.dart +++ b/em2rp/lib/utils/auth_guard_widget.dart @@ -1,3 +1,4 @@ +import 'package:em2rp/providers/local_auth_provider.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:em2rp/providers/user_provider.dart'; @@ -16,17 +17,17 @@ class AuthGuard extends StatelessWidget { @override Widget build(BuildContext context) { - final userProvider = Provider.of(context); + final localAuthProvider = Provider.of(context); - // Si l'utilisateur n'est pas connecté (aucun uid ou email) - if (userProvider.uid == null || userProvider.email == null) { + // Si l'utilisateur n'est pas connecté + if (localAuthProvider.currentUser == null) { // Retourne la page de connexion. // Vous pouvez aussi déclencher une redirection automatique si nécessaire. return const LoginPage(); } // Si la page requiert un rôle spécifique et que l'utilisateur ne le possède pas - if (requiredRole != null && userProvider.role != requiredRole) { + if (requiredRole != null && localAuthProvider.role != requiredRole) { return Scaffold( appBar: AppBar(title: const Text("Accès refusé")), body: const Center( diff --git a/em2rp/lib/utils/constants.dart b/em2rp/lib/utils/constants.dart new file mode 100644 index 0000000..ad670dd --- /dev/null +++ b/em2rp/lib/utils/constants.dart @@ -0,0 +1,3 @@ +class Constants { + static const List userRoles = ['USER', 'ADMIN']; +} diff --git a/em2rp/lib/view_model/login_view_model.dart b/em2rp/lib/view_model/login_view_model.dart new file mode 100644 index 0000000..b808a59 --- /dev/null +++ b/em2rp/lib/view_model/login_view_model.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import '../providers/local_auth_provider.dart'; +import 'package:provider/provider.dart'; + +class LoginViewModel extends ChangeNotifier { + final TextEditingController emailController = TextEditingController(); + final TextEditingController passwordController = TextEditingController(); + String errorMessage = ''; + bool isLoading = false; + bool obscurePassword = true; + bool highlightPasswordField = false; + bool highlightEmailField = false; + + void togglePasswordVisibility() { + obscurePassword = !obscurePassword; + notifyListeners(); + } + + Future signIn(BuildContext context) async { + final localAuthProvider = + Provider.of(context, listen: false); + isLoading = true; + errorMessage = ''; + highlightPasswordField = false; + highlightEmailField = false; + notifyListeners(); + + try { + await localAuthProvider.signInWithEmailAndPassword( + emailController.text.trim(), passwordController.text); + print('User signed in'); + + // Vérifier si le contexte est toujours valide + if (context.mounted) { + // Utiliser pushReplacementNamed pour une transition propre + Navigator.of(context, rootNavigator: true) + .pushReplacementNamed('/calendar'); + } + } on FirebaseAuthException catch (e) { + isLoading = false; + // Gérer les erreurs... + notifyListeners(); + } finally { + // S'assurer que isLoading est remis à false même en cas d'erreur inattendue + isLoading = false; + notifyListeners(); + } + } + + @override + void dispose() { + emailController.dispose(); + passwordController.dispose(); + super.dispose(); + } +} diff --git a/em2rp/lib/view_model/user_management_view_model.dart b/em2rp/lib/view_model/user_management_view_model.dart new file mode 100644 index 0000000..5e6309a --- /dev/null +++ b/em2rp/lib/view_model/user_management_view_model.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import '../models/user_model.dart'; +import '../services/user_service.dart'; + +class UserManagementViewModel extends ChangeNotifier { + final UserService _userService; + List _users = []; + bool _isLoading = false; + + List get users => _users; + bool get isLoading => _isLoading; + + UserManagementViewModel(this._userService); + + Future fetchUsers() async { + _isLoading = true; + notifyListeners(); + + _users = await _userService.fetchUsers(); + + _isLoading = false; + notifyListeners(); + } + + Future updateUser(UserModel user) async { + await _userService.updateUser(user); + fetchUsers(); + } + + Future deleteUser(String uid) async { + await _userService.deleteUser(uid); + fetchUsers(); + } + + Future resetPassword(String email) async { + await _userService.resetPassword(email); + } +} diff --git a/em2rp/lib/views/calendar_page.dart b/em2rp/lib/views/calendar_page.dart index 2327deb..dd59aba 100644 --- a/em2rp/lib/views/calendar_page.dart +++ b/em2rp/lib/views/calendar_page.dart @@ -1,3 +1,4 @@ +import 'package:em2rp/providers/local_auth_provider.dart'; import 'package:flutter/material.dart'; import 'package:em2rp/views/widgets/nav/main_drawer.dart'; import 'package:provider/provider.dart'; // Import Provider @@ -9,20 +10,19 @@ class CalendarPage extends StatelessWidget { @override Widget build(BuildContext context) { - final userProvider = Provider.of(context); + final localAuthProvider = Provider.of(context); return Scaffold( appBar: AppBar(title: const Text('Calendrier')), drawer: MainDrawer( - currentPage: '/calendar', - userProvider: userProvider), // Pass UserProvider to MainDrawer + currentPage: '/calendar'), // Pass UserProvider to MainDrawer body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text('Page Calendrier', style: TextStyle(fontSize: 24)), const SizedBox(height: 20), - if (userProvider.role == 'ADMIN') // Get role from UserProvider + if (localAuthProvider.role == 'ADMIN') // Get role from UserProvider const Text('Vue Admin du Calendrier', style: TextStyle(fontSize: 18, color: AppColors.rouge)) else diff --git a/em2rp/lib/views/login_page.dart b/em2rp/lib/views/login_page.dart index 843d61d..fba89c2 100644 --- a/em2rp/lib/views/login_page.dart +++ b/em2rp/lib/views/login_page.dart @@ -1,5 +1,4 @@ -import 'package:cloud_firestore/cloud_firestore.dart'; -import 'package:em2rp/providers/user_provider.dart'; +import 'package:em2rp/view_model/login_view_model.dart'; import 'package:em2rp/views/widgets/auth/mail_textfield.dart'; import 'package:em2rp/views/widgets/error_message.dart'; import 'package:em2rp/views/widgets/auth/forgot_password_button.dart'; @@ -8,117 +7,25 @@ import 'package:em2rp/views/widgets/auth/login_button.dart'; import 'package:em2rp/views/widgets/image/em2_logo_n_sur_b.dart'; import 'package:em2rp/views/widgets/auth/password_textfield.dart'; import 'package:em2rp/views/widgets/auth/welcome_text.dart'; -import 'package:firebase_auth/firebase_auth.dart'; -import 'package:flutter/material.dart'; import 'package:em2rp/views/widgets/auth/forgot_password_dialog.dart'; +import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -class LoginPage extends StatefulWidget { +class LoginPage extends StatelessWidget { const LoginPage({super.key}); - @override - State createState() => _LoginPageState(); -} - -class _LoginPageState extends State { - final _emailController = TextEditingController(); - final _passwordController = TextEditingController(); - String _errorMessage = ''; - bool _isLoading = false; - bool _obscurePassword = true; - bool _highlightPasswordField = false; - bool _highlightEmailField = false; - - @override - void dispose() { - _emailController.dispose(); - _passwordController.dispose(); - super.dispose(); - } - - Future _signInWithEmailAndPassword() async { - // Pas de vérification mounted ici, le widget reste monté pendant toute l'opération - setState(() { - _errorMessage = ''; - _isLoading = true; - _highlightPasswordField = false; - _highlightEmailField = false; - }); - - try { - final UserCredential userCredential = - await FirebaseAuth.instance.signInWithEmailAndPassword( - email: _emailController.text.trim(), - password: _passwordController.text, - ); - final uid = userCredential.user!.uid; - - final DocumentSnapshot userDoc = - await FirebaseFirestore.instance.collection('users').doc(uid).get(); - - if (userDoc.exists) { - print("User document found: ${userDoc.data()}"); - Provider.of(context, listen: false) - .setUserData(userDoc.data() as Map, uid); - - // 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') { - _errorMessage = "Email ou mot de passe incorrect."; - _highlightPasswordField = true; - _highlightEmailField = true; - } else { - _errorMessage = "Erreur de connexion. Veuillez réessayer."; - } - }); - } catch (e) { - if (!mounted) return; - setState(() { - _errorMessage = - "Erreur lors de la récupération des données utilisateur."; - _isLoading = false; - }); - } - } - - void _togglePasswordVisibility() { - if (!mounted) return; - setState(() { - _obscurePassword = !_obscurePassword; - }); - } - - void _forgotPassword() { - showDialog( - context: context, - builder: (BuildContext context) { - return const ForgotPasswordDialogWidget(); - }, - ); - } - @override Widget build(BuildContext context) { - return Scaffold( - body: LayoutBuilder( - builder: (context, constraints) { - if (constraints.maxWidth > 900) { - return _buildLargeScreenLayout(context); - } else { - return _buildSmallScreenLayout(context); - } - }, + return ChangeNotifierProvider( + create: (_) => LoginViewModel(), + child: Scaffold( + body: LayoutBuilder( + builder: (context, constraints) { + return constraints.maxWidth > 900 + ? _buildLargeScreenLayout(context) + : _buildSmallScreenLayout(context); + }, + ), ), ); } @@ -126,10 +33,7 @@ class _LoginPageState extends State { Widget _buildLargeScreenLayout(BuildContext context) { return Row( children: [ - Expanded( - flex: 6, - child: const BigLeftImageWidget(), - ), + const Expanded(flex: 6, child: BigLeftImageWidget()), Expanded( flex: 4, child: Padding( @@ -152,34 +56,45 @@ class _LoginPageState extends State { } Widget _buildLoginForm(BuildContext context) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - const LogoWidget(), - const SizedBox(height: 30), - const WelcomeTextWidget(), - const SizedBox(height: 40), - EmailTextFieldWidget( - emailController: _emailController, - highlightEmailField: _highlightEmailField, - ), - const SizedBox(height: 20), - PasswordTextFieldWidget( - passwordController: _passwordController, - obscurePassword: _obscurePassword, - highlightPasswordField: _highlightPasswordField, - onTogglePasswordVisibility: _togglePasswordVisibility, - ), - ForgotPasswordButtonWidget(onPressed: _forgotPassword), - const SizedBox(height: 30), - LoginButtonWidget( - isLoading: _isLoading, - onPressed: _signInWithEmailAndPassword, - ), - const SizedBox(height: 20), - ErrorMessageWidget(errorMessage: _errorMessage), - ], + return Consumer( + builder: (context, loginViewModel, child) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const LogoWidget(), + const SizedBox(height: 30), + const WelcomeTextWidget(), + const SizedBox(height: 40), + EmailTextFieldWidget( + emailController: loginViewModel.emailController, + highlightEmailField: loginViewModel.highlightEmailField, + ), + const SizedBox(height: 20), + PasswordTextFieldWidget( + passwordController: loginViewModel.passwordController, + obscurePassword: loginViewModel.obscurePassword, + highlightPasswordField: loginViewModel.highlightPasswordField, + onTogglePasswordVisibility: + loginViewModel.togglePasswordVisibility, + ), + ForgotPasswordButtonWidget( + onPressed: () => showDialog( + context: context, + builder: (BuildContext context) => + const ForgotPasswordDialogWidget(), + ), + ), + const SizedBox(height: 30), + LoginButtonWidget( + isLoading: loginViewModel.isLoading, + onPressed: () => loginViewModel.signIn(context), + ), + const SizedBox(height: 20), + ErrorMessageWidget(errorMessage: loginViewModel.errorMessage), + ], + ); + }, ); } } diff --git a/em2rp/lib/views/my_account_page.dart b/em2rp/lib/views/my_account_page.dart index e28a111..0f3dc01 100644 --- a/em2rp/lib/views/my_account_page.dart +++ b/em2rp/lib/views/my_account_page.dart @@ -1,3 +1,4 @@ +import 'package:em2rp/providers/local_auth_provider.dart'; import 'package:em2rp/providers/user_provider.dart'; import 'package:em2rp/utils/firebase_storage_manager.dart'; import 'package:em2rp/views/widgets/image/profile_picture.dart'; @@ -135,16 +136,15 @@ class _MyAccountPageState extends State { @override Widget build(BuildContext context) { - final userProvider = Provider.of( + final localAuthProvider = Provider.of( context, - ); + ); // Get UserProvider instance return Scaffold( - appBar: AppBar(title: const Text('Mon Compte')), + appBar: + AppBar(title: const Text('Mon Compte')), // More user-friendly title drawer: MainDrawer( - currentPage: '/my_account', - userProvider: userProvider, - ), // Pass UserProvider to MainDrawer + currentPage: '/my_account'), // Pass UserProvider to MainDrawer body: SingleChildScrollView( // Added SingleChildScrollView for better responsiveness child: Padding( diff --git a/em2rp/lib/views/user_management_page.dart b/em2rp/lib/views/user_management_page.dart index e8b7c86..cf23615 100644 --- a/em2rp/lib/views/user_management_page.dart +++ b/em2rp/lib/views/user_management_page.dart @@ -1,39 +1,67 @@ +import 'package:em2rp/providers/local_auth_provider.dart'; import 'package:flutter/material.dart'; -import 'package:em2rp/views/widgets/nav/main_drawer.dart'; -import 'package:em2rp/providers/user_provider.dart'; // Import UserProvider -import 'package:provider/provider.dart'; // Import Provider +import 'package:provider/provider.dart'; +import '../view_model/user_management_view_model.dart'; class UserManagementPage extends StatelessWidget { const UserManagementPage({super.key}); @override Widget build(BuildContext context) { - final userProvider = - Provider.of(context); // Get UserProvider instance + final userViewModel = Provider.of(context); + final authProvider = Provider.of(context); - if (userProvider.role != 'ADMIN') { - // Get role from UserProvider + if (authProvider.role != 'ADMIN') { return Scaffold( appBar: AppBar(title: const Text('Gestion des Utilisateurs')), - drawer: MainDrawer( - currentPage: '/user_management', - userProvider: userProvider), // Pass UserProvider to MainDrawer body: const Center( - child: Text('Accès non autorisé pour les utilisateurs non-Admin.', - style: TextStyle(fontSize: 18, color: Colors.red)), - ), - ); - } else { - return Scaffold( - appBar: AppBar(title: const Text('Gestion des Utilisateurs (Admin)')), - drawer: MainDrawer( - currentPage: '/user_management', - userProvider: userProvider), // Pass UserProvider to MainDrawer - body: const Center( - child: Text('Page de Gestion des Utilisateurs (Admin)', - style: TextStyle(fontSize: 24)), - ), + child: Text('Accès non autorisé', + style: TextStyle(color: Colors.red))), ); } + + return Scaffold( + appBar: AppBar(title: const Text('Gestion des Utilisateurs')), + body: userViewModel.isLoading + ? const Center(child: CircularProgressIndicator()) + : ListView.builder( + itemCount: userViewModel.users.length, + itemBuilder: (context, index) { + final user = userViewModel.users[index]; + return ListTile( + leading: CircleAvatar( + backgroundImage: NetworkImage(user.profilePhotoUrl)), + title: Text('${user.firstName} ${user.lastName}'), + subtitle: Text(user.email), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon(Icons.edit), + onPressed: () { + // Afficher la pop-up d'édition + }, + ), + IconButton( + icon: const Icon(Icons.lock_reset), + onPressed: () => + userViewModel.resetPassword(user.email), + ), + IconButton( + icon: const Icon(Icons.delete, color: Colors.red), + onPressed: () => userViewModel.deleteUser(user.uid), + ), + ], + ), + ); + }, + ), + floatingActionButton: FloatingActionButton( + onPressed: () { + // Ajouter un utilisateur + }, + child: const Icon(Icons.add), + ), + ); } } diff --git a/em2rp/lib/views/widgets/image/profile_picture.dart b/em2rp/lib/views/widgets/image/profile_picture.dart index 4e5621b..1abf26a 100644 --- a/em2rp/lib/views/widgets/image/profile_picture.dart +++ b/em2rp/lib/views/widgets/image/profile_picture.dart @@ -2,9 +2,9 @@ import 'package:flutter/material.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; class ProfilePictureWidget extends StatelessWidget { - final String userId; + final String? userId; // Modifié pour être nullable final double radius; - final String? defaultImageUrl; // URL de l'image par défaut (optionnel) + final String? defaultImageUrl; const ProfilePictureWidget({ super.key, @@ -15,42 +15,34 @@ class ProfilePictureWidget extends StatelessWidget { @override Widget build(BuildContext context) { + // Vérifier si userId est null ou vide + if (userId == null || userId!.isEmpty) { + return _buildDefaultAvatar(radius, defaultImageUrl); + } + return FutureBuilder( future: FirebaseFirestore.instance.collection('users').doc(userId).get(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { - return _buildLoadingAvatar( - radius); // Afficher un avatar de chargement + return _buildLoadingAvatar(radius); } else if (snapshot.hasError) { - print("Erreur FutureBuilder ProfilePictureWidget: ${snapshot.error}"); - return _buildDefaultAvatar(radius, - defaultImageUrl); // Afficher avatar par défaut en cas d'erreur Firestore + print("Error loading profile: ${snapshot.error}"); + return _buildDefaultAvatar(radius, defaultImageUrl); } else if (snapshot.data != null && snapshot.data!.exists) { - final userData = snapshot.data!; - final profilePhotoUrl = userData['profilePhotoUrl'] as String?; + final userData = snapshot.data!.data() as Map?; + 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 + backgroundImage: NetworkImage(profilePhotoUrl), + onBackgroundImageError: (e, stack) { + print("Error loading profile image: $e"); + }, ); - } 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 } + return _buildDefaultAvatar(radius, defaultImageUrl); }, ); } diff --git a/em2rp/lib/views/widgets/nav/main_drawer.dart b/em2rp/lib/views/widgets/nav/main_drawer.dart index 4bc2677..d56068a 100644 --- a/em2rp/lib/views/widgets/nav/main_drawer.dart +++ b/em2rp/lib/views/widgets/nav/main_drawer.dart @@ -1,128 +1,136 @@ -import 'package:em2rp/providers/user_provider.dart'; +import 'package:em2rp/providers/local_auth_provider.dart'; import 'package:em2rp/utils/colors.dart'; 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'; +import 'package:provider/provider.dart'; class MainDrawer extends StatelessWidget { final String currentPage; - final UserProvider userProvider; - const MainDrawer( - {super.key, required this.currentPage, required this.userProvider}); + const MainDrawer({super.key, required this.currentPage}); @override Widget build(BuildContext context) { - return Drawer( - child: ListView( - padding: EdgeInsets.zero, - children: [ - DrawerHeader( - // Header du drawer - decoration: BoxDecoration( - image: DecorationImage( - image: AssetImage('assets/EM2_NsurB.jpg'), - fit: BoxFit.cover, - colorFilter: ColorFilter.mode( - AppColors.noir.withOpacity(0.4), - BlendMode.darken, - ), - ), - ), - child: Container( - padding: const EdgeInsets.all(16.0), - alignment: Alignment.bottomLeft, - child: Column( - // Use Column to arrange logo and user name - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.end, - children: [ - ProfilePictureWidget( - userId: userProvider.uid!, - radius: 30, + return Consumer( + builder: (context, userProvider, child) { + final hasUser = userProvider.currentUser != null; + + return Drawer( + child: ListView( + padding: EdgeInsets.zero, + children: [ + DrawerHeader( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage('assets/EM2_NsurB.jpg'), + fit: BoxFit.cover, + colorFilter: ColorFilter.mode( + AppColors.noir.withOpacity(0.4), + BlendMode.darken, + ), ), - const SizedBox(height: 8), - Text( - 'Bonjour, ${userProvider.firstName ?? 'Erreur'}', - style: TextStyle( - color: AppColors.blanc, - fontSize: 18, - fontWeight: FontWeight.bold, - shadows: [ - Shadow( - blurRadius: 3.0, - color: AppColors.noir, - offset: Offset(1.0, 1.0), + ), + child: Container( + padding: const EdgeInsets.all(16.0), + alignment: Alignment.bottomLeft, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + if (hasUser) + ProfilePictureWidget( + userId: userProvider.currentUser!.uid, + radius: 30, + ) + else + CircleAvatar( + radius: 30, + child: Icon(Icons.account_circle, size: 45), + ), + const SizedBox(height: 8), + Text( + hasUser + ? 'Bonjour, ${userProvider.currentUser!.firstName}' + : 'Bonjour, Utilisateur', + style: TextStyle( + color: AppColors.blanc, + fontSize: 18, + fontWeight: FontWeight.bold, + shadows: [ + Shadow( + blurRadius: 3.0, + color: AppColors.noir, + offset: Offset(1.0, 1.0), + ), + ], + ), ), ], ), ), - ], + ), ), - ), - ), - ListTile( - // Lien vers la page Calendrier - leading: const Icon(Icons.calendar_today), - title: const Text('Calendrier'), - selected: currentPage == '/calendar', - selectedColor: AppColors.rouge, - onTap: () { - Navigator.pop(context); - Navigator.pushReplacement( - context, - MaterialPageRoute(builder: (context) => CalendarPage()), - ); - }, - ), - ExpansionTileTheme( - data: const ExpansionTileThemeData( - iconColor: AppColors.noir, - collapsedIconColor: AppColors.noir, - ), - child: ExpansionTile( - leading: const Icon(Icons.settings), - title: const Text('Paramètres'), - children: [ - ListTile( - leading: const Icon(Icons.account_circle), - // Lien vers "Mon Compte" - title: const Text('Mon Compte'), - selected: currentPage == '/my_account', - selectedColor: AppColors.rouge, - onTap: () { - Navigator.pop(context); - Navigator.pushReplacement( - context, - MaterialPageRoute( - builder: (context) => const MyAccountPage()), - ); - }, + ListTile( + leading: const Icon(Icons.calendar_today), + title: const Text('Calendrier'), + selected: currentPage == '/calendar', + selectedColor: AppColors.rouge, + onTap: () { + Navigator.pop(context); + Navigator.pushReplacement( + context, + MaterialPageRoute(builder: (context) => CalendarPage()), + ); + }, + ), + ExpansionTileTheme( + data: const ExpansionTileThemeData( + iconColor: AppColors.noir, + collapsedIconColor: AppColors.noir, ), - ListTile( - leading: const Icon(Icons.group), - // Lien vers "Gestion des Utilisateurs" - title: const Text('Gestion des Utilisateurs'), - selected: currentPage == - '/user_management', // Check if current page is UserManagementPage - selectedColor: AppColors.rouge, - onTap: () { - Navigator.pop(context); // Ferme le drawer - Navigator.pushReplacement( - // Navigue vers UserManagementPage - context, - MaterialPageRoute( - builder: (context) => const UserManagementPage()), - ); - }, + child: ExpansionTile( + leading: const Icon(Icons.settings), + title: const Text('Paramètres'), + children: [ + ListTile( + leading: const Icon(Icons.account_circle), + title: const Text('Mon Compte'), + selected: currentPage == '/my_account', + selectedColor: AppColors.rouge, + onTap: () { + Navigator.pop(context); + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => const MyAccountPage()), + ); + }, + ), + ListTile( + leading: const Icon(Icons.group), + title: const Text('Gestion des Utilisateurs'), + selected: currentPage == '/user_management', + selectedColor: AppColors.rouge, + onTap: () { + Navigator.pop(context); + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => const UserManagementPage()), + ); + }, + ), + ], ), - ], - ), + ), + ], ), - ], - ), + ); + }, ); } }