feat: implement setting to follow system theme

feat: check theme congruence on startup and on native theme update

fix: make toggle and startup following work

fix: should return here, but this breaks things
pull/2957/head
Kee Jefferys 2 years ago
parent 954a1a7002
commit 2fe29ca30e

@ -222,6 +222,8 @@
"noteToSelf": "Note to Self",
"hideMenuBarTitle": "Hide Menu Bar",
"hideMenuBarDescription": "Toggle system menu bar visibility.",
"matchThemeSystemSettingTitle": "Auto night-mode",
"matchThemeSystemSettingDescription": "Match system settings",
"startConversation": "Start New Conversation",
"invalidNumberError": "Please check the Session ID or ONS name and try again",
"failedResolveOns": "Failed to resolve ONS name",

@ -43,6 +43,7 @@ import {
getFreshSwarmFor,
} from '../../session/apis/snode_api/snodePool';
import { isDarkTheme } from '../../state/selectors/theme';
import { getOppositeTheme, checkThemeCongruency } from '../../themes/SessionTheme';
import { ThemeStateType } from '../../themes/constants/colors';
import { switchThemeTo } from '../../themes/switchTheme';
import { ConfigurationSync } from '../../session/utils/job_runners/jobs/ConfigurationSyncJob';
@ -62,10 +63,7 @@ const Section = (props: { type: SectionType }) => {
dispatch(editProfileModal({}));
} else if (type === SectionType.ColorMode) {
const currentTheme = String(window.Events.getThemeSetting());
const newTheme = (isDarkMode
? currentTheme.replace('dark', 'light')
: currentTheme.replace('light', 'dark')) as ThemeStateType;
const newTheme = getOppositeTheme(currentTheme) as ThemeStateType;
// We want to persist the primary color when using the color mode button
void switchThemeTo({
theme: newTheme,
@ -149,14 +147,25 @@ const cleanUpMediasInterval = DURATION.MINUTES * 60;
const fetchReleaseFromFileServerInterval = 1000 * 60; // try to fetch the latest release from the fileserver every minute
const setupTheme = async () => {
const shouldFollowSystemTheme = window.getSettingValue(SettingsKey.hasFollowSystemThemeEnabled);
const theme = window.Events.getThemeSetting();
// We don't want to reset the primary color on startup
await switchThemeTo({
const themeConfig = {
theme,
mainWindow: true,
usePrimaryColor: true,
dispatch: window?.inboxStore?.dispatch || undefined,
});
};
if (shouldFollowSystemTheme) {
const themeIsCongruent = await checkThemeCongruency();
// Only set theme if it matches with native theme, otherwise handled by checkThemeCongruency()
if (!themeIsCongruent) {
await switchThemeTo(themeConfig);
}
return;
}
await switchThemeTo(themeConfig);
};
// Do this only if we created a new Session ID, or if we already received the initial configuration message

@ -3,13 +3,15 @@ import React from 'react';
import useUpdate from 'react-use/lib/useUpdate';
import { SettingsKey } from '../../../data/settings-key';
import { isHideMenuBarSupported } from '../../../types/Settings';
import { useHasFollowSystemThemeEnabled } from '../../../state/selectors/settings';
import { checkThemeCongruency } from '../../../themes/SessionTheme';
import { SessionToggleWithDescription } from '../SessionSettingListItem';
import { SettingsThemeSwitcher } from '../SettingsThemeSwitcher';
import { ZoomingSessionSlider } from '../ZoomingSessionSlider';
export const SettingsCategoryAppearance = (props: { hasPassword: boolean | null }) => {
const forceUpdate = useUpdate();
const isFollowSystemThemeEnabled = useHasFollowSystemThemeEnabled();
if (props.hasPassword !== null) {
const isHideMenuBarActive =
@ -32,6 +34,18 @@ export const SettingsCategoryAppearance = (props: { hasPassword: boolean | null
active={isHideMenuBarActive}
/>
)}
<SessionToggleWithDescription
onClickToggle={() => {
const toggledValue = !isFollowSystemThemeEnabled;
void window.setSettingValue(SettingsKey.hasFollowSystemThemeEnabled, toggledValue);
if (!isFollowSystemThemeEnabled) {
void checkThemeCongruency();
}
}}
title={window.i18n('matchThemeSystemSettingTitle')}
description={window.i18n('matchThemeSystemSettingDescription')}
active={isFollowSystemThemeEnabled}
/>
</>
);
}

@ -14,6 +14,7 @@ const someDeviceOutdatedSyncing = 'someDeviceOutdatedSyncing';
const hasSyncedInitialConfigurationItem = 'hasSyncedInitialConfigurationItem';
const lastAvatarUploadTimestamp = 'lastAvatarUploadTimestamp';
const hasLinkPreviewPopupBeenDisplayed = 'hasLinkPreviewPopupBeenDisplayed';
const hasFollowSystemThemeEnabled = 'hasFollowSystemThemeEnabled';
// user config tracking timestamps (to discard incoming messages which would make a change we reverted in the last config message we merged)
const latestUserProfileEnvelopeTimestamp = 'latestUserProfileEnvelopeTimestamp';
@ -39,6 +40,7 @@ export const SettingsKey = {
latestUserProfileEnvelopeTimestamp,
latestUserGroupEnvelopeTimestamp,
latestUserContactsEnvelopeTimestamp,
hasFollowSystemThemeEnabled,
} as const;
export const KNOWN_BLINDED_KEYS_ITEM = 'KNOWN_BLINDED_KEYS_ITEM';

@ -10,6 +10,7 @@ import {
dialog,
ipcMain as ipc,
Menu,
nativeTheme,
protocol as electronProtocol,
screen,
shell,
@ -1117,6 +1118,15 @@ ipc.on('set-auto-update-setting', async (_event, enabled) => {
}
});
ipc.on('get-native-theme', event => {
event.sender.send('send-native-theme', nativeTheme.shouldUseDarkColors);
});
nativeTheme.on('updated', () => {
// Inform all renderer processes of the theme change
mainWindow?.webContents.send('native-theme-update', nativeTheme.shouldUseDarkColors);
});
async function getThemeFromMainWindow() {
return new Promise(resolve => {
ipc.once('get-success-theme-setting', (_event, value) => {

@ -1,7 +1,7 @@
import { ipcRenderer } from 'electron';
import _ from 'lodash';
import ReactDOM from 'react-dom';
import Backbone from 'backbone';
import React from 'react';
import nativeEmojiData from '@emoji-mart/data';
@ -27,6 +27,9 @@ import { switchPrimaryColorTo } from '../themes/switchPrimaryColor';
import { LibSessionUtil } from '../session/utils/libsession/libsession_utils';
import { runners } from '../session/utils/job_runners/JobRunner';
import { SettingsKey } from '../data/settings-key';
import { getOppositeTheme } from '../themes/SessionTheme';
import { switchThemeTo } from '../themes/switchTheme';
import { ThemeStateType } from '../themes/constants/colors';
// Globally disable drag and drop
document.body.addEventListener(
@ -109,6 +112,26 @@ function mapOldThemeToNew(theme: string) {
return theme;
}
}
// using __unused as lodash is imported using _
ipcRenderer.on('native-theme-update', (__unused, shouldUseDarkColors) => {
const shouldFollowSystemTheme = window.getSettingValue(SettingsKey.hasFollowSystemThemeEnabled);
if (shouldFollowSystemTheme) {
const theme = window.Events.getThemeSetting();
if (
(shouldUseDarkColors && theme.includes('light')) ||
(!shouldUseDarkColors && theme.includes('dark'))
) {
const newTheme = getOppositeTheme(theme) as ThemeStateType;
void switchThemeTo({
theme: newTheme,
mainWindow: true,
usePrimaryColor: true,
dispatch: window?.inboxStore?.dispatch || undefined,
});
}
}
});
async function startJobRunners() {
// start the job runners

@ -8,6 +8,7 @@ const SettingsBoolsKeyTrackedInRedux = [
SettingsKey.someDeviceOutdatedSyncing,
SettingsKey.settingsLinkPreview,
SettingsKey.hasBlindedMsgRequestsEnabled,
SettingsKey.hasFollowSystemThemeEnabled,
] as const;
export type SettingsState = {
@ -20,6 +21,7 @@ export function getSettingsInitialState() {
someDeviceOutdatedSyncing: false,
'link-preview-setting': false, // this is the value of SettingsKey.settingsLinkPreview
hasBlindedMsgRequestsEnabled: false,
hasFollowSystemThemeEnabled: false,
},
};
}
@ -47,6 +49,10 @@ const settingsSlice = createSlice({
SettingsKey.hasBlindedMsgRequestsEnabled,
false
);
const hasFollowSystemThemeEnabled = Storage.get(
SettingsKey.hasFollowSystemThemeEnabled,
false
);
state.settingsBools.someDeviceOutdatedSyncing = isBoolean(outdatedSync)
? outdatedSync
: false;
@ -54,6 +60,9 @@ const settingsSlice = createSlice({
state.settingsBools.hasBlindedMsgRequestsEnabled = isBoolean(hasBlindedMsgRequestsEnabled)
? hasBlindedMsgRequestsEnabled
: false;
state.settingsBools.hasFollowSystemThemeEnabled = isBoolean(hasFollowSystemThemeEnabled)
? hasFollowSystemThemeEnabled
: false;
return state;
},
updateSettingsBoolValue(state, action: PayloadAction<{ id: string; value: boolean }>) {

@ -11,6 +11,9 @@ const getHasDeviceOutdatedSyncing = (state: StateType) =>
const getHasBlindedMsgRequestsEnabled = (state: StateType) =>
state.settings.settingsBools[SettingsKey.hasBlindedMsgRequestsEnabled];
const getHasFollowSystemThemeEnabled = (state: StateType) =>
state.settings.settingsBools[SettingsKey.hasFollowSystemThemeEnabled];
export const useHasLinkPreviewEnabled = () => {
const value = useSelector(getLinkPreviewEnabled);
return Boolean(value);
@ -25,3 +28,8 @@ export const useHasBlindedMsgRequestsEnabled = () => {
const value = useSelector(getHasBlindedMsgRequestsEnabled);
return Boolean(value);
};
export const useHasFollowSystemThemeEnabled = () => {
const value = useSelector(getHasFollowSystemThemeEnabled);
return Boolean(value);
};

@ -1,6 +1,8 @@
import { ipcRenderer } from 'electron';
import React from 'react';
import { createGlobalStyle } from 'styled-components';
import { switchThemeTo } from './switchTheme';
import { ThemeStateType } from './constants/colors';
import { classicDark } from './classicDark';
import { declareCSSVariables, THEME_GLOBALS } from './globals';
@ -18,3 +20,39 @@ export const SessionTheme = ({ children }: { children: any }) => (
{children}
</>
);
export function getOppositeTheme(themeName: string) {
if (themeName.includes('dark')) {
return themeName.replace('dark', 'light');
}
if (themeName.includes('light')) {
return themeName.replace('light', 'dark');
}
// If neither 'dark' nor 'light' is in the theme name, return the original theme name.
return themeName;
}
export async function checkThemeCongruency(): Promise<boolean> {
const theme = window.Events.getThemeSetting();
return new Promise(resolve => {
ipcRenderer.send('get-native-theme');
ipcRenderer.once('send-native-theme', (_, shouldUseDarkColors) => {
const isMismatchedTheme =
(shouldUseDarkColors && theme.includes('light')) ||
(!shouldUseDarkColors && theme.includes('dark'));
if (isMismatchedTheme) {
const newTheme = getOppositeTheme(theme) as ThemeStateType;
void switchThemeTo({
theme: newTheme,
mainWindow: true,
usePrimaryColor: true,
dispatch: window?.inboxStore?.dispatch || undefined,
});
resolve(true); // Theme was switched
} else {
resolve(false); // Theme was not switched
}
});
});
}

@ -241,6 +241,8 @@ export type LocalizerKeys =
| 'mainMenuWindow'
| 'markAllAsRead'
| 'markUnread'
| 'matchThemeSystemSettingDescription'
| 'matchThemeSystemSettingTitle'
| 'maxPasswordAttempts'
| 'maximumAttachments'
| 'media'

Loading…
Cancel
Save