mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-27 04:31:27 +08:00
feat: Enhance theme management with auto mode support (#5374)
- Updated IPC channel to handle 'auto' theme mode, allowing dynamic theme changes based on system preferences. - Modified theme setting functions in preload scripts to accommodate the new 'auto' option. - Adjusted Sidebar component to display an icon for 'auto' theme mode. - Refactored ThemeProvider to manage theme state more effectively and listen for theme changes across windows.
This commit is contained in:
parent
6b113c19a3
commit
e5e04c8132
@ -5,7 +5,7 @@ import { isMac, isWin } from '@main/constant'
|
||||
import { getBinaryPath, isBinaryExists, runInstallScript } from '@main/utils/process'
|
||||
import { IpcChannel } from '@shared/IpcChannel'
|
||||
import { Shortcut, ThemeMode } from '@types'
|
||||
import { BrowserWindow, ipcMain, session, shell } from 'electron'
|
||||
import { BrowserWindow, ipcMain, nativeTheme, session, shell } from 'electron'
|
||||
import log from 'electron-log'
|
||||
|
||||
import { titleBarOverlayDark, titleBarOverlayLight } from './config'
|
||||
@ -119,23 +119,26 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
||||
})
|
||||
|
||||
// theme
|
||||
ipcMain.handle(IpcChannel.App_SetTheme, (event, theme: ThemeMode) => {
|
||||
if (theme === configManager.getTheme()) return
|
||||
ipcMain.handle(IpcChannel.App_SetTheme, (_, theme: ThemeMode) => {
|
||||
const notifyThemeChange = () => {
|
||||
const windows = BrowserWindow.getAllWindows()
|
||||
windows.forEach((win) =>
|
||||
win.webContents.send(IpcChannel.ThemeChange, nativeTheme.shouldUseDarkColors ? ThemeMode.dark : ThemeMode.light)
|
||||
)
|
||||
}
|
||||
|
||||
configManager.setTheme(theme)
|
||||
|
||||
// should sync theme change to all windows
|
||||
const senderWindowId = event.sender.id
|
||||
const windows = BrowserWindow.getAllWindows()
|
||||
// 向其他窗口广播主题变化
|
||||
windows.forEach((win) => {
|
||||
if (win.webContents.id !== senderWindowId) {
|
||||
win.webContents.send(IpcChannel.ThemeChange, theme)
|
||||
}
|
||||
})
|
||||
if (theme === ThemeMode.auto) {
|
||||
nativeTheme.themeSource = 'system'
|
||||
nativeTheme.on('updated', notifyThemeChange)
|
||||
} else {
|
||||
nativeTheme.themeSource = theme
|
||||
nativeTheme.removeAllListeners('updated')
|
||||
}
|
||||
|
||||
mainWindow?.setTitleBarOverlay &&
|
||||
mainWindow.setTitleBarOverlay(theme === 'dark' ? titleBarOverlayDark : titleBarOverlayLight)
|
||||
mainWindow.setTitleBarOverlay(nativeTheme.shouldUseDarkColors ? titleBarOverlayDark : titleBarOverlayLight)
|
||||
configManager.setTheme(theme)
|
||||
notifyThemeChange()
|
||||
})
|
||||
|
||||
// custom css
|
||||
|
||||
3
src/preload/index.d.ts
vendored
3
src/preload/index.d.ts
vendored
@ -28,7 +28,8 @@ declare global {
|
||||
setTray: (isActive: boolean) => void
|
||||
setTrayOnClose: (isActive: boolean) => void
|
||||
restartTray: () => void
|
||||
setTheme: (theme: 'light' | 'dark') => void
|
||||
setTheme: (theme: 'light' | 'dark' | 'auto') => void
|
||||
getTheme: () => Promise<'light' | 'dark' | 'auto'>
|
||||
setCustomCss: (css: string) => void
|
||||
setAutoUpdate: (isActive: boolean) => void
|
||||
reload: () => void
|
||||
|
||||
@ -18,7 +18,7 @@ const api = {
|
||||
setTray: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetTray, isActive),
|
||||
setTrayOnClose: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetTrayOnClose, isActive),
|
||||
restartTray: () => ipcRenderer.invoke(IpcChannel.App_RestartTray),
|
||||
setTheme: (theme: 'light' | 'dark') => ipcRenderer.invoke(IpcChannel.App_SetTheme, theme),
|
||||
setTheme: (theme: 'light' | 'dark' | 'auto') => ipcRenderer.invoke(IpcChannel.App_SetTheme, theme),
|
||||
setCustomCss: (css: string) => ipcRenderer.invoke(IpcChannel.App_SetCustomCss, css),
|
||||
setAutoUpdate: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetAutoUpdate, isActive),
|
||||
openWebsite: (url: string) => ipcRenderer.invoke(IpcChannel.Open_Website, url),
|
||||
|
||||
@ -19,6 +19,7 @@ import {
|
||||
MessageSquareQuote,
|
||||
Moon,
|
||||
Palette,
|
||||
RefreshCcw,
|
||||
Settings,
|
||||
Sparkle,
|
||||
Sun
|
||||
@ -98,7 +99,13 @@ const Sidebar: FC = () => {
|
||||
mouseEnterDelay={0.8}
|
||||
placement="right">
|
||||
<Icon theme={theme} onClick={() => toggleTheme()}>
|
||||
{theme === 'dark' ? <Moon size={20} className="icon" /> : <Sun size={20} className="icon" />}
|
||||
{settingTheme === 'dark' ? (
|
||||
<Moon size={20} className="icon" />
|
||||
) : settingTheme === 'light' ? (
|
||||
<Sun size={20} className="icon" />
|
||||
) : (
|
||||
<RefreshCcw size={20} className="icon" />
|
||||
)}
|
||||
</Icon>
|
||||
</Tooltip>
|
||||
<Tooltip title={t('settings.title')} mouseEnterDelay={0.8} placement="right">
|
||||
|
||||
@ -11,8 +11,8 @@ interface ThemeContextType {
|
||||
}
|
||||
|
||||
const ThemeContext = createContext<ThemeContextType>({
|
||||
theme: ThemeMode.light,
|
||||
settingTheme: ThemeMode.light,
|
||||
theme: ThemeMode.auto,
|
||||
settingTheme: ThemeMode.auto,
|
||||
toggleTheme: () => {}
|
||||
})
|
||||
|
||||
@ -22,43 +22,37 @@ interface ThemeProviderProps extends PropsWithChildren {
|
||||
|
||||
export const ThemeProvider: React.FC<ThemeProviderProps> = ({ children, defaultTheme }) => {
|
||||
const { theme, setTheme } = useSettings()
|
||||
const [_theme, _setTheme] = useState(theme)
|
||||
const [effectiveTheme, setEffectiveTheme] = useState(theme)
|
||||
|
||||
const toggleTheme = () => {
|
||||
setTheme(theme === ThemeMode.dark ? ThemeMode.light : ThemeMode.dark)
|
||||
// 主题顺序是light, dark, auto, 所以需要先判断当前主题,然后取下一个主题
|
||||
const nextTheme =
|
||||
theme === ThemeMode.light ? ThemeMode.dark : theme === ThemeMode.dark ? ThemeMode.auto : ThemeMode.light
|
||||
setTheme(nextTheme)
|
||||
}
|
||||
|
||||
useEffect((): any => {
|
||||
if (theme === ThemeMode.auto || defaultTheme === ThemeMode.auto) {
|
||||
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
|
||||
_setTheme(mediaQuery.matches ? ThemeMode.dark : ThemeMode.light)
|
||||
const handleChange = (e: MediaQueryListEvent) => _setTheme(e.matches ? ThemeMode.dark : ThemeMode.light)
|
||||
mediaQuery.addEventListener('change', handleChange)
|
||||
return () => mediaQuery.removeEventListener('change', handleChange)
|
||||
} else {
|
||||
_setTheme(theme)
|
||||
}
|
||||
useEffect(() => {
|
||||
window.api?.setTheme(defaultTheme || theme)
|
||||
}, [defaultTheme, theme])
|
||||
|
||||
useEffect(() => {
|
||||
document.body.setAttribute('theme-mode', _theme)
|
||||
// 移除迷你窗口的条件判断,让所有窗口都能设置主题
|
||||
window.api?.setTheme(_theme === ThemeMode.dark ? 'dark' : 'light')
|
||||
}, [_theme])
|
||||
document.body.setAttribute('theme-mode', effectiveTheme)
|
||||
}, [effectiveTheme])
|
||||
|
||||
useEffect(() => {
|
||||
document.body.setAttribute('os', isMac ? 'mac' : 'windows')
|
||||
|
||||
// listen theme change from main process from other windows
|
||||
const themeChangeListenerRemover = window.electron.ipcRenderer.on(IpcChannel.ThemeChange, (_, newTheme) => {
|
||||
setTheme(newTheme)
|
||||
})
|
||||
const themeChangeListenerRemover = window.electron.ipcRenderer.on(
|
||||
IpcChannel.ThemeChange,
|
||||
(_, realTheam: ThemeMode) => {
|
||||
setEffectiveTheme(realTheam)
|
||||
}
|
||||
)
|
||||
return () => {
|
||||
themeChangeListenerRemover()
|
||||
}
|
||||
})
|
||||
|
||||
return <ThemeContext value={{ theme: _theme, settingTheme: theme, toggleTheme }}>{children}</ThemeContext>
|
||||
return <ThemeContext value={{ theme: effectiveTheme, settingTheme: theme, toggleTheme }}>{children}</ThemeContext>
|
||||
}
|
||||
|
||||
export const useTheme = () => use(ThemeContext)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user