generated from knso/webklar-preview-template
- Brief-Archiv jetzt AES-256-GCM-verschlüsselt (expo-crypto, Schlüssel im SecureStore); Migration alter Klartext-Bestände beim ersten Laden; im Simulator auf der Platte verifiziert (nur Chiffrat, kein Klartext) - AppSchutz-Komponente: optionale App-Sperre (Face ID / Geräte-Code, expo-local-authentication) + Sichtschutz im App-Umschalter - Auto-Löschen alter Briefe (30/90 Tage, Einstellungen), pure Filter- Funktion mit Tests - UTF-8-Helfer (Umlaute/Arabisch/Emoji-sicher) mit Tests; gesamt 30 Tests - Einstellungen: neuer Abschnitt "Sicherheit" - Datenschutzerklärung (Web + Entwurf) um die neuen Maßnahmen ergänzt - Neu: rechtliches/datenschutz-factsheet.md für Kooperations-Pitches - EU-Verarbeitung geprüft: inference_geo bietet aktuell nur us/global, keine EU-Option (Haiku 4.5 gar nicht) — als Ausbaupfad dokumentiert Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
80 lines
2.4 KiB
TypeScript
80 lines
2.4 KiB
TypeScript
/**
|
|
* Globaler App-State (Zustand). Hält das Brief-Archiv im Speicher und
|
|
* synchronisiert jede Änderung sofort nach AsyncStorage.
|
|
*/
|
|
import { create } from 'zustand';
|
|
import { BriefEintrag, Uebersetzung, AntwortTyp } from '../types';
|
|
import { holeAutoLoeschTage, ladeBriefe, speichereBriefe } from '../services/storage';
|
|
import { filtereAlteBriefe } from '../utils/aufbewahrung';
|
|
|
|
interface AppState {
|
|
briefe: BriefEintrag[];
|
|
geladen: boolean;
|
|
|
|
/** Archiv beim App-Start aus AsyncStorage laden. */
|
|
initialisiere: () => Promise<void>;
|
|
addBrief: (brief: BriefEintrag) => Promise<void>;
|
|
removeBrief: (id: string) => Promise<void>;
|
|
/** Übersetzung in den Cache des Briefs schreiben. */
|
|
setzeUebersetzung: (id: string, sprachCode: string, u: Uebersetzung) => Promise<void>;
|
|
setzeAntwortEntwurf: (
|
|
id: string,
|
|
entwurf: { typ: AntwortTyp; betreff: string; text: string }
|
|
) => Promise<void>;
|
|
leereAlles: () => void;
|
|
}
|
|
|
|
export const useAppStore = create<AppState>((set, get) => ({
|
|
briefe: [],
|
|
geladen: false,
|
|
|
|
initialisiere: async () => {
|
|
let briefe = await ladeBriefe();
|
|
// Auto-Löschen (Datensparsamkeit): abgelaufene Briefe beim Start entfernen
|
|
const aufbewahrungTage = await holeAutoLoeschTage();
|
|
const gefiltert = filtereAlteBriefe(briefe, aufbewahrungTage);
|
|
if (gefiltert.length !== briefe.length) {
|
|
briefe = gefiltert;
|
|
await speichereBriefe(briefe);
|
|
}
|
|
set({ briefe, geladen: true });
|
|
},
|
|
|
|
addBrief: async (brief) => {
|
|
const briefe = [brief, ...get().briefe]; // neueste zuerst
|
|
set({ briefe });
|
|
await speichereBriefe(briefe);
|
|
},
|
|
|
|
removeBrief: async (id) => {
|
|
const briefe = get().briefe.filter((b) => b.id !== id);
|
|
set({ briefe });
|
|
await speichereBriefe(briefe);
|
|
},
|
|
|
|
setzeUebersetzung: async (id, sprachCode, u) => {
|
|
const briefe = get().briefe.map((b) =>
|
|
b.id === id
|
|
? { ...b, uebersetzungen: { ...b.uebersetzungen, [sprachCode]: u } }
|
|
: b
|
|
);
|
|
set({ briefe });
|
|
await speichereBriefe(briefe);
|
|
},
|
|
|
|
setzeAntwortEntwurf: async (id, entwurf) => {
|
|
const briefe = get().briefe.map((b) =>
|
|
b.id === id ? { ...b, antwortEntwurf: entwurf } : b
|
|
);
|
|
set({ briefe });
|
|
await speichereBriefe(briefe);
|
|
},
|
|
|
|
leereAlles: () => set({ briefe: [] }),
|
|
}));
|
|
|
|
/** Einfache eindeutige ID ohne externe Abhängigkeit. */
|
|
export function neueId(): string {
|
|
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
}
|