114 lines
3.4 KiB
TypeScript
114 lines
3.4 KiB
TypeScript
import { Injectable } from '@angular/core';
|
|
import { Preferences } from '@capacitor/preferences';
|
|
import { LocalNotifications, LocalNotificationSchema } from '@capacitor/local-notifications';
|
|
import { Haptics, ImpactStyle } from '@capacitor/haptics';
|
|
import { EventoAcademico } from './data';
|
|
|
|
@Injectable({
|
|
providedIn: 'root'
|
|
})
|
|
export class EventosService {
|
|
private readonly PREF_EVENTOS = 'eventos_academicos';
|
|
private eventos: EventoAcademico[] = [];
|
|
|
|
constructor() {
|
|
this.loadEventos();
|
|
}
|
|
|
|
async loadEventos(): Promise<EventoAcademico[]> {
|
|
const { value } = await Preferences.get({ key: this.PREF_EVENTOS });
|
|
if (value) {
|
|
this.eventos = JSON.parse(value);
|
|
}
|
|
return this.eventos;
|
|
}
|
|
|
|
getEventosByMateria(codigo: string): EventoAcademico[] {
|
|
return this.eventos.filter(e => e.materiaCodigo === codigo);
|
|
}
|
|
|
|
getAllEventos(): EventoAcademico[] {
|
|
return this.eventos;
|
|
}
|
|
|
|
async saveEvento(evento: EventoAcademico) {
|
|
const index = this.eventos.findIndex(e => e.id === evento.id);
|
|
if (index > -1) {
|
|
this.eventos[index] = evento;
|
|
} else {
|
|
this.eventos.push(evento);
|
|
}
|
|
await this.syncStorage();
|
|
await this.scheduleNotifications(evento);
|
|
await Haptics.impact({ style: ImpactStyle.Medium }).catch(() => {});
|
|
}
|
|
|
|
async deleteEvento(id: string) {
|
|
this.eventos = this.eventos.filter(e => e.id !== id);
|
|
await this.syncStorage();
|
|
await this.cancelNotifications(id);
|
|
await Haptics.impact({ style: ImpactStyle.Light }).catch(() => {});
|
|
}
|
|
|
|
private async syncStorage() {
|
|
await Preferences.set({
|
|
key: this.PREF_EVENTOS,
|
|
value: JSON.stringify(this.eventos)
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Programa notificaciones escalonadas para un hito académico: 7 días, 1 día y 2 horas antes.
|
|
*/
|
|
async scheduleNotifications(evento: EventoAcademico) {
|
|
const now = new Date();
|
|
const eventDate = new Date(evento.fecha);
|
|
|
|
if (eventDate <= now) return;
|
|
|
|
// Cancelamos notificaciones previas de este ID
|
|
await this.cancelNotifications(evento.id);
|
|
|
|
const notifs: LocalNotificationSchema[] = [];
|
|
const baseId = this.stringToNumericId(evento.id);
|
|
|
|
const milestones = [
|
|
{ label: 'en 7 días', offset: 7 * 24 * 60 * 60 * 1000, suffix: '7d' },
|
|
{ label: 'mañana', offset: 24 * 60 * 60 * 1000, suffix: '1d' },
|
|
{ label: 'en 2 horas', offset: 2 * 60 * 60 * 1000, suffix: '2h' }
|
|
];
|
|
|
|
milestones.forEach((m, i) => {
|
|
const scheduleAt = new Date(eventDate.getTime() - m.offset);
|
|
if (scheduleAt > now) {
|
|
notifs.push({
|
|
id: baseId + i,
|
|
title: `📌 Recordatorio: ${evento.tipo}`,
|
|
body: `${evento.materiaCodigo}: ${m.label} es el examen/entrega.`,
|
|
schedule: { at: scheduleAt },
|
|
extra: { eventoId: evento.id, codigo: evento.materiaCodigo }
|
|
});
|
|
}
|
|
});
|
|
|
|
if (notifs.length > 0) {
|
|
await LocalNotifications.schedule({ notifications: notifs });
|
|
}
|
|
}
|
|
|
|
async cancelNotifications(eventoId: string) {
|
|
const baseId = this.stringToNumericId(eventoId);
|
|
const ids = [baseId, baseId + 1, baseId + 2];
|
|
await LocalNotifications.cancel({ notifications: ids.map(id => ({ id })) }).catch(() => {});
|
|
}
|
|
|
|
private stringToNumericId(str: string): number {
|
|
let hash = 0;
|
|
for (let i = 0; i < str.length; i++) {
|
|
hash = ((hash << 5) - hash) + str.charCodeAt(i);
|
|
hash |= 0;
|
|
}
|
|
return Math.abs(hash % 100000) + 200000;
|
|
}
|
|
}
|