diff --git a/package.json b/package.json index 8c0682b6a..f1786e0c3 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "jshashes": "^1.0.7", "keytar-prebuild": "^4.0.4", "localforage": "^1.5.0", + "moment": "^2.20.1", "npm": "^5.5.1", "qrcode.react": "^0.7.2", "rc-progress": "^2.0.6", diff --git a/src/renderer/constants/action_types.js b/src/renderer/constants/action_types.js index 3c828f5cb..861957d36 100644 --- a/src/renderer/constants/action_types.js +++ b/src/renderer/constants/action_types.js @@ -95,6 +95,7 @@ export const SEARCH_CANCELLED = 'SEARCH_CANCELLED'; // Settings export const DAEMON_SETTINGS_RECEIVED = 'DAEMON_SETTINGS_RECEIVED'; export const CLIENT_SETTING_CHANGED = 'CLIENT_SETTING_CHANGED'; +export const UPDATE_IS_NIGHT = 'UPDATE_IS_NIGHT'; // User export const AUTHENTICATION_STARTED = 'AUTHENTICATION_STARTED'; diff --git a/src/renderer/constants/settings.js b/src/renderer/constants/settings.js index dcb90ab07..9d0040412 100644 --- a/src/renderer/constants/settings.js +++ b/src/renderer/constants/settings.js @@ -11,3 +11,4 @@ export const INSTANT_PURCHASE_ENABLED = 'instantPurchaseEnabled'; export const INSTANT_PURCHASE_MAX = 'instantPurchaseMax'; export const THEME = 'theme'; export const THEMES = 'themes'; +export const AUTOMATIC_DARK_MODE_ENABLED = 'automaticDarkModeEnabled'; diff --git a/src/renderer/index.js b/src/renderer/index.js index 06621e9de..55017fbc7 100644 --- a/src/renderer/index.js +++ b/src/renderer/index.js @@ -10,6 +10,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; import { doConditionalAuthNavigate, doDaemonReady, doShowSnackBar } from 'redux/actions/app'; +import { doUpdateIsNightAsync } from 'redux/actions/settings'; import { doNavigate } from 'redux/actions/navigation'; import { doDownloadLanguages } from 'redux/actions/settings'; import { doUserEmailVerify } from 'redux/actions/user'; @@ -90,6 +91,7 @@ document.addEventListener('click', event => { }); const init = () => { + app.store.dispatch(doUpdateIsNightAsync()); app.store.dispatch(doDownloadLanguages()); function onDaemonReady() { diff --git a/src/renderer/page/settings/index.js b/src/renderer/page/settings/index.js index 29d2b325c..ca277df82 100644 --- a/src/renderer/page/settings/index.js +++ b/src/renderer/page/settings/index.js @@ -28,6 +28,7 @@ const select = state => ({ themes: makeSelectClientSetting(settings.THEMES)(state), language: selectCurrentLanguage(state), languages: selectLanguages(state), + automaticDarkModeEnabled: makeSelectClientSetting(settings.AUTOMATIC_DARK_MODE_ENABLED)(state), }); const perform = dispatch => ({ diff --git a/src/renderer/page/settings/view.jsx b/src/renderer/page/settings/view.jsx index 67471841c..0b061e87a 100644 --- a/src/renderer/page/settings/view.jsx +++ b/src/renderer/page/settings/view.jsx @@ -65,6 +65,10 @@ class SettingsPage extends React.PureComponent { this.props.setClientSetting(settings.THEME, value); } + onAutomaticDarkModeChange(event) { + this.props.setClientSetting(settings.AUTOMATIC_DARK_MODE_ENABLED, event.target.checked); + } + onInstantPurchaseEnabledChange(enabled) { this.props.setClientSetting(settings.INSTANT_PURCHASE_ENABLED, enabled); } @@ -129,6 +133,7 @@ class SettingsPage extends React.PureComponent { showUnavailable, theme, themes, + automaticDarkModeEnabled, } = this.props; if (!daemonSettings || Object.keys(daemonSettings).length === 0) { @@ -317,6 +322,13 @@ class SettingsPage extends React.PureComponent { ))} + + diff --git a/src/renderer/redux/actions/settings.js b/src/renderer/redux/actions/settings.js index 7f0209132..9397efada 100644 --- a/src/renderer/redux/actions/settings.js +++ b/src/renderer/redux/actions/settings.js @@ -4,6 +4,9 @@ import Fs from 'fs'; import Http from 'http'; import Lbry from 'lbry'; +import moment from 'moment'; + +const UPDATE_IS_NIGHT_INTERVAL = 10 * 60 * 1000; export function doFetchDaemonSettings() { return dispatch => { @@ -51,6 +54,29 @@ export function doGetThemes() { }; } +export function doUpdateIsNightAsync() { + return dispatch => { + dispatch(doUpdateIsNight()); + const updateIsNightInterval = setInterval( + () => dispatch(doUpdateIsNight()), + UPDATE_IS_NIGHT_INTERVAL + ); + }; +} + +export function doUpdateIsNight() { + const momentNow = moment(); + return { + type: ACTIONS.UPDATE_IS_NIGHT, + data: { isNight: () => { + const startNightMoment = moment('19:00', 'HH:mm'); + const endNightMoment = moment('8:00', 'HH:mm'); + return !(momentNow.isAfter(endNightMoment) && momentNow.isBefore(startNightMoment)); + } + }, + }; +} + export function doDownloadLanguage(langFile) { return dispatch => { const destinationPath = `${app.i18n.directory}/${langFile}`; diff --git a/src/renderer/redux/reducers/settings.js b/src/renderer/redux/reducers/settings.js index 914404bb1..73cf69209 100644 --- a/src/renderer/redux/reducers/settings.js +++ b/src/renderer/redux/reducers/settings.js @@ -23,7 +23,9 @@ const defaultState = { language: getLocalStorageSetting(SETTINGS.LANGUAGE, 'en'), theme: getLocalStorageSetting(SETTINGS.THEME, 'light'), themes: getLocalStorageSetting(SETTINGS.THEMES, []), + automaticDarkModeEnabled: getLocalStorageSetting(SETTINGS.AUTOMATIC_DARK_MODE_ENABLED, false), }, + isNight: false, languages: {}, }; @@ -46,6 +48,11 @@ reducers[ACTIONS.CLIENT_SETTING_CHANGED] = (state, action) => { }); }; +reducers[ACTIONS.UPDATE_IS_NIGHT] = (state, action) => + Object.assign({}, state, { + isNight: action.data.isNight, + }); + reducers[ACTIONS.DOWNLOAD_LANGUAGE_SUCCEEDED] = (state, action) => { const languages = Object.assign({}, state.languages); const { language } = action.data; diff --git a/src/renderer/redux/selectors/settings.js b/src/renderer/redux/selectors/settings.js index 0e2887f59..9c90cfac2 100644 --- a/src/renderer/redux/selectors/settings.js +++ b/src/renderer/redux/selectors/settings.js @@ -18,7 +18,18 @@ export const selectShowNsfw = makeSelectClientSetting(SETTINGS.SHOW_NSFW); export const selectLanguages = createSelector(selectState, state => state.languages || {}); -export const selectThemePath = createSelector( - makeSelectClientSetting(SETTINGS.THEME), - theme => `${staticResourcesPath}/themes/${theme || 'light'}.css` +export const selectTheme = makeSelectClientSetting(SETTINGS.THEME); +export const selectAutomaticDarkModeEnabled = makeSelectClientSetting( + SETTINGS.AUTOMATIC_DARK_MODE_ENABLED +); +export const selectIsNight = createSelector(selectState, state => state.isNight); + +export const selectThemePath = createSelector( + selectTheme, + selectAutomaticDarkModeEnabled, + selectIsNight, + (theme, automaticDarkModeEnabled, isNight) => { + const dynamicTheme = automaticDarkModeEnabled && isNight ? 'dark' : theme; + return `${staticResourcesPath}/themes/${dynamicTheme || 'light'}.css`; + } );