Add option for automatic dark theme (#2759)

Summary:
Add an setting option to allow automatic dark theme

A feature requested by swrobel https://github.com/facebook/flipper/issues/2708

## Changelog

### UI change
Replace the `Enable Dark Theme` Toggle button with a Radio group containing three Radio buttons.

The available options are `Dark`, `Light` and `Use System Default`, of which `Use System Default` is the default value

<img width="548" alt="Screenshot 2021-08-31 at 3 32 44 PM" src="https://user-images.githubusercontent.com/410850/131462798-4e74f757-41fc-4a3f-ba28-53d00fc1cf03.png">

### Data structure change

The `darkMode` property of [Settings](c0cd32564a/desktop/app/src/reducers/settings.tsx (L20)) is changed from `boolean` to `string`, the available values are `dark`, `light` and `auto`

Pull Request resolved: https://github.com/facebook/flipper/pull/2759

Test Plan:
### Test 1
- Step: Choose `Dark` in the Settings page and click on `Apply and Restart` button
- Expect: The application is displayed in Dark mode
### Test 2
- Step: Choose `Light` in the Settings page and click on `Apply and Restart` button
- Expect: The application is displayed in Light mode
### Test 3
- Step: Choose `Use System Default` in the Settings page and click on `Apply and Restart` button
- Expect: The application is displayed in System Default mode

Reviewed By: mweststrate

Differential Revision: D30666966

Pulled By: passy

fbshipit-source-id: a63e91f2d0dbff96890e267062cb75bffd0007f4
This commit is contained in:
ZHANG Qichuan
2021-09-08 03:27:36 -07:00
committed by Facebook GitHub Bot
parent cef1468db7
commit 9a4d94c971
6 changed files with 57 additions and 29 deletions

View File

@@ -7,8 +7,9 @@
* @format * @format
*/ */
import {Button} from '../ui'; import {Button, FlexColumn} from '../ui';
import React, {Component, useContext} from 'react'; import React, {Component, useContext} from 'react';
import {Radio} from 'antd';
import {updateSettings, Action} from '../reducers/settings'; import {updateSettings, Action} from '../reducers/settings';
import { import {
Action as LauncherAction, Action as LauncherAction,
@@ -245,18 +246,23 @@ class SettingsSheet extends Component<Props, State> {
})); }));
}} }}
/> />
<ToggledSection <FlexColumn style={{paddingLeft: 15, paddingBottom: 10}}>
label="Enable dark theme" Theme Selection
toggled={darkMode} <Radio.Group
onChange={(enabled) => { value={darkMode}
this.setState((prevState) => ({ onChange={(event) => {
updatedSettings: { this.setState((prevState) => ({
...prevState.updatedSettings, updatedSettings: {
darkMode: enabled, ...prevState.updatedSettings,
}, darkMode: event.target.value,
})); },
}} }));
/> }}>
<Radio.Button value="dark">Dark</Radio.Button>
<Radio.Button value="light">Light</Radio.Button>
<Radio.Button value="system">Use System Setting</Radio.Button>
</Radio.Group>
</FlexColumn>
<ToggledSection <ToggledSection
label="React Native keyboard shortcuts" label="React Native keyboard shortcuts"
toggled={reactNative.shortcuts.enabled} toggled={reactNative.shortcuts.enabled}

View File

