Files
life-echo/app-expo/src/core/app-settings-context.tsx

158 lines
4.1 KiB
TypeScript
Raw Normal View History

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<void>;
largeText: boolean;
changeLargeText: (value: boolean) => Promise<void>;
darkMode: boolean;
changeDarkMode: (value: boolean) => Promise<void>;
themeName: ThemeName;
themeOptions: { value: ThemeName; label: string }[];
changeThemeName: (theme: ThemeName) => Promise<void>;
};
const AppSettingsContext = createContext<AppSettingsContextValue | null>(null);
export function AppSettingsProvider({ children }: PropsWithChildren) {
const { setColorScheme } = useColorScheme();
const { t } = useTranslation('app');
const [language, setLanguageState] = useState<AppLanguage | null>(null);
const [largeText, setLargeTextState] = useState(false);
const [darkMode, setDarkModeState] = useState(false);
const [themeName, setThemeNameState] = useState<ThemeName>('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 (
<AppSettingsContext.Provider value={value}>
{children}
</AppSettingsContext.Provider>
);
}
export function useAppSettingsContext(): AppSettingsContextValue {
const ctx = useContext(AppSettingsContext);
if (!ctx) {
throw new Error(
'useAppSettingsContext must be used within AppSettingsProvider',
);
}
return ctx;
}