import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:geolocator/geolocator.dart'; import 'package:http/http.dart' as http; import 'package:prosapp_web_app/providers/auth_provider.dart'; import 'package:prosapp_web_app/providers/theme_provider.dart'; import 'package:prosapp_web_app/router/router.dart'; import 'package:prosapp_web_app/services/api_service.dart'; import 'package:prosapp_web_app/services/navigation_service.dart'; import 'package:provider/provider.dart'; class SetupCityView extends StatefulWidget { const SetupCityView({super.key}); @override State createState() => _SetupCityViewState(); } class _SetupCityViewState extends State { List _cities = []; String? _selected; String? _detected; // nombre detectado por GPS bool _detectedInList = false; // si la ciudad GPS está en la lista bool _loadingCities = true; bool _detectingGps = true; bool _saving = false; @override void initState() { super.initState(); _loadCities(); _detectCity(); } Future _loadCities() async { try { final data = await ApiService.instance.get('/locations/cities/all'); final list = (data as List).map((c) => c['name'] as String).toList(); if (mounted) { setState(() { _cities = list; _loadingCities = false; if (_detected != null) _tryPreselect(_detected!); }); } } catch (_) { if (mounted) setState(() => _loadingCities = false); } } Future _detectCity() async { setState(() => _detectingGps = true); try { LocationPermission perm = await Geolocator.checkPermission(); if (perm == LocationPermission.denied) { perm = await Geolocator.requestPermission(); } if (perm == LocationPermission.deniedForever || perm == LocationPermission.denied) { if (mounted) setState(() => _detectingGps = false); return; } final pos = await Geolocator.getCurrentPosition( locationSettings: const LocationSettings( accuracy: LocationAccuracy.low, timeLimit: Duration(seconds: 8), ), ); final url = Uri.parse( 'https://nominatim.openstreetmap.org/reverse' '?format=json&lat=${pos.latitude}&lon=${pos.longitude}&zoom=10&addressdetails=1', ); final resp = await http.get(url, headers: {'User-Agent': 'ProsApp/1.0 (prosapp.co)'}); if (resp.statusCode == 200) { final json = jsonDecode(resp.body) as Map; final address = json['address'] as Map?; final city = address?['city'] as String? ?? address?['town'] as String? ?? address?['municipality'] as String? ?? address?['county'] as String?; if (city != null && city.isNotEmpty && mounted) { setState(() => _detected = city); if (!_loadingCities) _tryPreselect(city); } } } catch (_) { // GPS no disponible } finally { if (mounted) setState(() => _detectingGps = false); } } void _tryPreselect(String detectedCity) { final normalized = detectedCity.toLowerCase().trim(); String? match = _cities.firstWhere( (c) => c.toLowerCase() == normalized, orElse: () => '', ); if (match!.isEmpty) { match = _cities.firstWhere( (c) => c.toLowerCase().contains(normalized) || normalized.contains(c.toLowerCase()), orElse: () => '', ); } if (match.isNotEmpty) { setState(() { _selected = match; _detectedInList = true; }); } else { setState(() { _detectedInList = false; _selected = null; }); } } Future _save() async { if (_selected == null) return; setState(() => _saving = true); try { await context.read().updateCity(_selected!); if (mounted) NavigationService.replaceTo(Flurorouter.dashboardRoute); } catch (_) { if (mounted) setState(() => _saving = false); } } void _skip() => NavigationService.replaceTo(Flurorouter.dashboardRoute); @override Widget build(BuildContext context) { final isDark = context.watch().isDark; final cardBg = isDark ? const Color(0xFF1E293B) : Colors.white; final border = isDark ? const Color(0xFF334155) : const Color(0xFFE5E7EB); final textPrimary = isDark ? Colors.white : const Color(0xFF111827); final textSec = isDark ? const Color(0xFF94A3B8) : const Color(0xFF6B7280); // Ciudad GPS detectada pero no está en la lista final bool unavailable = _detected != null && !_detectingGps && !_loadingCities && !_detectedInList; return Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 440), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 32), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // ── Header ────────────────────────────────────────────────────── Column( children: [ Container( width: 64, height: 64, decoration: BoxDecoration( color: const Color(0xFF42A4EF).withOpacity(0.12), shape: BoxShape.circle, ), child: const Icon(Icons.location_city_rounded, color: Color(0xFF42A4EF), size: 32), ), const SizedBox(height: 14), Text('¿En qué ciudad estás?', style: TextStyle( fontSize: 21, fontWeight: FontWeight.bold, color: textPrimary), textAlign: TextAlign.center), const SizedBox(height: 5), Text('Selecciona una de las ciudades disponibles.', style: TextStyle(fontSize: 13, color: textSec), textAlign: TextAlign.center), ], ), const SizedBox(height: 18), // ── Banner GPS ────────────────────────────────────────────────── if (_detectingGps) _Banner( color: const Color(0xFF42A4EF), icon: null, loading: true, text: 'Detectando tu ubicación...', ) else if (_detected != null && _detectedInList) _Banner( color: const Color(0xFF10B981), icon: Icons.my_location, text: 'Tu ubicación: $_detected — disponible ✓', ) else if (unavailable) _Banner( color: const Color(0xFFF59E0B), icon: Icons.location_off_outlined, text: 'El servicio aún no está disponible en "$_detected". Selecciona una ciudad disponible.', multiline: true, ), if (_detectingGps || _detected != null) const SizedBox(height: 12), // ── Lista de ciudades ──────────────────────────────────────────── Expanded( child: _loadingCities ? const Center(child: CircularProgressIndicator()) : _cities.isEmpty ? Center( child: Text('No hay ciudades disponibles.', style: TextStyle(color: textSec)), ) : Container( decoration: BoxDecoration( color: cardBg, borderRadius: BorderRadius.circular(14), border: Border.all(color: border), ), child: ClipRRect( borderRadius: BorderRadius.circular(14), child: ListView.separated( itemCount: _cities.length, separatorBuilder: (_, __) => Divider(height: 1, color: border), itemBuilder: (_, i) { final city = _cities[i]; final isSelected = city == _selected; final isGps = city == _detected; return InkWell( onTap: () => setState(() => _selected = city), child: AnimatedContainer( duration: const Duration(milliseconds: 150), color: isSelected ? const Color(0xFF42A4EF) .withOpacity(0.10) : Colors.transparent, padding: const EdgeInsets.symmetric( horizontal: 18, vertical: 16), child: Row( children: [ Icon( Icons.location_on_outlined, size: 18, color: isSelected ? const Color(0xFF42A4EF) : textSec, ), const SizedBox(width: 12), Expanded( child: Text( city, style: TextStyle( fontSize: 15, fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, color: isSelected ? const Color(0xFF42A4EF) : textPrimary, ), ), ), if (isGps && !isSelected) Container( margin: const EdgeInsets.only( right: 8), padding: const EdgeInsets.symmetric( horizontal: 7, vertical: 2), decoration: BoxDecoration( color: const Color(0xFF10B981) .withOpacity(0.12), borderRadius: BorderRadius.circular(4), ), child: const Text('GPS', style: TextStyle( fontSize: 10, color: Color(0xFF10B981), fontWeight: FontWeight.bold)), ), if (isSelected) const Icon( Icons.check_circle_rounded, size: 20, color: Color(0xFF42A4EF)), ], ), ), ); }, ), ), ), ), const SizedBox(height: 14), // ── Botón guardar ──────────────────────────────────────────────── SizedBox( height: 48, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF42A4EF), foregroundColor: Colors.white, disabledBackgroundColor: const Color(0xFF42A4EF).withOpacity(0.35), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12)), elevation: 0, ), onPressed: _selected != null && !_saving ? _save : null, child: _saving ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white)) : Text( _selected != null ? 'Continuar con $_selected' : 'Selecciona una ciudad', style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600), ), ), ), const SizedBox(height: 8), TextButton( onPressed: _skip, child: Text('Omitir por ahora', style: TextStyle(color: textSec, fontSize: 12)), ), ], ), ), ), ); } } class _Banner extends StatelessWidget { final Color color; final IconData? icon; final String text; final bool loading; final bool multiline; const _Banner({ required this.color, required this.text, this.icon, this.loading = false, this.multiline = false, }); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), decoration: BoxDecoration( color: color.withOpacity(0.08), borderRadius: BorderRadius.circular(10), border: Border.all(color: color.withOpacity(0.3)), ), child: Row( crossAxisAlignment: multiline ? CrossAxisAlignment.start : CrossAxisAlignment.center, children: [ if (loading) SizedBox( width: 14, height: 14, child: CircularProgressIndicator( strokeWidth: 2, color: color), ) else if (icon != null) Icon(icon, size: 16, color: color), const SizedBox(width: 10), Expanded( child: Text(text, style: TextStyle( fontSize: 12, color: color, fontWeight: FontWeight.w500, height: 1.4)), ), ], ), ); } }