Files
AppRecoleccion/lib/screens/citizen/collection_calendar_screen.dart
2026-05-23 09:31:45 -06:00

254 lines
11 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:share_plus/share_plus.dart';
import '../../core/app_colors.dart';
import '../../database/db_helper.dart';
import '../../models/models.dart';
import '../../services/auth_service.dart';
class CollectionCalendarScreen extends StatefulWidget {
const CollectionCalendarScreen({super.key});
@override State<CollectionCalendarScreen> createState() => _CollectionCalendarScreenState();
}
class _CollectionCalendarScreenState extends State<CollectionCalendarScreen> {
RouteDefinitionModel? _routeDef;
List<ReviewModel> _myReviews = [];
bool _loading = true;
@override
void initState() { super.initState(); _load(); }
Future<void> _load() async {
final auth = context.read<AuthService>();
final dom = auth.primaryDomicilio;
if (dom != null) {
final rd = await DbHelper.getRouteDefinitionById(dom.routeId);
final rv = await DbHelper.getAllReviews();
final mine = rv.where((r) => r.userId == auth.currentUser?.id).toList();
if (mounted) setState(() { _routeDef = rd; _myReviews = mine; _loading = false; });
} else {
if (mounted) setState(() => _loading = false);
}
}
void _shareSchedule() {
final auth = context.read<AuthService>();
final dom = auth.primaryDomicilio;
if (dom == null) return;
final rd = _routeDef;
final diasStr = rd?.dias.map(_diaLabel).join(', ') ?? 'Lunes, Miércoles y Viernes';
final horario = rd != null ? '${rd.horaInicio}${rd.horaFin}' : dom.horarioEstimado;
Share.share(
' Horario de recolección de basura\n'
' Colonia: ${dom.colonia}\n'
' Días: $diasStr\n'
' Horario: $horario\n'
' Ruta: ${dom.routeId}\n\n'
'Descarga Celaya Limpia para recibir avisos en tiempo real.',
);
}
String _diaLabel(String d) {
const m = {'LUNES':'Lu','MARTES':'Ma','MIERCOLES':'Mi',
'JUEVES':'Ju','VIERNES':'Vi','SABADO':'Sa','DOMINGO':'Do'};
return m[d] ?? d;
}
// Días del mes actual con marcas de recolección
List<Widget> _buildCalendar() {
final now = DateTime.now();
final first = DateTime(now.year, now.month, 1);
final days = DateTime(now.year, now.month + 1, 0).day;
final dias = _routeDef?.dias ?? [];
const weekDays = ['LUNES','MARTES','MIERCOLES','JUEVES','VIERNES','SABADO','DOMINGO'];
final monthName = ['','Enero','Febrero','Marzo','Abril','Mayo','Junio',
'Julio','Agosto','Septiembre','Octubre','Noviembre','Diciembre'][now.month];
return [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Text('$monthName ${now.year}',
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16,
color: AppColors.guindaPrimary)),
),
const SizedBox(height: 8),
// Cabeceras días
Row(children: ['Lu','Ma','Mi','Ju','Vi','Sa','Do'].map((d) =>
Expanded(child: Center(child: Text(d, style: const TextStyle(
fontWeight: FontWeight.bold, fontSize: 11, color: AppColors.grisTexto))))).toList()),
const SizedBox(height: 4),
// Grilla de días
GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 7, childAspectRatio: 1),
itemCount: (first.weekday - 1) + days,
itemBuilder: (_, i) {
if (i < first.weekday - 1) return const SizedBox();
final day = i - (first.weekday - 1) + 1;
final date = DateTime(now.year, now.month, day);
final diaSem = weekDays[date.weekday - 1];
final isCollection = dias.contains(diaSem);
final isToday = day == now.day;
final isPast = date.isBefore(DateTime(now.year, now.month, now.day));
return Container(
margin: const EdgeInsets.all(2),
decoration: BoxDecoration(
color: isCollection
? (isPast ? AppColors.guindaPrimary.withOpacity(0.4) : AppColors.guindaPrimary)
: (isToday ? Colors.grey.shade200 : null),
shape: BoxShape.circle,
border: isToday ? Border.all(color: AppColors.dorado, width: 2) : null,
),
child: Stack(alignment: Alignment.center, children: [
Text('$day', style: TextStyle(
fontSize: 12,
fontWeight: isToday ? FontWeight.bold : FontWeight.normal,
color: isCollection ? Colors.white : AppColors.negroTexto,
)),
if (isCollection)
Positioned(bottom: 2, child: Container(
width: 4, height: 4,
decoration: const BoxDecoration(color: AppColors.dorado, shape: BoxShape.circle),
)),
]),
);
},
),
];
}
@override
Widget build(BuildContext context) {
final auth = context.read<AuthService>();
final dom = auth.primaryDomicilio;
return Scaffold(
backgroundColor: AppColors.grisFondo,
appBar: AppBar(
backgroundColor: AppColors.guindaPrimary, foregroundColor: Colors.white,
title: const Text('Calendario de Recoleccion'),
bottom: PreferredSize(preferredSize: const Size.fromHeight(4),
child: Container(height: 4, color: AppColors.dorado)),
actions: [
IconButton(icon: const Icon(Icons.share), tooltip: 'Compartir horario',
onPressed: _shareSchedule),
],
),
body: _loading
? const Center(child: CircularProgressIndicator())
: SingleChildScrollView(padding: const EdgeInsets.all(16), child: Column(
crossAxisAlignment: CrossAxisAlignment.start, children: [
// Info de la ruta
if (dom != null)
Card(child: Padding(padding: const EdgeInsets.all(14), child: Column(
crossAxisAlignment: CrossAxisAlignment.start, children: [
const Row(children: [
Icon(Icons.local_shipping, color: AppColors.guindaPrimary, size: 18),
SizedBox(width: 6),
Text('Tu servicio de recoleccion', style: TextStyle(
fontWeight: FontWeight.bold, color: AppColors.guindaPrimary)),
]),
const Divider(),
Text('Colonia: ${dom.colonia}',
style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 13)),
Text('Ruta: ${dom.routeId}',
style: const TextStyle(color: AppColors.grisTexto, fontSize: 12)),
if (_routeDef != null) ...[
Text('Dias: ${_routeDef!.dias.map(_diaLabel).join(" · ")}',
style: const TextStyle(fontSize: 12)),
Text('Horario: ${_routeDef!.horaInicio} - ${_routeDef!.horaFin}',
style: const TextStyle(fontSize: 12)),
] else
Text(dom.horarioEstimado,
style: const TextStyle(color: AppColors.grisTexto, fontSize: 12)),
]))),
const SizedBox(height: 16),
// Leyenda
Row(children: [
_Legend(color: AppColors.guindaPrimary, label: 'Dia de recoleccion'),
const SizedBox(width: 12),
_Legend(color: AppColors.dorado, label: 'Punto en dia activo'),
const SizedBox(width: 12),
_Legend(color: Colors.grey.shade200, label: 'Hoy'),
]),
const SizedBox(height: 12),
// Calendario
Card(child: Padding(padding: const EdgeInsets.all(14), child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildCalendar()))),
const SizedBox(height: 16),
// Consejos semanales
Card(color: Colors.blue.shade50,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10),
side: BorderSide(color: Colors.blue.shade200)),
child: Padding(padding: const EdgeInsets.all(14), child: Column(
crossAxisAlignment: CrossAxisAlignment.start, children: [
const Row(children: [
Icon(Icons.tips_and_updates, color: AppColors.azulInfo),
SizedBox(width: 8),
Text('Consejo de la semana', style: TextStyle(
fontWeight: FontWeight.bold, color: AppColors.azulInfo, fontSize: 14)),
]),
const SizedBox(height: 8),
Text(_weeklyTip(), style: const TextStyle(fontSize: 13, color: AppColors.negroTexto)),
])),
),
const SizedBox(height: 16),
// Mis calificaciones
if (_myReviews.isNotEmpty) ...[
const Text('Mis calificaciones', style: TextStyle(
fontWeight: FontWeight.bold, fontSize: 15, color: AppColors.guindaPrimary)),
const SizedBox(height: 8),
..._myReviews.take(3).map((r) => Card(margin: const EdgeInsets.only(bottom: 8),
child: ListTile(dense: true,
leading: CircleAvatar(backgroundColor: Colors.amber.shade100,
child: Text('${r.estrellas}', style: const TextStyle(
fontWeight: FontWeight.bold, color: Colors.amber))),
title: Text(r.colonia, style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600)),
subtitle: Text(r.comentario, maxLines: 1, overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 11)),
trailing: Text(
'${DateTime.tryParse(r.fecha)?.day}/${DateTime.tryParse(r.fecha)?.month}',
style: const TextStyle(fontSize: 10, color: AppColors.grisTexto)),
))),
],
const SizedBox(height: 30),
])),
);
}
String _weeklyTip() {
final tips = [
'Separa tus residuos en organicos (restos de comida) e inorganicos (plasticos, metales). Facilita el reciclaje y reduce la contaminacion.',
'Coloca tus bolsas en la acera SOLO cuando recibas el aviso de 15 minutos. Sacarlas antes atrae fauna nociva.',
'El reciclaje de 1 tonelada de papel salva 17 arboles. Dobla tus cajas y periodicos antes de depositarlos.',
'Los aceites usados de cocina NO van a la basura. Llevalos a los puntos de acopio del municipio.',
'Composta tus restos organicos si tienes jardin. Reduce hasta un 40% tu basura y mejora tu suelo.',
'Las pilas y baterias son residuos peligrosos. Depositalas en los contenedores especiales de tiendas.',
'Un celular viejo contiene oro, plata y cobre. Llevalo a un punto RAEE para su reciclaje correcto.',
];
return tips[DateTime.now().weekday % tips.length];
}
}
class _Legend extends StatelessWidget {
final Color color; final String label;
const _Legend({required this.color, required this.label});
@override
Widget build(BuildContext context) => Row(mainAxisSize: MainAxisSize.min, children: [
Container(width: 12, height: 12, decoration: BoxDecoration(color: color, shape: BoxShape.circle)),
const SizedBox(width: 4),
Text(label, style: const TextStyle(fontSize: 10, color: AppColors.grisTexto)),
]);
}