import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:dio/dio.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.dart'; import '../../core/theme/app_theme.dart'; import '../../core/widgets/app_widgets.dart'; import '../../core/services/auth_controller.dart'; import '../../core/models/auth_state.dart'; import '../addresses/colonias_selector.dart'; import '../../core/models/colonia.dart'; import '../home/colonias_data.dart'; class RegisterPage extends ConsumerStatefulWidget { const RegisterPage({super.key}); @override ConsumerState createState() => _RegisterPageState(); } class _RegisterPageState extends ConsumerState { final _pageController = PageController(); int _currentPage = 0; final _step1FormKey = GlobalKey(); // Paso 1 final _emailCtrl = TextEditingController(); final _telefonoCtrl = TextEditingController(); final _passCtrl = TextEditingController(); bool _obscurePass = true; // Paso 2 final _cpCtrl = TextEditingController(); final _calleCtrl = TextEditingController(); Colonia? _selectedColonia; LatLng? _selectedLocation; int _radioAlerta = 200; @override void initState() { super.initState(); ref.listenManual>(authControllerProvider, ( prev, next, ) { if (!mounted) return; if (next is AsyncError) { String errorMessage = 'Ocurrió un error inesperado'; final error = next.error; if (error is DioException) { if (error.response?.data != null && error.response?.data is Map) { errorMessage = error.response!.data['detail'] ?? 'Error al registrarse'; } else { errorMessage = 'Error de conexión con el servidor'; } } else { errorMessage = error.toString(); } ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(errorMessage), backgroundColor: AppTheme.danger, behavior: SnackBarBehavior.floating, ), ); } }); } @override void dispose() { _pageController.dispose(); _emailCtrl.dispose(); _telefonoCtrl.dispose(); _passCtrl.dispose(); _calleCtrl.dispose(); _cpCtrl.dispose(); super.dispose(); } void _nextPage() { if (!(_step1FormKey.currentState?.validate() ?? false)) return; _pageController.nextPage( duration: const Duration(milliseconds: 350), curve: Curves.easeInOut, ); setState(() => _currentPage = 1); } Future _register() async { if (_calleCtrl.text.trim().isEmpty || _selectedColonia == null) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Ingresa tu calle y selecciona una colonia'), behavior: SnackBarBehavior.floating, ), ); return; } // 1. Registra al usuario await ref .read(authControllerProvider.notifier) .register( email: _emailCtrl.text.trim(), phone: _telefonoCtrl.text.trim(), password: _passCtrl.text, ); // Detenernos si hubo algún error en el auth (ej. contraseña corta) if (ref.read(authControllerProvider).hasError) return; // 2. Guardar la dirección en el backend de forma silenciosa try { const storage = FlutterSecureStorage(); final token = await storage.read(key: 'token') ?? ''; if (token.isNotEmpty) { final dio = Dio( BaseOptions( baseUrl: const String.fromEnvironment( 'API_BASE_URL', defaultValue: 'http://localhost:8000', ), headers: {'Authorization': 'Bearer $token'}, ), ); await dio.post( '/addresses', data: { 'label': 'Mi Casa', 'calle': _calleCtrl.text.trim(), 'colonia': _selectedColonia!.nombre, }, ); } } catch (e) { debugPrint('Aviso: No se pudo guardar la dirección inicial: $e'); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text( 'Error al guardar tu dirección. Inténtalo más tarde.', ), backgroundColor: AppTheme.danger, ), ); } return; // No navegar si falla el guardado de la dirección } // 3. Navegar a inicio de manera limpia if (mounted) { context.go( '/home', ); // ¡Solución al GoException! Navega a la ruta correcta } } @override Widget build(BuildContext context) { final loading = ref.watch(authControllerProvider).isLoading; return Scaffold( backgroundColor: AppTheme.background, appBar: AppBar( backgroundColor: Colors.transparent, elevation: 0, iconTheme: const IconThemeData(color: AppTheme.textPrimary), title: Text( _currentPage == 0 ? 'Crear cuenta' : 'Mi dirección', style: const TextStyle(color: AppTheme.textPrimary, fontSize: 16), ), bottom: PreferredSize( preferredSize: const Size.fromHeight(8), child: _StepIndicator(current: _currentPage, total: 2), ), ), body: PageView( controller: _pageController, physics: const NeverScrollableScrollPhysics(), children: [ _Step1( formKey: _step1FormKey, emailCtrl: _emailCtrl, telefonoCtrl: _telefonoCtrl, passCtrl: _passCtrl, obscurePass: _obscurePass, onTogglePass: () => setState(() => _obscurePass = !_obscurePass), onNext: _nextPage, ), _Step2( cpCtrl: _cpCtrl, calleCtrl: _calleCtrl, selectedColonia: _selectedColonia, selectedLocation: _selectedLocation, radioAlerta: _radioAlerta, loading: loading, onColoniaChanged: (c) { setState(() { _selectedColonia = c; if (c != null && kColoniasCoordinates.containsKey(c.nombre)) { _selectedLocation = kColoniasCoordinates[c.nombre]; } }); }, onLocationChanged: (l) => setState(() => _selectedLocation = l), onRadioChanged: (v) => setState(() => _radioAlerta = v), onRegister: _register, ), ], ), ); } } // ── Indicador de pasos ──────────────────────────────────────────────────────── class _StepIndicator extends StatelessWidget { final int current; final int total; const _StepIndicator({required this.current, required this.total}); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 6), child: Row( children: List.generate(total, (i) { final active = i <= current; return Expanded( child: Container( margin: EdgeInsets.only(right: i < total - 1 ? 6 : 0), height: 4, decoration: BoxDecoration( color: active ? AppTheme.primary : AppTheme.border, borderRadius: BorderRadius.circular(4), ), ), ); }), ), ); } } // ── Paso 1: Cuenta ──────────────────────────────────────────────────────────── class _Step1 extends StatelessWidget { final GlobalKey formKey; final TextEditingController emailCtrl, telefonoCtrl, passCtrl; final bool obscurePass; final VoidCallback onTogglePass; final VoidCallback onNext; const _Step1({ required this.formKey, required this.emailCtrl, required this.telefonoCtrl, required this.passCtrl, required this.obscurePass, required this.onTogglePass, required this.onNext, }); @override Widget build(BuildContext context) { return SingleChildScrollView( padding: const EdgeInsets.all(24), child: Form( key: formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 4), AppFormCard( icon: Icons.person_outline, title: 'Información de cuenta', child: Column( children: [ AppFormField( label: 'Correo electrónico', hint: 'tu@correo.com', controller: emailCtrl, keyboardType: TextInputType.emailAddress, validator: (v) { if (v == null || v.trim().isEmpty) return 'Ingresa tu correo'; final emailRegex = RegExp(r'^[^@]+@[^@]+\.[^@]+'); if (!emailRegex.hasMatch(v.trim())) return 'Ingresa un correo válido'; return null; }, ), const SizedBox(height: 14), AppFormField( label: 'Teléfono', hint: '+52 461 123 4567', controller: telefonoCtrl, keyboardType: TextInputType.phone, ), const SizedBox(height: 14), AppFormField( label: 'Contraseña', hint: '••••••••', controller: passCtrl, obscureText: obscurePass, validator: (v) { if (v == null || v.isEmpty) return 'Ingresa una contraseña'; if (v.length < 6) return 'Mínimo 6 caracteres'; return null; }, suffix: IconButton( icon: Icon( obscurePass ? Icons.visibility_outlined : Icons.visibility_off_outlined, size: 18, color: AppTheme.textSecondary, ), onPressed: onTogglePass, ), ), ], ), ), const SizedBox(height: 28), SizedBox( width: double.infinity, height: 52, child: ElevatedButton( onPressed: onNext, child: const Row( mainAxisSize: MainAxisSize.min, children: [ Text('Siguiente'), SizedBox(width: 8), Icon(Icons.arrow_forward, size: 18), ], ), ), ), const SizedBox(height: 20), Center( child: Row( mainAxisSize: MainAxisSize.min, children: [ const Text( '¿Ya tienes cuenta? ', style: TextStyle( fontSize: 13, color: AppTheme.textSecondary, ), ), GestureDetector( onTap: () => context.go('/login'), child: const Text( 'Inicia sesión', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: AppTheme.primary, ), ), ), ], ), ), ], ), ), ); } } // ── Paso 2: Dirección ───────────────────────────────────────────────────────── class _Step2 extends StatelessWidget { final TextEditingController cpCtrl; final TextEditingController calleCtrl; final Colonia? selectedColonia; final LatLng? selectedLocation; final int radioAlerta; final bool loading; final ValueChanged onColoniaChanged; final ValueChanged onLocationChanged; final ValueChanged onRadioChanged; final VoidCallback onRegister; const _Step2({ required this.cpCtrl, required this.calleCtrl, required this.selectedColonia, required this.selectedLocation, required this.radioAlerta, required this.loading, required this.onColoniaChanged, required this.onLocationChanged, required this.onRadioChanged, required this.onRegister, }); @override Widget build(BuildContext context) { final mapCenter = selectedLocation ?? const LatLng(20.5222, -100.8123); return SingleChildScrollView( padding: const EdgeInsets.all(24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 4), AppFormCard( icon: Icons.home_outlined, title: 'Dirección de tu casa', child: Column( children: [ AppFormField( label: 'Código Postal', hint: 'Ej. 38000', controller: cpCtrl, keyboardType: TextInputType.number, ), const SizedBox(height: 14), ColoniasSelector( labelText: 'Colonia', initialValue: selectedColonia, onChanged: onColoniaChanged, ), const SizedBox(height: 14), AppFormField( label: 'Calle y número', hint: 'Av. Insurgentes 245', controller: calleCtrl, ), const SizedBox(height: 16), const Text( 'Toca el mapa para ubicar tu casa exacta:', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w500, color: AppTheme.textSecondary, ), ), const SizedBox(height: 8), Container( height: 200, decoration: BoxDecoration( borderRadius: BorderRadius.circular(AppTheme.radiusSm), border: Border.all(color: AppTheme.border), ), clipBehavior: Clip.hardEdge, child: FlutterMap( key: ValueKey(selectedColonia?.nombre ?? 'default'), options: MapOptions( initialCenter: mapCenter, initialZoom: 15.0, onTap: (_, latlng) => onLocationChanged(latlng), ), children: [ TileLayer( urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', userAgentPackageName: 'com.onlineshack.recolecta', ), if (selectedLocation != null) MarkerLayer( markers: [ Marker( point: selectedLocation!, width: 40, height: 40, child: const Icon( Icons.location_on, color: AppTheme.danger, size: 40, ), ), ], ), ], ), ), ], ), ), const SizedBox(height: 16), AppFormCard( icon: Icons.notifications_outlined, title: 'Distancia de alerta', child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Te avisamos cuando el camión esté a esta distancia de tu casa.', style: TextStyle( fontSize: 13, color: AppTheme.textSecondary, height: 1.4, ), ), const SizedBox(height: 14), ...[200, 400, 600].map( (dist) => _RadioOption( value: dist, groupValue: radioAlerta, label: '$dist metros', sublabel: dist == 200 ? '~2-3 min de anticipación' : dist == 400 ? '~4-5 min de anticipación' : '~6-8 min de anticipación', onChanged: onRadioChanged, ), ), ], ), ), const SizedBox(height: 28), SizedBox( width: double.infinity, height: 52, child: ElevatedButton( onPressed: loading ? null : onRegister, child: AnimatedSwitcher( duration: const Duration(milliseconds: 200), child: loading ? const SizedBox( key: ValueKey('loading'), width: 20, height: 20, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white, ), ) : const Row( key: ValueKey('text'), mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.check, size: 18), SizedBox(width: 8), Text('Registrarme'), ], ), ), ), ), const SizedBox(height: 16), const Center( child: Text( 'Al registrarte aceptas los Términos de Servicio\ny la Política de Privacidad.', textAlign: TextAlign.center, style: TextStyle( fontSize: 11, color: AppTheme.textSecondary, height: 1.5, ), ), ), ], ), ); } } // ── Opción radio ────────────────────────────────────────────────────────────── class _RadioOption extends StatelessWidget { final int value, groupValue; final String label, sublabel; final ValueChanged onChanged; const _RadioOption({ required this.value, required this.groupValue, required this.label, required this.sublabel, required this.onChanged, }); @override Widget build(BuildContext context) { final selected = value == groupValue; return GestureDetector( onTap: () => onChanged(value), child: Container( margin: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 11), decoration: BoxDecoration( color: selected ? AppTheme.primaryLight : AppTheme.background, borderRadius: BorderRadius.circular(AppTheme.radiusSm), border: Border.all( color: selected ? AppTheme.primary : AppTheme.border, width: selected ? 1.5 : 0.5, ), ), child: Row( children: [ Container( width: 18, height: 18, decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all( color: selected ? AppTheme.primary : AppTheme.border, width: 2, ), ), child: selected ? Center( child: Container( width: 8, height: 8, decoration: const BoxDecoration( shape: BoxShape.circle, color: AppTheme.primary, ), ), ) : null, ), const SizedBox(width: 10), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, color: selected ? AppTheme.primaryDark : AppTheme.textPrimary, ), ), Text( sublabel, style: TextStyle( fontSize: 11, color: selected ? AppTheme.primary : AppTheme.textSecondary, ), ), ], ), ], ), ), ); } }