398 lines
13 KiB
Dart
398 lines
13 KiB
Dart
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,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
} |