import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:latlong2/latlong.dart'; import '../../core/constants/auth_constants.dart'; import '../../core/models/colonia.dart'; import '../../core/theme/app_theme.dart'; import '../../core/widgets/app_widgets.dart'; import '../home/colonias_data.dart'; import 'colonias_provider.dart'; const Map _cpToColonia = { '38000': 'Zona Centro', '38060': 'Las Arboledas', '38027': 'San Juanico', '38037': 'Los Olivos', '38090': 'Rancho Seco', '38080': 'Las Insurgentes', '38086': 'Trojes', }; class AddAddressPage extends ConsumerStatefulWidget { const AddAddressPage({super.key}); @override ConsumerState createState() => _AddAddressPageState(); } class _AddAddressPageState extends ConsumerState { final _mapController = MapController(); final _cpCtrl = TextEditingController(); final _calleCtrl = TextEditingController(); final _labelCtrl = TextEditingController(text: 'Mi Casa'); Colonia? _selectedColonia; LatLng? _selectedLocation; bool _loading = false; @override void dispose() { _mapController.dispose(); _cpCtrl.dispose(); _calleCtrl.dispose(); _labelCtrl.dispose(); super.dispose(); } Future _fetchStreetName(LatLng latlng) async { setState(() => _selectedLocation = latlng); try { final dio = Dio(); final response = await dio.get( 'https://nominatim.openstreetmap.org/reverse', queryParameters: { 'lat': latlng.latitude, 'lon': latlng.longitude, 'format': 'json', 'addressdetails': 1, }, options: kIsWeb ? null : Options(headers: {'User-Agent': 'com.onlineshack.recolecta'}), ); if (response.data?['address'] != null) { final addr = response.data['address'] as Map; final road = addr['road'] ?? addr['pedestrian'] ?? addr['street'] ?? ''; final num = addr['house_number'] ?? ''; if ((road as String).isNotEmpty) { setState(() => _calleCtrl.text = '$road $num'.trim()); } } } catch (e) { debugPrint('Nominatim error: $e'); } } void _validarCP(String cp, List colonias) { if (cp.length != 5) { if (_selectedColonia != null) { setState(() { _selectedColonia = null; _selectedLocation = null; _calleCtrl.clear(); }); } return; } final nombre = _cpToColonia[cp]; if (nombre == null) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Código postal fuera de nuestra zona de servicio.'), backgroundColor: AppTheme.danger, behavior: SnackBarBehavior.floating, ), ); setState(() { _selectedColonia = null; _selectedLocation = null; }); return; } final backendC = colonias .where((c) => c.nombre.toLowerCase() == nombre.toLowerCase()) .firstOrNull; if (backendC == null) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Esta colonia aún no tiene horarios configurados.'), backgroundColor: AppTheme.danger, behavior: SnackBarBehavior.floating, ), ); setState(() { _selectedColonia = null; _selectedLocation = null; }); return; } setState(() { _selectedColonia = backendC; _selectedLocation = kColoniasCoordinates[nombre]; }); FocusScope.of(context).unfocus(); } Future _guardar() 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; } setState(() => _loading = true); try { const storage = FlutterSecureStorage(); final token = await storage.read(key: authTokenStorageKey) ?? ''; final dio = Dio( BaseOptions( baseUrl: const String.fromEnvironment( 'API_BASE_URL', defaultValue: 'http://localhost:8000', ), headers: {'Authorization': 'Bearer $token'}, ), ); final body = { 'label': _labelCtrl.text.trim().isEmpty ? 'Mi Casa' : _labelCtrl.text.trim(), 'calle': _calleCtrl.text.trim(), 'colonia': _selectedColonia!.nombre, }; await dio.post('/addresses', data: body); if (mounted) Navigator.pop(context, true); } on DioException catch (e) { if (mounted) { final msg = (e.response?.data is Map) ? e.response!.data['detail'] ?? 'Error al guardar' : 'Error al guardar la dirección'; ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(msg), backgroundColor: AppTheme.danger, behavior: SnackBarBehavior.floating, ), ); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Error: $e'), backgroundColor: AppTheme.danger, behavior: SnackBarBehavior.floating, ), ); } } finally { if (mounted) setState(() => _loading = false); } } @override Widget build(BuildContext context) { final coloniasList = ref.watch(coloniasProvider).value ?? []; final baseCenter = _selectedColonia != null ? kColoniaCenter(_selectedColonia!.nombre) : const LatLng(20.5222, -100.8123); final mapCenter = _selectedLocation ?? baseCenter; final bounds = _selectedColonia != null ? LatLngBounds( LatLng(baseCenter.latitude - 0.01, baseCenter.longitude - 0.01), LatLng(baseCenter.latitude + 0.01, baseCenter.longitude + 0.01), ) : null; return Scaffold( backgroundColor: AppTheme.background, body: CustomScrollView( slivers: [ SliverToBoxAdapter(child: _buildPageHeader(context)), SliverPadding( padding: const EdgeInsets.fromLTRB(24, 24, 24, 0), sliver: SliverList( delegate: SliverChildListDelegate([ AppFormCard( icon: Icons.home_outlined, title: 'Dirección de tu casa', child: Column( children: [ AppFormField( label: 'Etiqueta', hint: 'Ej. Mi Casa, Trabajo', controller: _labelCtrl, ), const SizedBox(height: 14), AppFormField( label: 'Código Postal', hint: 'Ej. 38000', controller: _cpCtrl, keyboardType: TextInputType.number, onChanged: (v) => _validarCP(v, coloniasList), ), if (_selectedColonia != null) ...[ const SizedBox(height: 14), Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: AppTheme.primaryLight.withValues(alpha: 0.5), borderRadius: BorderRadius.circular(AppTheme.radiusSm), border: Border.all(color: AppTheme.primaryMid), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon( Icons.check_circle_outline, color: AppTheme.primary, size: 18, ), const SizedBox(width: 8), Expanded( child: Text( 'Colonia: ${_selectedColonia!.nombre}', style: const TextStyle( fontWeight: FontWeight.w600, color: AppTheme.primaryDark, ), ), ), ], ), if (_selectedColonia!.horarioEstimado != null) ...[ const SizedBox(height: 8), Text( 'Horario ${_selectedColonia!.turno?.toLowerCase() ?? ''}', style: const TextStyle( fontSize: 13, color: AppTheme.textPrimary, ), ), Text( _selectedColonia!.horarioEstimado!, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: AppTheme.textPrimary, ), ), ], ], ), ), 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: 220, decoration: BoxDecoration( borderRadius: BorderRadius.circular(AppTheme.radiusMd), border: Border.all(color: AppTheme.border), boxShadow: AppTheme.softShadow, ), clipBehavior: Clip.hardEdge, child: FlutterMap( mapController: _mapController, options: MapOptions( initialCenter: mapCenter, initialZoom: 15.0, cameraConstraint: bounds != null ? CameraConstraint.containCenter( bounds: bounds) : const CameraConstraint.unconstrained(), onTap: (_, latlng) => _fetchStreetName(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, ), ), ], ), ], ), ), ] else ...[ const SizedBox(height: 24), const Center( child: Text( 'Ingresa un código postal con servicio\npara asignar tu colonia.', textAlign: TextAlign.center, style: TextStyle( color: AppTheme.textSecondary, fontSize: 13, ), ), ), ], ], ), ), const SizedBox(height: 32), ]), ), ), ], ), bottomNavigationBar: _buildSaveButton(), ); } Widget _buildPageHeader(BuildContext context) { return Container( padding: EdgeInsets.fromLTRB( 20, MediaQuery.of(context).padding.top + 12, 20, 24, ), decoration: const BoxDecoration( gradient: LinearGradient( colors: [Color(0xFF4A0E26), Color(0xFF9B1B4A)], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.only( bottomLeft: Radius.circular(28), bottomRight: Radius.circular(28), ), ), child: Row( children: [ GestureDetector( onTap: () => Navigator.of(context).pop(), child: Container( width: 36, height: 36, decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(10), ), child: const Icon( Icons.arrow_back, color: Colors.white, size: 20, ), ), ), const SizedBox(width: 14), const Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Agregar dirección', style: TextStyle( fontSize: 20, fontWeight: FontWeight.w700, color: Colors.white, ), ), SizedBox(height: 2), Text( 'Registra tu domicilio de recolección', style: TextStyle(fontSize: 13, color: Colors.white70), ), ], ), ), Container( width: 44, height: 44, decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(12), ), child: const Icon( Icons.add_home_outlined, color: Colors.white, size: 22, ), ), ], ), ); } Widget _buildSaveButton() { return SafeArea( child: Padding( padding: const EdgeInsets.fromLTRB(24, 12, 24, 16), child: SizedBox( width: double.infinity, child: ElevatedButton( onPressed: _loading ? null : _guardar, 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('Guardar dirección'), ], ), ), ), ), ), ); } }