feat: geolocator logic added

This commit is contained in:
imlildud
2026-05-22 22:07:24 -06:00
parent 8df86daf25
commit 2d56d6de0d
4 changed files with 218 additions and 51 deletions

View File

@@ -1,5 +1,6 @@
// main.dart // main.dart
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'src/views/domicilios.dart';
import 'src/views/rutas.dart'; import 'src/views/rutas.dart';
import 'src/views/main_screen.dart'; import 'src/views/main_screen.dart';
import 'src/views/login.dart'; // Importar LoginView import 'src/views/login.dart'; // Importar LoginView
@@ -63,7 +64,7 @@ class RegistroView extends StatelessWidget {
// Navegar a MainScreen (app principal) // Navegar a MainScreen (app principal)
Navigator.pushReplacement( Navigator.pushReplacement(
context, context,
MaterialPageRoute(builder: (context) => const MainScreen()), MaterialPageRoute(builder: (context) => const DomiciliosView()),
); );
}, },
child: const Text('Registrar', style: TextStyle(color: Colors.white, fontSize: 18)), child: const Text('Registrar', style: TextStyle(color: Colors.white, fontSize: 18)),

View File

@@ -1,44 +1,50 @@
import 'dart:convert'; import 'dart:convert';
class Domicilio { class Domicilio {
final String id;
final String nombre; final String nombre;
final String colonia; final String colonia;
final String calle; final String calle;
final String numero; final String numero;
final String id; final double latitud;
final double longitud;
Domicilio({ Domicilio({
required this.id,
required this.nombre, required this.nombre,
required this.colonia, required this.colonia,
required this.calle, required this.calle,
required this.numero, required this.numero,
required this.id, required this.latitud,
required this.longitud,
}); });
String get direccionCompleta => '$colonia, $calle $numero'; String get direccionCompleta => '$colonia, $calle $numero';
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
'id': id,
'nombre': nombre, 'nombre': nombre,
'colonia': colonia, 'colonia': colonia,
'calle': calle, 'calle': calle,
'numero': numero, 'numero': numero,
'id': id, 'latitud': latitud,
'longitud': longitud,
}; };
factory Domicilio.fromJson(Map<String, dynamic> json) { factory Domicilio.fromJson(Map<String, dynamic> json) {
return Domicilio( return Domicilio(
id: json['id'],
nombre: json['nombre'], nombre: json['nombre'],
colonia: json['colonia'], colonia: json['colonia'],
calle: json['calle'], calle: json['calle'],
numero: json['numero'], numero: json['numero'],
id: json['id'], latitud: json['latitud'].toDouble(),
longitud: json['longitud'].toDouble(),
); );
} }
static String encode(List<Domicilio> domicilios) { static String encode(List<Domicilio> domicilios) {
return json.encode( return json.encode(domicilios.map((d) => d.toJson()).toList());
domicilios.map((d) => d.toJson()).toList(),
);
} }
static List<Domicilio> decode(String domiciliosString) { static List<Domicilio> decode(String domiciliosString) {

View File

@@ -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<bool> requestPermission() async {
final status = await Permission.location.request();
return status.isGranted;
}
// Obtener ubicación actual
static Future<Position?> 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<bool> showPermissionDialog(BuildContext context) async {
final result = await showDialog<bool>(
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;
}
}

View File

@@ -1,9 +1,9 @@
// domicilios.dart
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:geolocator/geolocator.dart';
import 'rutas.dart'; import 'rutas.dart';
import '../models/domicilio_model.dart'; import '../models/domicilio_model.dart';
import 'dart:math'; import '../services/geolocation_service.dart';
class DomiciliosView extends StatefulWidget { class DomiciliosView extends StatefulWidget {
const DomiciliosView({super.key}); const DomiciliosView({super.key});
@@ -16,6 +16,10 @@ class _DomiciliosViewState extends State<DomiciliosView> {
List<Domicilio> domicilios = []; List<Domicilio> domicilios = [];
bool _isLoading = true; bool _isLoading = true;
// Ubicación actual
Position? _currentPosition;
bool _isLoadingLocation = false;
// Controladores para el formulario // Controladores para el formulario
final TextEditingController nombreController = TextEditingController(); final TextEditingController nombreController = TextEditingController();
final TextEditingController coloniaController = TextEditingController(); final TextEditingController coloniaController = TextEditingController();
@@ -37,42 +41,78 @@ class _DomiciliosViewState extends State<DomiciliosView> {
super.dispose(); super.dispose();
} }
// Cargar domicilios guardados
Future<void> _cargarDomicilios() async { Future<void> _cargarDomicilios() async {
try { try {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
final String? domiciliosString = prefs.getString('domicilios'); final String? domiciliosString = prefs.getString('domicilios');
if (domiciliosString != null && domiciliosString.isNotEmpty) { setState(() {
setState(() { if (domiciliosString != null && domiciliosString.isNotEmpty) {
domicilios = Domicilio.decode(domiciliosString); domicilios = Domicilio.decode(domiciliosString);
_isLoading = false; }
}); _isLoading = false;
} else { });
setState(() {
_isLoading = false;
});
}
} catch (e) { } catch (e) {
print('Error al cargar domicilios: $e');
setState(() { setState(() {
_isLoading = false; _isLoading = false;
}); });
} }
} }
// Guardar domicilios en SharedPreferences
Future<void> _guardarDomicilios() async { Future<void> _guardarDomicilios() async {
try { try {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
final String domiciliosString = Domicilio.encode(domicilios); final String domiciliosString = Domicilio.encode(domicilios);
await prefs.setString('domicilios', domiciliosString); await prefs.setString('domicilios', domiciliosString);
} catch (e) { } catch (e) {
print('Error al guardar domicilios: $e'); print('Error al guardar: $e');
} }
} }
void _mostrarDialogoAgregar() { // Obtener ubicación actual y mostrar diálogo
Future<void> _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 // Limpiar controladores
nombreController.clear(); nombreController.clear();
coloniaController.clear(); coloniaController.clear();
@@ -99,7 +139,46 @@ class _DomiciliosViewState extends State<DomiciliosView> {
color: colorAzul, 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 // Campo: Nombre del domicilio
_buildCampoTexto( _buildCampoTexto(
controller: nombreController, controller: nombreController,
@@ -133,7 +212,6 @@ class _DomiciliosViewState extends State<DomiciliosView> {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ children: [
// Botón Cancelar
Expanded( Expanded(
child: OutlinedButton( child: OutlinedButton(
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
@@ -153,7 +231,6 @@ class _DomiciliosViewState extends State<DomiciliosView> {
), ),
), ),
const SizedBox(width: 15), const SizedBox(width: 15),
// Botón Agregar
Expanded( Expanded(
child: ElevatedButton( child: ElevatedButton(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
@@ -164,7 +241,10 @@ class _DomiciliosViewState extends State<DomiciliosView> {
), ),
), ),
onPressed: () { onPressed: () {
_agregarDomicilio(); _agregarDomicilio(
latitud: position.latitude,
longitud: position.longitude,
);
}, },
child: const Text( child: const Text(
'Agregar', 'Agregar',
@@ -210,8 +290,8 @@ class _DomiciliosViewState extends State<DomiciliosView> {
); );
} }
void _agregarDomicilio() async { void _agregarDomicilio({required double latitud, required double longitud}) {
// Validar que todos los campos estén llenos // Validar campos
if (nombreController.text.isEmpty || if (nombreController.text.isEmpty ||
coloniaController.text.isEmpty || coloniaController.text.isEmpty ||
calleController.text.isEmpty || calleController.text.isEmpty ||
@@ -225,27 +305,24 @@ class _DomiciliosViewState extends State<DomiciliosView> {
return; return;
} }
// Crear nuevo domicilio con ID único // Crear nuevo domicilio con lat/lng
final nuevoDomicilio = Domicilio( final nuevoDomicilio = Domicilio(
id: DateTime.now().millisecondsSinceEpoch.toString(),
nombre: nombreController.text, nombre: nombreController.text,
colonia: coloniaController.text, colonia: coloniaController.text,
calle: calleController.text, calle: calleController.text,
numero: numeroController.text, numero: numeroController.text,
id: DateTime.now().millisecondsSinceEpoch.toString(), // ID único latitud: latitud,
longitud: longitud,
); );
// Agregar a la lista
setState(() { setState(() {
domicilios.add(nuevoDomicilio); domicilios.add(nuevoDomicilio);
}); });
// Guardar en SharedPreferences _guardarDomicilios();
await _guardarDomicilios();
// Cerrar el diálogo
Navigator.pop(context); Navigator.pop(context);
// Mostrar mensaje de éxito
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text('Domicilio "${nombreController.text}" agregado'), content: Text('Domicilio "${nombreController.text}" agregado'),
@@ -272,10 +349,7 @@ class _DomiciliosViewState extends State<DomiciliosView> {
setState(() { setState(() {
domicilios.removeAt(index); domicilios.removeAt(index);
}); });
// Guardar cambios en SharedPreferences
await _guardarDomicilios(); await _guardarDomicilios();
Navigator.pop(context); Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( const SnackBar(
@@ -300,7 +374,6 @@ class _DomiciliosViewState extends State<DomiciliosView> {
child: SafeArea( child: SafeArea(
child: Column( child: Column(
children: [ children: [
// AppBar personalizado
Container( Container(
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 16), padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 16),
@@ -321,14 +394,9 @@ class _DomiciliosViewState extends State<DomiciliosView> {
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
), ),
// Contenido
Expanded( Expanded(
child: _isLoading child: _isLoading
? const Center( ? const Center(child: CircularProgressIndicator(color: colorAzul))
child: CircularProgressIndicator(
color: colorAzul,
),
)
: domicilios.isEmpty : domicilios.isEmpty
? Center( ? Center(
child: Column( child: Column(
@@ -393,6 +461,14 @@ class _DomiciliosViewState extends State<DomiciliosView> {
fontWeight: FontWeight.bold, 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<DomiciliosView> {
}, },
), ),
), ),
// Botón flotante de agregar
Padding( Padding(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
child: GestureDetector( child: _isLoadingLocation
onTap: _mostrarDialogoAgregar, ? 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( child: Container(
width: double.infinity, width: double.infinity,
height: 100, height: 100,