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,624 @@
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 'login_screen.dart';
class RegisterScreen extends StatefulWidget {
const RegisterScreen({super.key});
@override
State<RegisterScreen> createState() => _RegisterScreenState();
}
class _RegisterScreenState extends State<RegisterScreen>
with TickerProviderStateMixin {
final _formKey = GlobalKey<FormState>();
final _firstNameController = TextEditingController();
final _lastNameController = TextEditingController();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
final _confirmPasswordController = TextEditingController();
late AnimationController _fadeController;
late AnimationController _slideController;
late Animation<double> _fadeAnimation;
late Animation<Offset> _slideAnimation;
bool _isLoading = false;
bool _obscurePassword = true;
bool _obscureConfirmPassword = true;
bool _acceptTerms = false;
bool _acceptNewsletter = 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() {
_firstNameController.dispose();
_lastNameController.dispose();
_emailController.dispose();
_passwordController.dispose();
_confirmPasswordController.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: 32),
_buildRegistrationForm(),
const SizedBox(height: 24),
_buildTermsAndConditions(),
const SizedBox(height: 32),
_buildRegisterButton(),
const SizedBox(height: 24),
_buildLoginLink(),
],
),
),
),
);
},
),
),
);
}
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.person_add_rounded,
color: Colors.white,
size: 30,
),
),
const SizedBox(height: 24),
// Titre
const Text(
'Créer un compte',
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: AppTheme.textPrimary,
),
),
const SizedBox(height: 8),
// Sous-titre
Text(
'Rejoignez UnionFlow et gérez votre association',
style: TextStyle(
fontSize: 16,
color: AppTheme.textSecondary,
),
),
],
);
}
Widget _buildRegistrationForm() {
return Form(
key: _formKey,
child: Column(
children: [
// Nom et Prénom
Row(
children: [
Expanded(
child: CustomTextField(
controller: _firstNameController,
label: 'Prénom',
hintText: 'Jean',
prefixIcon: Icons.person_outline,
textInputAction: TextInputAction.next,
validator: _validateFirstName,
onFieldSubmitted: (_) => FocusScope.of(context).nextFocus(),
),
),
const SizedBox(width: 16),
Expanded(
child: CustomTextField(
controller: _lastNameController,
label: 'Nom',
hintText: 'Dupont',
prefixIcon: Icons.person_outline,
textInputAction: TextInputAction.next,
validator: _validateLastName,
onFieldSubmitted: (_) => FocusScope.of(context).nextFocus(),
),
),
],
),
const SizedBox(height: 16),
// Email
CustomTextField(
controller: _emailController,
label: 'Adresse email',
hintText: 'jean.dupont@exemple.com',
prefixIcon: Icons.email_outlined,
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.next,
validator: _validateEmail,
onFieldSubmitted: (_) => FocusScope.of(context).nextFocus(),
),
const SizedBox(height: 16),
// Mot de passe
CustomTextField(
controller: _passwordController,
label: 'Mot de passe',
hintText: 'Minimum 8 caractères',
prefixIcon: Icons.lock_outline,
obscureText: _obscurePassword,
textInputAction: TextInputAction.next,
validator: _validatePassword,
onFieldSubmitted: (_) => FocusScope.of(context).nextFocus(),
suffixIcon: IconButton(
icon: Icon(
_obscurePassword ? Icons.visibility_off : Icons.visibility,
color: AppTheme.textHint,
),
onPressed: () {
setState(() {
_obscurePassword = !_obscurePassword;
});
},
),
),
const SizedBox(height: 16),
// Confirmer mot de passe
CustomTextField(
controller: _confirmPasswordController,
label: 'Confirmer le mot de passe',
hintText: 'Retapez votre mot de passe',
prefixIcon: Icons.lock_outline,
obscureText: _obscureConfirmPassword,
textInputAction: TextInputAction.done,
validator: _validateConfirmPassword,
onFieldSubmitted: (_) => _handleRegister(),
suffixIcon: IconButton(
icon: Icon(
_obscureConfirmPassword ? Icons.visibility_off : Icons.visibility,
color: AppTheme.textHint,
),
onPressed: () {
setState(() {
_obscureConfirmPassword = !_obscureConfirmPassword;
});
},
),
),
const SizedBox(height: 16),
// Indicateur de force du mot de passe
_buildPasswordStrengthIndicator(),
],
),
);
}
Widget _buildPasswordStrengthIndicator() {
final password = _passwordController.text;
final strength = _calculatePasswordStrength(password);
Color strengthColor;
String strengthText;
if (strength < 0.3) {
strengthColor = AppTheme.errorColor;
strengthText = 'Faible';
} else if (strength < 0.7) {
strengthColor = AppTheme.warningColor;
strengthText = 'Moyen';
} else {
strengthColor = AppTheme.successColor;
strengthText = 'Fort';
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'Force du mot de passe',
style: TextStyle(
fontSize: 12,
color: AppTheme.textSecondary,
),
),
if (password.isNotEmpty)
Text(
strengthText,
style: TextStyle(
fontSize: 12,
color: strengthColor,
fontWeight: FontWeight.w600,
),
),
],
),
const SizedBox(height: 8),
Container(
height: 4,
decoration: BoxDecoration(
color: AppTheme.borderColor,
borderRadius: BorderRadius.circular(2),
),
child: FractionallySizedBox(
alignment: Alignment.centerLeft,
widthFactor: password.isEmpty ? 0 : strength,
child: Container(
decoration: BoxDecoration(
color: strengthColor,
borderRadius: BorderRadius.circular(2),
),
),
),
),
],
);
}
double _calculatePasswordStrength(String password) {
if (password.isEmpty) return 0.0;
double strength = 0.0;
// Longueur
if (password.length >= 8) strength += 0.25;
if (password.length >= 12) strength += 0.25;
// Contient des minuscules
if (password.contains(RegExp(r'[a-z]'))) strength += 0.15;
// Contient des majuscules
if (password.contains(RegExp(r'[A-Z]'))) strength += 0.15;
// Contient des chiffres
if (password.contains(RegExp(r'[0-9]'))) strength += 0.1;
// Contient des caractères spéciaux
if (password.contains(RegExp(r'[!@#$%^&*(),.?":{}|<>]'))) strength += 0.1;
return strength.clamp(0.0, 1.0);
}
Widget _buildTermsAndConditions() {
return Column(
children: [
// Accepter les conditions
Row(
children: [
Checkbox(
value: _acceptTerms,
onChanged: (value) {
setState(() {
_acceptTerms = value ?? false;
});
},
activeColor: AppTheme.primaryColor,
),
Expanded(
child: RichText(
text: TextSpan(
style: const TextStyle(
color: AppTheme.textSecondary,
fontSize: 14,
),
children: [
const TextSpan(text: 'J\'accepte les '),
TextSpan(
text: 'Conditions d\'utilisation',
style: const TextStyle(
color: AppTheme.primaryColor,
fontWeight: FontWeight.w600,
decoration: TextDecoration.underline,
),
),
const TextSpan(text: ' et la '),
TextSpan(
text: 'Politique de confidentialité',
style: const TextStyle(
color: AppTheme.primaryColor,
fontWeight: FontWeight.w600,
decoration: TextDecoration.underline,
),
),
],
),
),
),
],
),
// Newsletter (optionnel)
Row(
children: [
Checkbox(
value: _acceptNewsletter,
onChanged: (value) {
setState(() {
_acceptNewsletter = value ?? false;
});
},
activeColor: AppTheme.primaryColor,
),
const Expanded(
child: Text(
'Je souhaite recevoir des actualités et conseils par email (optionnel)',
style: TextStyle(
color: AppTheme.textSecondary,
fontSize: 14,
),
),
),
],
),
],
);
}
Widget _buildRegisterButton() {
return LoadingButton(
onPressed: _acceptTerms ? _handleRegister : null,
isLoading: _isLoading,
text: 'Créer mon compte',
width: double.infinity,
height: 56,
enabled: _acceptTerms,
);
}
Widget _buildLoginLink() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Déjà un compte ? ',
style: TextStyle(
color: AppTheme.textSecondary,
fontSize: 14,
),
),
TextButton(
onPressed: () => _navigateToLogin(),
child: const Text(
'Se connecter',
style: TextStyle(
color: AppTheme.primaryColor,
fontWeight: FontWeight.w600,
fontSize: 14,
),
),
),
],
);
}
String? _validateFirstName(String? value) {
if (value == null || value.isEmpty) {
return 'Veuillez saisir votre prénom';
}
if (value.length < 2) {
return 'Le prénom doit contenir au moins 2 caractères';
}
return null;
}
String? _validateLastName(String? value) {
if (value == null || value.isEmpty) {
return 'Veuillez saisir votre nom';
}
if (value.length < 2) {
return 'Le nom doit contenir au moins 2 caractères';
}
return null;
}
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 un mot de passe';
}
if (value.length < 8) {
return 'Le mot de passe doit contenir au moins 8 caractères';
}
if (!value.contains(RegExp(r'[A-Z]'))) {
return 'Le mot de passe doit contenir au moins une majuscule';
}
if (!value.contains(RegExp(r'[a-z]'))) {
return 'Le mot de passe doit contenir au moins une minuscule';
}
if (!value.contains(RegExp(r'[0-9]'))) {
return 'Le mot de passe doit contenir au moins un chiffre';
}
return null;
}
String? _validateConfirmPassword(String? value) {
if (value == null || value.isEmpty) {
return 'Veuillez confirmer votre mot de passe';
}
if (value != _passwordController.text) {
return 'Les mots de passe ne correspondent pas';
}
return null;
}
Future<void> _handleRegister() async {
if (!_formKey.currentState!.validate()) {
return;
}
if (!_acceptTerms) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Veuillez accepter les conditions d\'utilisation'),
backgroundColor: AppTheme.errorColor,
behavior: SnackBarBehavior.floating,
),
);
return;
}
setState(() {
_isLoading = true;
});
try {
// Simulation d'inscription
await Future.delayed(const Duration(seconds: 2));
// Vibration de succès
HapticFeedback.lightImpact();
// Afficher message de succès
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Compte créé avec succès ! Vérifiez votre email.'),
backgroundColor: AppTheme.successColor,
behavior: SnackBarBehavior.floating,
),
);
// Navigation vers l'écran de connexion
_navigateToLogin();
}
} catch (e) {
// Gestion d'erreur
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Erreur lors de la création du compte: ${e.toString()}'),
backgroundColor: AppTheme.errorColor,
behavior: SnackBarBehavior.floating,
),
);
}
} finally {
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
void _navigateToLogin() {
Navigator.of(context).pushReplacement(
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) =>
const LoginScreen(),
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,
);
},
),
);
}
}