diff --git a/src/main/ipc.ts b/src/main/ipc.ts index 47e0b25386..0ae09d8b21 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -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 diff --git a/src/preload/index.d.ts b/src/preload/index.d.ts index 10fed67d3d..923fbd6bb5 100644 --- a/src/preload/index.d.ts +++ b/src/preload/index.d.ts @@ -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 diff --git a/src/preload/index.ts b/src/preload/index.ts index 251706e694..1671f72cf2 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -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), diff --git a/src/renderer/src/components/app/Sidebar.tsx b/src/renderer/src/components/app/Sidebar.tsx index d63c7b9992..c1fef66656 100644 --- a/src/renderer/src/components/app/Sidebar.tsx +++ b/src/renderer/src/components/app/Sidebar.tsx @@ -19,6 +19,7 @@ import { MessageSquareQuote, Moon, Palette, + RefreshCcw, Settings, Sparkle, Sun @@ -98,7 +99,13 @@ const Sidebar: FC = () => { mouseEnterDelay={0.8} placement="right"> toggleTheme()}> - {theme === 'dark' ? : } + {settingTheme === 'dark' ? ( + + ) : settingTheme === 'light' ? ( + + ) : ( + + )} diff --git a/src/renderer/src/context/ThemeProvider.tsx b/src/renderer/src/context/ThemeProvider.tsx index dd31b0aed0..b2eafc21f8 100644 --- a/src/renderer/src/context/ThemeProvider.tsx +++ b/src/renderer/src/context/ThemeProvider.tsx @@ -11,8 +11,8 @@ interface ThemeContextType { } const ThemeContext = createContext({ - 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 = ({ 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 {children} + return {children} } export const useTheme = () => use(ThemeContext)