diff --git a/lib/main.dart b/lib/main.dart index 6154fa1..f918a0f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,6 @@ // main.dart import 'package:flutter/material.dart'; +import 'src/views/domicilios.dart'; import 'src/views/rutas.dart'; import 'src/views/main_screen.dart'; import 'src/views/login.dart'; // Importar LoginView @@ -63,7 +64,7 @@ class RegistroView extends StatelessWidget { // Navegar a MainScreen (app principal) Navigator.pushReplacement( context, - MaterialPageRoute(builder: (context) => const MainScreen()), + MaterialPageRoute(builder: (context) => const DomiciliosView()), ); }, child: const Text('Registrar', style: TextStyle(color: Colors.white, fontSize: 18)), diff --git a/lib/src/models/domicilio_model.dart b/lib/src/models/domicilio_model.dart index b246c81..18c3ee7 100644 --- a/lib/src/models/domicilio_model.dart +++ b/lib/src/models/domicilio_model.dart @@ -1,44 +1,50 @@ import 'dart:convert'; class Domicilio { + final String id; final String nombre; final String colonia; final String calle; final String numero; - final String id; + final double latitud; + final double longitud; Domicilio({ + required this.id, required this.nombre, required this.colonia, required this.calle, required this.numero, - required this.id, + required this.latitud, + required this.longitud, }); String get direccionCompleta => '$colonia, $calle $numero'; Map toJson() => { + 'id': id, 'nombre': nombre, 'colonia': colonia, 'calle': calle, 'numero': numero, - 'id': id, + 'latitud': latitud, + 'longitud': longitud, }; factory Domicilio.fromJson(Map json) { return Domicilio( + id: json['id'], nombre: json['nombre'], colonia: json['colonia'], calle: json['calle'], numero: json['numero'], - id: json['id'], + latitud: json['latitud'].toDouble(), + longitud: json['longitud'].toDouble(), ); } static String encode(List domicilios) { - return json.encode( - domicilios.map((d) => d.toJson()).toList(), - ); + return json.encode(domicilios.map((d) => d.toJson()).toList()); } static List decode(String domiciliosString) { diff --git a/lib/src/services/geolocation_service.dart b/lib/src/services/geolocation_service.dart new file mode 100644 index 0000000..aa8eb58 --- /dev/null +++ b/lib/src/services/geolocation_service.dart @@ -0,0 +1,73 @@ +import 'package:geolocator/geolocator.dart'; +import 'package:permission_handler/permission_handler.dart'; + +class GeolocationService { + // Verificar y solicitar permisos de ubicación + static Future requestPermission() async { + final status = await Permission.location.request(); + return status.isGranted; + } + + // Obtener ubicación actual + static Future getCurrentLocation() async { + try { + // Verificar si los servicios de ubicación están activados + bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); + if (!serviceEnabled) { + return null; + } + + // Verificar permisos + LocationPermission permission = await Geolocator.checkPermission(); + if (permission == LocationPermission.denied) { + permission = await Geolocator.requestPermission(); + if (permission == LocationPermission.denied) { + return null; + } + } + + if (permission == LocationPermission.deniedForever) { + return null; + } + + // Obtener ubicación + Position position = await Geolocator.getCurrentPosition( + desiredAccuracy: LocationAccuracy.high, + ); + + return position; + } catch (e) { + print('Error getting location: $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(); + } + return false; + } +} \ No newline at end of file diff --git a/lib/src/views/domicilios.dart b/lib/src/views/domicilios.dart index 2907179..7d3da26 100644 --- a/lib/src/views/domicilios.dart +++ b/lib/src/views/domicilios.dart @@ -1,9 +1,9 @@ -// 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 'dart:math'; +import '../services/geolocation_service.dart'; class DomiciliosView extends StatefulWidget { const DomiciliosView({super.key}); @@ -16,6 +16,10 @@ 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(); @@ -37,42 +41,78 @@ class _DomiciliosViewState extends State { super.dispose(); } - // Cargar domicilios guardados Future _cargarDomicilios() async { try { final prefs = await SharedPreferences.getInstance(); final String? domiciliosString = prefs.getString('domicilios'); - if (domiciliosString != null && domiciliosString.isNotEmpty) { - setState(() { + setState(() { + if (domiciliosString != null && domiciliosString.isNotEmpty) { domicilios = Domicilio.decode(domiciliosString); - _isLoading = false; - }); - } else { - setState(() { - _isLoading = false; - }); - } + } + _isLoading = false; + }); } catch (e) { - print('Error al cargar domicilios: $e'); setState(() { _isLoading = false; }); } } - // Guardar domicilios en SharedPreferences Future _guardarDomicilios() async { try { final prefs = await SharedPreferences.getInstance(); final String domiciliosString = Domicilio.encode(domicilios); await prefs.setString('domicilios', domiciliosString); } catch (e) { - print('Error al guardar domicilios: $e'); + print('Error al guardar: $e'); } } - void _mostrarDialogoAgregar() { + // Obtener ubicación actual y mostrar diálogo + Future _obtenerUbicacionYAgregar() async { + setState(() { + _isLoadingLocation = true; + }); + + // Solicitar permisos + final hasPermission = await GeolocationService.showPermissionDialog(context); + if (!hasPermission) { + setState(() { + _isLoadingLocation = false; + }); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Necesitamos tu ubicación para agregar un domicilio'), + backgroundColor: Colors.orange, + ), + ); + return; + } + + // Obtener ubicación + final position = await GeolocationService.getCurrentLocation(); + + setState(() { + _isLoadingLocation = false; + _currentPosition = position; + }); + + if (position == null) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('No se pudo obtener tu ubicación. Activa el GPS.'), + backgroundColor: Colors.red, + ), + ); + return; + } + + // Mostrar diálogo con la ubicación obtenida + _mostrarDialogoAgregarConUbicacion(position); + } + + void _mostrarDialogoAgregarConUbicacion(Position position) { // Limpiar controladores nombreController.clear(); coloniaController.clear(); @@ -99,7 +139,46 @@ class _DomiciliosViewState extends State { color: colorAzul, ), ), - const SizedBox(height: 20), + 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: [ + Icon(Icons.location_on, color: Colors.green[700], size: 24), + const SizedBox(width: 10), + Expanded( + 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), + ), + ], + ), + ), + ], + ), + ), + const SizedBox(height: 15), // Campo: Nombre del domicilio _buildCampoTexto( controller: nombreController, @@ -133,7 +212,6 @@ class _DomiciliosViewState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - // Botón Cancelar Expanded( child: OutlinedButton( style: OutlinedButton.styleFrom( @@ -153,7 +231,6 @@ class _DomiciliosViewState extends State { ), ), const SizedBox(width: 15), - // Botón Agregar Expanded( child: ElevatedButton( style: ElevatedButton.styleFrom( @@ -164,7 +241,10 @@ class _DomiciliosViewState extends State { ), ), onPressed: () { - _agregarDomicilio(); + _agregarDomicilio( + latitud: position.latitude, + longitud: position.longitude, + ); }, child: const Text( 'Agregar', @@ -210,8 +290,8 @@ class _DomiciliosViewState extends State { ); } - void _agregarDomicilio() async { - // Validar que todos los campos estén llenos + void _agregarDomicilio({required double latitud, required double longitud}) { + // Validar campos if (nombreController.text.isEmpty || coloniaController.text.isEmpty || calleController.text.isEmpty || @@ -225,27 +305,24 @@ class _DomiciliosViewState extends State { return; } - // Crear nuevo domicilio con ID único + // 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, - id: DateTime.now().millisecondsSinceEpoch.toString(), // ID único + latitud: latitud, + longitud: longitud, ); - // Agregar a la lista setState(() { domicilios.add(nuevoDomicilio); }); - // Guardar en SharedPreferences - await _guardarDomicilios(); - - // Cerrar el diálogo + _guardarDomicilios(); Navigator.pop(context); - // Mostrar mensaje de éxito ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Domicilio "${nombreController.text}" agregado'), @@ -272,10 +349,7 @@ class _DomiciliosViewState extends State { setState(() { domicilios.removeAt(index); }); - - // Guardar cambios en SharedPreferences await _guardarDomicilios(); - Navigator.pop(context); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( @@ -300,7 +374,6 @@ class _DomiciliosViewState extends State { child: SafeArea( child: Column( children: [ - // AppBar personalizado Container( width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 16), @@ -321,14 +394,9 @@ class _DomiciliosViewState extends State { textAlign: TextAlign.center, ), ), - // Contenido Expanded( child: _isLoading - ? const Center( - child: CircularProgressIndicator( - color: colorAzul, - ), - ) + ? const Center(child: CircularProgressIndicator(color: colorAzul)) : domicilios.isEmpty ? Center( child: Column( @@ -393,6 +461,14 @@ class _DomiciliosViewState extends State { 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], + ), + ), ], ), ), @@ -408,11 +484,22 @@ class _DomiciliosViewState extends State { }, ), ), - // Botón flotante de agregar Padding( padding: const EdgeInsets.all(20), - child: GestureDetector( - onTap: _mostrarDialogoAgregar, + child: _isLoadingLocation + ? 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,