2026-03-19 01:12:17 +08:00
|
|
|
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);
|
2026-03-20 15:15:35 +08:00
|
|
|
const [largeText, setLargeTextState] = useState(true);
|
2026-03-19 01:12:17 +08:00
|
|
|
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;
|
|
|
|
|
}
|