import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../blocs/auth/auth_bloc.dart'; import '../blocs/auth/auth_event.dart'; import '../blocs/auth/auth_state.dart'; import 'package:sign_in_button/sign_in_button.dart'; /// Login page widget for user authentication. /// /// This page provides a user interface for signing in with email and password, /// as well as options for social sign-in (Google, Apple) and password reset. /// It integrates with the AuthBloc to handle authentication state management. class LoginPage extends StatefulWidget { /// Creates a new [LoginPage]. const LoginPage({super.key}); @override State createState() => _LoginPageState(); } class _LoginPageState extends State { /// Form key for validation final _formKey = GlobalKey(); /// Controller for email input field final _emailController = TextEditingController(); /// Controller for password input field final _passwordController = TextEditingController(); /// Whether the password field should hide its content bool _obscurePassword = true; @override void dispose() { _emailController.dispose(); _passwordController.dispose(); super.dispose(); } /// Validates email input. /// /// Returns an error message if the email is invalid or empty, /// null if the email is valid. String? _validateEmail(String? value) { if (value == null || value.trim().isEmpty) { return 'Email required'; } final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$'); if (!emailRegex.hasMatch(value.trim())) { return 'Invalid email'; } return null; } /// Validates password input. /// /// Returns an error message if the password is empty, /// null if the password is valid. String? _validatePassword(String? value) { if (value == null || value.isEmpty) { return 'Password required'; } return null; } /// Handles the login process. /// /// Validates the form and dispatches a sign-in event to the AuthBloc /// if all fields are valid. void _login(BuildContext context) { if (!_formKey.currentState!.validate()) { return; } context.read().add( AuthSignInRequested( email: _emailController.text.trim(), password: _passwordController.text, ), ); } @override Widget build(BuildContext context) { return Scaffold( body: BlocConsumer( listener: (context, state) { if (state is AuthAuthenticated) { Navigator.pushReplacementNamed(context, '/home'); } else if (state is AuthError) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(state.message), backgroundColor: Colors.red, ), ); } }, builder: (context, state) { final isLoading = state is AuthLoading; return SafeArea( child: SingleChildScrollView( child: Padding( padding: const EdgeInsets.all(24.0), child: Form( key: _formKey, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const SizedBox(height: 40), const Text( 'Travel Mate', style: TextStyle( fontSize: 28, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 12), const Text( 'Connectez-vous pour continuer', style: TextStyle(fontSize: 16, color: Colors.grey), ), const SizedBox(height: 32), // Email TextFormField( controller: _emailController, validator: _validateEmail, keyboardType: TextInputType.emailAddress, decoration: const InputDecoration( labelText: 'Email', border: OutlineInputBorder( borderRadius: BorderRadius.all(Radius.circular(12)), ), prefixIcon: Icon(Icons.email), ), ), const SizedBox(height: 16), // Password TextFormField( controller: _passwordController, validator: _validatePassword, obscureText: _obscurePassword, decoration: InputDecoration( labelText: 'Mot de passe', border: const OutlineInputBorder( borderRadius: BorderRadius.all(Radius.circular(12)), ), prefixIcon: const Icon(Icons.lock), suffixIcon: IconButton( icon: Icon( _obscurePassword ? Icons.visibility : Icons.visibility_off, ), onPressed: () { setState(() { _obscurePassword = !_obscurePassword; }); }, ), ), ), const SizedBox(height: 8), // Forgot password Align( alignment: Alignment.centerRight, child: TextButton( onPressed: () { Navigator.pushNamed(context, '/forgot'); }, child: const Text('Mot de passe oubliƩ?'), ), ), const SizedBox(height: 24), // Login button SizedBox( width: double.infinity, height: 50, child: ElevatedButton( onPressed: isLoading ? null : () => _login(context), style: ElevatedButton.styleFrom( backgroundColor: Theme.of(context).brightness == Brightness.dark ? Colors.white : Colors.black, foregroundColor: Theme.of(context).brightness == Brightness.dark ? Colors.black : Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), child: isLoading ? CircularProgressIndicator( color: Theme.of(context).brightness == Brightness.dark ? Colors.black : Colors.white, ) : const Text( 'Se connecter', style: TextStyle(fontSize: 16), ), ), ), const SizedBox(height: 24), // Sign up link Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text("Vous n'avez pas de compte?"), TextButton( onPressed: () { Navigator.pushNamed(context, '/signup'); }, child: const Text('S\'inscrire'), ), ], ), const SizedBox(height: 40), // Divider Container( width: double.infinity, height: 1, color: Colors.grey.shade300, ), const SizedBox(height: 40), const Text( 'Ou connectez-vous avec', style: TextStyle(color: Colors.grey), ), const SizedBox(height: 20), SignInButton( Buttons.google, onPressed: () { context.read().add( AuthGoogleSignInRequested(), ); }, ), const SizedBox(height: 16), SignInButton( Buttons.apple, onPressed: () { context.read().add( AuthAppleSignInRequested(), ); }, ), ], ), ), ), ), ); }, ), ); } }