import React, { createContext, useCallback, useContext, useEffect, useState, type PropsWithChildren, } from 'react'; import { useTranslation } from 'react-i18next'; import { clearAppLanguage, getAppLanguage, getDarkMode, getLargeText, getThemeName, setAppLanguage, setDarkMode, setLargeText, setThemeName, supportedLanguages, THEME_NAMES, type AppLanguage, type ThemeName, } from '@/core/settings/app-settings'; import i18n, { syncLanguageWithDevice } from '@/i18n'; import { useColorScheme } from '@/hooks/use-color-scheme'; type AppSettingsContextValue = { ready: boolean; language: AppLanguage | null; hasLanguageOverride: boolean; languageOptions: { code: AppLanguage | 'system'; label: string }[]; changeLanguage: (lang: AppLanguage | 'system') => Promise; largeText: boolean; changeLargeText: (value: boolean) => Promise; darkMode: boolean; changeDarkMode: (value: boolean) => Promise; themeName: ThemeName; themeOptions: { value: ThemeName; label: string }[]; changeThemeName: (theme: ThemeName) => Promise; }; const AppSettingsContext = createContext(null); export function AppSettingsProvider({ children }: PropsWithChildren) { const { setColorScheme } = useColorScheme(); const { t } = useTranslation('app'); const [language, setLanguageState] = useState(null); const [largeText, setLargeTextState] = useState(false); const [darkMode, setDarkModeState] = useState(false); const [themeName, setThemeNameState] = useState('default'); const [ready, setReady] = useState(false); useEffect(() => { let cancelled = false; async function load() { const [lang, large, dark, theme] = await Promise.all([ getAppLanguage(), getLargeText(), getDarkMode(), getThemeName(), ]); if (cancelled) return; setLanguageState(lang); setLargeTextState(large); setDarkModeState(dark); setThemeNameState(theme); if (dark) setColorScheme('dark'); setReady(true); } load(); return () => { cancelled = true; }; }, [setColorScheme]); const changeLanguage = useCallback(async (lang: AppLanguage | 'system') => { if (lang === 'system') { await clearAppLanguage(); await syncLanguageWithDevice(); setLanguageState(null); } else { await setAppLanguage(lang); await i18n.changeLanguage(lang); setLanguageState(lang); } }, []); const changeLargeText = useCallback(async (value: boolean) => { await setLargeText(value); setLargeTextState(value); }, []); const changeDarkMode = useCallback( async (value: boolean) => { await setDarkMode(value); setDarkModeState(value); setColorScheme(value ? 'dark' : 'light'); }, [setColorScheme], ); const changeThemeName = useCallback(async (theme: ThemeName) => { await setThemeName(theme); setThemeNameState(theme); }, []); const themeOptions: { value: ThemeName; label: string }[] = THEME_NAMES.map( (value) => ({ value, label: t(`theme.${value}`), }), ); const languageOptions: { code: AppLanguage | 'system'; label: string }[] = [ { code: 'system', label: t('languages.system') }, ...supportedLanguages.map((code) => ({ code: code as AppLanguage, label: t(`languages.${code}`), })), ]; const value: AppSettingsContextValue = { ready, language: (language ?? i18n.resolvedLanguage ?? 'zh') as AppLanguage, hasLanguageOverride: language !== null, languageOptions, changeLanguage, largeText, changeLargeText, darkMode, changeDarkMode, themeName, themeOptions, changeThemeName, }; return ( {children} ); } export function useAppSettingsContext(): AppSettingsContextValue { const ctx = useContext(AppSettingsContext); if (!ctx) { throw new Error( 'useAppSettingsContext must be used within AppSettingsProvider', ); } return ctx; }