Initial commit du projet Flutter
This commit is contained in:
946
lib/pages/login.dart
Normal file
946
lib/pages/login.dart
Normal file
@@ -0,0 +1,946 @@
|
||||
// ===== lib/pages/login.dart AVEC SUPPORT ENTREPRISE =====
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wtpe/widgets/wortis_logo.dart';
|
||||
import '../main.dart';
|
||||
import '../services/wortis_api_service.dart';
|
||||
import 'role_navigator.dart';
|
||||
|
||||
class LoginPage extends StatefulWidget {
|
||||
const LoginPage({super.key});
|
||||
|
||||
@override
|
||||
_LoginPageState createState() => _LoginPageState();
|
||||
}
|
||||
|
||||
class _LoginPageState extends State<LoginPage>
|
||||
with SingleTickerProviderStateMixin {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _agentIdController = TextEditingController();
|
||||
final _pinController = TextEditingController();
|
||||
bool _obscurePin = true;
|
||||
bool _apiConnected = false;
|
||||
bool _testingConnection = false;
|
||||
|
||||
late AnimationController _animationController;
|
||||
late Animation<double> _fadeAnimation;
|
||||
late Animation<Offset> _slideAnimation;
|
||||
|
||||
// Couleurs Wortis
|
||||
static const Color primaryColor = Color(0xFF006699);
|
||||
static const Color secondaryColor = Color(0xFF0088CC);
|
||||
static const Color accentColor = Color(0xFFFF6B35);
|
||||
static const Color backgroundColor = Color(0xFFF8FAFC);
|
||||
static const Color surfaceColor = Colors.white;
|
||||
static const Color errorColor = Color(0xFFE53E3E);
|
||||
static const Color textPrimaryColor = Color(0xFF1A202C);
|
||||
static const Color textSecondaryColor = Color(0xFF718096);
|
||||
static const Color borderColor = Color(0xFFE2E8F0);
|
||||
static const Color enterpriseColor = Color(0xFF8B5CF6);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_setupAnimations();
|
||||
_testApiConnection();
|
||||
}
|
||||
|
||||
void _setupAnimations() {
|
||||
_animationController = AnimationController(
|
||||
duration: Duration(milliseconds: 1500),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_fadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
|
||||
CurvedAnimation(
|
||||
parent: _animationController,
|
||||
curve: Interval(0.0, 0.8, curve: Curves.easeOut),
|
||||
),
|
||||
);
|
||||
|
||||
_slideAnimation = Tween<Offset>(
|
||||
begin: Offset(0, 0.3),
|
||||
end: Offset.zero,
|
||||
).animate(
|
||||
CurvedAnimation(
|
||||
parent: _animationController,
|
||||
curve: Interval(0.3, 1.0, curve: Curves.easeOut),
|
||||
),
|
||||
);
|
||||
|
||||
_animationController.forward();
|
||||
}
|
||||
|
||||
Future<void> _testApiConnection() async {
|
||||
setState(() {
|
||||
_testingConnection = true;
|
||||
});
|
||||
|
||||
final isConnected = await AuthApiService.testConnection();
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_apiConnected = isConnected;
|
||||
_testingConnection = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _createTestUser() async {
|
||||
String? nom = await _showCreateUserDialog();
|
||||
if (nom == null || nom.isEmpty) return;
|
||||
|
||||
final result = await AuthApiService.createUser(nom, '1234');
|
||||
|
||||
if (result['success'] == true) {
|
||||
final agentId = result['generated_agent_id'];
|
||||
setState(() {
|
||||
_agentIdController.text = agentId;
|
||||
_pinController.text = '1234';
|
||||
});
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Row(
|
||||
children: [
|
||||
Icon(Icons.check_circle, color: Colors.white),
|
||||
SizedBox(width: 8),
|
||||
Expanded(child: Text('Utilisateur créé: $agentId / PIN: 1234')),
|
||||
],
|
||||
),
|
||||
backgroundColor: Colors.green,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Erreur: ${result['message']}'),
|
||||
backgroundColor: Colors.orange,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> _showCreateUserDialog() async {
|
||||
final nameController = TextEditingController();
|
||||
|
||||
return showDialog<String>(
|
||||
context: context,
|
||||
builder:
|
||||
(context) => AlertDialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
title: Row(
|
||||
children: [
|
||||
Icon(Icons.person_add, color: primaryColor),
|
||||
SizedBox(width: 12),
|
||||
Text('Créer un utilisateur'),
|
||||
],
|
||||
),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: primaryColor.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: primaryColor.withOpacity(0.3)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.info_outline, color: primaryColor, size: 20),
|
||||
SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'L\'ID agent sera généré automatiquement (WRT####)',
|
||||
style: TextStyle(fontSize: 12, color: primaryColor),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: nameController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Nom complet',
|
||||
hintText: 'Ex: Jean Dupont',
|
||||
prefixIcon: Icon(Icons.person_outline),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
autofocus: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text('Annuler'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed:
|
||||
() => Navigator.pop(context, nameController.text.trim()),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: primaryColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
child: Text('Créer', style: TextStyle(color: Colors.white)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_agentIdController.dispose();
|
||||
_pinController.dispose();
|
||||
_animationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _login() async {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
final authController = Provider.of<AuthController>(
|
||||
context,
|
||||
listen: false,
|
||||
);
|
||||
|
||||
authController.clearError();
|
||||
|
||||
bool success = await authController.login(
|
||||
_agentIdController.text.trim(),
|
||||
_pinController.text.trim(),
|
||||
false,
|
||||
);
|
||||
|
||||
if (success && mounted) {
|
||||
// Afficher un message de bienvenue personnalisé
|
||||
final isEnterprise = authController.isEnterpriseMember;
|
||||
final accountType = isEnterprise ? 'Entreprise' : 'Agent';
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
isEnterprise ? Icons.business : Icons.person,
|
||||
color: Colors.white,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'Bienvenue ${authController.agentName}',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
if (isEnterprise &&
|
||||
authController.enterprise?.nomEntreprise != null) ...[
|
||||
SizedBox(height: 2),
|
||||
Text(
|
||||
authController.enterprise!.nomEntreprise,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withOpacity(0.9),
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
accountType,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
backgroundColor: isEnterprise ? enterpriseColor : Colors.green,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
duration: Duration(seconds: 2),
|
||||
margin: EdgeInsets.all(16),
|
||||
),
|
||||
);
|
||||
|
||||
// Navigation basée sur le rôle
|
||||
final userRole = authController.role ?? 'agent';
|
||||
RoleNavigator.navigateByRole(context, userRole);
|
||||
} else if (mounted) {
|
||||
final errorMessage =
|
||||
authController.errorMessage ?? 'Erreur de connexion';
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Row(
|
||||
children: [
|
||||
Icon(Icons.error_outline, color: Colors.white, size: 24),
|
||||
SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'Connexion échouée',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 2),
|
||||
Text(errorMessage, style: TextStyle(fontSize: 12)),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
backgroundColor: errorColor,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
duration: Duration(seconds: 4),
|
||||
margin: EdgeInsets.all(16),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _openWifiSettings() async {
|
||||
try {
|
||||
const platform = MethodChannel('com.wortis.agent/settings');
|
||||
await platform.invokeMethod('exitKioskMode');
|
||||
await platform.invokeMethod('openWifiSettings');
|
||||
} catch (e) {
|
||||
print('Erreur ouverture WiFi: $e');
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Impossible d\'ouvrir les paramètres WiFi'),
|
||||
backgroundColor: Colors.orange,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: backgroundColor,
|
||||
body: SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
physics: BouncingScrollPhysics(),
|
||||
padding: EdgeInsets.all(24),
|
||||
child: FadeTransition(
|
||||
opacity: _fadeAnimation,
|
||||
child: SlideTransition(
|
||||
position: _slideAnimation,
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(height: 20),
|
||||
_buildApiStatusIndicator(),
|
||||
SizedBox(height: 20),
|
||||
_buildHeader(),
|
||||
SizedBox(height: 50),
|
||||
_buildLoginForm(),
|
||||
SizedBox(height: 30),
|
||||
_buildHelpSection(),
|
||||
SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildApiStatusIndicator() {
|
||||
return Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
_testingConnection
|
||||
? Colors.orange.withOpacity(0.1)
|
||||
: _apiConnected
|
||||
? Colors.green.withOpacity(0.1)
|
||||
: Colors.red.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(25),
|
||||
border: Border.all(
|
||||
color:
|
||||
_testingConnection
|
||||
? Colors.orange.withOpacity(0.3)
|
||||
: _apiConnected
|
||||
? Colors.green.withOpacity(0.3)
|
||||
: Colors.red.withOpacity(0.3),
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (_testingConnection) ...[
|
||||
SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.orange),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'Test connexion...',
|
||||
style: TextStyle(
|
||||
color: Colors.orange.shade700,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
] else ...[
|
||||
Container(
|
||||
padding: EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
_apiConnected
|
||||
? Colors.green.withOpacity(0.2)
|
||||
: Colors.red.withOpacity(0.2),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
_apiConnected
|
||||
? Icons.cloud_done_rounded
|
||||
: Icons.cloud_off_rounded,
|
||||
size: 16,
|
||||
color:
|
||||
_apiConnected ? Colors.green.shade700 : Colors.red.shade700,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
_apiConnected ? 'API connectée' : 'API déconnectée',
|
||||
style: TextStyle(
|
||||
color:
|
||||
_apiConnected ? Colors.green.shade700 : Colors.red.shade700,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
GestureDetector(
|
||||
onTap: _testApiConnection,
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.5),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.refresh_rounded,
|
||||
size: 16,
|
||||
color:
|
||||
_apiConnected
|
||||
? Colors.green.shade700
|
||||
: Colors.red.shade700,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader() {
|
||||
return Column(
|
||||
children: [
|
||||
WortisLogoWidget(size: 100, isWhite: false, withShadow: true),
|
||||
SizedBox(height: 24),
|
||||
ShaderMask(
|
||||
shaderCallback:
|
||||
(bounds) => LinearGradient(
|
||||
colors: [primaryColor, secondaryColor],
|
||||
).createShader(bounds),
|
||||
child: Text(
|
||||
'WORTIS AGENT',
|
||||
style: TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
letterSpacing: 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
'Connexion à votre espace',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: textSecondaryColor,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
// Badges des types de comptes supportés
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
_buildAccountTypeBadge(
|
||||
icon: Icons.person,
|
||||
label: 'Agent',
|
||||
color: primaryColor,
|
||||
),
|
||||
SizedBox(width: 12),
|
||||
_buildAccountTypeBadge(
|
||||
icon: Icons.business,
|
||||
label: 'Entreprise',
|
||||
color: enterpriseColor,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAccountTypeBadge({
|
||||
required IconData icon,
|
||||
required String label,
|
||||
required Color color,
|
||||
}) {
|
||||
return Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: color.withOpacity(0.3), width: 1),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(icon, color: color, size: 14),
|
||||
SizedBox(width: 6),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
color: color,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLoginForm() {
|
||||
return Container(
|
||||
padding: EdgeInsets.all(28),
|
||||
decoration: BoxDecoration(
|
||||
color: surfaceColor,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.08),
|
||||
blurRadius: 20,
|
||||
offset: Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: primaryColor.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.login_rounded,
|
||||
color: primaryColor,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 12),
|
||||
Text(
|
||||
'Identifiants',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: textPrimaryColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
SizedBox(height: 24),
|
||||
|
||||
_buildTextField(
|
||||
controller: _agentIdController,
|
||||
label: 'ID Agent',
|
||||
hint: 'Entrez votre identifiant',
|
||||
prefixIcon: Icons.badge_outlined,
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'L\'ID agent est requis';
|
||||
}
|
||||
if (value.length < 3) {
|
||||
return 'ID agent trop court (min. 3 caractères)';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
|
||||
SizedBox(height: 20),
|
||||
|
||||
_buildTextField(
|
||||
controller: _pinController,
|
||||
label: 'Code PIN',
|
||||
hint: 'Entrez votre code PIN',
|
||||
prefixIcon: Icons.lock_outline,
|
||||
obscureText: _obscurePin,
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(
|
||||
_obscurePin
|
||||
? Icons.visibility_outlined
|
||||
: Icons.visibility_off_outlined,
|
||||
color: textSecondaryColor,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_obscurePin = !_obscurePin;
|
||||
});
|
||||
},
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Le code PIN est requis';
|
||||
}
|
||||
if (value.length < 4) {
|
||||
return 'Le PIN doit contenir au moins 4 chiffres';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
|
||||
SizedBox(height: 32),
|
||||
|
||||
Consumer<AuthController>(
|
||||
builder: (context, authController, child) {
|
||||
return _buildLoginButton(authController);
|
||||
},
|
||||
),
|
||||
|
||||
SizedBox(height: 16),
|
||||
|
||||
Container(
|
||||
width: double.infinity,
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: _openWifiSettings,
|
||||
icon: Icon(Icons.wifi_find_rounded, size: 20),
|
||||
label: Text('Paramètres WiFi'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: EdgeInsets.symmetric(vertical: 14),
|
||||
side: BorderSide(color: primaryColor, width: 1.5),
|
||||
foregroundColor: primaryColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Bouton de test (commenté par défaut)
|
||||
// if (_apiConnected) ...[
|
||||
// SizedBox(height: 16),
|
||||
// TextButton.icon(
|
||||
// onPressed: _createTestUser,
|
||||
// icon: Icon(Icons.add_circle_outline, size: 18),
|
||||
// label: Text('Créer utilisateur test'),
|
||||
// style: TextButton.styleFrom(foregroundColor: primaryColor),
|
||||
// ),
|
||||
// ],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTextField({
|
||||
required TextEditingController controller,
|
||||
required String label,
|
||||
required String hint,
|
||||
required IconData prefixIcon,
|
||||
Widget? suffixIcon,
|
||||
bool obscureText = false,
|
||||
String? Function(String?)? validator,
|
||||
}) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: textPrimaryColor,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
TextFormField(
|
||||
controller: controller,
|
||||
validator: validator,
|
||||
obscureText: obscureText,
|
||||
decoration: InputDecoration(
|
||||
hintText: hint,
|
||||
hintStyle: TextStyle(color: textSecondaryColor),
|
||||
prefixIcon: Icon(prefixIcon, color: primaryColor),
|
||||
suffixIcon: suffixIcon,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: borderColor),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: borderColor),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: primaryColor, width: 2),
|
||||
),
|
||||
errorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: errorColor),
|
||||
),
|
||||
focusedErrorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: errorColor, width: 2),
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
||||
filled: true,
|
||||
fillColor: backgroundColor.withOpacity(0.3),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLoginButton(AuthController authController) {
|
||||
return Container(
|
||||
height: 54,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [primaryColor, secondaryColor],
|
||||
begin: Alignment.centerLeft,
|
||||
end: Alignment.centerRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: primaryColor.withOpacity(0.4),
|
||||
blurRadius: 10,
|
||||
offset: Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: ElevatedButton(
|
||||
onPressed: authController.isLoading ? null : _login,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.transparent,
|
||||
shadowColor: Colors.transparent,
|
||||
foregroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child:
|
||||
authController.isLoading
|
||||
? SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2.5,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
||||
),
|
||||
)
|
||||
: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.login_rounded, size: 22),
|
||||
SizedBox(width: 10),
|
||||
Text(
|
||||
'SE CONNECTER',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 1.2,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHelpSection() {
|
||||
return Container(
|
||||
padding: EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
secondaryColor.withOpacity(0.1),
|
||||
primaryColor.withOpacity(0.05),
|
||||
],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: secondaryColor.withOpacity(0.3), width: 1.5),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: primaryColor.withOpacity(0.2),
|
||||
blurRadius: 10,
|
||||
offset: Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Icon(
|
||||
Icons.help_outline_rounded,
|
||||
color: primaryColor,
|
||||
size: 32,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
'Besoin d\'aide ?',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: textPrimaryColor,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
Container(
|
||||
padding: EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.7),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
|
||||
// child: Column(
|
||||
// children: [
|
||||
// Row(
|
||||
// children: [
|
||||
// Icon(Icons.person, color: primaryColor, size: 16),
|
||||
// SizedBox(width: 8),
|
||||
// Expanded(
|
||||
// child: Text(
|
||||
// 'Compte Agent - Accès individuel',
|
||||
// style: TextStyle(fontSize: 13, color: textPrimaryColor),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// SizedBox(height: 8),
|
||||
// Row(
|
||||
// children: [
|
||||
// Icon(Icons.business, color: enterpriseColor, size: 16),
|
||||
// SizedBox(width: 8),
|
||||
// Expanded(
|
||||
// child: Text(
|
||||
// 'Compte Entreprise - Solde partagé',
|
||||
// style: TextStyle(fontSize: 13, color: textPrimaryColor),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: primaryColor,
|
||||
borderRadius: BorderRadius.circular(25),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: primaryColor.withOpacity(0.3),
|
||||
blurRadius: 8,
|
||||
offset: Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.phone, color: Colors.white, size: 18),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'Support: 50 05',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user