feat: geolocator logic added
This commit is contained in:
@@ -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)),
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
73
lib/src/services/geolocation_service.dart
Normal file
73
lib/src/services/geolocation_service.dart
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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');
|
||||||
|
|
||||||
|
setState(() {
|
||||||
if (domiciliosString != null && domiciliosString.isNotEmpty) {
|
if (domiciliosString != null && domiciliosString.isNotEmpty) {
|
||||||
setState(() {
|
|
||||||
domicilios = Domicilio.decode(domiciliosString);
|
domicilios = Domicilio.decode(domiciliosString);
|
||||||
_isLoading = false;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setState(() {
|
|
||||||
_isLoading = false;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
_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,
|
||||||
|
|||||||
Reference in New Issue
Block a user