You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

538 lines
18 KiB

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:youmazgestion/Services/stock_managementDatabase.dart';
import 'package:youmazgestion/Services/PermissionCacheService.dart'; // Nouveau import
import 'package:youmazgestion/Views/Dashboard.dart';
import 'package:youmazgestion/Views/mobilepage.dart';
import 'package:youmazgestion/Views/particles.dart' show ParticleBackground;
import 'package:youmazgestion/accueil.dart';
import '../Models/users.dart';
import '../controller/userController.dart';
class LoginPage extends StatefulWidget {
const LoginPage({super.key});
@override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
late TextEditingController _usernameController;
late TextEditingController _passwordController;
final UserController userController = Get.put(UserController());
final PermissionCacheService _cacheService = PermissionCacheService.instance; // Nouveau
bool _isErrorVisible = false;
bool _isLoading = false;
String _errorMessage = 'Nom d\'utilisateur ou mot de passe invalide';
String _loadingMessage = 'Connexion en cours...'; // Nouveau
@override
void initState() {
super.initState();
_usernameController = TextEditingController();
_passwordController = TextEditingController();
checkUserCount();
}
void checkUserCount() async {
try {
final userCount = await AppDatabase.instance.getUserCount();
print('Nombre d\'utilisateurs trouvés: $userCount');
} catch (error) {
print('Erreur lors de la vérification du nombre d\'utilisateurs: $error');
setState(() {
_errorMessage = 'Erreur de connexion à la base de données';
_isErrorVisible = true;
});
}
}
@override
void dispose() {
_usernameController.dispose();
_passwordController.dispose();
super.dispose();
}
// /// ✅ OPTIMISÉ: Sauvegarde avec préchargement des permissions
// Future<void> saveUserData(Users user, String role, int userId) async {
// try {
// userController.setUserWithCredentials(user, role, userId);
// if (user.pointDeVenteId != null) {
// await userController.loadPointDeVenteDesignation();
// }
// print('✅ Utilisateur sauvegardé avec point de vente: ${userController.pointDeVenteDesignation}');
// } catch (error) {
// print('❌ Erreur lors de la sauvegarde: $error');
// throw Exception('Erreur lors de la sauvegarde des données utilisateur');
// }
// }
/// ✅ NOUVEAU: Préchargement des permissions en arrière-plan
Future<void> _preloadUserPermissions(String username) async {
try {
setState(() {
_loadingMessage = 'Préparation du menu...';
});
// Lancer le préchargement en parallèle avec les autres tâches
final permissionFuture = _cacheService.preloadUserData(username);
// Attendre maximum 2 secondes pour les permissions
await Future.any([
permissionFuture,
Future.delayed(const Duration(seconds: 2))
]);
print('✅ Permissions préparées (ou timeout)');
} catch (e) {
print('⚠️ Erreur préchargement permissions: $e');
// Continuer même en cas d'erreur
}
}
/// ✅ OPTIMISÉ: Connexion avec préchargement parallèle
void _login() async {
if (_isLoading) return;
final String username = _usernameController.text.trim();
final String password = _passwordController.text.trim();
if (username.isEmpty || password.isEmpty) {
setState(() {
_errorMessage = 'Veuillez saisir le nom d\'utilisateur et le mot de passe';
_isErrorVisible = true;
});
return;
}
setState(() {
_isLoading = true;
_isErrorVisible = false;
_loadingMessage = 'Connexion...';
});
try {
print('🔐 Tentative de connexion pour: $username');
final dbInstance = AppDatabase.instance;
// 1. Vérification rapide de la base
setState(() {
_loadingMessage = 'Vérification...';
});
try {
final userCount = await dbInstance.getUserCount();
print('✅ Base accessible, $userCount utilisateurs');
} catch (dbError) {
throw Exception('Base de données inaccessible: $dbError');
}
// 2. Vérification des identifiants
setState(() {
_loadingMessage = 'Authentification...';
});
bool isValidUser = await dbInstance.verifyUser(username, password);
if (isValidUser) {
setState(() {
_loadingMessage = 'Chargement du profil...';
});
// 3. Récupération parallèle des données
final futures = await Future.wait([
dbInstance.getUser(username),
dbInstance.getUserCredentials(username, password),
]);
final user = futures[0] as Users;
final userCredentials = futures[1] as Map<String, dynamic>?;
if (userCredentials != null) {
print('✅ Connexion réussie pour: ${user.username}');
print(' Rôle: ${userCredentials['role']}');
setState(() {
_loadingMessage = 'Préparation...';
});
// 4. Sauvegarde des données utilisateur
await saveUserData(
user,
userCredentials['role'] as String,
userCredentials['id'] as int,
);
// 5. Préchargement des permissions EN PARALLÈLE avec la navigation
setState(() {
_loadingMessage = 'Finalisation...';
});
// Lancer le préchargement en arrière-plan SANS attendre
_cacheService.preloadUserDataAsync(username);
// 6. Navigation immédiate
if (mounted) {
if (userCredentials['role'] == 'commercial') {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => const MainLayout()),
);
} else {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => DashboardPage()),
);
}
}
// Les permissions se chargeront en arrière-plan après la navigation
print('🚀 Navigation immédiate, permissions en arrière-plan');
} else {
throw Exception('Erreur lors de la récupération des credentials');
}
} else {
setState(() {
_errorMessage = 'Nom d\'utilisateur ou mot de passe invalide';
_isErrorVisible = true;
});
}
} catch (error) {
setState(() {
_errorMessage = 'Erreur de connexion: ${error.toString()}';
_isErrorVisible = true;
});
} finally {
if (mounted) {
setState(() {
_isLoading = false;
_loadingMessage = 'Connexion en cours...';
});
}
}
}
/// ✅ OPTIMISÉ: Sauvegarde rapide
Future<void> saveUserData(Users user, String role, int userId) async {
try {
userController.setUserWithCredentials(user, role, userId);
// Charger le point de vente en parallèle si nécessaire
if (user.pointDeVenteId != null) {
// Ne pas attendre, charger en arrière-plan
unawaited(userController.loadPointDeVenteDesignation());
}
print('✅ Utilisateur sauvegardé rapidement');
} catch (error) {
print('❌ Erreur lors de la sauvegarde: $error');
throw Exception('Erreur lors de la sauvegarde des données utilisateur');
}
}
@override
Widget build(BuildContext context) {
final Color primaryBlue = const Color(0xFF0033A1);
final Color accentRed = const Color(0xFFD70000);
final Color secondaryBlue = const Color(0xFF1976D2);
final Color primaryColor = primaryBlue;
final Color accentColor = secondaryBlue;
final Color cardColor = Colors.white;
return Scaffold(
backgroundColor: primaryColor,
body: ParticleBackground(
child: Center(
child: SingleChildScrollView(
child: Container(
width: MediaQuery.of(context).size.width < 500
? double.infinity
: 400,
padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 32.0),
decoration: BoxDecoration(
color: cardColor.withOpacity(0.98),
borderRadius: BorderRadius.circular(30.0),
boxShadow: [
BoxShadow(
color: primaryColor.withOpacity(0.2),
blurRadius: 16,
spreadRadius: 4,
offset: const Offset(0, 8),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Header
Center(
child: Column(
children: [
CircleAvatar(
radius: 38,
backgroundColor: accentColor.withOpacity(0.15),
child: Icon(
Icons.lock_outline,
color: accentColor,
size: 50,
),
),
const SizedBox(height: 14),
Text(
'GUYCOM',
style: TextStyle(
color: primaryColor,
fontWeight: FontWeight.bold,
fontSize: 28,
),
),
const SizedBox(height: 4),
Text(
'Connectez-vous à votre compte',
style: TextStyle(
color: primaryColor.withOpacity(.8),
fontSize: 16,
),
),
],
),
),
const SizedBox(height: 24),
// Username Field
TextField(
controller: _usernameController,
enabled: !_isLoading,
decoration: InputDecoration(
labelText: 'Nom d\'utilisateur',
labelStyle: TextStyle(
color: primaryColor.withOpacity(0.7),
),
prefixIcon: Icon(Icons.person, color: accentColor),
filled: true,
fillColor: accentColor.withOpacity(0.045),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(30.0),
borderSide: BorderSide(color: accentColor, width: 2),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(30.0),
borderSide: BorderSide(color: accentColor, width: 2),
),
),
),
const SizedBox(height: 18.0),
// Password Field
TextField(
controller: _passwordController,
enabled: !_isLoading,
obscureText: true,
decoration: InputDecoration(
labelText: 'Mot de passe',
labelStyle: TextStyle(
color: primaryColor.withOpacity(0.7),
),
prefixIcon: Icon(Icons.lock, color: accentColor),
filled: true,
fillColor: accentColor.withOpacity(0.045),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(30.0),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(30.0),
borderSide: BorderSide(color: accentColor, width: 2),
),
),
onSubmitted: (_) => _login(),
),
if (_isLoading) ...[
const SizedBox(height: 16.0),
Column(
children: [
// Barre de progression animée
Container(
height: 4,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(2),
color: accentColor.withOpacity(0.2),
),
child: LayoutBuilder(
builder: (context, constraints) {
return AnimatedContainer(
duration: const Duration(milliseconds: 300),
width: constraints.maxWidth * 0.7,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(2),
gradient: LinearGradient(
colors: [accentColor, accentColor.withOpacity(0.7)],
),
),
);
},
),
),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
height: 16,
width: 16,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(accentColor),
),
),
const SizedBox(width: 12),
Expanded(
child: Text(
_loadingMessage,
style: TextStyle(
color: accentColor,
fontSize: 14,
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.left,
),
),
],
),
const SizedBox(height: 4),
Text(
"Le menu se chargera en arrière-plan",
style: TextStyle(
color: Colors.grey.shade600,
fontSize: 12,
),
textAlign: TextAlign.center,
),
],
),
],
// Error Message
if (_isErrorVisible) ...[
const SizedBox(height: 12.0),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.red.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.red.withOpacity(0.3)),
),
child: Row(
children: [
Icon(Icons.error_outline, color: Colors.red, size: 20),
const SizedBox(width: 8),
Expanded(
child: Text(
_errorMessage,
style: const TextStyle(
color: Colors.red,
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
),
],
),
),
],
const SizedBox(height: 26.0),
// Login Button
ElevatedButton(
onPressed: _isLoading ? null : _login,
style: ElevatedButton.styleFrom(
backgroundColor: accentColor,
disabledBackgroundColor: accentColor.withOpacity(0.3),
foregroundColor: Colors.white,
elevation: 7.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30.0),
),
minimumSize: const Size(double.infinity, 52),
),
child: _isLoading
? Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
color: Colors.white,
strokeWidth: 2,
),
),
const SizedBox(width: 12),
Text(
'Connexion...',
style: const TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
],
)
: const Text(
'Se connecter',
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
letterSpacing: .4,
),
),
),
// Debug Button (à enlever en production)
if (_isErrorVisible && !_isLoading) ...[
const SizedBox(height: 8),
TextButton(
onPressed: () async {
try {
final count = await AppDatabase.instance.getUserCount();
final stats = _cacheService.getCacheStats();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'BDD: $count utilisateurs\n'
'Cache: ${stats['users_cached']} utilisateurs en cache',
),
duration: const Duration(seconds: 3),
),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Erreur: $e')),
);
}
},
child: Text(
'Debug: Vérifier BDD & Cache',
style: TextStyle(
color: primaryColor.withOpacity(0.6),
fontSize: 12,
),
),
),
],
],
),
),
),
),
),
);
}
}