Actualizacion del programa
This commit is contained in:
253
lib/screens/citizen/collection_calendar_screen.dart
Normal file
253
lib/screens/citizen/collection_calendar_screen.dart
Normal file
@@ -0,0 +1,253 @@
|
||||
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)),
|
||||
]);
|
||||
}
|
||||
Reference in New Issue
Block a user