feat: refactor theme state

added hooks for is light or dark theme and cleaned up any references
pull/3083/head
William Grant 10 months ago
parent c20bda442e
commit 4f44a7a5fa

@ -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 = () => {
<Spacer2XL />
<StyledHeading>{window.i18n('onboardingAccountCreated')}</StyledHeading>
<StyledSessionWelcome
color={theme.includes('dark') ? 'var(--primary-color)' : 'var(--text-primary-color)'}
color={isDarkTheme ? 'var(--primary-color)' : 'var(--text-primary-color)'}
>
{window.i18n('onboardingBubbleWelcomeToSession')}
</StyledSessionWelcome>

@ -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;
}

@ -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'
)
);
}

@ -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<HTMLDivElement, Props>((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<HTMLDivElement, Props>((props: Props
ref={ref}
>
<Picker
theme={isDarkMode ? 'dark' : 'light'}
theme={isDarkTheme ? 'dark' : 'light'}
i18n={i18nEmojiData}
onEmojiSelect={onEmojiClicked}
onKeyDown={onKeyDown}

@ -3,7 +3,7 @@ import { useSelector } from 'react-redux';
import styled from 'styled-components';
import { useScrollToLoadedMessage } from '../../contexts/ScrollToLoadedMessage';
import { getQuotedMessageToAnimate } from '../../state/selectors/conversations';
import { isDarkTheme } from '../../state/selectors/theme';
import { useIsDarkTheme } from '../../state/selectors/theme';
const LastSeenBar = styled.div`
height: 2px;
@ -24,7 +24,7 @@ const LastSeenText = styled.div`
margin-inline: 1rem;
`;
const LastSeenBarContainer = styled.div<{ darkMode?: boolean }>`
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 (
<LastSeenBarContainer id="unread-indicator" darkMode={darkMode}>
<LastSeenBarContainer id="unread-indicator" isDarkTheme={isDarkTheme}>
<LastSeenBar />
<LastSeenText>{window.i18n('unreadMessages')}</LastSeenText>

@ -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<string>, count: number) => {
const darkMode = useSelector(isDarkTheme);
const isDarkTheme = useIsDarkTheme();
if (!(contacts?.length > 0)) {
return null;
@ -123,7 +122,7 @@ const Contacts = (contacts: Array<string>, count: number) => {
return (
<StyledContacts>
{window.i18n('reactionPopupMany', [contacts[0], contacts[1], contacts[3]])}{' '}
<StyledOthers darkMode={darkMode}>
<StyledOthers isDarkTheme={isDarkTheme}>
{window.i18n(reactors === 4 ? 'otherSingular' : 'otherPlural', [`${count - 3}`])}
</StyledOthers>{' '}
<span>{window.i18n('reactionPopup')}</span>

@ -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: {
<SessionHeaderSearchInput
ref={inputRef}
type="text"
darkMode={darkMode}
isDarkTheme={isDarkTheme}
placeholder={i18n('accountIdEnter')}
dir="auto"
onChange={onPubkeyBoxChanges}

@ -1,12 +1,12 @@
import { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useDispatch } from 'react-redux';
import { sogsV3AddAdmin } from '../../session/apis/open_group_api/sogsv3/sogsV3AddRemoveMods';
import { getConversationController } from '../../session/conversations';
import { PubKey } from '../../session/types';
import { ToastUtils } from '../../session/utils';
import { updateAddModeratorsModal } 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';
@ -21,7 +21,7 @@ export const AddModeratorsDialog = (props: 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) => {
<p>Add Moderator:</p>
<SessionHeaderSearchInput
type="text"
darkMode={darkMode}
isDarkTheme={isDarkTheme}
placeholder={i18n('accountIdEnter')}
dir="auto"
onChange={onPubkeyBoxChanges}

@ -1,6 +1,5 @@
import { useSelector } from 'react-redux';
import styled from 'styled-components';
import { getTheme } from '../../../state/selectors/theme';
import { useIsDarkTheme, useTheme } from '../../../state/selectors/theme';
import { getThemeValue } from '../../../themes/globals';
import { SessionQRCode } from '../../SessionQRCode';
import { Avatar, AvatarSize } from '../../avatar/Avatar';
@ -9,7 +8,8 @@ import { SpacerSM } from '../../basic/Text';
import { SessionIconButton } from '../../icon';
export const QRView = ({ sessionID }: { sessionID: string }) => {
const theme = useSelector(getTheme);
const theme = useTheme();
const isDarkTheme = useIsDarkTheme();
return (
<SessionQRCode
@ -17,10 +17,10 @@ export const QRView = ({ sessionID }: { sessionID: string }) => {
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}

@ -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 (
<SessionIconButton
iconSize="medium"
iconType={isDarkMode ? 'moon' : 'sun'}
iconType={isDarkTheme ? 'moon' : 'sun'}
dataTestId="theme-section"
onClick={handleClick}
isSelected={isSelected}

@ -10,7 +10,7 @@ import {
import { disableRecoveryPhrasePrompt } from '../../state/ducks/userConfig';
import { getFocusedSection, getLeftOverlayMode } from '../../state/selectors/section';
import { useHideRecoveryPasswordEnabled } from '../../state/selectors/settings';
import { getTheme } from '../../state/selectors/theme';
import { useIsDarkTheme } from '../../state/selectors/theme';
import { getShowRecoveryPhrasePrompt } from '../../state/selectors/userConfig';
import { isSignWithRecoveryPhrase } from '../../util/storage';
import { Flex } from '../basic/Flex';
@ -80,7 +80,7 @@ const StyledLeftPaneBanner = styled.div`
`;
export const LeftPaneBanner = () => {
const theme = useSelector(getTheme);
const isDarkTheme = useIsDarkTheme();
const section = useSelector(getFocusedSection);
const isSignInWithRecoveryPhrase = isSignWithRecoveryPhrase();
const hideRecoveryPassword = useHideRecoveryPasswordEnabled();
@ -112,7 +112,7 @@ export const LeftPaneBanner = () => {
<Flex container={true} width={'100%'} alignItems="flex-start">
<StyledBannerTitle>{window.i18n('saveRecoveryPassword')}</StyledBannerTitle>
<SessionIcon
iconType={theme.includes('dark') ? 'recoveryPasswordFill' : 'recoveryPasswordOutline'}
iconType={isDarkTheme ? 'recoveryPasswordFill' : 'recoveryPasswordOutline'}
iconSize="medium"
iconColor="var(--text-primary-color)"
/>

@ -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 (

@ -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'] }}

@ -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

@ -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 {

@ -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<ThemeStateType>) {
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;

@ -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);

@ -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();

Loading…
Cancel
Save