From 49451dba208bbac894a4c85147caf796189aa6c4 Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Thu, 21 May 2020 00:47:38 +0100 Subject: [PATCH] Desktop: Resolves #2665: Add support for system theme auto-switching --- ElectronClient/app.js | 22 ++++++++ ElectronClient/bridge.js | 14 ++++- ReactNativeClient/lib/models/Setting.js | 73 ++++++++++++++++++++----- 3 files changed, 93 insertions(+), 16 deletions(-) diff --git a/ElectronClient/app.js b/ElectronClient/app.js index 34314e9da..b9d4dcba3 100644 --- a/ElectronClient/app.js +++ b/ElectronClient/app.js @@ -59,6 +59,8 @@ class Application extends BaseApplication { constructor() { super(); this.lastMenuScreen_ = null; + + this.bridge_nativeThemeUpdated = this.bridge_nativeThemeUpdated.bind(this); } hasGui() { @@ -311,11 +313,25 @@ class Application extends BaseApplication { await Folder.expandTree(newState.folders, action.folderId); } + if (this.hasGui() && ((action.type == 'SETTING_UPDATE_ONE' && ['themeAutoDetect', 'theme', 'preferredLightTheme', 'preferredDarkTheme'].includes(action.key)) || action.type == 'SETTING_UPDATE_ALL')) { + this.handleThemeAutoDetect(); + } + if (mustUpdateMenuItemStates) this.updateMenuItemStates(newState); return result; } + handleThemeAutoDetect() { + if (!Setting.value('themeAutoDetect')) return; + + if (bridge().shouldUseDarkColors()) { + Setting.setValue('theme', Setting.value('preferredDarkTheme')); + } else { + Setting.setValue('theme', Setting.value('preferredLightTheme')); + } + } + async refreshMenu() { const screen = this.lastMenuScreen_; this.lastMenuScreen_ = null; @@ -1263,6 +1279,10 @@ class Application extends BaseApplication { menuItem.checked = state.devToolsVisible; } + bridge_nativeThemeUpdated() { + this.handleThemeAutoDetect(); + } + updateTray() { const app = bridge().electronApp(); @@ -1489,6 +1509,8 @@ class Application extends BaseApplication { window.revisionService = RevisionService.instance(); window.migrationService = MigrationService.instance(); window.decryptionWorker = DecryptionWorker.instance(); + + bridge().addEventListener('nativeThemeUpdated', this.bridge_nativeThemeUpdated); } } diff --git a/ElectronClient/bridge.js b/ElectronClient/bridge.js index 2325b27e5..5100c8bf8 100644 --- a/ElectronClient/bridge.js +++ b/ElectronClient/bridge.js @@ -1,6 +1,6 @@ const { _, setLocale } = require('lib/locale.js'); const { dirname } = require('lib/path-utils.js'); -const { BrowserWindow } = require('electron'); +const { BrowserWindow, nativeTheme } = require('electron'); class Bridge { @@ -170,6 +170,18 @@ class Bridge { return require('electron').screen; } + shouldUseDarkColors() { + return nativeTheme.shouldUseDarkColors; + } + + addEventListener(name, fn) { + if (name === 'nativeThemeUpdated') { + nativeTheme.on('updated', fn); + } else { + throw new Error(`Unsupported event: ${name}`); + } + } + } let bridge_ = null; diff --git a/ReactNativeClient/lib/models/Setting.js b/ReactNativeClient/lib/models/Setting.js index 0712925fd..ddec027e3 100644 --- a/ReactNativeClient/lib/models/Setting.js +++ b/ReactNativeClient/lib/models/Setting.js @@ -37,6 +37,21 @@ class Setting extends BaseModel { // if if private a setting might still be handled and modified by the app. For instance, the settings related to sorting notes are not // public for the mobile and desktop apps because they are handled separately in menus. + const themeOptions = () => { + const output = {}; + output[Setting.THEME_LIGHT] = _('Light'); + output[Setting.THEME_DARK] = _('Dark'); + if (platform !== mobilePlatform) { + output[Setting.THEME_DRACULA] = _('Dracula'); + output[Setting.THEME_SOLARIZED_LIGHT] = _('Solarised Light'); + output[Setting.THEME_SOLARIZED_DARK] = _('Solarised Dark'); + output[Setting.THEME_NORD] = _('Nord'); + } else { + output[Setting.THEME_OLED_DARK] = _('OLED Dark'); + } + return output; + }; + this.metadata_ = { 'clientId': { value: '', @@ -228,30 +243,58 @@ class Setting extends BaseModel { return options; }, }, + theme: { value: Setting.THEME_LIGHT, type: Setting.TYPE_INT, public: true, appTypes: ['mobile', 'desktop'], + show: (settings) => { + return !settings['themeAutoDetect']; + }, isEnum: true, label: () => _('Theme'), section: 'appearance', - options: () => { - const output = {}; - output[Setting.THEME_LIGHT] = _('Light'); - output[Setting.THEME_DARK] = _('Dark'); - if (platform !== mobilePlatform) { - output[Setting.THEME_DRACULA] = _('Dracula'); - output[Setting.THEME_SOLARIZED_LIGHT] = _('Solarised Light'); - output[Setting.THEME_SOLARIZED_DARK] = _('Solarised Dark'); - output[Setting.THEME_NORD] = _('Nord'); - output[Setting.THEME_ARITIM_DARK] = _('Aritim Dark'); - } else { - output[Setting.THEME_OLED_DARK] = _('OLED Dark'); - } - return output; - }, + options: () => themeOptions(), }, + + themeAutoDetect: { + value: false, + type: Setting.TYPE_BOOL, + section: 'appearance', + appTypes: ['desktop'], + public: true, + label: () => _('Automatically switch theme to match system theme'), + }, + + preferredLightTheme: { + value: Setting.THEME_LIGHT, + type: Setting.TYPE_INT, + public: true, + show: (settings) => { + return settings['themeAutoDetect']; + }, + appTypes: ['desktop'], + isEnum: true, + label: () => _('Preferred light theme'), + section: 'appearance', + options: () => themeOptions(), + }, + + preferredDarkTheme: { + value: Setting.THEME_DARK, + type: Setting.TYPE_INT, + public: true, + show: (settings) => { + return settings['themeAutoDetect']; + }, + appTypes: ['desktop'], + isEnum: true, + label: () => _('Preferred dark theme'), + section: 'appearance', + options: () => themeOptions(), + }, + showNoteCounts: { value: true, type: Setting.TYPE_BOOL, public: true, advanced: true, appTypes: ['desktop'], label: () => _('Show note counts') }, layoutButtonSequence: { value: Setting.LAYOUT_ALL,