fix #1584: automatically switching between dark and light theme

This commit is contained in:
antelle 2020-12-19 12:14:32 +01:00
parent f99ff95a57
commit 677f1ce75f
No known key found for this signature in database
GPG Key ID: 63C9777AAB7C563C
9 changed files with 145 additions and 26 deletions

View File

@ -9,6 +9,7 @@ import { UsbListener } from 'comp/app/usb-listener';
import { FeatureTester } from 'comp/browser/feature-tester';
import { FocusDetector } from 'comp/browser/focus-detector';
import { IdleTracker } from 'comp/browser/idle-tracker';
import { ThemeWatcher } from 'comp/browser/theme-watcher';
import { KeyHandler } from 'comp/browser/key-handler';
import { PopupNotifier } from 'comp/browser/popup-notifier';
import { Launcher } from 'comp/launcher';
@ -91,6 +92,8 @@ ready(() => {
KdbxwebInit.init();
FocusDetector.init();
AutoType.init();
ThemeWatcher.init();
SettingsManager.init();
window.kw = ExportApi;
return PluginManager.init().then(() => {
StartProfiler.milestone('initializing modules');
@ -111,13 +114,13 @@ ready(() => {
function loadRemoteConfig() {
return Promise.resolve()
.then(() => {
SettingsManager.setBySettings(appModel.settings);
SettingsManager.setBySettings();
const configParam = getConfigParam();
if (configParam) {
return appModel
.loadConfig(configParam)
.then(() => {
SettingsManager.setBySettings(appModel.settings);
SettingsManager.setBySettings();
})
.catch((e) => {
if (!appModel.settings.cacheConfigSettings) {

View File

@ -0,0 +1,17 @@
import { Events } from 'framework/events';
const ThemeWatcher = {
dark: false,
init() {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
mediaQuery.addEventListener('change', (e) => {
const dark = e.matches;
this.dark = dark;
Events.emit('dark-mode-changed', { dark });
});
this.dark = mediaQuery.matches;
}
};
export { ThemeWatcher };

View File

@ -1,6 +1,11 @@
import { Events } from 'framework/events';
import { Features } from 'util/features';
import { Locale } from 'util/locale';
import { ThemeWatcher } from 'comp/browser/theme-watcher';
import { AppSettingsModel } from 'models/app-settings-model';
import { Logger } from 'util/logger';
const logger = new Logger('settings-manager');
const SettingsManager = {
neutralLocale: null,
@ -16,23 +21,40 @@ const SettingsManager = {
allThemes: {
dark: 'setGenThemeDark',
light: 'setGenThemeLight',
fb: 'setGenThemeFb',
db: 'setGenThemeDb',
sd: 'setGenThemeSd',
sl: 'setGenThemeSl',
fb: 'setGenThemeFb',
db: 'setGenThemeDb',
te: 'setGenThemeTe',
hc: 'setGenThemeHc'
},
autoSwitchedThemes: [
{
name: 'setGenThemeDefault',
dark: 'dark',
light: 'light'
},
{
name: 'setGenThemeSol',
dark: 'sd',
light: 'sl'
}
],
customLocales: {},
setBySettings(settings) {
this.setTheme(settings.theme);
this.setFontSize(settings.fontSize);
const locale = settings.locale;
init() {
Events.on('dark-mode-changed', () => this.darkModeChanged());
},
setBySettings() {
this.setTheme(AppSettingsModel.theme);
this.setFontSize(AppSettingsModel.fontSize);
const locale = AppSettingsModel.locale;
try {
if (locale) {
this.setLocale(settings.locale);
this.setLocale(AppSettingsModel.locale);
} else {
this.setLocale(this.getBrowserLocale());
}
@ -55,18 +77,45 @@ const SettingsManager = {
document.body.classList.remove(cls);
}
}
if (AppSettingsModel.autoSwitchTheme) {
theme = this.selectDarkOrLightTheme(theme);
}
document.body.classList.add(this.getThemeClass(theme));
const metaThemeColor = document.head.querySelector('meta[name=theme-color]');
if (metaThemeColor) {
metaThemeColor.content = window.getComputedStyle(document.body).backgroundColor;
}
this.activeTheme = theme;
logger.debug('Theme changed', theme);
Events.emit('theme-applied');
},
getThemeClass(theme) {
return 'th-' + theme;
},
selectDarkOrLightTheme(theme) {
for (const config of this.autoSwitchedThemes) {
if (config.light === theme || config.dark === theme) {
return ThemeWatcher.dark ? config.dark : config.light;
}
}
return theme;
},
darkModeChanged() {
if (AppSettingsModel.autoSwitchTheme) {
for (const config of this.autoSwitchedThemes) {
if (config.light === this.activeTheme || config.dark === this.activeTheme) {
const newTheme = ThemeWatcher.dark ? config.dark : config.light;
logger.debug('Setting theme triggered by system settings change', newTheme);
this.setTheme(newTheme);
break;
}
}
}
},
setFontSize(fontSize) {
const defaultFontSize = Features.isMobile ? 14 : 12;
document.documentElement.style.fontSize = defaultFontSize + (fontSize || 0) * 2 + 'px';

View File

@ -1,5 +1,6 @@
const DefaultAppSettings = {
theme: null, // UI theme
autoSwitchTheme: false, // automatically switch between light and dark theme
locale: null, // user interface language
expandGroups: true, // show entries from all subgroups
listViewWidth: null, // width of the entry list representation

View File

@ -371,15 +371,18 @@
"setGenDownloadAndRestart": "Download update and restart",
"setGenAppearance": "Appearance",
"setGenTheme": "Theme",
"setGenThemeDefault": "Default",
"setGenThemeDark": "Dark",
"setGenThemeLight": "Light",
"setGenThemeFb": "Flat blue",
"setGenThemeDb": "Dark brown",
"setGenThemeTe": "Terminal",
"setGenThemeHc": "High contrast",
"setGenThemeSol": "Solarized",
"setGenThemeSd": "Solarized dark",
"setGenThemeSl": "Solarized light",
"setGenMoreThemes": "More themes",
"setGenAutoSwitchTheme": "Automatically switch between light and dark theme when possible",
"setGenLocale": "Language",
"setGenLocOther": "other languages are available as plugins",
"setGenFontSize": "Font size",

View File

@ -17,6 +17,7 @@ import { Locale } from 'util/locale';
import { SettingsLogsView } from 'views/settings/settings-logs-view';
import { SettingsPrvView } from 'views/settings/settings-prv-view';
import { mapObject } from 'util/fn';
import { ThemeWatcher } from 'comp/browser/theme-watcher';
import template from 'templates/settings/settings-general.hbs';
class SettingsGeneralView extends View {
@ -24,6 +25,7 @@ class SettingsGeneralView extends View {
events = {
'click .settings__general-theme': 'changeTheme',
'click .settings__general-auto-switch-theme': 'changeAuthSwitchTheme',
'change .settings__general-locale': 'changeLocale',
'change .settings__general-font-size': 'changeFontSize',
'change .settings__general-expand': 'changeExpandGroups',
@ -63,6 +65,7 @@ class SettingsGeneralView extends View {
super(model, options);
this.listenTo(UpdateModel, 'change:status', this.render);
this.listenTo(UpdateModel, 'change:updateStatus', this.render);
this.listenTo(Events, 'theme-applied', this.render);
}
render() {
@ -72,7 +75,8 @@ class SettingsGeneralView extends View {
const storageProviders = this.getStorageProviders();
super.render({
themes: mapObject(SettingsManager.allThemes, (theme) => Locale[theme]),
themes: this.getAllThemes(),
autoSwitchTheme: AppSettingsModel.autoSwitchTheme,
activeTheme: SettingsManager.activeTheme,
locales: SettingsManager.allLocales,
activeLocale: SettingsManager.activeLocale,
@ -204,16 +208,49 @@ class SettingsGeneralView extends View {
}));
}
getAllThemes() {
const { autoSwitchTheme } = AppSettingsModel;
if (autoSwitchTheme) {
const themes = {};
const ignoredThemes = {};
for (const config of SettingsManager.autoSwitchedThemes) {
ignoredThemes[config.dark] = true;
ignoredThemes[config.light] = true;
const activeTheme = ThemeWatcher.dark ? config.dark : config.light;
themes[activeTheme] = Locale[config.name];
}
for (const [th, name] of Object.entries(SettingsManager.allThemes)) {
if (!ignoredThemes[th]) {
themes[th] = Locale[name];
}
}
return themes;
} else {
return mapObject(SettingsManager.allThemes, (theme) => Locale[theme]);
}
}
changeTheme(e) {
const theme = e.target.closest('.settings__general-theme').dataset.theme;
if (theme === '...') {
this.goToPlugins();
} else {
AppSettingsModel.theme = theme;
this.render();
const changedInSettings = AppSettingsModel.theme !== theme;
if (changedInSettings) {
AppSettingsModel.theme = theme;
} else {
SettingsManager.setTheme(theme);
}
}
}
changeAuthSwitchTheme(e) {
const autoSwitchTheme = e.target.checked;
AppSettingsModel.autoSwitchTheme = autoSwitchTheme;
SettingsManager.darkModeChanged();
this.render();
}
changeLocale(e) {
const locale = e.target.value;
if (locale === '...') {

View File

@ -66,6 +66,10 @@
</div>
</div>
</div>
<div>
<input type="checkbox" class="settings__input input-base settings__general-auto-switch-theme" id="settings__general-auto-switch-theme" {{#if autoSwitchTheme}}checked{{/if}} />
<label for="settings__general-auto-switch-theme">{{res 'setGenAutoSwitchTheme'}}</label>
</div>
<div>
<label for="settings__general-font-size">{{res 'setGenFontSize'}}:</label>
<select class="settings__general-font-size settings__select input-base" id="settings__general-font-size">

View File

@ -66,6 +66,10 @@ const themeBgColors = {
sd: '#002b36',
sl: '#fdf6e3'
};
const darkLightThemes = {
dark: 'light',
sd: 'sl'
};
const defaultBgColor = '#282C34';
logProgress('defining args');
@ -102,7 +106,6 @@ app.on('ready', () => {
settingsPromise
.then(() => {
setSystemAppearance();
createMainWindow();
setGlobalShortcuts(appSettings);
subscribePowerEvents();
@ -214,15 +217,6 @@ function logStartupMessage(msg) {
}
}
function setSystemAppearance() {
if (process.platform === 'darwin') {
if (electron.nativeTheme.shouldUseDarkColors) {
electron.systemPreferences.appLevelAppearance = 'dark';
}
}
logProgress('setting system appearance');
}
function checkSettingsTheme(theme) {
// old settings migration
if (theme === 'macdark') {
@ -235,14 +229,24 @@ function checkSettingsTheme(theme) {
}
function getDefaultTheme() {
if (process.platform === 'darwin' && !electron.nativeTheme.shouldUseDarkColors) {
return 'light';
}
return 'dark';
}
function selectDarkOrLightTheme(theme) {
const dark = electron.nativeTheme.shouldUseDarkColors;
for (const [darkTheme, lightTheme] of Object.entries(darkLightThemes)) {
if (darkTheme === theme || lightTheme === theme) {
return dark ? darkTheme : lightTheme;
}
}
return theme;
}
function createMainWindow() {
const theme = checkSettingsTheme(appSettings.theme) || getDefaultTheme();
let theme = checkSettingsTheme(appSettings.theme) || getDefaultTheme();
if (appSettings.autoSwitchTheme) {
theme = selectDarkOrLightTheme(theme);
}
const bgColor = themeBgColors[theme] || defaultBgColor;
const windowOptions = {
show: false,

View File

@ -1,6 +1,7 @@
Release notes
-------------
##### v1.17.0 (TBD)
`+` automatically switching between dark and light theme
`+` clear searchbox button
##### v1.16.5 (2020-12-18)