Squash merge feat/expo-app: app-expo, .cursor, workflows, package.json, .husky; remove app-android, app-ios, react-app
This commit is contained in:
157
app-expo/src/core/app-settings-context.tsx
Normal file
157
app-expo/src/core/app-settings-context.tsx
Normal file
@@ -0,0 +1,157 @@
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user