Actualizacion del programa
This commit is contained in:
79
celaya_limpia/lib/services/auth_service.dart
Normal file
79
celaya_limpia/lib/services/auth_service.dart
Normal file
@@ -0,0 +1,79 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../models/models.dart';
|
||||
import '../database/db_helper.dart';
|
||||
|
||||
class AuthService extends ChangeNotifier {
|
||||
UserModel? _user;
|
||||
DomicilioModel? _primaryDomicilio;
|
||||
List<DomicilioModel> _allDomicilios = [];
|
||||
bool _loading = true;
|
||||
|
||||
UserModel? get currentUser => _user;
|
||||
DomicilioModel? get primaryDomicilio => _primaryDomicilio;
|
||||
List<DomicilioModel> get allDomicilios => _allDomicilios;
|
||||
bool get isLoggedIn => _user != null;
|
||||
bool get loading => _loading;
|
||||
String get rol => _user?.rol ?? '';
|
||||
|
||||
AuthService() { _checkSession(); }
|
||||
|
||||
Future<void> _checkSession() async {
|
||||
final p = await SharedPreferences.getInstance();
|
||||
final id = p.getInt('user_id');
|
||||
if (id != null) {
|
||||
_user = await DbHelper.getUserById(id);
|
||||
if (_user?.rol == 'CIUDADANO') await reloadDomicilios();
|
||||
}
|
||||
_loading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> reloadDomicilios() async {
|
||||
if (_user == null) return;
|
||||
_allDomicilios = await DbHelper.getDomiciliosByUser(_user!.id!);
|
||||
_primaryDomicilio = _allDomicilios.isNotEmpty
|
||||
? _allDomicilios.firstWhere((d) => d.isPrimary,
|
||||
orElse: () => _allDomicilios.first)
|
||||
: null;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<String?> login(String email, String password) async {
|
||||
final user = await DbHelper.getUserByEmail(email.trim().toLowerCase());
|
||||
if (user == null) return 'Correo no registrado';
|
||||
if (user.password != password) return 'Contraseña incorrecta';
|
||||
_user = user;
|
||||
if (user.rol == 'CIUDADANO') await reloadDomicilios();
|
||||
final p = await SharedPreferences.getInstance();
|
||||
await p.setInt('user_id', user.id!);
|
||||
notifyListeners();
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<String?> register({required String nombre, required String email,
|
||||
required String password, required String calle, required String colonia,
|
||||
required String routeId, required String horarioEstimado}) async {
|
||||
final ex = await DbHelper.getUserByEmail(email.trim().toLowerCase());
|
||||
if (ex != null) return 'Correo ya registrado';
|
||||
final user = UserModel(nombre:nombre.trim(),
|
||||
email:email.trim().toLowerCase(), password:password, rol:'CIUDADANO');
|
||||
final uid = await DbHelper.insertUser(user);
|
||||
await DbHelper.insertDomicilio(DomicilioModel(userId:uid, alias:'Casa',
|
||||
calle:calle.trim(), colonia:colonia, routeId:routeId,
|
||||
horarioEstimado:horarioEstimado, isPrimary:true));
|
||||
_user = await DbHelper.getUserById(uid);
|
||||
await reloadDomicilios();
|
||||
final p = await SharedPreferences.getInstance();
|
||||
await p.setInt('user_id', uid);
|
||||
notifyListeners();
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<void> logout() async {
|
||||
_user = null; _primaryDomicilio = null; _allDomicilios = [];
|
||||
final p = await SharedPreferences.getInstance();
|
||||
await p.remove('user_id');
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
237
celaya_limpia/lib/services/route_simulator_service.dart
Normal file
237
celaya_limpia/lib/services/route_simulator_service.dart
Normal file
@@ -0,0 +1,237 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import '../models/route_model.dart';
|
||||
import '../models/models.dart';
|
||||
import '../data/routes_data.dart';
|
||||
import '../database/db_helper.dart';
|
||||
|
||||
enum NotifEvent {
|
||||
routeStart, truckProximity, truckApproaching15min, routeCompleted,
|
||||
gpsLost, truckStopped, routeCancelled, reviewPrompt, none
|
||||
}
|
||||
|
||||
class AppNotification {
|
||||
final NotifEvent event;
|
||||
final String title;
|
||||
final String body;
|
||||
final String routeId;
|
||||
final DateTime timestamp;
|
||||
AppNotification({required this.event, required this.title,
|
||||
required this.body, required this.routeId})
|
||||
: timestamp = DateTime.now();
|
||||
}
|
||||
|
||||
class SimulatorState {
|
||||
final String routeId;
|
||||
int positionIndex;
|
||||
bool gpsActive;
|
||||
DateTime lastMoved;
|
||||
bool stoppedAlertSent;
|
||||
bool reviewPromptSent;
|
||||
SimulatorState({required this.routeId, this.positionIndex=0,
|
||||
this.gpsActive=true, required this.lastMoved,
|
||||
this.stoppedAlertSent=false, this.reviewPromptSent=false});
|
||||
}
|
||||
|
||||
class RouteSimulatorService extends ChangeNotifier {
|
||||
final Map<String, SimulatorState> _states = {};
|
||||
Timer? _globalTimer;
|
||||
Timer? _gpsMonitorTimer;
|
||||
|
||||
AppNotification? _lastNotification;
|
||||
final List<AppNotification> _history = [];
|
||||
|
||||
// ── Getters ──────────────────────────────────────────────────────────────
|
||||
AppNotification? get lastNotification => _lastNotification;
|
||||
|
||||
// Ciudadano/Conductor: solo su ruta
|
||||
AppNotification? getNotificationForRoute(String routeId) {
|
||||
if (_lastNotification?.routeId == routeId) return _lastNotification;
|
||||
return null;
|
||||
}
|
||||
|
||||
List<AppNotification> get history => List.unmodifiable(_history);
|
||||
List<AppNotification> historyForRoute(String routeId) =>
|
||||
_history.where((n) => n.routeId == routeId).toList();
|
||||
|
||||
bool needsReviewPrompt(String routeId) =>
|
||||
_states[routeId]?.reviewPromptSent == true;
|
||||
|
||||
// ── Inicio ───────────────────────────────────────────────────────────────
|
||||
void startAllRoutes() {
|
||||
for (final r in routesData) {
|
||||
_states[r.routeId] = SimulatorState(routeId:r.routeId, lastMoved:DateTime.now());
|
||||
}
|
||||
_globalTimer?.cancel();
|
||||
_globalTimer = Timer.periodic(const Duration(seconds:30), (_) => _tick());
|
||||
_gpsMonitorTimer?.cancel();
|
||||
_gpsMonitorTimer = Timer.periodic(const Duration(minutes:5), (_) => _monitorGps());
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void startRoute(String routeId) {
|
||||
_states[routeId] = SimulatorState(routeId:routeId, lastMoved:DateTime.now());
|
||||
_globalTimer ??= Timer.periodic(const Duration(seconds:30), (_) => _tick());
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void _tick() {
|
||||
bool changed = false;
|
||||
for (final entry in _states.entries) {
|
||||
final state = entry.value;
|
||||
final route = getRouteById(state.routeId);
|
||||
if (route == null || !state.gpsActive) continue;
|
||||
if (state.positionIndex < route.positions.length - 1) {
|
||||
state.positionIndex++;
|
||||
state.lastMoved = DateTime.now();
|
||||
_checkNotification(state, route);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (changed) notifyListeners();
|
||||
}
|
||||
|
||||
void _monitorGps() {
|
||||
for (final state in _states.values) {
|
||||
if (!state.gpsActive) continue;
|
||||
final diff = DateTime.now().difference(state.lastMoved);
|
||||
if (diff.inMinutes >= 30 && !state.stoppedAlertSent) {
|
||||
state.stoppedAlertSent = true;
|
||||
_fireAndSave(event:NotifEvent.truckStopped, routeId:state.routeId,
|
||||
title:'⚠️ Camión detenido',
|
||||
body:'El camión ${state.routeId} lleva +30 min sin moverse. Verifica.',
|
||||
tipo:'CAMION_DETENIDO');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _checkNotification(SimulatorState state, RouteModel route) {
|
||||
final idx = state.positionIndex;
|
||||
final total = route.positions.length;
|
||||
|
||||
if (idx == 1) {
|
||||
// Ruta iniciada
|
||||
_fireNotif(NotifEvent.routeStart, '¡Ruta Iniciada! 🚛',
|
||||
'El camión ha salido del Relleno Sanitario rumbo a tu sector. '
|
||||
'Prepara tus bolsas pero espera la señal para sacarlas.', state.routeId);
|
||||
} else if (idx == 2) {
|
||||
// ~30 min — aviso preventivo
|
||||
_fireNotif(NotifEvent.truckApproaching15min, '🕐 El camión se acerca',
|
||||
'Tu camión recolector está en camino. Tendrás otro aviso cuando esté a '
|
||||
'15 minutos. ⚠️ No saques la basura todavía — espera el aviso.', state.routeId);
|
||||
} else if (idx == 3) {
|
||||
// ~15 min — MOMENTO de sacar la basura
|
||||
_fireNotif(NotifEvent.truckProximity, '⚠️ ¡Saca tus bolsas AHORA!',
|
||||
'El camión llega en aprox. 15 minutos a tu colonia. '
|
||||
'Este es el momento de sacar tus bolsas a la acera. '
|
||||
'🚫 No persigas ni interceptes la unidad.', state.routeId);
|
||||
} else if (idx == total - 2) {
|
||||
// Pasando por la zona
|
||||
_fireNotif(NotifEvent.truckProximity, '✅ El camión está en tu zona',
|
||||
'El camión recolector está pasando por tu colonia. '
|
||||
'Si ya sacaste tus bolsas, el servicio está en curso.', state.routeId);
|
||||
} else if (idx == total - 1) {
|
||||
// Servicio finalizado → prompt de reseña
|
||||
state.reviewPromptSent = true;
|
||||
_fireNotif(NotifEvent.reviewPrompt, '🌟 ¿Cómo fue el servicio?',
|
||||
'¡El camión concluyó su jornada! Ayúdanos calificando el servicio '
|
||||
'de recolección de hoy. Tu opinión mejora el servicio.', state.routeId);
|
||||
}
|
||||
}
|
||||
|
||||
void _fireNotif(NotifEvent event, String title, String body, String routeId) {
|
||||
final n = AppNotification(event:event, title:title, body:body, routeId:routeId);
|
||||
_lastNotification = n;
|
||||
_history.insert(0, n);
|
||||
// Persistir en DB para historial
|
||||
DbHelper.insertNotifHistory(
|
||||
routeId: routeId, eventType: event.name,
|
||||
title: title, body: body,
|
||||
).catchError((_) {});
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> _fireAndSave({required NotifEvent event, required String routeId,
|
||||
required String title, required String body, required String tipo}) async {
|
||||
_fireNotif(event, title, body, routeId);
|
||||
await DbHelper.insertAlerta(AlertaModel(tipo:tipo, routeId:routeId,
|
||||
mensaje:body, fecha:DateTime.now().toIso8601String()));
|
||||
}
|
||||
|
||||
void fireCustomNotification(String title, String body, String routeId, NotifEvent event) {
|
||||
_fireNotif(event, title, body, routeId);
|
||||
}
|
||||
|
||||
// ── GPS ──────────────────────────────────────────────────────────────────
|
||||
Future<void> simulateGpsLost(String routeId) async {
|
||||
final state = _states[routeId];
|
||||
if (state == null) return;
|
||||
state.gpsActive = false;
|
||||
await _fireAndSave(event:NotifEvent.gpsLost, routeId:routeId,
|
||||
title:'📡 GPS Desactivado',
|
||||
body:'Se perdió la señal GPS del camión $routeId.',
|
||||
tipo:'GPS_PERDIDO');
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void restoreGps(String routeId) {
|
||||
final state = _states[routeId];
|
||||
if (state == null) return;
|
||||
state.gpsActive = true;
|
||||
state.stoppedAlertSent = false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// ── Getters de estado ────────────────────────────────────────────────────
|
||||
SimulatorState? getState(String routeId) => _states[routeId];
|
||||
int getPositionIndex(String routeId) => _states[routeId]?.positionIndex ?? 0;
|
||||
bool isTruckClose(String routeId) => getPositionIndex(routeId) >= 3;
|
||||
bool isRouteCompleted(String routeId) {
|
||||
final state = _states[routeId];
|
||||
if (state == null) return false;
|
||||
final route = getRouteById(routeId);
|
||||
if (route == null) return false;
|
||||
return state.positionIndex >= route.positions.length - 1;
|
||||
}
|
||||
bool isGpsActive(String routeId) => _states[routeId]?.gpsActive ?? true;
|
||||
|
||||
String getEtaText(String routeId) {
|
||||
final state = _states[routeId];
|
||||
if (state == null) return 'Sin información';
|
||||
if (!state.gpsActive) return '📡 Señal GPS perdida';
|
||||
final route = getRouteById(routeId);
|
||||
if (route == null) return 'Ruta no encontrada';
|
||||
final idx = state.positionIndex;
|
||||
if (idx >= route.positions.length) return '✅ Servicio finalizado';
|
||||
switch (idx) {
|
||||
case 0: return '🕐 Ruta por iniciar';
|
||||
case 1: return '🚛 Camión en camino — mantén tus bolsas adentro';
|
||||
case 2: return '🚛 Aprox. 30 min — espera el aviso de 15 min';
|
||||
case 3: return '⚠️ ¡15 min! Saca tus bolsas a la acera ahora';
|
||||
case 4: return '🔔 El camión está en tu colonia';
|
||||
case 5: return '✅ Recogiendo basura en tu zona';
|
||||
case 6: return '↩️ Regresando al relleno sanitario';
|
||||
default: return '🏁 Servicio del día finalizado';
|
||||
}
|
||||
}
|
||||
|
||||
void dismissNotification() { _lastNotification = null; notifyListeners(); }
|
||||
void dismissRouteNotification(String routeId) {
|
||||
if (_lastNotification?.routeId == routeId) {
|
||||
_lastNotification = null;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
void clearReviewPrompt(String routeId) {
|
||||
final state = _states[routeId];
|
||||
if (state != null) state.reviewPromptSent = false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_globalTimer?.cancel();
|
||||
_gpsMonitorTimer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
24
celaya_limpia/lib/services/theme_service.dart
Normal file
24
celaya_limpia/lib/services/theme_service.dart
Normal file
@@ -0,0 +1,24 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class ThemeService extends ChangeNotifier {
|
||||
ThemeMode _themeMode = ThemeMode.light;
|
||||
ThemeMode get themeMode => _themeMode;
|
||||
bool get isDark => _themeMode == ThemeMode.dark;
|
||||
|
||||
ThemeService() { _load(); }
|
||||
|
||||
Future<void> _load() async {
|
||||
final p = await SharedPreferences.getInstance();
|
||||
final isDark = p.getBool('dark_mode') ?? false;
|
||||
_themeMode = isDark ? ThemeMode.dark : ThemeMode.light;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> toggle() async {
|
||||
_themeMode = _themeMode == ThemeMode.dark ? ThemeMode.light : ThemeMode.dark;
|
||||
final p = await SharedPreferences.getInstance();
|
||||
await p.setBool('dark_mode', _themeMode == ThemeMode.dark);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user