@@ -49,7 +49,7 @@ import styled from '@emotion/styled';
import {CopyOutlined} from '@ant-design/icons'; import {CopyOutlined} from '@ant-design/icons';
import {getVersionString} from './utils/versionString'; import {getVersionString} from './utils/versionString';
import {PersistGate} from 'redux-persist/integration/react'; import {PersistGate} from 'redux-persist/integration/react';
import {ipcRenderer} from 'electron'; import {ipcRenderer, remote} from 'electron';
if (process.env.NODE_ENV === 'development' && os.platform() === 'darwin') { if (process.env.NODE_ENV === 'development' && os.platform() === 'darwin') {
// By default Node.JS has its internal certificate storage and doesn't use // By default Node.JS has its internal certificate storage and doesn't use
@@ -213,14 +213,26 @@ function init() {
sideEffect( sideEffect(
store, store,
{name: 'loadTheme', fireImmediately: false, throttleMs: 500}, {name: 'loadTheme', fireImmediately: false, throttleMs: 500},
(state) => ({ (state) => {
dark: state.settingsState.darkMode, const theme = state.settingsState.darkMode;
}), let shouldUseDarkMode = remote.nativeTheme.shouldUseDarkColors;
(theme) => { if (theme === 'dark') {
shouldUseDarkMode = true;
} else if (theme === 'light') {
shouldUseDarkMode = false;
} else if (theme === 'system') {
shouldUseDarkMode = remote.nativeTheme.shouldUseDarkColors;
}
return {
shouldUseDarkMode: shouldUseDarkMode,
theme: theme,
};
},
(result) => {
( (
document.getElementById('flipper-theme-import') as HTMLLinkElement document.getElementById('flipper-theme-import') as HTMLLinkElement
).href = `themes/${theme.dark ? 'dark' : 'light'}.css`; ).href = `themes/${result.shouldUseDarkMode ? 'dark' : 'light'}.css`;
ipcRenderer.send('setTheme', theme.dark ? 'dark' : 'light'); ipcRenderer.send('setTheme', result.theme);
}, },
); );
} }

View File

@@ -43,7 +43,7 @@ export type Settings = {
openDevMenu: string; openDevMenu: string;
}; };
}; };
darkMode: boolean; darkMode: string;
showWelcomeAtStartup: boolean; showWelcomeAtStartup: boolean;
suppressPluginErrors: boolean; suppressPluginErrors: boolean;
}; };
@@ -78,7 +78,7 @@ const initialState: Settings = {
openDevMenu: 'Alt+Shift+D', openDevMenu: 'Alt+Shift+D',
}, },
}, },
darkMode: false, darkMode: 'auto',
showWelcomeAtStartup: true, showWelcomeAtStartup: true,
suppressPluginErrors: false, suppressPluginErrors: false,
}; };

View File

@@ -8,6 +8,7 @@
*/ */
import {useStore} from './useStore'; import {useStore} from './useStore';
import {remote} from 'electron';
/** /**
* This hook returns whether dark mode is currently being used. * This hook returns whether dark mode is currently being used.
@@ -15,5 +16,15 @@ import {useStore} from './useStore';
* which will provide colors that reflect the theme * which will provide colors that reflect the theme
*/ */
export function useIsDarkMode(): boolean { export function useIsDarkMode(): boolean {
return useStore((state) => state.settingsState.darkMode); return useStore((state) => {
const darkMode = state.settingsState.darkMode;
if (darkMode === 'dark') {
return true;
} else if (darkMode === 'light') {
return false;
} else if (darkMode === 'system') {
return remote.nativeTheme.shouldUseDarkColors;
}
return false;
});
} }

View File

@@ -109,9 +109,7 @@ if (argv['disable-gpu'] || process.env.FLIPPER_DISABLE_GPU === '1') {
} }
process.env.CONFIG = JSON.stringify(config); process.env.CONFIG = JSON.stringify(config);
if (config.darkMode) { nativeTheme.themeSource = config.darkMode || 'light';
nativeTheme.themeSource = 'dark';
}
// possible reference to main app window // possible reference to main app window
let win: BrowserWindow; let win: BrowserWindow;
@@ -290,7 +288,7 @@ ipcMain.on('getLaunchTime', (event) => {
} }
}); });
ipcMain.on('setTheme', (_e, mode: 'light' | 'dark') => { ipcMain.on('setTheme', (_e, mode: 'light' | 'dark' | 'system') => {
nativeTheme.themeSource = mode; nativeTheme.themeSource = mode;
}); });
@@ -382,7 +380,7 @@ function createWindow() {
configPath, configPath,
JSON.stringify({ JSON.stringify({
...config, ...config,
darkMode: nativeTheme.themeSource === 'dark', darkMode: nativeTheme.themeSource,
lastWindowPosition: { lastWindowPosition: {
x, x,
y, y,

View File

@@ -24,7 +24,7 @@ export type Config = {
launcherMsg?: string | undefined; launcherMsg?: string | undefined;
updaterEnabled?: boolean; updaterEnabled?: boolean;
launcherEnabled?: boolean; launcherEnabled?: boolean;
darkMode?: boolean; darkMode: 'system' | 'light' | 'dark';
}; };
export default function setup(argv: any) { export default function setup(argv: any) {
@@ -38,6 +38,7 @@ export default function setup(argv: any) {
let config: Config = { let config: Config = {
pluginPaths: [], pluginPaths: [],
disabledPlugins: [], disabledPlugins: [],
darkMode: 'light',
}; };
try { try {