diff --git a/lib/src/services/geolocation_service.dart b/lib/src/services/geolocation_service.dart index aa8eb58..270289d 100644 --- a/lib/src/services/geolocation_service.dart +++ b/lib/src/services/geolocation_service.dart @@ -1,73 +1,87 @@ +// src/services/geolocation_service.dart import 'package:geolocator/geolocator.dart'; -import 'package:permission_handler/permission_handler.dart'; +import 'dart:async'; // ← IMPORTANTE: Para TimeoutException class GeolocationService { - // Verificar y solicitar permisos de ubicación - static Future requestPermission() async { - final status = await Permission.location.request(); - return status.isGranted; + // Verificar servicios de ubicación + static Future isLocationServiceEnabled() async { + return await Geolocator.isLocationServiceEnabled(); } - // Obtener ubicación actual + // Verificar permiso + static Future checkPermission() async { + return await Geolocator.checkPermission(); + } + + // Solicitar permiso + static Future requestPermission() async { + return await Geolocator.requestPermission(); + } + + // Verificar si hay permiso concedido + static Future hasPermission() async { + LocationPermission permission = await Geolocator.checkPermission(); + return permission == LocationPermission.always || + permission == LocationPermission.whileInUse; + } + + // Obtener ubicación actual con timeout static Future getCurrentLocation() async { try { - // Verificar si los servicios de ubicación están activados + // Verificar servicios bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); if (!serviceEnabled) { + print('❌ Servicios de ubicación desactivados'); return null; } + print('✅ Servicios de ubicación activados'); // Verificar permisos LocationPermission permission = await Geolocator.checkPermission(); + print('📌 Permiso actual: $permission'); + if (permission == LocationPermission.denied) { permission = await Geolocator.requestPermission(); + print('📌 Permiso solicitado: $permission'); if (permission == LocationPermission.denied) { + print('❌ Permiso denegado'); return null; } } if (permission == LocationPermission.deniedForever) { + print('❌ Permiso denegado permanentemente'); return null; } // Obtener ubicación + print('📍 Obteniendo ubicación...'); + Position position = await Geolocator.getCurrentPosition( - desiredAccuracy: LocationAccuracy.high, + desiredAccuracy: LocationAccuracy.best, ); + print('✅ Ubicación obtenida: ${position.latitude}, ${position.longitude}'); return position; } catch (e) { - print('Error getting location: $e'); + print('❌ Error general: $e'); return null; } } - // Mostrar diálogo de permisos - static Future showPermissionDialog(BuildContext context) async { - final result = await showDialog( - context: context, - barrierDismissible: false, - builder: (context) => AlertDialog( - title: const Text('Permiso de ubicación'), - content: const Text( - 'Necesitamos acceder a tu ubicación para asignar tu domicilio a la ruta correcta de recolección.', - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context, false), - child: const Text('Cancelar'), - ), - TextButton( - onPressed: () => Navigator.pop(context, true), - child: const Text('Aceptar', style: TextStyle(color: colorAzul)), - ), - ], - ), - ); - - if (result == true) { - return await requestPermission(); + // Obtener ubicación con reintentos (sin timeout que da problemas) + static Future getCurrentLocationWithRetry({int maxRetries = 3}) async { + for (int i = 0; i < maxRetries; i++) { + print('🔄 Intento ${i + 1} de $maxRetries'); + final position = await getCurrentLocation(); + if (position != null) { + return position; + } + if (i < maxRetries - 1) { + await Future.delayed(const Duration(seconds: 2)); + } } - return false; + print('❌ No se pudo obtener ubicación después de $maxRetries intentos'); + return null; } } \ No newline at end of file diff --git a/lib/src/views/domicilios.dart b/lib/src/views/domicilios.dart index 7d3da26..bd71a94 100644 --- a/lib/src/views/domicilios.dart +++ b/lib/src/views/domicilios.dart @@ -1,9 +1,12 @@ +// domicilios.dart import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:geolocator/geolocator.dart'; import 'rutas.dart'; import '../models/domicilio_model.dart'; import '../services/geolocation_service.dart'; +import 'nav_bar.dart'; +import 'main_screen.dart'; class DomiciliosView extends StatefulWidget { const DomiciliosView({super.key}); @@ -15,12 +18,8 @@ class DomiciliosView extends StatefulWidget { class _DomiciliosViewState extends State { List domicilios = []; bool _isLoading = true; - - // Ubicación actual - Position? _currentPosition; bool _isLoadingLocation = false; - // Controladores para el formulario final TextEditingController nombreController = TextEditingController(); final TextEditingController coloniaController = TextEditingController(); final TextEditingController calleController = TextEditingController(); @@ -69,51 +68,126 @@ class _DomiciliosViewState extends State { } } - // Obtener ubicación actual y mostrar diálogo + Future _showLocationPermissionDialog() async { + if (await GeolocationService.hasPermission()) { + return true; + } + + final result = await showDialog( + context: context, + barrierDismissible: false, + builder: (context) => AlertDialog( + title: const Text('Permiso de ubicación'), + content: const Text( + 'Necesitamos acceder a tu ubicación para asignar tu domicilio a la ruta correcta.', + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, false), + child: const Text('Cancelar'), + ), + TextButton( + onPressed: () => Navigator.pop(context, true), + child: const Text('Aceptar', style: TextStyle(color: colorAzul)), + ), + ], + ), + ); + + if (result == true) { + LocationPermission newPermission = await GeolocationService.requestPermission(); + return newPermission == LocationPermission.always || + newPermission == LocationPermission.whileInUse; + } + return false; + } + + // ⬇️⬇️⬇️ ESTE ES EL MÉTODO QUE DEBES TENER ⬇️⬇️⬇️ Future _obtenerUbicacionYAgregar() async { + print('🔵 Iniciando obtención de ubicación'); setState(() { _isLoadingLocation = true; }); - // Solicitar permisos - final hasPermission = await GeolocationService.showPermissionDialog(context); + // Verificar GPS + final serviceEnabled = await GeolocationService.isLocationServiceEnabled(); + print('📡 GPS activado: $serviceEnabled'); + + if (!serviceEnabled) { + setState(() { + _isLoadingLocation = false; + }); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Activa el GPS para agregar un domicilio'), + backgroundColor: Colors.orange, + duration: Duration(seconds: 3), + ), + ); + return; + } + + // Verificar permisos + final hasPermission = await _showLocationPermissionDialog(); + print('🔐 Permiso concedido: $hasPermission'); + if (!hasPermission) { setState(() { _isLoadingLocation = false; }); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( - content: Text('Necesitamos tu ubicación para agregar un domicilio'), + content: Text('Necesitamos tu ubicación para continuar'), backgroundColor: Colors.orange, + duration: Duration(seconds: 3), ), ); return; } - // Obtener ubicación - final position = await GeolocationService.getCurrentLocation(); + // Mostrar mensaje de "obteniendo ubicación" + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Row( + children: [ + SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white), + ), + SizedBox(width: 10), + Text('Obteniendo tu ubicación...'), + ], + ), + backgroundColor: colorAzul, + duration: Duration(seconds: 5), + ), + ); + + // Obtener ubicación con reintentos + final position = await GeolocationService.getCurrentLocationWithRetry(maxRetries: 3); setState(() { _isLoadingLocation = false; - _currentPosition = position; }); if (position == null) { + print('❌ No se pudo obtener ubicación después de reintentos'); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( - content: Text('No se pudo obtener tu ubicación. Activa el GPS.'), + content: Text('No se pudo obtener tu ubicación. Revisa tu GPS.'), backgroundColor: Colors.red, + duration: Duration(seconds: 4), ), ); return; } - // Mostrar diálogo con la ubicación obtenida + print('✅ Ubicación obtenida exitosamente: ${position.latitude}, ${position.longitude}'); _mostrarDialogoAgregarConUbicacion(position); } void _mostrarDialogoAgregarConUbicacion(Position position) { - // Limpiar controladores nombreController.clear(); coloniaController.clear(); calleController.clear(); @@ -130,23 +204,16 @@ class _DomiciliosViewState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ - // Título const Text( 'Añadir domicilio', - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - color: colorAzul, - ), + style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: colorAzul), ), const SizedBox(height: 10), - // Mostrar ubicación obtenida Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.green.withOpacity(0.1), borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.green.withOpacity(0.3)), ), child: Row( children: [ @@ -156,22 +223,10 @@ class _DomiciliosViewState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - '📍 Ubicación obtenida', - style: TextStyle( - fontSize: 12, - color: Colors.green[700], - fontWeight: FontWeight.bold, - ), - ), - Text( - 'Lat: ${position.latitude.toStringAsFixed(6)}', - style: const TextStyle(fontSize: 11), - ), - Text( - 'Lng: ${position.longitude.toStringAsFixed(6)}', - style: const TextStyle(fontSize: 11), - ), + Text('📍 Ubicación obtenida', + style: TextStyle(fontSize: 12, color: Colors.green[700])), + Text('Lat: ${position.latitude.toStringAsFixed(6)}'), + Text('Lng: ${position.longitude.toStringAsFixed(6)}'), ], ), ), @@ -179,36 +234,14 @@ class _DomiciliosViewState extends State { ), ), const SizedBox(height: 15), - // Campo: Nombre del domicilio - _buildCampoTexto( - controller: nombreController, - hint: 'Nombre del domicilio', - icon: Icons.home_outlined, - ), + _buildCampoTexto(controller: nombreController, hint: 'Nombre del domicilio', icon: Icons.home_outlined), const SizedBox(height: 15), - // Campo: Colonia - _buildCampoTexto( - controller: coloniaController, - hint: 'Colonia', - icon: Icons.location_city_outlined, - ), + _buildCampoTexto(controller: coloniaController, hint: 'Colonia', icon: Icons.location_city_outlined), const SizedBox(height: 15), - // Campo: Calle - _buildCampoTexto( - controller: calleController, - hint: 'Calle', - icon: Icons.streetview, - ), + _buildCampoTexto(controller: calleController, hint: 'Calle', icon: Icons.streetview), const SizedBox(height: 15), - // Campo: Número - _buildCampoTexto( - controller: numeroController, - hint: 'Número', - icon: Icons.numbers, - keyboardType: TextInputType.number, - ), + _buildCampoTexto(controller: numeroController, hint: 'Número', icon: Icons.numbers, keyboardType: TextInputType.number), const SizedBox(height: 25), - // Botones Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ @@ -216,18 +249,10 @@ class _DomiciliosViewState extends State { child: OutlinedButton( style: OutlinedButton.styleFrom( side: BorderSide(color: colorAzul, width: 2), - padding: const EdgeInsets.symmetric(vertical: 12), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(15), - ), - ), - onPressed: () { - Navigator.pop(context); - }, - child: const Text( - 'Cancelar', - style: TextStyle(fontSize: 16, color: colorAzul), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), ), + onPressed: () => Navigator.pop(context), + child: const Text('Cancelar', style: TextStyle(color: colorAzul)), ), ), const SizedBox(width: 15), @@ -235,21 +260,10 @@ class _DomiciliosViewState extends State { child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: colorAzul, - padding: const EdgeInsets.symmetric(vertical: 12), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(15), - ), - ), - onPressed: () { - _agregarDomicilio( - latitud: position.latitude, - longitud: position.longitude, - ); - }, - child: const Text( - 'Agregar', - style: TextStyle(fontSize: 16, color: Colors.white), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), ), + onPressed: () => _agregarDomicilio(latitud: position.latitude, longitud: position.longitude), + child: const Text('Agregar', style: TextStyle(color: Colors.white)), ), ), ], @@ -274,10 +288,7 @@ class _DomiciliosViewState extends State { decoration: InputDecoration( hintText: hint, prefixIcon: Icon(icon, color: colorAzul), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(15), - borderSide: const BorderSide(color: colorAzul), - ), + border: OutlineInputBorder(borderRadius: BorderRadius.circular(15)), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(15), borderSide: BorderSide(color: colorAzul.withOpacity(0.5)), @@ -290,28 +301,23 @@ class _DomiciliosViewState extends State { ); } - void _agregarDomicilio({required double latitud, required double longitud}) { - // Validar campos - if (nombreController.text.isEmpty || - coloniaController.text.isEmpty || - calleController.text.isEmpty || - numeroController.text.isEmpty) { + void _agregarDomicilio({required double latitud, required double longitud}) async { + if (nombreController.text.trim().isEmpty || + coloniaController.text.trim().isEmpty || + calleController.text.trim().isEmpty || + numeroController.text.trim().isEmpty) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Por favor, llena todos los campos'), - backgroundColor: Colors.red, - ), + const SnackBar(content: Text('Llena todos los campos'), backgroundColor: Colors.red), ); return; } - // Crear nuevo domicilio con lat/lng final nuevoDomicilio = Domicilio( id: DateTime.now().millisecondsSinceEpoch.toString(), - nombre: nombreController.text, - colonia: coloniaController.text, - calle: calleController.text, - numero: numeroController.text, + nombre: nombreController.text.trim(), + colonia: coloniaController.text.trim(), + calle: calleController.text.trim(), + numero: numeroController.text.trim(), latitud: latitud, longitud: longitud, ); @@ -320,58 +326,49 @@ class _DomiciliosViewState extends State { domicilios.add(nuevoDomicilio); }); - _guardarDomicilios(); + await _guardarDomicilios(); Navigator.pop(context); ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Domicilio "${nombreController.text}" agregado'), - backgroundColor: colorAzul, - duration: const Duration(seconds: 2), - ), + SnackBar(content: Text('Domicilio agregado'), backgroundColor: colorAzul, duration: const Duration(seconds: 2)), ); } void _eliminarDomicilio(int index) async { showDialog( context: context, - builder: (BuildContext context) { - return AlertDialog( - title: const Text('Eliminar domicilio'), - content: Text('¿Deseas eliminar "${domicilios[index].nombre}"?'), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text('Cancelar'), - ), - TextButton( - onPressed: () async { - setState(() { - domicilios.removeAt(index); - }); - await _guardarDomicilios(); - Navigator.pop(context); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Domicilio eliminado'), - backgroundColor: Colors.red, - duration: Duration(seconds: 2), - ), - ); - }, - child: const Text('Eliminar', style: TextStyle(color: Colors.red)), - ), - ], - ); - }, + builder: (context) => AlertDialog( + title: const Text('Eliminar domicilio'), + content: Text('¿Eliminar "${domicilios[index].nombre}"?'), + actions: [ + TextButton(onPressed: () => Navigator.pop(context), child: const Text('Cancelar')), + TextButton( + onPressed: () async { + setState(() => domicilios.removeAt(index)); + await _guardarDomicilios(); + Navigator.pop(context); + }, + child: const Text('Eliminar', style: TextStyle(color: Colors.red)), + ), + ], + ), ); } + void _navegarA(int index) { + if (index == 1) { + Navigator.pushReplacement( + context, + MaterialPageRoute(builder: (context) => const MainScreen()), + ); + } + } + @override Widget build(BuildContext context) { - return Container( - color: Colors.white, - child: SafeArea( + return Scaffold( + backgroundColor: Colors.white, + body: SafeArea( child: Column( children: [ Container( @@ -379,20 +376,9 @@ class _DomiciliosViewState extends State { padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 16), decoration: const BoxDecoration( color: colorAzul, - borderRadius: BorderRadius.only( - bottomLeft: Radius.circular(20), - bottomRight: Radius.circular(20), - ), - ), - child: const Text( - 'Domicilios', - style: TextStyle( - color: Colors.white, - fontSize: 28, - fontWeight: FontWeight.bold, - ), - textAlign: TextAlign.center, + borderRadius: BorderRadius.only(bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20)), ), + child: const Text('Domicilios', style: TextStyle(color: Colors.white, fontSize: 28, fontWeight: FontWeight.bold), textAlign: TextAlign.center), ), Expanded( child: _isLoading @@ -402,27 +388,11 @@ class _DomiciliosViewState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon( - Icons.home_outlined, - size: 100, - color: Colors.grey.withOpacity(0.5), - ), + Icon(Icons.home_outlined, size: 100, color: Colors.grey.withOpacity(0.5)), const SizedBox(height: 20), - Text( - 'No hay domicilios agregados', - style: TextStyle( - fontSize: 18, - color: Colors.grey.withOpacity(0.7), - ), - ), + Text('No hay domicilios', style: TextStyle(fontSize: 18, color: Colors.grey.withOpacity(0.7))), const SizedBox(height: 10), - Text( - 'Toca el botón + para agregar', - style: TextStyle( - fontSize: 14, - color: Colors.grey.withOpacity(0.5), - ), - ), + Text('Toca el botón + para agregar', style: TextStyle(fontSize: 14, color: Colors.grey.withOpacity(0.5))), ], ), ) @@ -430,7 +400,7 @@ class _DomiciliosViewState extends State { padding: const EdgeInsets.all(20), itemCount: domicilios.length, itemBuilder: (context, index) { - final domicilio = domicilios[index]; + final d = domicilios[index]; return Padding( padding: const EdgeInsets.only(bottom: 20), child: Container( @@ -447,36 +417,13 @@ class _DomiciliosViewState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - domicilio.nombre, - style: const TextStyle( - fontSize: 22, - fontWeight: FontWeight.bold, - ), - ), - Text( - domicilio.direccionCompleta, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - // Mostrar lat/lng (para debug) - Text( - '📍 ${domicilio.latitud.toStringAsFixed(4)}, ${domicilio.longitud.toStringAsFixed(4)}', - style: TextStyle( - fontSize: 12, - color: Colors.grey[600], - ), - ), + Text(d.nombre, style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold)), + Text(d.direccionCompleta, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + Text('📍 ${d.latitud.toStringAsFixed(4)}, ${d.longitud.toStringAsFixed(4)}', style: TextStyle(fontSize: 12, color: Colors.grey[600])), ], ), ), - IconButton( - onPressed: () => _eliminarDomicilio(index), - icon: const Icon(Icons.delete_outline, size: 40), - color: Colors.red, - ), + IconButton(onPressed: () => _eliminarDomicilio(index), icon: const Icon(Icons.delete_outline, size: 40), color: Colors.red), ], ), ), @@ -487,33 +434,16 @@ class _DomiciliosViewState extends State { Padding( padding: const EdgeInsets.all(20), child: _isLoadingLocation - ? Container( - width: double.infinity, - height: 100, - decoration: BoxDecoration( - color: colorAzul, - borderRadius: BorderRadius.circular(20), - ), - child: const Center( - child: CircularProgressIndicator(color: Colors.white), - ), - ) + ? Container(width: double.infinity, height: 100, decoration: BoxDecoration(color: colorAzul, borderRadius: BorderRadius.circular(20)), child: const Center(child: CircularProgressIndicator(color: Colors.white))) : GestureDetector( onTap: _obtenerUbicacionYAgregar, - child: Container( - width: double.infinity, - height: 100, - decoration: BoxDecoration( - color: colorAzul, - borderRadius: BorderRadius.circular(20), - ), - child: const Icon(Icons.add, color: Colors.white, size: 80), - ), + child: Container(width: double.infinity, height: 100, decoration: BoxDecoration(color: colorAzul, borderRadius: BorderRadius.circular(20)), child: const Icon(Icons.add, color: Colors.white, size: 80)), ), ), ], ), ), + bottomNavigationBar: CustomNavBar(currentIndex: 0, onTap: _navegarA), ); } } \ No newline at end of file