import { getLocales } from 'expo-localization'; import { Platform } from 'react-native'; import { deleteSecureItem, getSecureItem, setSecureItem, } from '@/core/storage/secure'; import { fallbackLanguage, supportedLanguages, type AppLanguage, } from '@/i18n/resources'; import { THEME_NAMES, type ThemeName } from '@/constants/theme-bridge'; import { mergeMemoirReadingPreferences, parseMemoirReadingPreferences, type MemoirReadingPreferences, } from './memoir-reading-settings'; const KEY_LANGUAGE = 'app_settings_language'; const KEY_LARGE_TEXT = 'app_settings_large_text'; const KEY_LARGE_TEXT_LEVEL = 'app_settings_large_text_level'; const KEY_DARK_MODE = 'app_settings_dark_mode'; const KEY_THEME_NAME = 'app_settings_theme_name'; const KEY_TTS_SPEAK_DEFAULT = 'app_settings_tts_speak_default'; const KEY_MEMOIR_READING = 'app_settings_memoir_reading'; const webFallback: Record = {}; async function getStored(key: string): Promise { if (Platform.OS === 'web') return webFallback[key] ?? null; return getSecureItem(key); } async function setStored(key: string, value: string): Promise { if (Platform.OS === 'web') { webFallback[key] = value; return; } await setSecureItem(key, value); } async function deleteStored(key: string): Promise { if (Platform.OS === 'web') { delete webFallback[key]; return; } await deleteSecureItem(key); } export async function getAppLanguage(): Promise { const v = await getStored(KEY_LANGUAGE); if (!v) return null; return supportedLanguages.includes(v as AppLanguage) ? (v as AppLanguage) : null; } export async function setAppLanguage(lang: AppLanguage): Promise { await setStored(KEY_LANGUAGE, lang); } export async function clearAppLanguage(): Promise { await deleteStored(KEY_LANGUAGE); } export type LargeTextLevel = 0 | 1 | 2; function parseStoredLargeTextLevel(raw: string | null): LargeTextLevel | null { if (raw === '0' || raw === '1' || raw === '2') { return Number(raw) as LargeTextLevel; } return null; } function deviceLanguageFromLocales(): AppLanguage { const locale = getLocales()[0]; const tag = locale?.languageCode ?? locale?.languageTag; if (!tag) return fallbackLanguage; return tag.toLowerCase().startsWith('zh') ? 'zh' : 'en'; } async function effectiveAppLanguage(): Promise { const override = await getAppLanguage(); return override ?? deviceLanguageFromLocales(); } /** 无存储时的默认档位:英文关大字、中文沿用偏大默认 */ export function defaultLargeTextLevelForLanguage(lang: AppLanguage): LargeTextLevel { return lang === 'en' ? 0 : 1; } /** * 纯函数:由原始存储值与当前生效语言解析档位(供单测与实现共用)。 */ export function computeLargeTextLevelFromStorage( levelRaw: string | null, legacyLargeTextRaw: string | null, effectiveLanguage: AppLanguage, ): LargeTextLevel { const parsed = parseStoredLargeTextLevel(levelRaw); if (parsed !== null) return parsed; if (legacyLargeTextRaw === 'true') return 1; if (legacyLargeTextRaw === 'false') return 0; return defaultLargeTextLevelForLanguage(effectiveLanguage); } export async function getLargeTextLevel(): Promise { const [levelRaw, legacyRaw, lang] = await Promise.all([ getStored(KEY_LARGE_TEXT_LEVEL), getStored(KEY_LARGE_TEXT), effectiveAppLanguage(), ]); const level = computeLargeTextLevelFromStorage(levelRaw, legacyRaw, lang); const hasValidLevel = parseStoredLargeTextLevel(levelRaw) !== null; if (hasValidLevel && legacyRaw != null && legacyRaw !== '') { await deleteStored(KEY_LARGE_TEXT); } else if ( !hasValidLevel && legacyRaw != null && legacyRaw !== '' ) { await setStored(KEY_LARGE_TEXT_LEVEL, String(level)); await deleteStored(KEY_LARGE_TEXT); } return level; } export async function setLargeTextLevel(level: LargeTextLevel): Promise { await setStored(KEY_LARGE_TEXT_LEVEL, String(level)); await deleteStored(KEY_LARGE_TEXT); } export async function getDarkMode(): Promise { const v = await getStored(KEY_DARK_MODE); return v === 'true'; } export async function setDarkMode(value: boolean): Promise { await setStored(KEY_DARK_MODE, value ? 'true' : 'false'); } export async function getThemeName(): Promise { const v = await getStored(KEY_THEME_NAME); if (!v || !THEME_NAMES.includes(v as ThemeName)) return 'default'; return v as ThemeName; } export async function setThemeName(value: ThemeName): Promise { await setStored(KEY_THEME_NAME, value); } /** 会话页「Speak / 本轮朗读」开关是否默认开启(跨会话记忆) */ export async function getTtsSpeakDefault(): Promise { const v = await getStored(KEY_TTS_SPEAK_DEFAULT); if (v == null || v === '') return false; return v === 'true'; } export async function setTtsSpeakDefault(value: boolean): Promise { await setStored(KEY_TTS_SPEAK_DEFAULT, value ? 'true' : 'false'); } export async function getMemoirReadingPreferences(): Promise { const raw = await getStored(KEY_MEMOIR_READING); return parseMemoirReadingPreferences(raw); } export async function setMemoirReadingPreferences( patch: Partial, ): Promise { const raw = await getStored(KEY_MEMOIR_READING); const current = parseMemoirReadingPreferences(raw); const next = mergeMemoirReadingPreferences(current, patch); await setStored(KEY_MEMOIR_READING, JSON.stringify(next)); } export { supportedLanguages, THEME_NAMES }; export type { AppLanguage, ThemeName }; export type { MemoirReadingPreferences } from './memoir-reading-settings';