feat: map deploy logic
This commit is contained in:
@@ -1,20 +1,46 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'rutas.dart';
|
import 'rutas.dart';
|
||||||
|
import '../models/domicilio_model.dart';
|
||||||
|
import 'mapa_expandible.dart';
|
||||||
|
|
||||||
class HorariosView extends StatelessWidget {
|
class HorariosView extends StatefulWidget {
|
||||||
const HorariosView({super.key});
|
const HorariosView({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
State<HorariosView> createState() => _HorariosViewState();
|
||||||
final List<Map<String, String>> rutas = List.generate(
|
}
|
||||||
6,
|
|
||||||
(index) => {
|
|
||||||
'colonia': 'Colonia ${index + 1}',
|
|
||||||
'ruta': 'Ruta ${index + 1}',
|
|
||||||
'horario': '${8 + (index % 3)}:${30 * (index % 2)} ${index < 3 ? 'AM' : 'PM'}',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
|
class _HorariosViewState extends State<HorariosView> {
|
||||||
|
List<Domicilio> domicilios = [];
|
||||||
|
bool _isLoading = true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_cargarDomicilios();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _cargarDomicilios() async {
|
||||||
|
try {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
final String? domiciliosString = prefs.getString('domicilios');
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
if (domiciliosString != null && domiciliosString.isNotEmpty) {
|
||||||
|
domicilios = Domicilio.decode(domiciliosString);
|
||||||
|
}
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
setState(() {
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
@@ -32,7 +58,7 @@ class HorariosView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: const Text(
|
child: const Text(
|
||||||
'Horarios',
|
'Mis Rutas',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 32,
|
fontSize: 32,
|
||||||
@@ -41,32 +67,46 @@ class HorariosView extends StatelessWidget {
|
|||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Lista de horarios
|
// Lista de domicilios
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ListView.builder(
|
child: _isLoading
|
||||||
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 24),
|
? const Center(child: CircularProgressIndicator(color: colorAzul))
|
||||||
itemCount: rutas.length,
|
: domicilios.isEmpty
|
||||||
|
? Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.location_on_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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Text(
|
||||||
|
'Agrega domicilios desde la pestaña Domicilios',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.grey.withOpacity(0.5),
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: ListView.builder(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
itemCount: domicilios.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final item = rutas[index];
|
final domicilio = domicilios[index];
|
||||||
return Padding(
|
return _buildDomicilioCard(domicilio, index);
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(item['colonia']!,
|
|
||||||
style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold)),
|
|
||||||
Text(item['ruta']!,
|
|
||||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Text(item['horario']!,
|
|
||||||
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -75,4 +115,110 @@ class HorariosView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildDomicilioCard(Domicilio domicilio, int index) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 20),
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: Colors.black, width: 4),
|
||||||
|
borderRadius: BorderRadius.circular(25),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
// Contenido principal del domicilio
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(15),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
// Info principal
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.home_outlined, size: 60),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
domicilio.nombre,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
domicilio.direccionCompleta,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
'📍 ${domicilio.latitud.toStringAsFixed(4)}, ${domicilio.longitud.toStringAsFixed(4)}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.grey[600],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
// Fila de horario y botón de mapa
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
// Horario (placeholder para API del backend)
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: colorAzul.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.access_time, color: colorAzul, size: 20),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
'Horario: ${_obtenerHorarioEstimado(index)}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: colorAzul,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
// Botón/flecha para mapa desplegable
|
||||||
|
MapaExpandible(
|
||||||
|
latitud: domicilio.latitud,
|
||||||
|
longitud: domicilio.longitud,
|
||||||
|
nombreDomicilio: domicilio.nombre,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Horario estimado (placeholder para cuando conectes con el backend)
|
||||||
|
String _obtenerHorarioEstimado(int index) {
|
||||||
|
// Esto es solo un placeholder - aquí irá la llamada a tu API
|
||||||
|
final horarios = ['8:00 AM - 9:00 AM', '9:30 AM - 10:30 AM', '11:00 AM - 12:00 PM', '1:00 PM - 2:00 PM', '3:00 PM - 4:00 PM', '5:00 PM - 6:00 PM'];
|
||||||
|
return horarios[index % horarios.length];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
106
lib/src/views/mapa_expandible.dart
Normal file
106
lib/src/views/mapa_expandible.dart
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_map/flutter_map.dart';
|
||||||
|
import 'package:latlong2/latlong.dart';
|
||||||
|
|
||||||
|
class MapaExpandible extends StatefulWidget {
|
||||||
|
final double latitud;
|
||||||
|
final double longitud;
|
||||||
|
final String nombreDomicilio;
|
||||||
|
|
||||||
|
const MapaExpandible({
|
||||||
|
super.key,
|
||||||
|
required this.latitud,
|
||||||
|
required this.longitud,
|
||||||
|
required this.nombreDomicilio,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MapaExpandible> createState() => _MapaExpandibleState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MapaExpandibleState extends State<MapaExpandible> {
|
||||||
|
bool _isExpanded = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
// Botón para expandir/colapsar
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_isExpanded = !_isExpanded;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey[200],
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
_isExpanded ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down,
|
||||||
|
color: colorAzul,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Text(
|
||||||
|
_isExpanded ? 'Ocultar mapa' : 'Ver mapa',
|
||||||
|
style: TextStyle(
|
||||||
|
color: colorAzul,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Mapa (visible solo cuando está expandido)
|
||||||
|
if (_isExpanded)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 12),
|
||||||
|
child: Container(
|
||||||
|
height: 250,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(15),
|
||||||
|
border: Border.all(color: colorAzul.withOpacity(0.3), width: 1),
|
||||||
|
),
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(15),
|
||||||
|
child: FlutterMap(
|
||||||
|
options: MapOptions(
|
||||||
|
initialCenter: LatLng(widget.latitud, widget.longitud),
|
||||||
|
initialZoom: 15,
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
TileLayer(
|
||||||
|
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||||
|
userAgentPackageName: 'com.example.app',
|
||||||
|
),
|
||||||
|
MarkerLayer(
|
||||||
|
markers: [
|
||||||
|
Marker(
|
||||||
|
point: LatLng(widget.latitud, widget.longitud),
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
child: const Icon(
|
||||||
|
Icons.location_pin,
|
||||||
|
color: Colors.red,
|
||||||
|
size: 40,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user