first commit

This commit is contained in:
DahoudG
2025-08-20 21:00:35 +00:00
commit b2a23bdf89
583 changed files with 243074 additions and 0 deletions

View File

@@ -0,0 +1,517 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../../../../shared/theme/app_theme.dart';
import '../../../../shared/widgets/custom_text_field.dart';
import '../../../../shared/widgets/loading_button.dart';
import '../../../navigation/presentation/pages/main_navigation.dart';
import 'forgot_password_screen.dart';
import 'register_screen.dart';
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
@override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen>
with TickerProviderStateMixin {
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
late AnimationController _fadeController;
late AnimationController _slideController;
late Animation<double> _fadeAnimation;
late Animation<Offset> _slideAnimation;
bool _isLoading = false;
bool _obscurePassword = true;
bool _rememberMe = false;
@override
void initState() {
super.initState();
_initializeAnimations();
_startAnimations();
}
void _initializeAnimations() {
_fadeController = AnimationController(
duration: const Duration(milliseconds: 800),
vsync: this,
);
_slideController = AnimationController(
duration: const Duration(milliseconds: 600),
vsync: this,
);
_fadeAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _fadeController,
curve: Curves.easeInOut,
));
_slideAnimation = Tween<Offset>(
begin: const Offset(0, 0.3),
end: Offset.zero,
).animate(CurvedAnimation(
parent: _slideController,
curve: Curves.easeOutCubic,
));
}
void _startAnimations() async {
await Future.delayed(const Duration(milliseconds: 100));
_fadeController.forward();
_slideController.forward();
}
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
_fadeController.dispose();
_slideController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppTheme.backgroundLight,
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios, color: AppTheme.textPrimary),
onPressed: () => Navigator.of(context).pop(),
),
),
body: SafeArea(
child: AnimatedBuilder(
animation: _fadeAnimation,
builder: (context, child) {
return Opacity(
opacity: _fadeAnimation.value,
child: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: SlideTransition(
position: _slideAnimation,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildHeader(),
const SizedBox(height: 40),
_buildLoginForm(),
const SizedBox(height: 24),
_buildForgotPassword(),
const SizedBox(height: 32),
_buildLoginButton(),
const SizedBox(height: 24),
_buildDivider(),
const SizedBox(height: 24),
_buildSocialLogin(),
const SizedBox(height: 32),
_buildSignUpLink(),
],
),
),
),
);
},
),
),
);
}
Widget _buildHeader() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Logo petit
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: AppTheme.primaryColor,
borderRadius: BorderRadius.circular(15),
),
child: const Icon(
Icons.groups_rounded,
color: Colors.white,
size: 30,
),
),
const SizedBox(height: 24),
// Titre
const Text(
'Bienvenue !',
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: AppTheme.textPrimary,
),
),
const SizedBox(height: 8),
// Sous-titre
Text(
'Connectez-vous à votre compte UnionFlow',
style: TextStyle(
fontSize: 16,
color: AppTheme.textSecondary,
),
),
],
);
}
Widget _buildLoginForm() {
return Form(
key: _formKey,
child: Column(
children: [
// Champ Email
CustomTextField(
controller: _emailController,
label: 'Adresse email',
hintText: 'votre.email@exemple.com',
prefixIcon: Icons.email_outlined,
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.next,
validator: _validateEmail,
onFieldSubmitted: (_) => FocusScope.of(context).nextFocus(),
),
const SizedBox(height: 16),
// Champ Mot de passe
CustomTextField(
controller: _passwordController,
label: 'Mot de passe',
hintText: 'Votre mot de passe',
prefixIcon: Icons.lock_outline,
obscureText: _obscurePassword,
textInputAction: TextInputAction.done,
validator: _validatePassword,
onFieldSubmitted: (_) => _handleLogin(),
suffixIcon: IconButton(
icon: Icon(
_obscurePassword ? Icons.visibility_off : Icons.visibility,
color: AppTheme.textHint,
),
onPressed: () {
setState(() {
_obscurePassword = !_obscurePassword;
});
},
),
),
const SizedBox(height: 16),
// Remember me
Row(
children: [
Checkbox(
value: _rememberMe,
onChanged: (value) {
setState(() {
_rememberMe = value ?? false;
});
},
activeColor: AppTheme.primaryColor,
),
const Text(
'Se souvenir de moi',
style: TextStyle(
color: AppTheme.textSecondary,
fontSize: 14,
),
),
],
),
],
),
);
}
Widget _buildForgotPassword() {
return Align(
alignment: Alignment.centerRight,
child: TextButton(
onPressed: () => _navigateToForgotPassword(),
child: const Text(
'Mot de passe oublié ?',
style: TextStyle(
color: AppTheme.primaryColor,
fontWeight: FontWeight.w600,
),
),
),
);
}
Widget _buildLoginButton() {
return LoadingButton(
onPressed: _handleLogin,
isLoading: _isLoading,
text: 'Se connecter',
width: double.infinity,
height: 56,
);
}
Widget _buildDivider() {
return Row(
children: [
const Expanded(child: Divider()),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
'ou',
style: TextStyle(
color: AppTheme.textHint,
fontSize: 14,
),
),
),
const Expanded(child: Divider()),
],
);
}
Widget _buildSocialLogin() {
return Column(
children: [
// Google Login
SizedBox(
width: double.infinity,
height: 56,
child: OutlinedButton.icon(
onPressed: () => _handleGoogleLogin(),
icon: Image.asset(
'assets/icons/google.png',
width: 20,
height: 20,
errorBuilder: (context, error, stackTrace) => const Icon(
Icons.g_mobiledata,
color: Colors.red,
size: 20,
),
),
label: const Text('Continuer avec Google'),
style: OutlinedButton.styleFrom(
foregroundColor: AppTheme.textPrimary,
side: const BorderSide(color: AppTheme.borderColor),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
),
const SizedBox(height: 12),
// Microsoft Login
SizedBox(
width: double.infinity,
height: 56,
child: OutlinedButton.icon(
onPressed: () => _handleMicrosoftLogin(),
icon: const Icon(
Icons.business,
color: Color(0xFF00A4EF),
size: 20,
),
label: const Text('Continuer avec Microsoft'),
style: OutlinedButton.styleFrom(
foregroundColor: AppTheme.textPrimary,
side: const BorderSide(color: AppTheme.borderColor),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
),
],
);
}
Widget _buildSignUpLink() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Pas encore de compte ? ',
style: TextStyle(
color: AppTheme.textSecondary,
fontSize: 14,
),
),
TextButton(
onPressed: () => _navigateToRegister(),
child: const Text(
'S\'inscrire',
style: TextStyle(
color: AppTheme.primaryColor,
fontWeight: FontWeight.w600,
fontSize: 14,
),
),
),
],
);
}
String? _validateEmail(String? value) {
if (value == null || value.isEmpty) {
return 'Veuillez saisir votre adresse email';
}
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value)) {
return 'Veuillez saisir une adresse email valide';
}
return null;
}
String? _validatePassword(String? value) {
if (value == null || value.isEmpty) {
return 'Veuillez saisir votre mot de passe';
}
if (value.length < 6) {
return 'Le mot de passe doit contenir au moins 6 caractères';
}
return null;
}
Future<void> _handleLogin() async {
if (!_formKey.currentState!.validate()) {
return;
}
setState(() {
_isLoading = true;
});
try {
// Simulation d'authentification
await Future.delayed(const Duration(seconds: 2));
// Vibration de succès
HapticFeedback.lightImpact();
// Navigation vers le dashboard
if (mounted) {
Navigator.of(context).pushReplacement(
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) =>
const MainNavigation(),
transitionDuration: const Duration(milliseconds: 600),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return FadeTransition(
opacity: animation,
child: SlideTransition(
position: Tween<Offset>(
begin: const Offset(1.0, 0.0),
end: Offset.zero,
).animate(CurvedAnimation(
parent: animation,
curve: Curves.easeInOut,
)),
child: child,
),
);
},
),
);
}
} catch (e) {
// Gestion d'erreur
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Erreur de connexion: ${e.toString()}'),
backgroundColor: AppTheme.errorColor,
behavior: SnackBarBehavior.floating,
),
);
}
} finally {
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
void _handleGoogleLogin() {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Connexion Google - En cours de développement'),
backgroundColor: AppTheme.infoColor,
behavior: SnackBarBehavior.floating,
),
);
}
void _handleMicrosoftLogin() {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Connexion Microsoft - En cours de développement'),
backgroundColor: AppTheme.infoColor,
behavior: SnackBarBehavior.floating,
),
);
}
void _navigateToForgotPassword() {
Navigator.of(context).push(
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) =>
const ForgotPasswordScreen(),
transitionDuration: const Duration(milliseconds: 400),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(1.0, 0.0),
end: Offset.zero,
).animate(CurvedAnimation(
parent: animation,
curve: Curves.easeInOut,
)),
child: child,
);
},
),
);
}
void _navigateToRegister() {
Navigator.of(context).pushReplacement(
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) =>
const RegisterScreen(),
transitionDuration: const Duration(milliseconds: 400),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(1.0, 0.0),
end: Offset.zero,
).animate(CurvedAnimation(
parent: animation,
curve: Curves.easeInOut,
)),
child: child,
);
},
),
);
}
}