Skip to content

Commit

Permalink
feat: implement setting to follow system theme
Browse files Browse the repository at this point in the history
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
  • Loading branch information
KeeJef committed Oct 20, 2023
1 parent 954a1a7 commit 2fe29ca
Show file tree
Hide file tree
Showing 10 changed files with 127 additions and 10 deletions.
2 changes: 2 additions & 0 deletions _locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
23 changes: 16 additions & 7 deletions ts/components/leftpane/ActionsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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,
Expand Down Expand Up @@ -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
Expand Down
16 changes: 15 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 { 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 =
Expand All @@ -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}
/>
</>
);
}
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 @@ -39,6 +40,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
25 changes: 24 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,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(
Expand Down Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions 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,
] as const;

export type SettingsState = {
Expand All @@ -20,6 +21,7 @@ export function getSettingsInitialState() {
someDeviceOutdatedSyncing: false,
'link-preview-setting': false, // this is the value of SettingsKey.settingsLinkPreview
hasBlindedMsgRequestsEnabled: false,
hasFollowSystemThemeEnabled: false,
},
};
}
Expand Down Expand Up @@ -47,13 +49,20 @@ const settingsSlice = createSlice({
SettingsKey.hasBlindedMsgRequestsEnabled,
false
);
const hasFollowSystemThemeEnabled = Storage.get(
SettingsKey.hasFollowSystemThemeEnabled,
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;
return state;
},
updateSettingsBoolValue(state, action: PayloadAction<{ id: string; value: boolean }>) {
Expand Down
8 changes: 8 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];

export const useHasLinkPreviewEnabled = () => {
const value = useSelector(getLinkPreviewEnabled);
return Boolean(value);
Expand All @@ -25,3 +28,8 @@ export const useHasBlindedMsgRequestsEnabled = () => {
const value = useSelector(getHasBlindedMsgRequestsEnabled);
return Boolean(value);
};

export const useHasFollowSystemThemeEnabled = () => {
const value = useSelector(getHasFollowSystemThemeEnabled);
return Boolean(value);
};
40 changes: 39 additions & 1 deletion ts/themes/SessionTheme.tsx
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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
}
});
});
}
2 changes: 2 additions & 0 deletions ts/types/LocalizerKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,8 @@ export type LocalizerKeys =
| 'mainMenuWindow'
| 'markAllAsRead'
| 'markUnread'
| 'matchThemeSystemSettingDescription'
| 'matchThemeSystemSettingTitle'
| 'maxPasswordAttempts'
| 'maximumAttachments'
| 'media'
Expand Down

0 comments on commit 2fe29ca

Please sign in to comment.