feat: app completa recordaLexia (fases 1-5)
App web familiar de rutinas visuales para niños con TDAH: muestra cada día el material del cole y las rutinas de tarde, con gamificación por monedas y tienda de recompensas. Multi-niño y bilingüe ES/CA. Uso doméstico/homelab. Backend (Spring Boot 3.5 / Java 21 / Gradle): - Dominio por capas, PostgreSQL + Liquibase, datos semilla. - API REST con DTOs: /today, toggle con monedas y bonos de bloque/día, monedero, tienda/canje, ajustes y CRUD del panel de padres. - Seguridad ligera por PIN (BCrypt + sesion en memoria), sin Keycloak. - Tests JUnit: generacion del dia, monedas/bonos con reversion, canje, seguridad. Frontend (Angular 19, standalone + signals): - Perfiles, Home (Tablero y Foco), Tienda y panel de padres (5 pestañas). - Tipografia OpenDyslexic conmutable (accesibilidad), i18n ES/CA, TTS y sonido. - Tokens de diseño fieles al handoff (paleta, animaciones, monedas voladoras). Empaquetado: - Docker multi-stage + docker-compose (PostgreSQL + backend + Nginx). - Decisiones de arquitectura documentadas en docs/adr.
This commit is contained in:
80
frontend/src/app/core/font-preference.service.ts
Normal file
80
frontend/src/app/core/font-preference.service.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
import { Injectable, inject, signal } from '@angular/core';
|
||||
|
||||
/**
|
||||
* Gestiona la preferencia de tipografía OpenDyslexic.
|
||||
*
|
||||
* Es la "costura" de accesibilidad: aplica (o quita) el atributo
|
||||
* `data-dyslexia-font` en el elemento <html>, que es el interruptor que el
|
||||
* fichero de tokens (_theme.scss) usa para alternar entre OpenDyslexic y las
|
||||
* tipografías de marca del handoff (Fredoka/Nunito).
|
||||
*
|
||||
* Decisión de producto (Fase 1): OpenDyslexic activada POR DEFECTO y aplicada a
|
||||
* TODO el texto. Es una preferencia por niño; de momento se persiste en
|
||||
* localStorage. En la Fase 5 esta preferencia pasará a leerse/escribirse contra
|
||||
* el backend (ajustes por niño), sustituyendo el almacenamiento local.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class FontPreferenceService {
|
||||
/** Clave de persistencia temporal hasta el cableado con el backend. */
|
||||
private static readonly STORAGE_KEY = 'recordalexia.dyslexiaFont';
|
||||
|
||||
private readonly document = inject(DOCUMENT);
|
||||
|
||||
/** Estado reactivo: ¿está activada OpenDyslexic? Por defecto, sí. */
|
||||
private readonly enabledSignal = signal<boolean>(this.readInitialState());
|
||||
|
||||
/** Señal de solo lectura para que la consuma la UI. */
|
||||
readonly enabled = this.enabledSignal.asReadonly();
|
||||
|
||||
constructor() {
|
||||
// Sincroniza el DOM con el estado inicial al arrancar la app.
|
||||
this.applyToDom(this.enabledSignal());
|
||||
}
|
||||
|
||||
/** Activa o desactiva OpenDyslexic y propaga el cambio al DOM y a la persistencia. */
|
||||
setEnabled(enabled: boolean): void {
|
||||
this.enabledSignal.set(enabled);
|
||||
this.applyToDom(enabled);
|
||||
this.persist(enabled);
|
||||
}
|
||||
|
||||
/** Alterna el estado actual. */
|
||||
toggle(): void {
|
||||
this.setEnabled(!this.enabledSignal());
|
||||
}
|
||||
|
||||
/** Lee el estado inicial de localStorage; si no hay nada guardado, ACTIVA por defecto. */
|
||||
private readInitialState(): boolean {
|
||||
const stored = this.safeGetItem(FontPreferenceService.STORAGE_KEY);
|
||||
return stored === null ? true : stored === 'true';
|
||||
}
|
||||
|
||||
/** Refleja la preferencia en <html data-dyslexia-font="on|off">. */
|
||||
private applyToDom(enabled: boolean): void {
|
||||
this.document.documentElement.setAttribute(
|
||||
'data-dyslexia-font',
|
||||
enabled ? 'on' : 'off',
|
||||
);
|
||||
}
|
||||
|
||||
/** Guarda la preferencia, tolerando entornos sin localStorage. */
|
||||
private persist(enabled: boolean): void {
|
||||
try {
|
||||
this.document.defaultView?.localStorage.setItem(
|
||||
FontPreferenceService.STORAGE_KEY,
|
||||
String(enabled),
|
||||
);
|
||||
} catch {
|
||||
// localStorage no disponible (modo kiosko restringido): se ignora.
|
||||
}
|
||||
}
|
||||
|
||||
private safeGetItem(key: string): string | null {
|
||||
try {
|
||||
return this.document.defaultView?.localStorage.getItem(key) ?? null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user