diff --git a/ts/components/EmptyMessageView.tsx b/ts/components/EmptyMessageView.tsx
index 64d1599d2..1dfbd89cd 100644
--- a/ts/components/EmptyMessageView.tsx
+++ b/ts/components/EmptyMessageView.tsx
@@ -1,7 +1,7 @@
import { useSelector } from 'react-redux';
import styled from 'styled-components';
import { getLeftPaneConversationIdsCount } from '../state/selectors/conversations';
-import { getTheme } from '../state/selectors/theme';
+import { useIsDarkTheme } from '../state/selectors/theme';
import { isSignWithRecoveryPhrase } from '../util/storage';
import { Flex } from './basic/Flex';
import { Spacer2XL, SpacerXS } from './basic/Text';
@@ -67,7 +67,7 @@ const StyledNoConversations = styled(StyledP)`
`;
export const EmptyMessageView = () => {
- const theme = useSelector(getTheme);
+ const isDarkTheme = useIsDarkTheme();
const conversationCount = useSelector(getLeftPaneConversationIdsCount);
const isSignInWithRecoveryPhrase = isSignWithRecoveryPhrase();
@@ -89,7 +89,7 @@ export const EmptyMessageView = () => {
{window.i18n('onboardingAccountCreated')}
{window.i18n('onboardingBubbleWelcomeToSession')}
diff --git a/ts/components/SessionHeaderSearchInput.tsx b/ts/components/SessionHeaderSearchInput.tsx
index da7e355af..7333f3e69 100644
--- a/ts/components/SessionHeaderSearchInput.tsx
+++ b/ts/components/SessionHeaderSearchInput.tsx
@@ -1,6 +1,6 @@
import styled from 'styled-components';
-export const SessionHeaderSearchInput = styled.input<{ darkMode: boolean }>`
+export const SessionHeaderSearchInput = styled.input<{ isDarkTheme: boolean }>`
color: var(--search-bar-text-control-color);
background-color: var(--search-bar-background-color);
border: 1px solid var(--input-border-color);
@@ -25,7 +25,7 @@ export const SessionHeaderSearchInput = styled.input<{ darkMode: boolean }>`
&:focus {
border: solid 1px
- ${props => (props.darkMode ? 'var(--primary-color)' : 'var(--search-bar-text-user-color)')};
+ ${props => (props.isDarkTheme ? 'var(--primary-color)' : 'var(--search-bar-text-user-color)')};
color: var(--search-bar-text-user-color);
outline: none;
}
diff --git a/ts/components/SessionQRCode.tsx b/ts/components/SessionQRCode.tsx
index c80396f8c..d6f6a3c9e 100644
--- a/ts/components/SessionQRCode.tsx
+++ b/ts/components/SessionQRCode.tsx
@@ -1,10 +1,12 @@
import { isEmpty } from 'lodash';
-import { CSSProperties, MouseEvent, useCallback, useEffect, useState } from 'react';
+import { MouseEvent, useCallback, useEffect, useState } from 'react';
import { QRCode } from 'react-qrcode-logo';
import useMount from 'react-use/lib/useMount';
-import styled from 'styled-components';
+import styled, { CSSProperties } from 'styled-components';
+import { ThemeStateType } from '../themes/constants/colors';
import { THEME_GLOBALS, getThemeValue } from '../themes/globals';
import { saveQRCode } from '../util/saveQRCode';
+import { checkDarkTheme } from '../util/theme';
import { AnimatedFlex } from './basic/Flex';
/** AnimatedFlex because we fade in the QR code to hide the logo flickering on first render
@@ -37,7 +39,7 @@ export type SessionQRCodeProps = {
logoWidth?: number;
logoHeight?: number;
logoIsSVG?: boolean;
- theme?: string;
+ theme?: ThemeStateType;
ignoreTheme?: boolean;
ariaLabel?: string;
dataTestId?: string;
@@ -77,7 +79,7 @@ export function SessionQRCode(props: SessionQRCodeProps) {
svgString = svgString.replaceAll(
'black',
getThemeValue(
- theme.includes('dark') ? '--background-primary-color' : '--text-primary-color'
+ checkDarkTheme(theme) ? '--background-primary-color' : '--text-primary-color'
)
);
}
diff --git a/ts/components/conversation/SessionEmojiPanel.tsx b/ts/components/conversation/SessionEmojiPanel.tsx
index 1f6fc3463..402ae55f7 100644
--- a/ts/components/conversation/SessionEmojiPanel.tsx
+++ b/ts/components/conversation/SessionEmojiPanel.tsx
@@ -5,7 +5,7 @@ import { useSelector } from 'react-redux';
import styled from 'styled-components';
import { getPrimaryColor } from '../../state/selectors/primaryColor';
-import { getTheme, isDarkTheme } from '../../state/selectors/theme';
+import { useIsDarkTheme, useTheme } from '../../state/selectors/theme';
import {
COLORS,
ColorsType,
@@ -105,8 +105,8 @@ const pickerProps = {
export const SessionEmojiPanel = forwardRef((props: Props, ref) => {
const { onEmojiClicked, show, isModal = false, onKeyDown } = props;
const primaryColor = useSelector(getPrimaryColor);
- const theme = useSelector(getTheme);
- const isDarkMode = useSelector(isDarkTheme);
+ const theme = useTheme();
+ const isDarkTheme = useIsDarkTheme();
let panelBackgroundRGB = hexColorToRGB(THEMES.CLASSIC_DARK.COLOR1);
let panelTextRGB = hexColorToRGB(THEMES.CLASSIC_DARK.COLOR6);
@@ -142,7 +142,7 @@ export const SessionEmojiPanel = forwardRef((props: Props
ref={ref}
>
`
+const LastSeenBarContainer = styled.div<{ isDarkTheme?: boolean }>`
padding-bottom: 35px;
max-width: 300px;
align-self: center;
@@ -36,11 +36,11 @@ const LastSeenBarContainer = styled.div<{ darkMode?: boolean }>`
${LastSeenBar} {
background-color: ${props =>
- props.darkMode ? 'var(--primary-color)' : 'var(--text-primary-color)'};
+ props.isDarkTheme ? 'var(--primary-color)' : 'var(--text-primary-color)'};
}
${LastSeenText} {
- color: ${props => (props.darkMode ? 'var(--primary-color)' : 'var(--text-primary-color)')};
+ color: ${props => (props.isDarkTheme ? 'var(--primary-color)' : 'var(--text-primary-color)')};
}
`;
@@ -49,7 +49,7 @@ export const SessionLastSeenIndicator = (props: {
didScroll: boolean;
setDidScroll: (scroll: boolean) => void;
}) => {
- const darkMode = useSelector(isDarkTheme);
+ const isDarkTheme = useIsDarkTheme();
// if this unread-indicator is not unique it's going to cause issues
const quotedMessageToAnimate = useSelector(getQuotedMessageToAnimate);
const scrollToLoadedMessage = useScrollToLoadedMessage();
@@ -74,7 +74,7 @@ export const SessionLastSeenIndicator = (props: {
});
return (
-
+
{window.i18n('unreadMessages')}
diff --git a/ts/components/conversation/message/reactions/ReactionPopup.tsx b/ts/components/conversation/message/reactions/ReactionPopup.tsx
index 1c6409120..e5f57e3eb 100644
--- a/ts/components/conversation/message/reactions/ReactionPopup.tsx
+++ b/ts/components/conversation/message/reactions/ReactionPopup.tsx
@@ -1,10 +1,9 @@
import { useEffect, useState } from 'react';
-import { useSelector } from 'react-redux';
import styled from 'styled-components';
import { Data } from '../../../../data/data';
import { findAndFormatContact } from '../../../../models/message';
import { PubKey } from '../../../../session/types/PubKey';
-import { isDarkTheme } from '../../../../state/selectors/theme';
+import { useIsDarkTheme } from '../../../../state/selectors/theme';
import { nativeEmojiData } from '../../../../util/emoji';
export type TipPosition = 'center' | 'left' | 'right';
@@ -66,8 +65,8 @@ const StyledContacts = styled.span`
}
`;
-const StyledOthers = styled.span<{ darkMode: boolean }>`
- color: ${props => (props.darkMode ? 'var(--primary-color)' : 'var(--text-primary-color)')};
+const StyledOthers = styled.span<{ isDarkTheme: boolean }>`
+ color: ${props => (props.isDarkTheme ? 'var(--primary-color)' : 'var(--text-primary-color)')};
`;
const generateContactsString = async (
@@ -97,7 +96,7 @@ const generateContactsString = async (
};
const Contacts = (contacts: Array, count: number) => {
- const darkMode = useSelector(isDarkTheme);
+ const isDarkTheme = useIsDarkTheme();
if (!(contacts?.length > 0)) {
return null;
@@ -123,7 +122,7 @@ const Contacts = (contacts: Array, count: number) => {
return (
{window.i18n('reactionPopupMany', [contacts[0], contacts[1], contacts[3]])}{' '}
-
+
{window.i18n(reactors === 4 ? 'otherSingular' : 'otherPlural', [`${count - 3}`])}
{' '}
{window.i18n('reactionPopup')}
diff --git a/ts/components/dialog/BanOrUnbanUserDialog.tsx b/ts/components/dialog/BanOrUnbanUserDialog.tsx
index 7f55b9ca9..1c04f1819 100644
--- a/ts/components/dialog/BanOrUnbanUserDialog.tsx
+++ b/ts/components/dialog/BanOrUnbanUserDialog.tsx
@@ -1,5 +1,5 @@
import { ChangeEvent, useRef, useState } from 'react';
-import { useDispatch, useSelector } from 'react-redux';
+import { useDispatch } from 'react-redux';
import { useFocusMount } from '../../hooks/useFocusMount';
import { useConversationPropsById } from '../../hooks/useParamSelector';
@@ -12,7 +12,7 @@ import { getConversationController } from '../../session/conversations/Conversat
import { PubKey } from '../../session/types';
import { ToastUtils } from '../../session/utils';
import { BanType, updateBanOrUnbanUserModal } from '../../state/ducks/modalDialog';
-import { isDarkTheme } from '../../state/selectors/theme';
+import { useIsDarkTheme } from '../../state/selectors/theme';
import { SessionHeaderSearchInput } from '../SessionHeaderSearchInput';
import { SessionWrapperModal } from '../SessionWrapperModal';
import { Flex } from '../basic/Flex';
@@ -68,7 +68,7 @@ export const BanOrUnBanUserDialog = (props: {
const { i18n } = window;
const isBan = banType === 'ban';
const dispatch = useDispatch();
- const darkMode = useSelector(isDarkTheme);
+ const isDarkTheme = useIsDarkTheme();
const convo = getConversationController().get(conversationId);
const inputRef = useRef(null);
@@ -133,7 +133,7 @@ export const BanOrUnBanUserDialog = (props: {
{
const { conversationId } = props;
const dispatch = useDispatch();
- const darkMode = useSelector(isDarkTheme);
+ const isDarkTheme = useIsDarkTheme();
const convo = getConversationController().get(conversationId);
const [inputBoxValue, setInputBoxValue] = useState('');
@@ -85,7 +85,7 @@ export const AddModeratorsDialog = (props: Props) => {
Add Moderator:
{
- const theme = useSelector(getTheme);
+ const theme = useTheme();
+ const isDarkTheme = useIsDarkTheme();
return (
{
value={sessionID}
size={170}
backgroundColor={getThemeValue(
- theme.includes('dark') ? '--text-primary-color' : '--background-primary-color'
+ isDarkTheme ? '--text-primary-color' : '--background-primary-color'
)}
foregroundColor={getThemeValue(
- theme.includes('dark') ? '--background-primary-color' : '--text-primary-color'
+ isDarkTheme ? '--background-primary-color' : '--text-primary-color'
)}
logoImage={'./images/session/qr/brand.svg'}
logoWidth={40}
diff --git a/ts/components/leftpane/ActionsPanel.tsx b/ts/components/leftpane/ActionsPanel.tsx
index ccc7777e7..3e6241c91 100644
--- a/ts/components/leftpane/ActionsPanel.tsx
+++ b/ts/components/leftpane/ActionsPanel.tsx
@@ -43,7 +43,7 @@ import {
getFreshSwarmFor,
} from '../../session/apis/snode_api/snodePool';
import { ConfigurationSync } from '../../session/utils/job_runners/jobs/ConfigurationSyncJob';
-import { isDarkTheme } from '../../state/selectors/theme';
+import { useIsDarkTheme } from '../../state/selectors/theme';
import { switchThemeTo } from '../../themes/switchTheme';
import { ReleasedFeatures } from '../../util/releaseFeature';
import { getOppositeTheme } from '../../util/theme';
@@ -55,7 +55,7 @@ const Section = (props: { type: SectionType }) => {
const dispatch = useDispatch();
const { type } = props;
- const isDarkMode = useSelector(isDarkTheme);
+ const isDarkTheme = useIsDarkTheme();
const focusedSection = useSelector(getFocusedSection);
const isSelected = focusedSection === props.type;
@@ -132,7 +132,7 @@ const Section = (props: { type: SectionType }) => {
return (
{
- const theme = useSelector(getTheme);
+ const isDarkTheme = useIsDarkTheme();
const section = useSelector(getFocusedSection);
const isSignInWithRecoveryPhrase = isSignWithRecoveryPhrase();
const hideRecoveryPassword = useHideRecoveryPasswordEnabled();
@@ -112,7 +112,7 @@ export const LeftPaneBanner = () => {
{window.i18n('saveRecoveryPassword')}
diff --git a/ts/components/settings/SettingsThemeSwitcher.tsx b/ts/components/settings/SettingsThemeSwitcher.tsx
index 27e82705d..8b8f8c1b2 100644
--- a/ts/components/settings/SettingsThemeSwitcher.tsx
+++ b/ts/components/settings/SettingsThemeSwitcher.tsx
@@ -1,7 +1,7 @@
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';
import { getPrimaryColor } from '../../state/selectors/primaryColor';
-import { getTheme } from '../../state/selectors/theme';
+import { useTheme } from '../../state/selectors/theme';
import {
StyleSessionSwitcher,
getPrimaryColors,
@@ -73,7 +73,7 @@ const ThemePreview = (props: { style: StyleSessionSwitcher }) => {
const Themes = () => {
const themes = getThemeColors();
- const selectedTheme = useSelector(getTheme);
+ const selectedTheme = useTheme();
const dispatch = useDispatch();
return (
diff --git a/ts/components/settings/section/CategoryRecoveryPassword.tsx b/ts/components/settings/section/CategoryRecoveryPassword.tsx
index 9eab10090..3e82602dd 100644
--- a/ts/components/settings/section/CategoryRecoveryPassword.tsx
+++ b/ts/components/settings/section/CategoryRecoveryPassword.tsx
@@ -1,6 +1,6 @@
import { isEmpty } from 'lodash';
import { useState } from 'react';
-import { useDispatch, useSelector } from 'react-redux';
+import { useDispatch } from 'react-redux';
import useMount from 'react-use/lib/useMount';
import styled from 'styled-components';
import { usePasswordModal } from '../../../hooks/usePasswordModal';
@@ -8,7 +8,7 @@ import { mnDecode } from '../../../session/crypto/mnemonic';
import { updateHideRecoveryPasswordModel } from '../../../state/ducks/modalDialog';
import { showSettingsSection } from '../../../state/ducks/section';
import { useHideRecoveryPasswordEnabled } from '../../../state/selectors/settings';
-import { getTheme } from '../../../state/selectors/theme';
+import { useIsDarkTheme, useTheme } from '../../../state/selectors/theme';
import { THEME_GLOBALS, getThemeValue } from '../../../themes/globals';
import { getCurrentRecoveryPhrase } from '../../../util/storage';
import { SessionQRCode } from '../../SessionQRCode';
@@ -61,7 +61,8 @@ export const SettingsCategoryRecoveryPassword = () => {
dispatch(showSettingsSection('privacy'));
},
});
- const theme = useSelector(getTheme);
+ const theme = useTheme();
+ const isDarkTheme = useIsDarkTheme();
const fetchRecoverPhrase = () => {
const newRecoveryPhrase = getCurrentRecoveryPhrase();
@@ -103,10 +104,10 @@ export const SettingsCategoryRecoveryPassword = () => {
value={hexEncodedSeed}
size={240}
backgroundColor={getThemeValue(
- theme.includes('dark') ? '--text-primary-color' : '--background-primary-color'
+ isDarkTheme ? '--text-primary-color' : '--background-primary-color'
)}
foregroundColor={getThemeValue(
- theme.includes('dark') ? '--background-primary-color' : '--text-primary-color'
+ isDarkTheme ? '--background-primary-color' : '--text-primary-color'
)}
logoImage={'./images/session/qr/shield.svg'}
logoWidth={56}
@@ -124,7 +125,7 @@ export const SettingsCategoryRecoveryPassword = () => {
justifyContent={'space-between'}
alignItems={'center'}
width={'100%'}
- color={theme.includes('dark') ? 'var(--primary-color)' : 'var(--text-primary-color)'}
+ color={isDarkTheme ? 'var(--primary-color)' : 'var(--text-primary-color)'}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: THEME_GLOBALS['--default-duration-seconds'] }}
diff --git a/ts/receiver/queuedJob.ts b/ts/receiver/queuedJob.ts
index c730bd85a..b4b4c7980 100644
--- a/ts/receiver/queuedJob.ts
+++ b/ts/receiver/queuedJob.ts
@@ -102,9 +102,9 @@ async function copyFromQuotedMessage(
: quotedMessage.propsForMessage.text) || '';
if (isMessageModel(quotedMessage)) {
- window.inboxStore.dispatch(pushQuotedMessageDetails(quotedMessage.getMessageModelProps()));
+ window.inboxStore?.dispatch(pushQuotedMessageDetails(quotedMessage.getMessageModelProps()));
} else {
- window.inboxStore.dispatch(pushQuotedMessageDetails(quotedMessage));
+ window.inboxStore?.dispatch(pushQuotedMessageDetails(quotedMessage));
}
// no attachments, just save the quote with the body
diff --git a/ts/state/actions.ts b/ts/state/actions.ts
index 526a1de7c..8902cae87 100644
--- a/ts/state/actions.ts
+++ b/ts/state/actions.ts
@@ -1,12 +1,12 @@
import { bindActionCreators, Dispatch } from '@reduxjs/toolkit';
-import { actions as search } from './ducks/search';
import { actions as conversations } from './ducks/conversations';
-import { actions as user } from './ducks/user';
-import { actions as sections } from './ducks/section';
-import { actions as theme } from './ducks/theme';
import { actions as modalDialog } from './ducks/modalDialog';
import { actions as primaryColor } from './ducks/primaryColor';
+import { actions as search } from './ducks/search';
+import { actions as sections } from './ducks/section';
+import { actions as theme } from './ducks/theme';
+import { actions as user } from './ducks/user';
export function mapDispatchToProps(dispatch: Dispatch): object {
return {
diff --git a/ts/state/ducks/theme.tsx b/ts/state/ducks/theme.tsx
index 389bc44a1..1692d129a 100644
--- a/ts/state/ducks/theme.tsx
+++ b/ts/state/ducks/theme.tsx
@@ -1,34 +1,19 @@
+import { PayloadAction, createSlice } from '@reduxjs/toolkit';
import { ThemeStateType } from '../../themes/constants/colors';
-export const APPLY_THEME = 'APPLY_THEME';
+// TODO Move primary color into this slice
+export const initialThemeState: ThemeStateType = 'classic-dark' as ThemeStateType;
-export const applyTheme = (theme: ThemeStateType) => {
- return {
- type: APPLY_THEME,
- payload: theme,
- };
-};
+const themeSlice = createSlice({
+ name: 'theme',
+ initialState: initialThemeState,
+ reducers: {
+ updateTheme(_, action: PayloadAction) {
+ return action.payload;
+ },
+ },
+});
-export const initialThemeState: ThemeStateType = 'classic-dark';
-
-export const reducer = (
- state: any = initialThemeState,
- {
- type,
- payload,
- }: {
- type: string;
- payload: ThemeStateType;
- }
-): ThemeStateType => {
- switch (type) {
- case APPLY_THEME:
- return payload;
- default:
- return state;
- }
-};
-
-export const actions = {
- applyTheme,
-};
+export const { actions, reducer } = themeSlice;
+export const { updateTheme } = actions;
+export const defaultThemeReducer = reducer;
diff --git a/ts/state/selectors/theme.ts b/ts/state/selectors/theme.ts
index b1878f359..8856154d1 100644
--- a/ts/state/selectors/theme.ts
+++ b/ts/state/selectors/theme.ts
@@ -1,9 +1,17 @@
+import { createSelector } from '@reduxjs/toolkit';
+import { useSelector } from 'react-redux';
import { ThemeStateType } from '../../themes/constants/colors';
-import { StateType } from '../reducer';
import { checkDarkTheme, checkLightTheme } from '../../util/theme';
+import { StateType } from '../reducer';
export const getTheme = (state: StateType): ThemeStateType => state.theme;
-export const isDarkTheme = (state: StateType): boolean => checkDarkTheme(state.theme);
+const getIsDarkTheme = createSelector(getTheme, (state): boolean => checkDarkTheme(state));
+
+const getIsLightTheme = createSelector(getTheme, (state): boolean => checkLightTheme(state));
+
+export const useTheme = () => useSelector(getTheme);
+
+export const useIsDarkTheme = () => useSelector(getIsDarkTheme);
-export const isLightTheme = (state: StateType): boolean => checkLightTheme(state.theme);
+export const useIsLightTheme = () => useSelector(getIsLightTheme);
diff --git a/ts/themes/switchTheme.tsx b/ts/themes/switchTheme.tsx
index f82c2b3ef..13438beea 100644
--- a/ts/themes/switchTheme.tsx
+++ b/ts/themes/switchTheme.tsx
@@ -1,6 +1,6 @@
import { Dispatch } from '@reduxjs/toolkit';
import { classicDark, classicLight, oceanDark, oceanLight } from '.';
-import { applyTheme } from '../state/ducks/theme';
+import { updateTheme } from '../state/ducks/theme';
import { THEMES, ThemeStateType, convertThemeStateToName } from './constants/colors';
import { setThemeValues } from './globals';
import { findPrimaryColorId, switchPrimaryColorTo } from './switchPrimaryColor';
@@ -43,7 +43,7 @@ export async function switchThemeTo(props: SwitchThemeProps) {
}
if (dispatch) {
- dispatch(applyTheme(newTheme));
+ dispatch(updateTheme(newTheme));
if (usePrimaryColor) {
// Set primary color after the theme is loaded so that it's not overwritten
const primaryColor = window.Events.getPrimaryColorSetting();