134 lines
3.4 KiB
TypeScript
134 lines
3.4 KiB
TypeScript
|
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||
|
|
import { useColorScheme } from 'react-native';
|
||
|
|
import { create } from 'zustand';
|
||
|
|
import { useEffect } from 'react';
|
||
|
|
import { lightColors, darkColors, type AppColors } from '../theme/palettes';
|
||
|
|
import { buildPaperTheme } from '../theme/paperTheme';
|
||
|
|
import type { MD3Theme } from 'react-native-paper';
|
||
|
|
|
||
|
|
const STORAGE_KEY = 'app_theme_preference';
|
||
|
|
|
||
|
|
export type ThemePreference = 'light' | 'dark' | 'system';
|
||
|
|
|
||
|
|
export type ResolvedScheme = 'light' | 'dark';
|
||
|
|
|
||
|
|
function computeResolved(pref: ThemePreference, system: ResolvedScheme): ResolvedScheme {
|
||
|
|
return pref === 'system' ? system : pref;
|
||
|
|
}
|
||
|
|
|
||
|
|
function buildState(
|
||
|
|
preference: ThemePreference,
|
||
|
|
systemScheme: ResolvedScheme
|
||
|
|
): {
|
||
|
|
resolvedScheme: ResolvedScheme;
|
||
|
|
colors: AppColors;
|
||
|
|
paperTheme: MD3Theme;
|
||
|
|
} {
|
||
|
|
const resolvedScheme = computeResolved(preference, systemScheme);
|
||
|
|
const isDark = resolvedScheme === 'dark';
|
||
|
|
const colors = isDark ? darkColors : lightColors;
|
||
|
|
return {
|
||
|
|
resolvedScheme,
|
||
|
|
colors,
|
||
|
|
paperTheme: buildPaperTheme(colors, isDark),
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
type ThemeState = {
|
||
|
|
preference: ThemePreference;
|
||
|
|
systemScheme: ResolvedScheme;
|
||
|
|
hydrated: boolean;
|
||
|
|
resolvedScheme: ResolvedScheme;
|
||
|
|
colors: AppColors;
|
||
|
|
paperTheme: MD3Theme;
|
||
|
|
setPreference: (p: ThemePreference) => Promise<void>;
|
||
|
|
setSystemScheme: (s: ResolvedScheme) => void;
|
||
|
|
hydrate: () => Promise<void>;
|
||
|
|
};
|
||
|
|
|
||
|
|
const initialSystem: ResolvedScheme = 'light';
|
||
|
|
|
||
|
|
export const useThemeStore = create<ThemeState>((set, get) => ({
|
||
|
|
preference: 'system',
|
||
|
|
systemScheme: initialSystem,
|
||
|
|
hydrated: false,
|
||
|
|
...buildState('system', initialSystem),
|
||
|
|
|
||
|
|
setSystemScheme: (systemScheme) => {
|
||
|
|
const { preference, systemScheme: cur } = get();
|
||
|
|
if (cur === systemScheme) return;
|
||
|
|
set({
|
||
|
|
systemScheme,
|
||
|
|
...buildState(preference, systemScheme),
|
||
|
|
});
|
||
|
|
},
|
||
|
|
|
||
|
|
setPreference: async (preference) => {
|
||
|
|
try {
|
||
|
|
await AsyncStorage.setItem(STORAGE_KEY, preference);
|
||
|
|
} catch {
|
||
|
|
/* ignore */
|
||
|
|
}
|
||
|
|
const { systemScheme } = get();
|
||
|
|
set({
|
||
|
|
preference,
|
||
|
|
...buildState(preference, systemScheme),
|
||
|
|
});
|
||
|
|
},
|
||
|
|
|
||
|
|
hydrate: async () => {
|
||
|
|
let preference: ThemePreference = 'system';
|
||
|
|
try {
|
||
|
|
const raw = await AsyncStorage.getItem(STORAGE_KEY);
|
||
|
|
if (raw === 'light' || raw === 'dark' || raw === 'system') {
|
||
|
|
preference = raw;
|
||
|
|
}
|
||
|
|
} catch {
|
||
|
|
/* ignore */
|
||
|
|
}
|
||
|
|
const { systemScheme } = get();
|
||
|
|
set({
|
||
|
|
preference,
|
||
|
|
hydrated: true,
|
||
|
|
...buildState(preference, systemScheme),
|
||
|
|
});
|
||
|
|
},
|
||
|
|
}));
|
||
|
|
|
||
|
|
/** 在根布局中同步系统配色并恢复持久化偏好 */
|
||
|
|
export function ThemeBootstrap() {
|
||
|
|
const system = useColorScheme();
|
||
|
|
const setSystemScheme = useThemeStore((s) => s.setSystemScheme);
|
||
|
|
const hydrate = useThemeStore((s) => s.hydrate);
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
void hydrate();
|
||
|
|
}, [hydrate]);
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
setSystemScheme(system === 'dark' ? 'dark' : 'light');
|
||
|
|
}, [system, setSystemScheme]);
|
||
|
|
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function useAppColors() {
|
||
|
|
return useThemeStore((s) => s.colors);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function useThemePreference() {
|
||
|
|
return useThemeStore((s) => s.preference);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function useResolvedColorScheme() {
|
||
|
|
return useThemeStore((s) => s.resolvedScheme);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function useSetThemePreference() {
|
||
|
|
return useThemeStore((s) => s.setPreference);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function usePaperThemeFromStore() {
|
||
|
|
return useThemeStore((s) => s.paperTheme);
|
||
|
|
}
|