modificaciones en el sistema
This commit is contained in:
@@ -1,8 +1,20 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'notificaciones_service.dart';
|
||||
|
||||
// ← Lista de colonias disponibles (igual que en ruta_exclusiva.dart)
|
||||
const List<String> _coloniasDisponibles = [
|
||||
'Zona Centro',
|
||||
'Las Arboledas',
|
||||
'Trojes',
|
||||
'San Juanico',
|
||||
'Los Olivos',
|
||||
'Rancho Seco',
|
||||
'Las Insurgentes',
|
||||
];
|
||||
|
||||
class PanelConfiguracionBottomSheet extends StatefulWidget {
|
||||
final Function(String etiqueta, String direccion) onDomicilioGuardado;
|
||||
// ← colonia agregada al callback
|
||||
final Function(String etiqueta, String direccion, String colonia) onDomicilioGuardado;
|
||||
final bool notificarInicioRuta;
|
||||
final bool notificarAproximacion;
|
||||
final bool notificarRetrasosFallas;
|
||||
@@ -18,13 +30,19 @@ class PanelConfiguracionBottomSheet extends StatefulWidget {
|
||||
});
|
||||
|
||||
@override
|
||||
State<PanelConfiguracionBottomSheet> createState() => _PanelConfiguracionBottomSheetState();
|
||||
State<PanelConfiguracionBottomSheet> createState() =>
|
||||
_PanelConfiguracionBottomSheetState();
|
||||
}
|
||||
|
||||
class _PanelConfiguracionBottomSheetState extends State<PanelConfiguracionBottomSheet> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
class _PanelConfiguracionBottomSheetState
|
||||
extends State<PanelConfiguracionBottomSheet> {
|
||||
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _direccionController = TextEditingController();
|
||||
|
||||
String _etiquetaSeleccionada = 'Casa';
|
||||
String _coloniaSeleccionada = _coloniasDisponibles[0]; // ← NUEVO
|
||||
|
||||
final List<String> _opcionesEtiquetas = ['Casa', 'Trabajo', 'Otro'];
|
||||
|
||||
late bool _inicioRuta;
|
||||
@@ -34,8 +52,8 @@ class _PanelConfiguracionBottomSheetState extends State<PanelConfiguracionBottom
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_inicioRuta = widget.notificarInicioRuta;
|
||||
_aproximacion = widget.notificarAproximacion;
|
||||
_inicioRuta = widget.notificarInicioRuta;
|
||||
_aproximacion = widget.notificarAproximacion;
|
||||
_retrasosFallas = widget.notificarRetrasosFallas;
|
||||
}
|
||||
|
||||
@@ -59,46 +77,112 @@ class _PanelConfiguracionBottomSheetState extends State<PanelConfiguracionBottom
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
|
||||
Text(
|
||||
'Registrar Nuevo Domicilio',
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold),
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Etiqueta:', style: TextStyle(fontWeight: FontWeight.bold, color: Colors.green[800])),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: _opcionesEtpciones(),
|
||||
|
||||
// ── Etiqueta ──
|
||||
Text(
|
||||
'Etiqueta:',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.green[800],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(children: _opcionesEtpciones()),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// ── Dirección ──
|
||||
TextFormField(
|
||||
controller: _direccionController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Dirección completa',
|
||||
prefixIcon: const Icon(Icons.location_on, color: Colors.green),
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.trim().isEmpty) return 'Ingresa una dirección';
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return 'Ingresa una dirección';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// ── Selector de colonia ── NUEVO
|
||||
Text(
|
||||
'Colonia:',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.green[800],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.grey),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: DropdownButtonHideUnderline(
|
||||
child: DropdownButton<String>(
|
||||
value: _coloniaSeleccionada,
|
||||
isExpanded: true,
|
||||
icon: const Icon(Icons.arrow_drop_down, color: Colors.green),
|
||||
items: _coloniasDisponibles.map((colonia) {
|
||||
return DropdownMenuItem(
|
||||
value: colonia,
|
||||
child: Text(colonia),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_coloniaSeleccionada = value!;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// ── Botón guardar ──
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.green[700],
|
||||
foregroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
widget.onDomicilioGuardado(_etiquetaSeleccionada, _direccionController.text);
|
||||
// ← pasa también la colonia
|
||||
widget.onDomicilioGuardado(
|
||||
_etiquetaSeleccionada,
|
||||
_direccionController.text,
|
||||
_coloniaSeleccionada,
|
||||
);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
@@ -108,23 +192,30 @@ class _PanelConfiguracionBottomSheetState extends State<PanelConfiguracionBottom
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 16.0),
|
||||
child: Divider(color: Colors.black),
|
||||
),
|
||||
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.notifications_active_rounded, color: Colors.green[800]),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Alertas Operativas Push',
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 8),
|
||||
|
||||
SwitchListTile(
|
||||
title: const Text('Inicio de Ruta', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600)),
|
||||
title: const Text('Inicio de Ruta',
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600)),
|
||||
value: _inicioRuta,
|
||||
activeThumbColor: Colors.green[700],
|
||||
onChanged: (bool value) {
|
||||
@@ -132,8 +223,10 @@ class _PanelConfiguracionBottomSheetState extends State<PanelConfiguracionBottom
|
||||
widget.onAlertasChanged(value, 'inicio');
|
||||
},
|
||||
),
|
||||
|
||||
SwitchListTile(
|
||||
title: const Text('Camión Próximo', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600)),
|
||||
title: const Text('Camión Próximo',
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600)),
|
||||
value: _aproximacion,
|
||||
activeThumbColor: Colors.green[700],
|
||||
onChanged: (bool value) {
|
||||
@@ -141,8 +234,10 @@ class _PanelConfiguracionBottomSheetState extends State<PanelConfiguracionBottom
|
||||
widget.onAlertasChanged(value, 'aproximacion');
|
||||
},
|
||||
),
|
||||
|
||||
SwitchListTile(
|
||||
title: const Text('Imprevistos y Retrasos', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600)),
|
||||
title: const Text('Imprevistos y Retrasos',
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600)),
|
||||
value: _retrasosFallas,
|
||||
activeThumbColor: Colors.green[700],
|
||||
onChanged: (bool value) {
|
||||
@@ -167,9 +262,7 @@ class _PanelConfiguracionBottomSheetState extends State<PanelConfiguracionBottom
|
||||
selectedColor: Colors.green[100],
|
||||
checkmarkColor: Colors.green[800],
|
||||
onSelected: (bool selected) {
|
||||
setState(() {
|
||||
_etiquetaSeleccionada = etiqueta;
|
||||
});
|
||||
setState(() => _etiquetaSeleccionada = etiqueta);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
@@ -2,16 +2,20 @@ import 'package:flutter/material.dart';
|
||||
import 'tarjetaeta.dart';
|
||||
import 'configuraciondomicilio.dart';
|
||||
import 'notificaciones_service.dart';
|
||||
import 'ruta_exclusiva.dart';
|
||||
import 'guia_separacion.dart';
|
||||
|
||||
class Domicilio {
|
||||
final String id;
|
||||
final String etiqueta;
|
||||
final String direccion;
|
||||
final String colonia; // ← NUEVO
|
||||
|
||||
Domicilio({
|
||||
required this.id,
|
||||
required this.etiqueta,
|
||||
required this.direccion,
|
||||
required this.colonia, // ← NUEVO
|
||||
});
|
||||
}
|
||||
|
||||
@@ -31,11 +35,13 @@ class _GestionDomiciliosScreenState
|
||||
id: '1',
|
||||
etiqueta: 'Casa',
|
||||
direccion: 'Av. Reforma 222, CDMX',
|
||||
colonia: 'Zona Centro', // ← NUEVO
|
||||
),
|
||||
Domicilio(
|
||||
id: '2',
|
||||
etiqueta: 'Trabajo',
|
||||
direccion: 'Benito Juárez 45',
|
||||
colonia: 'Las Arboledas', // ← NUEVO
|
||||
),
|
||||
];
|
||||
|
||||
@@ -57,13 +63,15 @@ class _GestionDomiciliosScreenState
|
||||
notificarInicioRuta: _notificarInicioRuta,
|
||||
notificarAproximacion: _notificarAproximacion,
|
||||
notificarRetrasosFallas: _notificarRetrasosFallas,
|
||||
onDomicilioGuardado: (String etiqueta, String direccion) {
|
||||
// ← ahora recibe también colonia
|
||||
onDomicilioGuardado: (String etiqueta, String direccion, String colonia) {
|
||||
setState(() {
|
||||
_misDomicilios.add(
|
||||
Domicilio(
|
||||
id: DateTime.now().toString(),
|
||||
etiqueta: etiqueta,
|
||||
direccion: direccion,
|
||||
colonia: colonia, // ← NUEVO
|
||||
),
|
||||
);
|
||||
});
|
||||
@@ -108,112 +116,390 @@ class _GestionDomiciliosScreenState
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey[100],
|
||||
|
||||
appBar: AppBar(
|
||||
title: const Text('Mis Domicilios'),
|
||||
backgroundColor: Colors.green,
|
||||
foregroundColor: Colors.white,
|
||||
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.notifications_outlined),
|
||||
tooltip: 'Alertas y Notificaciones',
|
||||
onPressed: () async {
|
||||
final resultado = await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => AlertasNotificacionesScreen(
|
||||
notificarInicioRuta: _notificarInicioRuta,
|
||||
notificarAproximacion: _notificarAproximacion,
|
||||
notificarRetrasosFallas: _notificarRetrasosFallas,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (resultado != null) {
|
||||
setState(() {
|
||||
_notificarInicioRuta = resultado['inicio'];
|
||||
_notificarAproximacion = resultado['aproximacion'];
|
||||
_notificarRetrasosFallas = resultado['retrasos'];
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
body: Column(
|
||||
body: Stack(
|
||||
children: [
|
||||
|
||||
// ← TARJETA ETA corregida
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: TarjetaEtaWidget(), // ← child bien cerrado aquí
|
||||
Container(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
color: const Color(0xFF5BB8E8),
|
||||
),
|
||||
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
'Lugares Registrados',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
Positioned(
|
||||
top: 80,
|
||||
left: -40,
|
||||
right: -40,
|
||||
child: Container(
|
||||
height: MediaQuery.of(context).size.height * 0.75,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF4AA8D8).withValues(alpha: 0.5),
|
||||
borderRadius: BorderRadius.circular(200),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Positioned(
|
||||
bottom: 80,
|
||||
right: -10,
|
||||
child: CustomPaint(
|
||||
size: const Size(160, 130),
|
||||
painter: _MountainPainter(color: const Color(0xFF2E7D32)),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 80,
|
||||
right: 60,
|
||||
child: CustomPaint(
|
||||
size: const Size(120, 100),
|
||||
painter: _MountainPainter(color: const Color(0xFF388E3C)),
|
||||
),
|
||||
),
|
||||
|
||||
Positioned(
|
||||
bottom: 0, left: 0, right: 0,
|
||||
child: Container(height: 90, color: const Color(0xFF4CAF50)),
|
||||
),
|
||||
|
||||
Positioned(
|
||||
bottom: 30, left: 0, right: 0,
|
||||
child: Container(
|
||||
height: 18,
|
||||
color: const Color(0xFF424242),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: List.generate(
|
||||
12,
|
||||
(_) => Container(width: 20, height: 3, color: Colors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 10),
|
||||
Positioned(bottom: 45, left: 20,
|
||||
child: _buildHouse(const Color(0xFF90A4AE), 40)),
|
||||
Positioned(bottom: 45, left: 80,
|
||||
child: _buildHouse(const Color(0xFF795548), 35)),
|
||||
Positioned(bottom: 45, right: 20,
|
||||
child: _buildHouse(const Color(0xFFFFCC80), 45)),
|
||||
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: _misDomicilios.length,
|
||||
itemBuilder: (context, index) {
|
||||
final domicilio = _misDomicilios[index];
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 8,
|
||||
Positioned(bottom: 33, left: 110,
|
||||
child: _buildTruck(const Color(0xFFE53935))),
|
||||
Positioned(bottom: 33, right: 80,
|
||||
child: _buildTruck(const Color(0xFFE53935))),
|
||||
|
||||
Column(
|
||||
children: [
|
||||
|
||||
Column(
|
||||
children: [
|
||||
Container(height: 10, color: const Color(0xFFE8534A)),
|
||||
Container(height: 8, color: const Color(0xFFF5A623)),
|
||||
Container(height: 8, color: const Color(0xFFFFD700)),
|
||||
Container(height: 8, color: const Color(0xFF4CAF50)),
|
||||
],
|
||||
),
|
||||
|
||||
SafeArea(
|
||||
bottom: false,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
|
||||
const Text(
|
||||
'ECOUBICEL.',
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Colors.black87,
|
||||
letterSpacing: 1.5,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Icon(Icons.recycling, color: Color(0xFF2E7D32), size: 28),
|
||||
const Spacer(),
|
||||
|
||||
// ── Botón notificaciones ──
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF4CAF50),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.notifications_outlined, color: Colors.white),
|
||||
tooltip: 'Alertas y Notificaciones',
|
||||
onPressed: () async {
|
||||
final resultado = await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => AlertasNotificacionesScreen(
|
||||
notificarInicioRuta: _notificarInicioRuta,
|
||||
notificarAproximacion: _notificarAproximacion,
|
||||
notificarRetrasosFallas: _notificarRetrasosFallas,
|
||||
),
|
||||
),
|
||||
);
|
||||
if (resultado != null) {
|
||||
setState(() {
|
||||
_notificarInicioRuta = resultado['inicio'];
|
||||
_notificarAproximacion = resultado['aproximacion'];
|
||||
_notificarRetrasosFallas = resultado['retrasos'];
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
// ── Botón Mi Ruta ──
|
||||
const SizedBox(width: 8),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF1E88E5),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.map_outlined, color: Colors.white),
|
||||
tooltip: 'Mi Ruta',
|
||||
onPressed: () {
|
||||
// ← usa colonia real del primer domicilio
|
||||
final domicilio = _misDomicilios.isNotEmpty
|
||||
? _misDomicilios[0]
|
||||
: null;
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => RutaExclusivaScreen(
|
||||
direccionUsuario: domicilio?.direccion ?? 'Sin dirección',
|
||||
coloniaUsuario: domicilio?.colonia ?? '', // ← CAMBIADO
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF558B2F),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.eco_outlined, color: Colors.white),
|
||||
tooltip: 'Guía de Separación',
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const GuiaSeparacionScreen(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
child: ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: Colors.green[100],
|
||||
child: Icon(
|
||||
_obtenerIcono(domicilio.etiqueta),
|
||||
color: Colors.green,
|
||||
),
|
||||
),
|
||||
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
child: TarjetaEtaWidget(),
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 5,
|
||||
height: 22,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF4CAF50),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
),
|
||||
title: Text(domicilio.etiqueta),
|
||||
subtitle: Text(domicilio.direccion),
|
||||
trailing: IconButton(
|
||||
icon: const Icon(Icons.delete, color: Colors.red),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_misDomicilios.removeAt(index);
|
||||
});
|
||||
},
|
||||
const SizedBox(width: 8),
|
||||
const Text(
|
||||
'Lugares Registrados',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 10),
|
||||
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsets.only(bottom: 100),
|
||||
itemCount: _misDomicilios.length,
|
||||
itemBuilder: (context, index) {
|
||||
final domicilio = _misDomicilios[index];
|
||||
final cardColors = [
|
||||
const Color(0xFF4CAF50),
|
||||
const Color(0xFF1E88E5),
|
||||
const Color(0xFFE65100),
|
||||
];
|
||||
final cardColor = cardColors[index % cardColors.length];
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: cardColor,
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: cardColor.withValues(alpha: 0.4),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 3),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 20, vertical: 4),
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: Colors.white.withValues(alpha: 0.3),
|
||||
child: Icon(
|
||||
_obtenerIcono(domicilio.etiqueta),
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
domicilio.etiqueta,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
// ← muestra colonia como subtítulo
|
||||
subtitle: Text(
|
||||
'${domicilio.direccion} • ${domicilio.colonia}',
|
||||
style: const TextStyle(color: Colors.white70),
|
||||
),
|
||||
trailing: IconButton(
|
||||
icon: const Icon(Icons.delete, color: Colors.white),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_misDomicilios.removeAt(index);
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
onPressed: _mostrarFormularioAgregar,
|
||||
backgroundColor: Colors.green,
|
||||
backgroundColor: const Color(0xFFE65100),
|
||||
foregroundColor: Colors.white,
|
||||
shape: const StadiumBorder(),
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text('Agregar'),
|
||||
label: const Text(
|
||||
'Agregar',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHouse(Color color, double size) {
|
||||
return CustomPaint(
|
||||
size: Size(size, size * 0.9),
|
||||
painter: _HousePainter(color: color),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTruck(Color color) {
|
||||
return Container(
|
||||
width: 36,
|
||||
height: 18,
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Container(
|
||||
width: 12,
|
||||
height: 14,
|
||||
margin: const EdgeInsets.only(right: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withValues(alpha: 0.7),
|
||||
borderRadius: const BorderRadius.only(
|
||||
topRight: Radius.circular(3),
|
||||
topLeft: Radius.circular(2),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _MountainPainter extends CustomPainter {
|
||||
final Color color;
|
||||
_MountainPainter({required this.color});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint()..color = color;
|
||||
final path = Path()
|
||||
..moveTo(0, size.height)
|
||||
..lineTo(size.width / 2, 0)
|
||||
..lineTo(size.width, size.height)
|
||||
..close();
|
||||
canvas.drawPath(path, paint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_) => false;
|
||||
}
|
||||
|
||||
class _HousePainter extends CustomPainter {
|
||||
final Color color;
|
||||
_HousePainter({required this.color});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint()..color = color;
|
||||
canvas.drawRect(
|
||||
Rect.fromLTWH(0, size.height * 0.4, size.width, size.height * 0.6),
|
||||
paint,
|
||||
);
|
||||
final roof = Paint()..color = const Color(0xFFB71C1C);
|
||||
final path = Path()
|
||||
..moveTo(0, size.height * 0.4)
|
||||
..lineTo(size.width / 2, 0)
|
||||
..lineTo(size.width, size.height * 0.4)
|
||||
..close();
|
||||
canvas.drawPath(path, roof);
|
||||
canvas.drawRect(
|
||||
Rect.fromLTWH(size.width * 0.25, size.height * 0.55,
|
||||
size.width * 0.2, size.height * 0.2),
|
||||
Paint()..color = const Color(0xFFB3E5FC),
|
||||
);
|
||||
canvas.drawRect(
|
||||
Rect.fromLTWH(size.width * 0.55, size.height * 0.65,
|
||||
size.width * 0.2, size.height * 0.35),
|
||||
Paint()..color = const Color(0xFF5D4037),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_) => false;
|
||||
}
|
||||
359
lib/guia_separacion.dart
Normal file
359
lib/guia_separacion.dart
Normal file
@@ -0,0 +1,359 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// ─── DATOS DE CATEGORÍAS (sin conexión, todo local) ─────────────
|
||||
const List<Map<String, dynamic>> _categorias = [
|
||||
{
|
||||
'nombre' : 'Orgánicos',
|
||||
'icono' : '🍃',
|
||||
'color' : Color(0xFF2E7D32),
|
||||
'colorFondo': Color(0xFFE8F5E9),
|
||||
'descripcion': 'Residuos de origen natural que se descomponen fácilmente.',
|
||||
'ejemplos': [
|
||||
'🥦 Restos de frutas y verduras',
|
||||
'🥚 Cáscaras de huevo',
|
||||
'☕ Café molido y filtros de té',
|
||||
'🌿 Pasto, hojas y flores',
|
||||
'🍖 Restos de comida cocinada',
|
||||
],
|
||||
'noIncluir': [
|
||||
'🚫 Aceites o grasas',
|
||||
'🚫 Plásticos o envases',
|
||||
'🚫 Papel de baño usado',
|
||||
],
|
||||
'consejo': 'Deposítalos en bolsa VERDE. Son ideales para compostar y nutrir la tierra.',
|
||||
},
|
||||
{
|
||||
'nombre' : 'Reciclables',
|
||||
'icono' : '♻️',
|
||||
'color' : Color(0xFF1565C0),
|
||||
'colorFondo': Color(0xFFE3F2FD),
|
||||
'descripcion': 'Materiales que pueden transformarse en nuevos productos.',
|
||||
'ejemplos': [
|
||||
'🧴 Botellas y envases de plástico (PET)',
|
||||
'📦 Cajas y cartón limpio',
|
||||
'🗞️ Papel y periódico',
|
||||
'🥫 Latas de aluminio y acero',
|
||||
'🍶 Botellas y frascos de vidrio',
|
||||
],
|
||||
'noIncluir': [
|
||||
'🚫 Papel sucio o con grasa',
|
||||
'🚫 Vidrio roto sin envolver',
|
||||
'🚫 Plásticos con comida',
|
||||
],
|
||||
'consejo': 'Deposítalos en bolsa AZUL. Enjuaga los envases antes de separarlos.',
|
||||
},
|
||||
{
|
||||
'nombre' : 'Sanitarios',
|
||||
'icono' : '🚽',
|
||||
'color' : Color(0xFF6D4C41),
|
||||
'colorFondo': Color(0xFFEFEBE9),
|
||||
'descripcion': 'Residuos que por higiene no pueden mezclarse con reciclables.',
|
||||
'ejemplos': [
|
||||
'🧻 Papel higiénico usado',
|
||||
'👶 Pañales desechables',
|
||||
'🩹 Curitas y vendas usadas',
|
||||
'😷 Cubrebocas desechables',
|
||||
'🧼 Toallas húmedas y servilletas',
|
||||
],
|
||||
'noIncluir': [
|
||||
'🚫 Medicamentos (van en especiales)',
|
||||
'🚫 Jeringas o material médico cortante',
|
||||
],
|
||||
'consejo': 'Deposítalos en bolsa NEGRA bien cerrada. No mezclar con reciclables.',
|
||||
},
|
||||
{
|
||||
'nombre' : 'Especiales',
|
||||
'icono' : '⚠️',
|
||||
'color' : Color(0xFFE65100),
|
||||
'colorFondo': Color(0xFFFFF3E0),
|
||||
'descripcion': 'Residuos peligrosos que requieren manejo especial.',
|
||||
'ejemplos': [
|
||||
'🔋 Pilas y baterías',
|
||||
'💊 Medicamentos caducados',
|
||||
'💡 Focos y luminarias',
|
||||
'📱 Electrónicos en desuso',
|
||||
'🛢️ Aceite vegetal o de motor usado',
|
||||
],
|
||||
'noIncluir': [
|
||||
'🚫 NO van en el camión regular',
|
||||
'🚫 NO tirar a la basura común',
|
||||
],
|
||||
'consejo': '⚠️ Llévalos al punto ECORED más cercano. ¡Nunca al camión recolector!',
|
||||
},
|
||||
];
|
||||
|
||||
// ════════════════════════════════════════════════════════════════
|
||||
// PANTALLA GUÍA DE SEPARACIÓN
|
||||
// ════════════════════════════════════════════════════════════════
|
||||
class GuiaSeparacionScreen extends StatefulWidget {
|
||||
const GuiaSeparacionScreen({super.key});
|
||||
|
||||
@override
|
||||
State<GuiaSeparacionScreen> createState() => _GuiaSeparacionScreenState();
|
||||
}
|
||||
|
||||
class _GuiaSeparacionScreenState extends State<GuiaSeparacionScreen> {
|
||||
|
||||
// Controla qué categoría está expandida (-1 = ninguna)
|
||||
int _expandida = -1;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey[100],
|
||||
|
||||
appBar: AppBar(
|
||||
title: const Text('Guía de Separación'),
|
||||
backgroundColor: const Color(0xFF2E7D32),
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
|
||||
// ── ENCABEZADO ────────────────────────────────────
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF2E7D32),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: const Row(
|
||||
children: [
|
||||
Text('♻️', style: TextStyle(fontSize: 36)),
|
||||
SizedBox(width: 14),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Separa correctamente',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Text(
|
||||
'Toca cada categoría para ver qué residuos incluye. Funciona sin conexión.',
|
||||
style: TextStyle(
|
||||
color: Colors.white70,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// ── CATEGORÍAS ────────────────────────────────────
|
||||
..._categorias.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final categoria = entry.value;
|
||||
final expandida = _expandida == index;
|
||||
final color = categoria['color'] as Color;
|
||||
final colorFondo = categoria['colorFondo'] as Color;
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: color.withValues(alpha: 0.15),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 3),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
|
||||
// ── Header de categoría (siempre visible) ──
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_expandida = expandida ? -1 : index;
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16, vertical: 14),
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
borderRadius: expandida
|
||||
? const BorderRadius.vertical(
|
||||
top: Radius.circular(16))
|
||||
: BorderRadius.circular(16),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
categoria['icono'] as String,
|
||||
style: const TextStyle(fontSize: 28),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
categoria['nombre'] as String,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
categoria['descripcion'] as String,
|
||||
style: const TextStyle(
|
||||
color: Colors.white70,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
expandida
|
||||
? Icons.keyboard_arrow_up
|
||||
: Icons.keyboard_arrow_down,
|
||||
color: Colors.white,
|
||||
size: 28,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// ── Contenido expandible ──
|
||||
if (expandida)
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: colorFondo,
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
bottom: Radius.circular(16),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
|
||||
// Sí incluir
|
||||
Text(
|
||||
'✅ Sí incluir:',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
...(categoria['ejemplos'] as List<String>).map(
|
||||
(e) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 6),
|
||||
child: Text(
|
||||
e,
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// No incluir
|
||||
Text(
|
||||
'🚫 No incluir:',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
...(categoria['noIncluir'] as List<String>).map(
|
||||
(e) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 6),
|
||||
child: Text(
|
||||
e,
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Consejo
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(
|
||||
color: color.withValues(alpha: 0.3),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text('💡', style: TextStyle(fontSize: 16)),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
categoria['consejo'] as String,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: color,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// ── PIE DE PÁGINA ─────────────────────────────────
|
||||
Container(
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const Row(
|
||||
children: [
|
||||
Icon(Icons.wifi_off, color: Colors.grey, size: 18),
|
||||
SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Esta guía funciona completamente sin conexión a internet.',
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
636
lib/login.dart
636
lib/login.dart
@@ -11,18 +11,13 @@ class LoginScreen extends StatefulWidget {
|
||||
class _LoginScreenState extends State<LoginScreen> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
|
||||
final TextEditingController emailController =
|
||||
TextEditingController();
|
||||
|
||||
final TextEditingController passwordController =
|
||||
TextEditingController();
|
||||
final TextEditingController emailController = TextEditingController();
|
||||
final TextEditingController passwordController = TextEditingController();
|
||||
|
||||
bool ocultarPassword = true;
|
||||
|
||||
void iniciarSesion() {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
|
||||
// Simulación de login exitoso
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Inicio de sesión exitoso'),
|
||||
@@ -30,12 +25,10 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||
),
|
||||
);
|
||||
|
||||
// Navegar a la pantalla principal
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
const GestionDomiciliosScreen(),
|
||||
builder: (context) => const GestionDomiciliosScreen(),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -44,190 +37,455 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
body: Stack(
|
||||
children: [
|
||||
// ── Fondo azul cielo ──
|
||||
Container(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
color: const Color(0xFF5BB8E8),
|
||||
),
|
||||
|
||||
body: Center(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(25),
|
||||
// ── Barras de colores en la parte superior ──
|
||||
Column(
|
||||
children: [
|
||||
Container(height: 10, color: const Color(0xFFE8534A)),
|
||||
Container(height: 8, color: const Color(0xFFF5A623)),
|
||||
Container(height: 8, color: const Color(0xFFFFD700)),
|
||||
Container(height: 8, color: const Color(0xFF4CAF50)),
|
||||
],
|
||||
),
|
||||
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
|
||||
children: [
|
||||
|
||||
// Logo
|
||||
CircleAvatar(
|
||||
radius: 50,
|
||||
backgroundColor: Colors.green.shade100,
|
||||
|
||||
child: Icon(
|
||||
Icons.recycling,
|
||||
size: 60,
|
||||
color: Colors.green.shade700,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
const Text(
|
||||
'EcoRecolección',
|
||||
style: TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 10),
|
||||
|
||||
Text(
|
||||
'Inicia sesión para continuar',
|
||||
style: TextStyle(
|
||||
color: Colors.grey.shade600,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 40),
|
||||
|
||||
// Campo correo
|
||||
TextFormField(
|
||||
controller: emailController,
|
||||
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Correo Electrónico',
|
||||
hintText: 'ejemplo@correo.com',
|
||||
|
||||
prefixIcon: const Icon(Icons.email),
|
||||
|
||||
border: OutlineInputBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(15),
|
||||
),
|
||||
),
|
||||
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Ingresa tu correo';
|
||||
}
|
||||
|
||||
if (!value.contains('@')) {
|
||||
return 'Correo inválido';
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Campo contraseña
|
||||
TextFormField(
|
||||
controller: passwordController,
|
||||
obscureText: ocultarPassword,
|
||||
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Contraseña',
|
||||
|
||||
prefixIcon:
|
||||
const Icon(Icons.lock),
|
||||
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(
|
||||
ocultarPassword
|
||||
? Icons.visibility_off
|
||||
: Icons.visibility,
|
||||
),
|
||||
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
ocultarPassword =
|
||||
!ocultarPassword;
|
||||
});
|
||||
},
|
||||
),
|
||||
|
||||
border: OutlineInputBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(15),
|
||||
),
|
||||
),
|
||||
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Ingresa tu contraseña';
|
||||
}
|
||||
|
||||
if (value.length < 6) {
|
||||
return 'Mínimo 6 caracteres';
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(height: 30),
|
||||
|
||||
// Botón iniciar sesión
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 55,
|
||||
|
||||
child: ElevatedButton(
|
||||
onPressed: iniciarSesion,
|
||||
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.green,
|
||||
foregroundColor: Colors.white,
|
||||
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(15),
|
||||
),
|
||||
),
|
||||
|
||||
child: const Text(
|
||||
'Iniciar Sesión',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Registro
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.center,
|
||||
|
||||
children: [
|
||||
const Text(
|
||||
'¿No tienes cuenta?',
|
||||
),
|
||||
|
||||
TextButton(
|
||||
onPressed: () {},
|
||||
|
||||
child: const Text(
|
||||
'Registrarse',
|
||||
style: TextStyle(
|
||||
color: Colors.green,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
// ── Forma orgánica azul oscuro detrás del contenido ──
|
||||
Positioned(
|
||||
top: 60,
|
||||
left: -40,
|
||||
right: -40,
|
||||
child: Container(
|
||||
height: 420,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF4AA8D8).withValues(alpha:0.6),
|
||||
borderRadius: BorderRadius.circular(200),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// ── Montañas verdes atrás ──
|
||||
Positioned(
|
||||
bottom: 80,
|
||||
right: -10,
|
||||
child: CustomPaint(
|
||||
size: const Size(160, 130),
|
||||
painter: _MountainPainter(color: const Color(0xFF2E7D32)),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 80,
|
||||
right: 60,
|
||||
child: CustomPaint(
|
||||
size: const Size(120, 100),
|
||||
painter: _MountainPainter(color: const Color(0xFF388E3C)),
|
||||
),
|
||||
),
|
||||
|
||||
// ── Suelo verde ──
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
height: 90,
|
||||
color: const Color(0xFF4CAF50),
|
||||
),
|
||||
),
|
||||
|
||||
// ── Carretera ──
|
||||
Positioned(
|
||||
bottom: 30,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
height: 18,
|
||||
color: const Color(0xFF424242),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: List.generate(
|
||||
12,
|
||||
(_) => Container(
|
||||
width: 20,
|
||||
height: 3,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// ── Casas pequeñas ──
|
||||
Positioned(
|
||||
bottom: 45,
|
||||
left: 20,
|
||||
child: _buildHouse(const Color(0xFF90A4AE), 40),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 45,
|
||||
left: 80,
|
||||
child: _buildHouse(const Color(0xFF795548), 35),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 45,
|
||||
right: 20,
|
||||
child: _buildHouse(const Color(0xFFFFCC80), 45),
|
||||
),
|
||||
|
||||
// ── Camiones de basura ──
|
||||
Positioned(
|
||||
bottom: 33,
|
||||
left: 110,
|
||||
child: _buildTruck(const Color(0xFFE53935)),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 33,
|
||||
right: 80,
|
||||
child: _buildTruck(const Color(0xFFE53935)),
|
||||
),
|
||||
|
||||
// ── Basura (montón gris izquierda) ──
|
||||
Positioned(
|
||||
bottom: 90,
|
||||
left: 10,
|
||||
child: CustomPaint(
|
||||
size: const Size(60, 35),
|
||||
painter: _TrashPilePainter(),
|
||||
),
|
||||
),
|
||||
|
||||
// ── Contenido principal ──
|
||||
SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 30),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 50),
|
||||
|
||||
// Título ECOUBICEL
|
||||
const Text(
|
||||
'ECOUBICEL.',
|
||||
style: TextStyle(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Colors.black87,
|
||||
letterSpacing: 2,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Botes de basura
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
_buildTrashCan(const Color(0xFF2E7D32), Icons.recycling),
|
||||
const SizedBox(width: 8),
|
||||
_buildTrashCan(const Color(0xFFF9A825), Icons.recycling),
|
||||
const SizedBox(width: 8),
|
||||
_buildTrashCan(const Color(0xFFC62828), Icons.recycling),
|
||||
const SizedBox(width: 12),
|
||||
// Ícono de reciclaje
|
||||
Icon(
|
||||
Icons.recycling,
|
||||
size: 48,
|
||||
color: Colors.green.shade600,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 28),
|
||||
|
||||
// Campo correo — barra verde
|
||||
_buildColorField(
|
||||
controller: emailController,
|
||||
hint: 'Correo Electrónico',
|
||||
icon: Icons.email,
|
||||
color: const Color(0xFF4CAF50),
|
||||
obscure: false,
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) return 'Ingresa tu correo';
|
||||
if (!value.contains('@')) return 'Correo inválido';
|
||||
return null;
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Campo contraseña — barra azul
|
||||
_buildColorField(
|
||||
controller: passwordController,
|
||||
hint: 'Contraseña',
|
||||
icon: Icons.lock,
|
||||
color: const Color(0xFF1E88E5),
|
||||
obscure: ocultarPassword,
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(
|
||||
ocultarPassword ? Icons.visibility_off : Icons.visibility,
|
||||
color: Colors.white70,
|
||||
),
|
||||
onPressed: () => setState(() => ocultarPassword = !ocultarPassword),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) return 'Ingresa tu contraseña';
|
||||
if (value.length < 6) return 'Mínimo 6 caracteres';
|
||||
return null;
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Botón iniciar sesión — barra naranja
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 48,
|
||||
child: ElevatedButton(
|
||||
onPressed: iniciarSesion,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFFE65100),
|
||||
foregroundColor: Colors.white,
|
||||
shape: const StadiumBorder(),
|
||||
elevation: 3,
|
||||
),
|
||||
child: const Text(
|
||||
'Iniciar Sesión',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Botón registrarse — barra gris
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 48,
|
||||
child: OutlinedButton(
|
||||
onPressed: () {},
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: Colors.white,
|
||||
side: const BorderSide(color: Colors.white54, width: 2),
|
||||
backgroundColor: Colors.white24,
|
||||
shape: const StadiumBorder(),
|
||||
),
|
||||
child: const Text(
|
||||
'¿No tienes cuenta? Registrarse',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 120),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Widget bote de basura
|
||||
Widget _buildTrashCan(Color color, IconData icon) {
|
||||
return Container(
|
||||
width: 52,
|
||||
height: 64,
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
boxShadow: [
|
||||
BoxShadow(color: Colors.black26, blurRadius: 4, offset: const Offset(2, 2)),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
height: 8,
|
||||
margin: const EdgeInsets.symmetric(horizontal: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withValues(alpha:0.7),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Icon(icon, color: Colors.white, size: 28),
|
||||
Container(
|
||||
height: 6,
|
||||
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black26,
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Campo de texto con fondo de color tipo barra
|
||||
Widget _buildColorField({
|
||||
required TextEditingController controller,
|
||||
required String hint,
|
||||
required IconData icon,
|
||||
required Color color,
|
||||
required bool obscure,
|
||||
Widget? suffixIcon,
|
||||
required String? Function(String?) validator,
|
||||
}) {
|
||||
return TextFormField(
|
||||
controller: controller,
|
||||
obscureText: obscure,
|
||||
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w600),
|
||||
decoration: InputDecoration(
|
||||
hintText: hint,
|
||||
hintStyle: const TextStyle(color: Colors.white70),
|
||||
prefixIcon: Icon(icon, color: Colors.white),
|
||||
suffixIcon: suffixIcon,
|
||||
filled: true,
|
||||
fillColor: color,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
errorStyle: const TextStyle(color: Colors.yellow),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14),
|
||||
),
|
||||
validator: validator,
|
||||
);
|
||||
}
|
||||
|
||||
// Casa pequeña
|
||||
Widget _buildHouse(Color color, double size) {
|
||||
return CustomPaint(
|
||||
size: Size(size, size * 0.9),
|
||||
painter: _HousePainter(color: color),
|
||||
);
|
||||
}
|
||||
|
||||
// Camión pequeño
|
||||
Widget _buildTruck(Color color) {
|
||||
return Container(
|
||||
width: 36,
|
||||
height: 18,
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Container(
|
||||
width: 12,
|
||||
height: 14,
|
||||
margin: const EdgeInsets.only(right: 2, top: 0),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withValues(alpha:0.7),
|
||||
borderRadius: const BorderRadius.only(
|
||||
topRight: Radius.circular(3),
|
||||
topLeft: Radius.circular(2),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Pintor de montañas
|
||||
class _MountainPainter extends CustomPainter {
|
||||
final Color color;
|
||||
_MountainPainter({required this.color});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint()..color = color;
|
||||
final path = Path()
|
||||
..moveTo(0, size.height)
|
||||
..lineTo(size.width / 2, 0)
|
||||
..lineTo(size.width, size.height)
|
||||
..close();
|
||||
canvas.drawPath(path, paint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_) => false;
|
||||
}
|
||||
|
||||
// Pintor de casa
|
||||
class _HousePainter extends CustomPainter {
|
||||
final Color color;
|
||||
_HousePainter({required this.color});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint()..color = color;
|
||||
// Cuerpo
|
||||
canvas.drawRect(
|
||||
Rect.fromLTWH(0, size.height * 0.4, size.width, size.height * 0.6),
|
||||
paint,
|
||||
);
|
||||
// Techo
|
||||
final roof = Paint()..color = const Color(0xFFB71C1C);
|
||||
final path = Path()
|
||||
..moveTo(0, size.height * 0.4)
|
||||
..lineTo(size.width / 2, 0)
|
||||
..lineTo(size.width, size.height * 0.4)
|
||||
..close();
|
||||
canvas.drawPath(path, roof);
|
||||
// Ventana
|
||||
canvas.drawRect(
|
||||
Rect.fromLTWH(size.width * 0.25, size.height * 0.55, size.width * 0.2, size.height * 0.2),
|
||||
Paint()..color = const Color(0xFFB3E5FC),
|
||||
);
|
||||
// Puerta
|
||||
canvas.drawRect(
|
||||
Rect.fromLTWH(size.width * 0.55, size.height * 0.65, size.width * 0.2, size.height * 0.35),
|
||||
Paint()..color = const Color(0xFF5D4037),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_) => false;
|
||||
}
|
||||
|
||||
// Pintor de montón de basura
|
||||
class _TrashPilePainter extends CustomPainter {
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint()..color = const Color(0xFF757575);
|
||||
canvas.drawOval(
|
||||
Rect.fromLTWH(0, size.height * 0.3, size.width, size.height * 0.7),
|
||||
paint,
|
||||
);
|
||||
canvas.drawOval(
|
||||
Rect.fromLTWH(size.width * 0.1, 0, size.width * 0.5, size.height * 0.6),
|
||||
Paint()..color = const Color(0xFF616161),
|
||||
);
|
||||
canvas.drawOval(
|
||||
Rect.fromLTWH(size.width * 0.5, size.height * 0.1, size.width * 0.4, size.height * 0.5),
|
||||
Paint()..color = const Color(0xFF9E9E9E),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_) => false;
|
||||
}
|
||||
398
lib/ruta_exclusiva.dart
Normal file
398
lib/ruta_exclusiva.dart
Normal file
@@ -0,0 +1,398 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'camion_estado.dart';
|
||||
|
||||
// ─── DATOS DE COLONIAS (de tu colonias-rutas.json) ──────────────
|
||||
const List<Map<String, String>> _coloniasRutas = [
|
||||
{'colonia': 'Zona Centro', 'routeId': 'RUTA-01', 'horario': 'Matutino (06:30 - 07:15)'},
|
||||
{'colonia': 'Las Arboledas', 'routeId': 'RUTA-01', 'horario': 'Matutino (07:00 - 07:30)'},
|
||||
{'colonia': 'Trojes', 'routeId': 'RUTA-13', 'horario': 'Matutino (06:40 - 07:10)'},
|
||||
{'colonia': 'San Juanico', 'routeId': 'RUTA-03', 'horario': 'Matutino (06:45 - 07:15)'},
|
||||
{'colonia': 'Los Olivos', 'routeId': 'RUTA-04', 'horario': 'Matutino (07:00 - 07:40)'},
|
||||
{'colonia': 'Rancho Seco', 'routeId': 'RUTA-05', 'horario': 'Vespertino (14:15 - 15:00)'},
|
||||
{'colonia': 'Las Insurgentes', 'routeId': 'RUTA-12', 'horario': 'Matutino (06:35 - 07:10)'},
|
||||
];
|
||||
|
||||
// ════════════════════════════════════════════════════════════════
|
||||
// PANTALLA RUTA EXCLUSIVA
|
||||
// ════════════════════════════════════════════════════════════════
|
||||
class RutaExclusivaScreen extends StatefulWidget {
|
||||
final String direccionUsuario; // ← viene desde domicilios.dart
|
||||
final String coloniaUsuario; // ← viene desde domicilios.dart
|
||||
|
||||
const RutaExclusivaScreen({
|
||||
super.key,
|
||||
required this.direccionUsuario,
|
||||
required this.coloniaUsuario,
|
||||
});
|
||||
|
||||
@override
|
||||
State<RutaExclusivaScreen> createState() => _RutaExclusivaScreenState();
|
||||
}
|
||||
|
||||
class _RutaExclusivaScreenState extends State<RutaExclusivaScreen> {
|
||||
|
||||
Map<String, String>? _rutaAsignada; // solo la ruta del usuario
|
||||
bool _accesoValidado = false;
|
||||
bool _cargando = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
camionEstado.addListener(_actualizar);
|
||||
_validarAcceso();
|
||||
}
|
||||
|
||||
void _actualizar() {
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
camionEstado.removeListener(_actualizar);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// ── Valida que la colonia del usuario exista en las rutas ────
|
||||
void _validarAcceso() async {
|
||||
await Future.delayed(const Duration(milliseconds: 800)); // simula carga
|
||||
|
||||
final coloniaLower = widget.coloniaUsuario.trim().toLowerCase();
|
||||
|
||||
final encontrada = _coloniasRutas.firstWhere(
|
||||
(r) => r['colonia']!.toLowerCase() == coloniaLower,
|
||||
orElse: () => {},
|
||||
);
|
||||
|
||||
setState(() {
|
||||
_cargando = false;
|
||||
if (encontrada.isNotEmpty) {
|
||||
_rutaAsignada = encontrada;
|
||||
_accesoValidado = true;
|
||||
} else {
|
||||
_accesoValidado = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ── Color según etapa del camión ─────────────────────────────
|
||||
Color _colorEtapa() {
|
||||
final id = camionEstado.positionId;
|
||||
if (id <= 1) return Colors.grey;
|
||||
if (id <= 3) return Colors.blue;
|
||||
if (id <= 5) return Colors.orange;
|
||||
if (id == 6) return Colors.green;
|
||||
return Colors.grey;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey[100],
|
||||
|
||||
appBar: AppBar(
|
||||
title: const Text('Mi Ruta Asignada'),
|
||||
backgroundColor: Colors.green,
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
|
||||
body: _cargando
|
||||
? _buildCargando()
|
||||
: _accesoValidado
|
||||
? _buildRutaValidada()
|
||||
: _buildSinAcceso(),
|
||||
);
|
||||
}
|
||||
|
||||
// ── Pantalla de carga ────────────────────────────────────────
|
||||
Widget _buildCargando() {
|
||||
return const Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
CircularProgressIndicator(color: Colors.green),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
'Validando tu dirección...',
|
||||
style: TextStyle(color: Colors.grey, fontSize: 14),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ── Sin acceso: colonia no registrada ────────────────────────
|
||||
Widget _buildSinAcceso() {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
|
||||
Icon(Icons.lock_outline, size: 80, color: Colors.red[300]),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
const Text(
|
||||
'Dirección no reconocida',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
Text(
|
||||
'Tu colonia "${widget.coloniaUsuario}" no está registrada en el sistema de rutas. Actualiza tu domicilio o contacta al municipio.',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 14, color: Colors.grey[600]),
|
||||
),
|
||||
|
||||
const SizedBox(height: 28),
|
||||
|
||||
ElevatedButton.icon(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.green,
|
||||
foregroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
label: const Text('Actualizar domicilio'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ── Ruta validada: solo info de SU ruta ──────────────────────
|
||||
Widget _buildRutaValidada() {
|
||||
final ruta = _rutaAsignada!;
|
||||
final etapa = CamionEstado.etapas[camionEstado.positionId] ?? '';
|
||||
final etaInfo = CamionEstado.etaInfo[camionEstado.positionId]!;
|
||||
final color = _colorEtapa();
|
||||
final minutos = etaInfo['minutos'] as int;
|
||||
|
||||
return ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
|
||||
// ── BADGE DE ACCESO VALIDADO ──────────────────────
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green[50],
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: Colors.green),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.verified_user, color: Colors.green),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Acceso validado para ${widget.coloniaUsuario}. Solo ves la información de tu ruta.',
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
color: Colors.green,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// ── CARD: DATOS DE LA RUTA ────────────────────────
|
||||
Card(
|
||||
elevation: 4,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.route, color: Colors.green, size: 28),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
ruta['routeId']!,
|
||||
style: const TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.green,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
_filaInfo(Icons.location_on, 'Colonia', ruta['colonia']!),
|
||||
_filaInfo(Icons.schedule, 'Horario', ruta['horario']!),
|
||||
_filaInfo(Icons.home, 'Dirección', widget.direccionUsuario),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// ── CARD: ESTADO EN TIEMPO REAL ───────────────────
|
||||
Card(
|
||||
elevation: 4,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
|
||||
const Row(
|
||||
children: [
|
||||
Icon(Icons.local_shipping, color: Colors.green, size: 28),
|
||||
SizedBox(width: 10),
|
||||
Text(
|
||||
'Estado en tiempo real',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Barra de progreso
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: LinearProgressIndicator(
|
||||
value: camionEstado.positionId / 8,
|
||||
minHeight: 12,
|
||||
backgroundColor: Colors.grey[200],
|
||||
valueColor: AlwaysStoppedAnimation<Color>(color),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Etapa actual
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withValues(alpha:0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: color.withValues(alpha:0.3)),
|
||||
),
|
||||
child: Text(
|
||||
etapa,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// ETA
|
||||
if (minutos > 0)
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green[50],
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.timer, color: Colors.green),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
'Llega en aproximadamente $minutos minutos',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.green,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// ── AVISO PRIVACIDAD ──────────────────────────────
|
||||
Container(
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue[50],
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: Colors.blue.shade200),
|
||||
),
|
||||
child: const Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(Icons.shield, color: Colors.blue),
|
||||
SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Visión de túnel activa: No puedes ver rutas, horarios ni ubicaciones de otras colonias.',
|
||||
style: TextStyle(fontSize: 13, color: Colors.blue),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// ── Widget auxiliar para filas de info ───────────────────────
|
||||
Widget _filaInfo(IconData icono, String label, String valor) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(icono, color: Colors.green, size: 20),
|
||||
const SizedBox(width: 10),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey[500]),
|
||||
),
|
||||
Text(
|
||||
valor,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user