Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add setting to follow system theming #2957

Merged
merged 7 commits into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions _locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
10 changes: 5 additions & 5 deletions stylesheets/_modules.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
28 changes: 19 additions & 9 deletions ts/components/leftpane/ActionsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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,
Expand Down Expand Up @@ -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
Expand Down
18 changes: 17 additions & 1 deletion ts/components/settings/section/CategoryAppearance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Bilb marked this conversation as resolved.
Show resolved Hide resolved

if (props.hasPassword !== null) {
const isHideMenuBarActive =
Expand All @@ -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"
/>
</>
);
}
Expand Down
2 changes: 2 additions & 0 deletions ts/data/settings-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -40,6 +41,7 @@ export const SettingsKey = {
latestUserProfileEnvelopeTimestamp,
latestUserGroupEnvelopeTimestamp,
latestUserContactsEnvelopeTimestamp,
hasFollowSystemThemeEnabled,
} as const;

export const KNOWN_BLINDED_KEYS_ITEM = 'KNOWN_BLINDED_KEYS_ITEM';
Expand Down
10 changes: 10 additions & 0 deletions ts/mains/main_node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
dialog,
ipcMain as ipc,
Menu,
nativeTheme,
protocol as electronProtocol,
screen,
shell,
Expand Down Expand Up @@ -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) => {
Expand Down
21 changes: 20 additions & 1 deletion ts/mains/main_renderer.tsx
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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(
Expand Down Expand Up @@ -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
Expand Down
13 changes: 12 additions & 1 deletion ts/state/ducks/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const SettingsBoolsKeyTrackedInRedux = [
SettingsKey.someDeviceOutdatedSyncing,
SettingsKey.settingsLinkPreview,
SettingsKey.hasBlindedMsgRequestsEnabled,
SettingsKey.hasFollowSystemThemeEnabled,
SettingsKey.hasShiftSendEnabled,
] as const;

Expand All @@ -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,
},
};
Expand Down Expand Up @@ -49,18 +51,27 @@ 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;
state.settingsBools['link-preview-setting'] = isBoolean(linkPreview) ? linkPreview : false; // this is the value of SettingsKey.settingsLinkPreview
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 }>) {
Expand Down
9 changes: 9 additions & 0 deletions ts/state/selectors/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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];

Expand All @@ -29,7 +32,13 @@ export const useHasBlindedMsgRequestsEnabled = () => {
return Boolean(value);
};

export const useHasFollowSystemThemeEnabled = () => {
const value = useSelector(getHasFollowSystemThemeEnabled);
return Boolean(value);
};

export const useHasEnterSendEnabled = () => {
const value = useSelector(getHasShiftSendEnabled);

return Boolean(value);
};
5 changes: 3 additions & 2 deletions ts/state/selectors/theme.ts
Original file line number Diff line number Diff line change
@@ -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);
27 changes: 26 additions & 1 deletion ts/themes/SessionTheme.tsx
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
}
});
});
}
2 changes: 2 additions & 0 deletions ts/types/LocalizerKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,8 @@ export type LocalizerKeys =
| 'mainMenuWindow'
| 'markAllAsRead'
| 'markUnread'
| 'matchThemeSystemSettingDescription'
| 'matchThemeSystemSettingTitle'
| 'maxPasswordAttempts'
| 'maximumAttachments'
| 'media'
Expand Down
21 changes: 21 additions & 0 deletions ts/util/theme.ts
Original file line number Diff line number Diff line change
@@ -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