feat: actualización a v1.1.9 con Agenda interactiva y correcciones visuales
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user