Merge pull request #2957 from KeeJef/follow-system-theme
feat: Add setting to follow system theming
This commit is contained in:
commit
173d01c4e8
|
@ -223,6 +223,8 @@
|
|||
"savedMessages": "Saved Messages",
|
||||
"hideMenuBarTitle": "Hide Menu Bar",
|
||||
"hideMenuBarDescription": "Toggle system menu bar visibility.",
|
||||
"matchThemeSystemSettingTitle": "Auto dark-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",
|
||||
|
|
|
@ -834,11 +834,11 @@
|
|||
|
||||
color: var(--white-color);
|
||||
|
||||
font-size: 20px;
|
||||
font-weight: normal;
|
||||
letter-spacing: 0;
|
||||
text-align: center;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
font-size: 20px;
|
||||
font-weight: normal;
|
||||
letter-spacing: 0;
|
||||
text-align: center;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
.module-image__close-button {
|
||||
|
|
|
@ -43,7 +43,8 @@ import {
|
|||
getFreshSwarmFor,
|
||||
} from '../../session/apis/snode_api/snodePool';
|
||||
import { isDarkTheme } from '../../state/selectors/theme';
|
||||
import { ThemeStateType } from '../../themes/constants/colors';
|
||||
import { ensureThemeConsistency } from '../../themes/SessionTheme';
|
||||
import { getOppositeTheme } from '../../util/theme';
|
||||
import { switchThemeTo } from '../../themes/switchTheme';
|
||||
import { ConfigurationSync } from '../../session/utils/job_runners/jobs/ConfigurationSyncJob';
|
||||
|
||||
|
@ -61,11 +62,8 @@ const Section = (props: { type: SectionType }) => {
|
|||
if (type === SectionType.Profile) {
|
||||
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 currentTheme = window.Events.getThemeSetting();
|
||||
const newTheme = getOppositeTheme(currentTheme);
|
||||
// We want to persist the primary color when using the color mode button
|
||||
void switchThemeTo({
|
||||
theme: newTheme,
|
||||
|
@ -149,14 +147,26 @@ 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) {
|
||||
// Check if system theme matches currently set theme, if not switch it and return true, if matching return false
|
||||
const wasThemeSwitched = await ensureThemeConsistency();
|
||||
if (!wasThemeSwitched) {
|
||||
// if theme wasn't switched them set theme to default
|
||||
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 { ensureThemeConsistency } 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,20 @@ export const SettingsCategoryAppearance = (props: { hasPassword: boolean | null
|
|||
active={isHideMenuBarActive}
|
||||
/>
|
||||
)}
|
||||
<SessionToggleWithDescription
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
onClickToggle={async () => {
|
||||
const toggledValue = !isFollowSystemThemeEnabled;
|
||||
await window.setSettingValue(SettingsKey.hasFollowSystemThemeEnabled, toggledValue);
|
||||
if (!isFollowSystemThemeEnabled) {
|
||||
await ensureThemeConsistency();
|
||||
}
|
||||
}}
|
||||
title={window.i18n('matchThemeSystemSettingTitle')}
|
||||
description={window.i18n('matchThemeSystemSettingDescription')}
|
||||
active={isFollowSystemThemeEnabled}
|
||||
dataTestId="enable-follow-system-theme"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
@ -40,6 +41,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,8 @@ 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, isThemeMismatched } from '../util/theme';
|
||||
import { switchThemeTo } from '../themes/switchTheme';
|
||||
|
||||
// Globally disable drag and drop
|
||||
document.body.addEventListener(
|
||||
|
@ -109,6 +111,23 @@ 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 (isThemeMismatched(theme, shouldUseDarkColors)) {
|
||||
const newTheme = getOppositeTheme(theme);
|
||||
void switchThemeTo({
|
||||
theme: newTheme,
|
||||
mainWindow: true,
|
||||
usePrimaryColor: true,
|
||||
dispatch: window?.inboxStore?.dispatch,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
async function startJobRunners() {
|
||||
// start the job runners
|
||||
|
|
|
@ -8,6 +8,7 @@ const SettingsBoolsKeyTrackedInRedux = [
|
|||
SettingsKey.someDeviceOutdatedSyncing,
|
||||
SettingsKey.settingsLinkPreview,
|
||||
SettingsKey.hasBlindedMsgRequestsEnabled,
|
||||
SettingsKey.hasFollowSystemThemeEnabled,
|
||||
SettingsKey.hasShiftSendEnabled,
|
||||
] as const;
|
||||
|
||||
|
@ -21,6 +22,7 @@ export function getSettingsInitialState() {
|
|||
someDeviceOutdatedSyncing: false,
|
||||
'link-preview-setting': false, // this is the value of SettingsKey.settingsLinkPreview
|
||||
hasBlindedMsgRequestsEnabled: false,
|
||||
hasFollowSystemThemeEnabled: false,
|
||||
hasShiftSendEnabled: false,
|
||||
},
|
||||
};
|
||||
|
@ -49,8 +51,11 @@ const settingsSlice = createSlice({
|
|||
SettingsKey.hasBlindedMsgRequestsEnabled,
|
||||
false
|
||||
);
|
||||
const hasFollowSystemThemeEnabled = Storage.get(
|
||||
SettingsKey.hasFollowSystemThemeEnabled,
|
||||
false
|
||||
);
|
||||
const hasShiftSendEnabled = Storage.get(SettingsKey.hasShiftSendEnabled, false);
|
||||
|
||||
state.settingsBools.someDeviceOutdatedSyncing = isBoolean(outdatedSync)
|
||||
? outdatedSync
|
||||
: false;
|
||||
|
@ -58,9 +63,15 @@ const settingsSlice = createSlice({
|
|||
state.settingsBools.hasBlindedMsgRequestsEnabled = isBoolean(hasBlindedMsgRequestsEnabled)
|
||||
? hasBlindedMsgRequestsEnabled
|
||||
: false;
|
||||
|
||||
state.settingsBools.hasFollowSystemThemeEnabled = isBoolean(hasFollowSystemThemeEnabled)
|
||||
? hasFollowSystemThemeEnabled
|
||||
: false;
|
||||
|
||||
state.settingsBools.hasShiftSendEnabled = isBoolean(hasShiftSendEnabled)
|
||||
? hasShiftSendEnabled
|
||||
: 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];
|
||||
|
||||
const getHasShiftSendEnabled = (state: StateType) =>
|
||||
state.settings.settingsBools[SettingsKey.hasShiftSendEnabled];
|
||||
|
||||
|
@ -29,7 +32,13 @@ export const useHasBlindedMsgRequestsEnabled = () => {
|
|||
return Boolean(value);
|
||||
};
|
||||
|
||||
export const useHasEnterSendEnabled = () => {
|
||||
const value = useSelector(getHasShiftSendEnabled);
|
||||
export const useHasFollowSystemThemeEnabled = () => {
|
||||
const value = useSelector(getHasFollowSystemThemeEnabled);
|
||||
return Boolean(value);
|
||||
};
|
||||
|
||||
export const useHasEnterSendEnabled = () => {
|
||||
const value = useSelector(getHasShiftSendEnabled);
|
||||
|
||||
return Boolean(value);
|
||||
};
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { ThemeStateType } from '../../themes/constants/colors';
|
||||
import { StateType } from '../reducer';
|
||||
import { checkDarkTheme, checkLightTheme } from '../../util/theme';
|
||||
|
||||
export const getTheme = (state: StateType): ThemeStateType => state.theme;
|
||||
|
||||
export const isDarkTheme = (state: StateType): boolean => state.theme.includes('dark');
|
||||
export const isDarkTheme = (state: StateType): boolean => checkDarkTheme(state.theme);
|
||||
|
||||
export const isLightTheme = (state: StateType): boolean => state.theme.includes('light');
|
||||
export const isLightTheme = (state: StateType): boolean => checkLightTheme(state.theme);
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import { ipcRenderer } from 'electron';
|
||||
import React from 'react';
|
||||
|
||||
import { createGlobalStyle } from 'styled-components';
|
||||
import { switchThemeTo } from './switchTheme';
|
||||
import { classicDark } from './classicDark';
|
||||
import { getOppositeTheme, isThemeMismatched } from '../util/theme';
|
||||
import { declareCSSVariables, THEME_GLOBALS } from './globals';
|
||||
|
||||
// Defaults to Classic Dark theme
|
||||
|
@ -18,3 +20,26 @@ export const SessionTheme = ({ children }: { children: any }) => (
|
|||
{children}
|
||||
</>
|
||||
);
|
||||
|
||||
export async function ensureThemeConsistency(): Promise<boolean> {
|
||||
const theme = window.Events.getThemeSetting();
|
||||
|
||||
return new Promise(resolve => {
|
||||
ipcRenderer.send('get-native-theme');
|
||||
ipcRenderer.once('send-native-theme', (_, shouldUseDarkColors) => {
|
||||
const isMismatchedTheme = isThemeMismatched(theme, shouldUseDarkColors);
|
||||
if (isMismatchedTheme) {
|
||||
const newTheme = getOppositeTheme(theme);
|
||||
void switchThemeTo({
|
||||
theme: newTheme,
|
||||
mainWindow: true,
|
||||
usePrimaryColor: true,
|
||||
dispatch: window?.inboxStore?.dispatch,
|
||||
});
|
||||
resolve(true); // Theme was switched
|
||||
} else {
|
||||
resolve(false); // Theme was not switched
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -245,6 +245,8 @@ export type LocalizerKeys =
|
|||
| 'mainMenuWindow'
|
||||
| 'markAllAsRead'
|
||||
| 'markUnread'
|
||||
| 'matchThemeSystemSettingDescription'
|
||||
| 'matchThemeSystemSettingTitle'
|
||||
| 'maxPasswordAttempts'
|
||||
| 'maximumAttachments'
|
||||
| 'media'
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import { ThemeStateType } from '../themes/constants/colors';
|
||||
|
||||
export const checkDarkTheme = (theme: ThemeStateType): boolean => theme.includes('dark');
|
||||
export const checkLightTheme = (theme: ThemeStateType): boolean => theme.includes('light');
|
||||
|
||||
export function getOppositeTheme(themeName: ThemeStateType): ThemeStateType {
|
||||
if (checkDarkTheme(themeName)) {
|
||||
return themeName.replace('dark', 'light') as ThemeStateType;
|
||||
}
|
||||
if (checkLightTheme(themeName)) {
|
||||
return themeName.replace('light', 'dark') as ThemeStateType;
|
||||
}
|
||||
// If neither 'dark' nor 'light' is in the theme name, return the original theme name.
|
||||
return themeName as ThemeStateType;
|
||||
}
|
||||
|
||||
export function isThemeMismatched(themeName: ThemeStateType, prefersDark: boolean): boolean {
|
||||
const systemLightTheme = checkLightTheme(themeName);
|
||||
const systemDarkTheme = checkDarkTheme(themeName);
|
||||
return (prefersDark && systemLightTheme) || (!prefersDark && systemDarkTheme);
|
||||
}
|
Loading…
Reference in New Issue