// Stan ekranu zapamietywany miedzy sesjami (prosba z apki 2026-09-02: filtry znikaly // po zamknieciu apki). SecureStore, bo to jedyny magazyn w projekcie; wartosc to JSON. // // Dwie zasady: // 1. Zapis dopiero PO wczytaniu. Inaczej pierwszy render z domyslna wartoscia nadpisalby // to, co user zapisal wczesniej, zanim odczyt zdazyl wrocic. // 2. Odczyt scalany z domyslnymi (`{...defaults, ...saved}`), zeby nowe pole dodane w // kolejnej wersji dostalo swoja wartosc domyslna, a nie `undefined`. // // SecureStore na Androidzie ostrzega powyzej ~2 KB na klucz. Filtry to kilkaset bajtow, // ale przy bardzo dlugiej liscie tagow zapis moze sie nie udac; wtedy zostaje stan w // pamieci, a blad jest polykany, bo utrata zapamietanego filtra to nie powod, zeby // wywracac ekran. import * as SecureStore from 'expo-secure-store'; import React from 'react'; const PREFIX = 'persisted_v1_'; function keyFor(name: string): string { // SecureStore przyjmuje tylko [A-Za-z0-9._-]; dwukropki i spacje odpadaja. return PREFIX + name.replace(/[^A-Za-z0-9._-]/g, '_'); } export function usePersistedState( name: string, defaults: T, ): [T, React.Dispatch>, boolean] { const [value, setValue] = React.useState(defaults); const [hydrated, setHydrated] = React.useState(false); const key = keyFor(name); React.useEffect(() => { let cancelled = false; (async () => { try { const raw = await SecureStore.getItemAsync(key); if (!cancelled && raw) { const saved = JSON.parse(raw); if (saved && typeof saved === 'object') setValue({ ...defaults, ...saved }); } } catch { // uszkodzony zapis albo brak SecureStore: zostaja domyslne } finally { if (!cancelled) setHydrated(true); } })(); return () => { cancelled = true; }; // `defaults` to stala modulu, `key` zmienia sie tylko z sekcja ekranu. // eslint-disable-next-line react-hooks/exhaustive-deps }, [key]); React.useEffect(() => { if (!hydrated) return; SecureStore.setItemAsync(key, JSON.stringify(value)).catch(() => {}); }, [key, value, hydrated]); return [value, setValue, hydrated]; }