97 lines
3.0 KiB
TypeScript
97 lines
3.0 KiB
TypeScript
import { createContext, useContext, useState, useCallback, useEffect } from 'react'
|
|
import Taro, { useDidShow } from '@tarojs/taro'
|
|
import { getTheme, setTheme as saveTheme, resolveTheme, type ThemeMode } from '../utils/store'
|
|
import { applyPageBackground } from '../utils/themeBackground'
|
|
|
|
interface ThemeContextValue {
|
|
theme: ThemeMode
|
|
resolvedTheme: 'light' | 'dark'
|
|
toggleTheme: () => void
|
|
setTheme: (t: ThemeMode) => void
|
|
}
|
|
|
|
const ThemeContext = createContext<ThemeContextValue>({
|
|
theme: 'auto',
|
|
resolvedTheme: 'light',
|
|
toggleTheme: () => {},
|
|
setTheme: () => {}
|
|
})
|
|
|
|
export { ThemeContext }
|
|
|
|
type ThemeChangeListener = (result: { theme?: string }) => void
|
|
|
|
type ThemeChangeApi = {
|
|
onThemeChange?: (listener: ThemeChangeListener) => void
|
|
offThemeChange?: (listener: ThemeChangeListener) => void
|
|
}
|
|
|
|
/** 主题广播事件名:自定义 tabBar 等无法继承 React Context 的独立组件通过它同步主题 */
|
|
export const THEME_CHANGE_EVENT = 'themeChange'
|
|
|
|
export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
|
const [theme, set] = useState<ThemeMode>(getTheme())
|
|
const [resolvedTheme, setResolved] = useState<'light' | 'dark'>(resolveTheme(getTheme()))
|
|
|
|
const updateResolved = useCallback((mode: ThemeMode) => {
|
|
setResolved(resolveTheme(mode))
|
|
}, [])
|
|
|
|
const setTheme = useCallback((t: ThemeMode) => {
|
|
saveTheme(t)
|
|
set(t)
|
|
updateResolved(t)
|
|
}, [updateResolved])
|
|
|
|
const toggleTheme = useCallback(() => {
|
|
const next = resolvedTheme === 'light' ? 'dark' : 'light'
|
|
setTheme(next)
|
|
}, [resolvedTheme, setTheme])
|
|
|
|
// 广播实际生效主题,供 custom-tab-bar 等独立组件订阅
|
|
useEffect(() => {
|
|
Taro.eventCenter.trigger(THEME_CHANGE_EVENT, resolvedTheme)
|
|
}, [resolvedTheme])
|
|
|
|
// App 启动时立即同步页面背景(自定义导航区在 iOS 上由 backgroundColorTop 控制),
|
|
// 避免首屏/切换 tab 时顶部导航区先显示白色再变主题色
|
|
useEffect(() => {
|
|
applyPageBackground(resolvedTheme)
|
|
}, [resolvedTheme])
|
|
|
|
// 页面每次显示时(从小程序后台切回前台)重新检测系统主题
|
|
useDidShow(() => {
|
|
if (theme === 'auto') {
|
|
setResolved(resolveTheme('auto'))
|
|
}
|
|
})
|
|
|
|
// auto模式下监听系统主题变化
|
|
useEffect(() => {
|
|
if (theme === 'auto') {
|
|
// 先同步一次当前系统主题
|
|
setResolved(resolveTheme('auto'))
|
|
const listener: ThemeChangeListener = (res) => {
|
|
setResolved(res.theme === 'dark' ? 'dark' : 'light')
|
|
}
|
|
const themeApi = Taro as typeof Taro & ThemeChangeApi
|
|
themeApi.onThemeChange?.(listener)
|
|
return () => {
|
|
themeApi.offThemeChange?.(listener)
|
|
}
|
|
} else {
|
|
updateResolved(theme)
|
|
}
|
|
}, [theme, updateResolved])
|
|
|
|
return (
|
|
<ThemeContext.Provider value={{ theme, resolvedTheme, toggleTheme, setTheme }}>
|
|
{children}
|
|
</ThemeContext.Provider>
|
|
)
|
|
}
|
|
|
|
export function useThemeContext(): ThemeContextValue {
|
|
return useContext(ThemeContext)
|
|
}
|