import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../models/demo_profile.dart'; import '../models/auth_session.dart'; import '../services/address_repository.dart'; import '../services/auth_repository.dart'; import '../services/local_seed_repository.dart'; import 'address_screen.dart'; final RegExp _lettersOnly = RegExp(r"[a-zA-ZáéíóúÁÉÍÓÚñÑüÜ\s]"); final RegExp _addressText = RegExp(r"[a-zA-Z0-9áéíóúÁÉÍÓÚñÑüÜ#\-\s]"); final RegExp _emailChars = RegExp(r"[a-zA-Z0-9@._+\-]"); class AuthScreen extends StatefulWidget { const AuthScreen({ super.key, required this.authRepository, required this.addressRepository, this.enableLiveFeatures = true, }); final AuthRepository authRepository; final AddressRepository addressRepository; final bool enableLiveFeatures; @override State createState() => _AuthScreenState(); } class _AuthScreenState extends State { final GlobalKey _loginFormKey = GlobalKey(); final GlobalKey _registerFormKey = GlobalKey(); final TextEditingController _loginEmailController = TextEditingController(); final TextEditingController _loginPasswordController = TextEditingController(); final TextEditingController _registerNameController = TextEditingController(); final TextEditingController _registerEmailController = TextEditingController(); final TextEditingController _registerPasswordController = TextEditingController(); final TextEditingController _registerConfirmPasswordController = TextEditingController(); bool _isLoading = false; String? _errorMessage; LocalSeedData? _seedData; bool _loadingSeedData = true; @override void initState() { super.initState(); _loadSeedData(); } @override void dispose() { _loginEmailController.dispose(); _loginPasswordController.dispose(); _registerNameController.dispose(); _registerEmailController.dispose(); _registerPasswordController.dispose(); _registerConfirmPasswordController.dispose(); super.dispose(); } Future _loadSeedData() async { final seedData = await LocalSeedRepository.instance.load(); if (!mounted) { return; } setState(() { _seedData = seedData; _loadingSeedData = false; }); } void _fillDemoProfile(DemoProfile profile) { _loginEmailController.text = profile.email; _loginPasswordController.text = profile.password; _registerNameController.text = profile.name; _registerEmailController.text = profile.email; _registerPasswordController.text = profile.password; _registerConfirmPasswordController.text = profile.password; } Future _useDemoProfile(DemoProfile profile) async { _fillDemoProfile(profile); await _submit(() { return widget.authRepository.signIn( email: profile.email, password: profile.password, ); }); } Future _signIn() async { if (!(_loginFormKey.currentState?.validate() ?? false)) { setState(() { _errorMessage = 'Respete los campos'; }); return; } await _submit(() { return widget.authRepository.signIn( email: _loginEmailController.text.trim(), password: _loginPasswordController.text, ); }); } Future _signUp() async { if (!(_registerFormKey.currentState?.validate() ?? false)) { setState(() { _errorMessage = 'Respete los campos'; }); return; } await _submit(() { return widget.authRepository.signUp( name: _registerNameController.text.trim(), email: _registerEmailController.text.trim(), password: _registerPasswordController.text, ); }); } Future _submit(Future Function() action) async { setState(() { _isLoading = true; _errorMessage = null; }); try { final session = await action(); if (!mounted) { return; } Navigator.of(context).pushReplacement( MaterialPageRoute( builder: (_) => AddressScreen( authRepository: widget.authRepository, addressRepository: widget.addressRepository, session: session, enableLiveFeatures: widget.enableLiveFeatures, ), ), ); } on AuthException catch (error) { if (!mounted) { return; } setState(() { _errorMessage = error.message; }); } catch (_) { if (!mounted) { return; } setState(() { _errorMessage = 'No se pudo completar la operación. Revisa los datos locales y vuelve a intentar.'; }); } finally { if (mounted) { setState(() { _isLoading = false; }); } } } @override Widget build(BuildContext context) { return Scaffold( body: Container( decoration: const BoxDecoration( gradient: LinearGradient( colors: [Color(0xFF06141B), Color(0xFF0F766E), Color(0xFFE2E8F0)], begin: Alignment.topLeft, end: Alignment.bottomRight, ), ), child: SafeArea( child: Center( child: SingleChildScrollView( padding: const EdgeInsets.all(20), child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 440), child: Card( elevation: 18, color: Colors.white.withValues(alpha: 0.94), shadowColor: Colors.black26, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(28)), child: SingleChildScrollView( padding: const EdgeInsets.all(24), child: DefaultTabController( length: 2, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min, children: [ Container( width: 72, height: 72, decoration: BoxDecoration( color: const Color(0xFF0F766E).withValues(alpha: 0.12), borderRadius: BorderRadius.circular(20), ), child: const Icon(Icons.lock_outline, size: 36, color: Color(0xFF0F766E)), ), const SizedBox(height: 20), const Text( 'Bienvenido', style: TextStyle(fontSize: 30, fontWeight: FontWeight.w800), ), const SizedBox(height: 8), Text( 'Inicia sesión o crea una cuenta para continuar. Luego irás a la pantalla Dirección.', style: TextStyle(color: Colors.grey.shade700, height: 1.4), ), const SizedBox(height: 20), if (!_loadingSeedData && _seedData != null && _seedData!.demoProfiles.isNotEmpty) ...[ _DemoProfilesSection( profiles: _seedData!.demoProfiles, onProfileSelected: _useDemoProfile, ), const SizedBox(height: 16), ], Container( decoration: BoxDecoration( color: const Color(0xFFF1F5F9), borderRadius: BorderRadius.circular(16), ), child: TabBar( onTap: (_) { setState(() { _errorMessage = null; }); }, indicatorSize: TabBarIndicatorSize.tab, dividerColor: Colors.transparent, indicator: BoxDecoration( color: const Color(0xFF0F766E), borderRadius: BorderRadius.circular(16), ), labelColor: Colors.white, unselectedLabelColor: Colors.grey.shade700, tabs: const [ Tab(text: 'Entrar'), Tab(text: 'Crear cuenta'), ], ), ), const SizedBox(height: 20), if (_errorMessage != null) ...[ _AuthStatusBanner(message: _errorMessage!), const SizedBox(height: 16), ], SizedBox( height: 200, child: TabBarView( children: [ _LoginForm( formKey: _loginFormKey, emailController: _loginEmailController, passwordController: _loginPasswordController, onSubmit: _signIn, isLoading: _isLoading, ), _RegisterForm( formKey: _registerFormKey, nameController: _registerNameController, emailController: _registerEmailController, passwordController: _registerPasswordController, confirmPasswordController: _registerConfirmPasswordController, onSubmit: _signUp, isLoading: _isLoading, ), ], ), ), const SizedBox(height: 12), ], ), ), ), ), ), ), ), ), ), ); } } class _LoginForm extends StatelessWidget { const _LoginForm({ required this.formKey, required this.emailController, required this.passwordController, required this.onSubmit, required this.isLoading, }); final GlobalKey formKey; final TextEditingController emailController; final TextEditingController passwordController; final Future Function() onSubmit; final bool isLoading; @override Widget build(BuildContext context) { return Form( key: formKey, child: Column( children: [ TextFormField( controller: emailController, keyboardType: TextInputType.emailAddress, inputFormatters: [FilteringTextInputFormatter.allow(_emailChars)], decoration: const InputDecoration( labelText: 'Correo electrónico', prefixIcon: Icon(Icons.email_outlined), border: OutlineInputBorder(), ), validator: (value) { if (value == null || value.trim().isEmpty) { return 'Ingresa tu correo'; } if (!value.contains('@') || value.startsWith('@') || value.endsWith('@')) { return 'Ingresa un correo válido'; } return null; }, ), const SizedBox(height: 16), TextFormField( controller: passwordController, obscureText: true, decoration: const InputDecoration( labelText: 'Contraseña', prefixIcon: Icon(Icons.lock_outline), border: OutlineInputBorder(), ), validator: (value) { if (value == null || value.isEmpty) { return 'Ingresa tu contraseña'; } if (value.length < 6) { return 'Usa al menos 6 caracteres'; } return null; }, ), const SizedBox(height: 20), SizedBox( width: double.infinity, height: 52, child: FilledButton( onPressed: isLoading ? null : onSubmit, child: isLoading ? const SizedBox( width: 22, height: 22, child: CircularProgressIndicator(strokeWidth: 2.2, color: Colors.white), ) : const Text('Ingresar'), ), ), ], ), ); } } class _RegisterForm extends StatelessWidget { const _RegisterForm({ required this.formKey, required this.nameController, required this.emailController, required this.passwordController, required this.confirmPasswordController, required this.onSubmit, required this.isLoading, }); final GlobalKey formKey; final TextEditingController nameController; final TextEditingController emailController; final TextEditingController passwordController; final TextEditingController confirmPasswordController; final Future Function() onSubmit; final bool isLoading; @override Widget build(BuildContext context) { return Form( key: formKey, child: ListView( children: [ TextFormField( controller: nameController, textCapitalization: TextCapitalization.words, keyboardType: TextInputType.name, inputFormatters: [FilteringTextInputFormatter.allow(_lettersOnly)], decoration: const InputDecoration( labelText: 'Nombre', prefixIcon: Icon(Icons.person_outline), border: OutlineInputBorder(), ), validator: (value) { if (value == null || value.trim().isEmpty) { return 'Ingresa tu nombre'; } return null; }, ), const SizedBox(height: 16), TextFormField( controller: emailController, keyboardType: TextInputType.emailAddress, inputFormatters: [FilteringTextInputFormatter.allow(_emailChars)], decoration: const InputDecoration( labelText: 'Correo electrónico', prefixIcon: Icon(Icons.email_outlined), border: OutlineInputBorder(), ), validator: (value) { if (value == null || value.trim().isEmpty) { return 'Ingresa tu correo'; } if (!value.contains('@') || value.startsWith('@') || value.endsWith('@')) { return 'Ingresa un correo válido'; } return null; }, ), const SizedBox(height: 16), TextFormField( controller: passwordController, obscureText: true, inputFormatters: [FilteringTextInputFormatter.deny(RegExp(r'\s'))], decoration: const InputDecoration( labelText: 'Contraseña', prefixIcon: Icon(Icons.lock_outline), border: OutlineInputBorder(), ), validator: (value) { if (value == null || value.isEmpty) { return 'Ingresa una contraseña'; } if (value.length < 6) { return 'Usa al menos 6 caracteres'; } return null; }, ), const SizedBox(height: 16), TextFormField( controller: confirmPasswordController, obscureText: true, inputFormatters: [FilteringTextInputFormatter.deny(RegExp(r'\s'))], decoration: const InputDecoration( labelText: 'Confirmar contraseña', prefixIcon: Icon(Icons.lock_reset_outlined), border: OutlineInputBorder(), ), validator: (value) { if (value == null || value.isEmpty) { return 'Confirma tu contraseña'; } if (value != passwordController.text) { return 'Las contraseñas no coinciden'; } return null; }, ), const SizedBox(height: 20), SizedBox( width: double.infinity, height: 52, child: FilledButton( onPressed: isLoading ? null : onSubmit, child: isLoading ? const SizedBox( width: 22, height: 22, child: CircularProgressIndicator(strokeWidth: 2.2, color: Colors.white), ) : const Text('Registrarme'), ), ), ], ), ); } } class _AuthStatusBanner extends StatelessWidget { const _AuthStatusBanner({required this.message}); final String message; @override Widget build(BuildContext context) { return Container( width: double.infinity, padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: const Color(0xFFFEE2E2), borderRadius: BorderRadius.circular(16), border: Border.all(color: const Color(0xFFFCA5A5)), ), child: Text( message, style: const TextStyle(color: Color(0xFF991B1B), height: 1.35), ), ); } } class _DemoProfilesSection extends StatelessWidget { const _DemoProfilesSection({ required this.profiles, required this.onProfileSelected, }); final List profiles; final Future Function(DemoProfile profile) onProfileSelected; @override Widget build(BuildContext context) { return Card( elevation: 2, color: const Color(0xFFF8FAFC), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Perfiles demo', style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w800)), const SizedBox(height: 8), Text('Toca un perfil para llenar el formulario de acceso.', style: TextStyle(color: Colors.grey.shade700)), const SizedBox(height: 12), Wrap( spacing: 8, runSpacing: 8, children: profiles .map( (profile) => ActionChip( label: Text('${profile.name} • ${profile.routeId}'), onPressed: () => onProfileSelected(profile), ), ) .toList(growable: false), ), ], ), ), ); } }