feat: add theme switcher +accent color from settings
not all of the logic is linked and not all of the colors are there as this is part of theming
This commit is contained in:
parent
f137cad2a0
commit
1e202fcdab
|
@ -140,6 +140,19 @@
|
|||
"typingIndicatorsSettingDescription": "See and share typing indicators in one-to-one chats.",
|
||||
"typingIndicatorsSettingTitle": "Typing Indicators",
|
||||
"zoomFactorSettingTitle": "Zoom Factor",
|
||||
"themesSettingTitle": "Themes",
|
||||
"primaryColor": "Primary Color",
|
||||
"primaryColorGreen": "Primary color green",
|
||||
"primaryColorBlue": "Primary color blue",
|
||||
"primaryColorYellow": "Primary color yellow",
|
||||
"primaryColorPink": "Primary color pink",
|
||||
"primaryColorPurple": "Primary color purple",
|
||||
"primaryColorOrange": "Primary color orange",
|
||||
"primaryColorRed": "Primary color red",
|
||||
"classicDarkThemeTitle": "Classic Dark",
|
||||
"classicLightThemeTitle": "Classic Light",
|
||||
"oceanDarkThemeTitle": "Ocean Dark",
|
||||
"oceanLightThemeTitle": "Ocean Light",
|
||||
"pruneSettingTitle": "Trim Communities",
|
||||
"pruneSettingDescription": "Delete messages from Communities older than 6 months, and where there are over 2,000 messages.",
|
||||
"pruningOpengroupDialogTitle": "Community pruning",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, { ChangeEvent } from 'react';
|
||||
import styled, { CSSProperties } from 'styled-components';
|
||||
import styled from 'styled-components';
|
||||
import { Flex } from '../basic/Flex';
|
||||
// tslint:disable: react-unused-props-and-state
|
||||
|
||||
|
@ -11,37 +11,43 @@ type Props = {
|
|||
onClick?: (value: string) => void;
|
||||
};
|
||||
|
||||
const StyledInput = styled.input`
|
||||
const StyledInput = styled.input<{
|
||||
filledSize: number;
|
||||
outlineOffset: number;
|
||||
selectedColor: string;
|
||||
}>`
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
width: calc(var(--filled-size) + var(--outline-offset));
|
||||
height: calc(var(--filled-size) + var(--outline-offset));
|
||||
width: ${props => props.filledSize + props.outlineOffset}px;
|
||||
height: ${props => props.filledSize + props.outlineOffset}px;
|
||||
|
||||
:checked + label:before,
|
||||
:hover + label:before {
|
||||
background: var(--color-accent);
|
||||
background: ${props => props.selectedColor};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledLabel = styled.label`
|
||||
const StyledLabel = styled.label<{
|
||||
selectedColor: string;
|
||||
filledSize: number;
|
||||
outlineOffset: number;
|
||||
}>`
|
||||
cursor: pointer;
|
||||
|
||||
:before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
margin-inline-end: var(--filled-size);
|
||||
border-radius: 100%;
|
||||
|
||||
transition: var(--default-duration);
|
||||
padding: calc(var(--filled-size) / 2);
|
||||
outline-offset: 2px;
|
||||
padding: ${props => props.filledSize}px;
|
||||
outline: var(--color-text) solid 1px;
|
||||
border: none;
|
||||
margin-top: var(--filled-size);
|
||||
outline-offset: ${props => props.outlineOffset}px;
|
||||
|
||||
:hover {
|
||||
background: var(--color-accent);
|
||||
background: ${props => props.selectedColor};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -53,20 +59,16 @@ export const SessionRadio = (props: Props) => {
|
|||
if (onClick) {
|
||||
// let something else catch the event if our click handler is not set
|
||||
e.stopPropagation();
|
||||
onClick?.(value);
|
||||
onClick(value);
|
||||
}
|
||||
}
|
||||
|
||||
const selectedColor = 'var(--color-accent)';
|
||||
const filledSize = 15 / 2;
|
||||
const outlineOffset = 2;
|
||||
|
||||
return (
|
||||
<Flex
|
||||
container={true}
|
||||
padding="0 0 5px"
|
||||
style={
|
||||
{
|
||||
'--filled-size': '15px',
|
||||
} as CSSProperties
|
||||
}
|
||||
>
|
||||
<Flex container={true} padding="0 0 0 var(--margins-lg)">
|
||||
<StyledInput
|
||||
type="radio"
|
||||
name={inputName || ''}
|
||||
|
@ -74,11 +76,85 @@ export const SessionRadio = (props: Props) => {
|
|||
aria-checked={active}
|
||||
checked={active}
|
||||
onChange={clickHandler}
|
||||
filledSize={filledSize}
|
||||
outlineOffset={outlineOffset}
|
||||
selectedColor={selectedColor}
|
||||
/>
|
||||
|
||||
<StyledLabel role="button" onClick={clickHandler}>
|
||||
<StyledLabel
|
||||
role="button"
|
||||
onClick={clickHandler}
|
||||
selectedColor={selectedColor}
|
||||
filledSize={filledSize}
|
||||
outlineOffset={outlineOffset}
|
||||
>
|
||||
{label}
|
||||
</StyledLabel>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const StyledInputOutlineSelected = styled(StyledInput)`
|
||||
label:before,
|
||||
label:before {
|
||||
outline: none;
|
||||
}
|
||||
:checked + label:before {
|
||||
outline: var(--color-text) solid 1px;
|
||||
}
|
||||
`;
|
||||
const StyledLabelOutlineSelected = styled(StyledLabel)<{ selectedColor: string }>`
|
||||
:before {
|
||||
background: ${props => props.selectedColor};
|
||||
outline: #0000 solid 1px;
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* Keeping this component here so we can reuse the `StyledInput` and `StyledLabel` defined locally rather than exporting them
|
||||
*/
|
||||
export const SessionRadioPrimaryColors = (props: {
|
||||
value: string;
|
||||
active: boolean;
|
||||
inputName?: string;
|
||||
onClick: (value: string) => void;
|
||||
ariaLabel: string;
|
||||
color: string; // by default, we use the theme accent color but for the settings screen we need to be able to force it
|
||||
}) => {
|
||||
const { inputName, value, active, onClick, color, ariaLabel } = props;
|
||||
|
||||
function clickHandler(e: ChangeEvent<any>) {
|
||||
e.stopPropagation();
|
||||
onClick(value);
|
||||
}
|
||||
|
||||
const filledSize = 31 / 2;
|
||||
const outlineOffset = 5;
|
||||
|
||||
return (
|
||||
<Flex container={true} padding="0 0 5px 0">
|
||||
<StyledInputOutlineSelected
|
||||
type="radio"
|
||||
name={inputName || ''}
|
||||
value={value}
|
||||
aria-checked={active}
|
||||
checked={active}
|
||||
onChange={clickHandler}
|
||||
filledSize={filledSize}
|
||||
outlineOffset={outlineOffset}
|
||||
selectedColor={color}
|
||||
aria-label={ariaLabel}
|
||||
/>
|
||||
|
||||
<StyledLabelOutlineSelected
|
||||
role="button"
|
||||
onClick={clickHandler}
|
||||
selectedColor={color}
|
||||
filledSize={filledSize}
|
||||
outlineOffset={outlineOffset}
|
||||
>
|
||||
{''}
|
||||
</StyledLabelOutlineSelected>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -18,7 +18,6 @@ import {
|
|||
getOurPrimaryConversation,
|
||||
getUnreadMessageCount,
|
||||
} from '../../state/selectors/conversations';
|
||||
import { applyTheme } from '../../state/ducks/theme';
|
||||
import { getFocusedSection } from '../../state/selectors/section';
|
||||
import { clearSearch } from '../../state/ducks/search';
|
||||
import { resetOverlayMode, SectionType, showLeftPaneSection } from '../../state/ducks/section';
|
||||
|
@ -39,7 +38,6 @@ import { debounce, isEmpty, isString } from 'lodash';
|
|||
// tslint:disable-next-line: no-import-side-effect no-submodule-imports
|
||||
|
||||
import { ActionPanelOnionStatusLight } from '../dialog/OnionStatusPathDialog';
|
||||
import { switchHtmlToDarkTheme, switchHtmlToLightTheme } from '../../state/ducks/SessionTheme';
|
||||
import { loadDefaultRooms } from '../../session/apis/open_group_api/opengroupV2/ApiUtil';
|
||||
import { getOpenGroupManager } from '../../session/apis/open_group_api/opengroupV2/OpenGroupManagerV2';
|
||||
import { getSwarmPollingInstance } from '../../session/apis/snode_api';
|
||||
|
@ -57,6 +55,7 @@ import { UserUtils } from '../../session/utils';
|
|||
import { Storage } from '../../util/storage';
|
||||
import { SettingsKey } from '../../data/settings-key';
|
||||
import { getLatestReleaseFromFileServer } from '../../session/apis/file_server_api/FileServerApi';
|
||||
import { switchThemeTo } from '../../session/utils/Theme';
|
||||
|
||||
const Section = (props: { type: SectionType }) => {
|
||||
const ourNumber = useSelector(getOurNumber);
|
||||
|
@ -67,22 +66,15 @@ const Section = (props: { type: SectionType }) => {
|
|||
const focusedSection = useSelector(getFocusedSection);
|
||||
const isSelected = focusedSection === props.type;
|
||||
|
||||
const handleClick = () => {
|
||||
const handleClick = async () => {
|
||||
/* tslint:disable:no-void-expression */
|
||||
if (type === SectionType.Profile) {
|
||||
dispatch(editProfileModal({}));
|
||||
} else if (type === SectionType.Moon) {
|
||||
const themeFromSettings = window.Events.getThemeSetting();
|
||||
const updatedTheme = themeFromSettings === 'dark' ? 'light' : 'dark';
|
||||
window.setTheme(updatedTheme);
|
||||
if (updatedTheme === 'dark') {
|
||||
switchHtmlToDarkTheme();
|
||||
} else {
|
||||
switchHtmlToLightTheme();
|
||||
}
|
||||
const currentTheme = window.Events.getThemeSetting();
|
||||
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
||||
|
||||
const newThemeObject = updatedTheme === 'dark' ? 'dark' : 'light';
|
||||
dispatch(applyTheme(newThemeObject));
|
||||
await switchThemeTo(newTheme, dispatch);
|
||||
} else if (type === SectionType.PathIndicator) {
|
||||
// Show Path Indicator Modal
|
||||
dispatch(onionPathModal({}));
|
||||
|
@ -163,16 +155,9 @@ const cleanUpMediasInterval = DURATION.MINUTES * 60;
|
|||
// * if there is a version on the fileserver more recent than our current, we fetch github to get the UpdateInfos and trigger an update as usual (asking user via dialog)
|
||||
const fetchReleaseFromFileServerInterval = 1000 * 60; // try to fetch the latest release from the fileserver every minute
|
||||
|
||||
const setupTheme = () => {
|
||||
const setupTheme = async () => {
|
||||
const theme = window.Events.getThemeSetting();
|
||||
window.setTheme(theme);
|
||||
if (theme === 'dark') {
|
||||
switchHtmlToDarkTheme();
|
||||
} else {
|
||||
switchHtmlToLightTheme();
|
||||
}
|
||||
const newThemeObject = theme === 'dark' ? 'dark' : 'light';
|
||||
window?.inboxStore?.dispatch(applyTheme(newThemeObject));
|
||||
await switchThemeTo(theme, window?.inboxStore?.dispatch || null);
|
||||
};
|
||||
|
||||
// Do this only if we created a new Session ID, or if we already received the initial configuration message
|
||||
|
|
|
@ -15,7 +15,7 @@ type ButtonSettingsProps = {
|
|||
onClick: () => void;
|
||||
};
|
||||
|
||||
const StyledDescription = styled.div`
|
||||
export const StyledDescriptionSettingsItem = styled.div`
|
||||
font-family: var(--font-default);
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 400;
|
||||
|
@ -23,7 +23,7 @@ const StyledDescription = styled.div`
|
|||
color: var(--color-text-subtle);
|
||||
`;
|
||||
|
||||
const StyledTitle = styled.div`
|
||||
export const StyledTitleSettingsItem = styled.div`
|
||||
line-height: 1.7;
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: bold;
|
||||
|
@ -33,7 +33,7 @@ const StyledInfo = styled.div`
|
|||
padding-inline-end: var(--margins-lg);
|
||||
`;
|
||||
|
||||
const StyledDescriptionContainer = styled(StyledDescription)`
|
||||
const StyledDescriptionContainer = styled(StyledDescriptionSettingsItem)`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`;
|
||||
|
@ -69,9 +69,11 @@ export const SettingsTitleAndDescription = (props: {
|
|||
const { description, childrenDescription, title } = props;
|
||||
return (
|
||||
<StyledInfo>
|
||||
<StyledTitle>{title}</StyledTitle>
|
||||
<StyledTitleSettingsItem>{title}</StyledTitleSettingsItem>
|
||||
<StyledDescriptionContainer>
|
||||
{description && <StyledDescription>{description}</StyledDescription>}
|
||||
{description && (
|
||||
<StyledDescriptionSettingsItem>{description}</StyledDescriptionSettingsItem>
|
||||
)}
|
||||
<>{childrenDescription}</>
|
||||
</StyledDescriptionContainer>
|
||||
</StyledInfo>
|
||||
|
|
|
@ -0,0 +1,203 @@
|
|||
import React, { useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import styled from 'styled-components';
|
||||
import { switchThemeTo } from '../../session/utils/Theme';
|
||||
import {
|
||||
darkColorReceivedMessageBg,
|
||||
darkColorSentMessageBg,
|
||||
getPrimaryColors,
|
||||
lightColorReceivedMessageBg,
|
||||
lightColorSentMessageBg,
|
||||
OceanBlueDark,
|
||||
OceanBlueLight,
|
||||
PrimaryColorIds,
|
||||
} from '../../state/ducks/SessionTheme';
|
||||
import { ThemeStateType } from '../../state/ducks/theme';
|
||||
import { getTheme } from '../../state/selectors/theme';
|
||||
import { SessionRadio, SessionRadioPrimaryColors } from '../basic/SessionRadio';
|
||||
import { SpacerLG, SpacerMD } from '../basic/Text';
|
||||
import { StyledDescriptionSettingsItem, StyledTitleSettingsItem } from './SessionSettingListItem';
|
||||
|
||||
// tslint:disable: use-simple-attributes
|
||||
|
||||
const StyledSwitcherContainer = styled.div`
|
||||
font-size: var(--font-size-md);
|
||||
padding: var(--margins-lg);
|
||||
background: var(--color-cell-background);
|
||||
`;
|
||||
|
||||
const ThemeContainer = styled.button`
|
||||
background: var(--color-conversation-list);
|
||||
border: 1px solid var(--color-clickable-hovered);
|
||||
border-radius: 8px;
|
||||
padding: var(--margins-sm);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
width: 285px;
|
||||
height: 90px;
|
||||
|
||||
transition: var(--default-duration);
|
||||
|
||||
:hover {
|
||||
background: var(--color-clickable-hovered);
|
||||
}
|
||||
`;
|
||||
|
||||
const ThemesContainer = styled.div`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--margins-lg);
|
||||
`;
|
||||
|
||||
type ThemeType = {
|
||||
id: ThemeStateType;
|
||||
title: string;
|
||||
style: StyleSessionSwitcher;
|
||||
};
|
||||
|
||||
type StyleSessionSwitcher = {
|
||||
background: string;
|
||||
border: string;
|
||||
receivedBg: string;
|
||||
sentBg: string;
|
||||
};
|
||||
|
||||
const StyledPreview = styled.svg`
|
||||
max-height: 100%;
|
||||
`;
|
||||
|
||||
const ThemePreview = (props: { style: StyleSessionSwitcher }) => {
|
||||
return (
|
||||
<StyledPreview xmlSpace="preserve" viewBox="0 0 80 72" fill={props.style.background}>
|
||||
<path
|
||||
stroke={props.style.border}
|
||||
d="M7.5.9h64.6c3.6 0 6.5 2.9 6.5 6.5v56.9c0 3.6-2.9 6.5-6.5 6.5H7.5c-3.6 0-6.5-2.9-6.5-6.5V7.4C1 3.9 3.9.9 7.5.9z"
|
||||
/>
|
||||
<path
|
||||
fill={props.style.receivedBg}
|
||||
d="M8.7 27.9c0-3.2 2.6-5.7 5.7-5.7h30.4c3.2 0 5.7 2.6 5.7 5.7 0 3.2-2.6 5.7-5.7 5.7H14.4c-3.1.1-5.7-2.5-5.7-5.7z"
|
||||
/>
|
||||
<path
|
||||
fill={props.style.sentBg}
|
||||
d="M32.6 42.2c0-3.2 2.6-5.7 5.7-5.7h27c3.2 0 5.7 2.6 5.7 5.7 0 3.2-2.6 5.7-5.7 5.7h-27c-3.1 0-5.7-2.5-5.7-5.7z"
|
||||
/>
|
||||
</StyledPreview>
|
||||
);
|
||||
};
|
||||
|
||||
const Themes = (props: { selectedAccent?: PrimaryColorIds }) => {
|
||||
const { selectedAccent } = props;
|
||||
|
||||
// I am not too sure if we want to override the accent color on the Theme switcher of not.
|
||||
// If we do, we also need a way to rollback to the default, I guess?
|
||||
const overridenAccent = selectedAccent
|
||||
? getPrimaryColors().find(e => {
|
||||
return e.id === selectedAccent;
|
||||
})?.color
|
||||
: undefined;
|
||||
|
||||
const themes: Array<ThemeType> = [
|
||||
{
|
||||
id: 'dark',
|
||||
title: window.i18n('classicDarkThemeTitle'),
|
||||
style: {
|
||||
background: '#000000',
|
||||
border: '#414141',
|
||||
receivedBg: darkColorReceivedMessageBg,
|
||||
sentBg: overridenAccent || darkColorSentMessageBg,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'light',
|
||||
title: window.i18n('classicLightThemeTitle'),
|
||||
style: {
|
||||
background: '#ffffff',
|
||||
border: '#414141',
|
||||
receivedBg: lightColorReceivedMessageBg,
|
||||
sentBg: overridenAccent || lightColorSentMessageBg,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'ocean-dark',
|
||||
title: window.i18n('oceanDarkThemeTitle'),
|
||||
style: {
|
||||
background: OceanBlueDark.background,
|
||||
border: OceanBlueDark.border,
|
||||
receivedBg: OceanBlueDark.received,
|
||||
sentBg: overridenAccent || OceanBlueDark.sent,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'ocean-light',
|
||||
title: window.i18n('oceanLightThemeTitle'),
|
||||
style: {
|
||||
background: OceanBlueLight.background,
|
||||
border: OceanBlueLight.border,
|
||||
receivedBg: OceanBlueLight.received,
|
||||
sentBg: overridenAccent || OceanBlueLight.sent,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const selectedTheme = useSelector(getTheme);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
return (
|
||||
<>
|
||||
{themes.map(theme => {
|
||||
function onSelectTheme() {
|
||||
void switchThemeTo(theme.id, dispatch);
|
||||
}
|
||||
return (
|
||||
<ThemeContainer key={theme.id} onClick={onSelectTheme}>
|
||||
<ThemePreview style={theme.style} />
|
||||
<SpacerLG />
|
||||
|
||||
<StyledTitleSettingsItem>{theme.title}</StyledTitleSettingsItem>
|
||||
<SessionRadio
|
||||
active={selectedTheme === theme.id}
|
||||
label={''}
|
||||
value={theme.id}
|
||||
inputName={'theme-switcher'}
|
||||
/>
|
||||
</ThemeContainer>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const SettingsThemeSwitcher = () => {
|
||||
//FIXME store that value somewhere in the theme object
|
||||
const [selectedAccent, setSelectedAccent] = useState<PrimaryColorIds | undefined>(undefined);
|
||||
|
||||
return (
|
||||
<StyledSwitcherContainer>
|
||||
<StyledTitleSettingsItem>{window.i18n('themesSettingTitle')}</StyledTitleSettingsItem>
|
||||
<ThemesContainer>
|
||||
<Themes selectedAccent={selectedAccent} />
|
||||
</ThemesContainer>
|
||||
<SpacerMD />
|
||||
<StyledDescriptionSettingsItem>{window.i18n('primaryColor')}</StyledDescriptionSettingsItem>
|
||||
<SpacerMD />
|
||||
<ThemesContainer style={{ marginInlineStart: 'var(--margins-xs)' }}>
|
||||
{getPrimaryColors().map(item => {
|
||||
return (
|
||||
<SessionRadioPrimaryColors
|
||||
key={item.id}
|
||||
active={item.id === selectedAccent}
|
||||
value={item.id}
|
||||
inputName="primary-colors"
|
||||
ariaLabel={item.ariaLabel}
|
||||
color={item.color}
|
||||
onClick={() => {
|
||||
setSelectedAccent(item.id);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</ThemesContainer>
|
||||
</StyledSwitcherContainer>
|
||||
);
|
||||
};
|
|
@ -5,6 +5,7 @@ import { SettingsKey } from '../../../data/settings-key';
|
|||
import { isHideMenuBarSupported } from '../../../types/Settings';
|
||||
|
||||
import { SessionToggleWithDescription } from '../SessionSettingListItem';
|
||||
import { SettingsThemeSwitcher } from '../SettingsThemeSwitcher';
|
||||
import { ZoomingSessionSlider } from '../ZoomingSessionSlider';
|
||||
|
||||
export const SettingsCategoryAppearance = (props: { hasPassword: boolean | null }) => {
|
||||
|
@ -18,7 +19,7 @@ export const SettingsCategoryAppearance = (props: { hasPassword: boolean | null
|
|||
|
||||
return (
|
||||
<>
|
||||
{/* TODO: add theme switching here */}
|
||||
<SettingsThemeSwitcher />
|
||||
<ZoomingSessionSlider />
|
||||
{isHideMenuBarSupported() && (
|
||||
<SessionToggleWithDescription
|
||||
|
|
|
@ -169,7 +169,7 @@ Storage.onready(async () => {
|
|||
|
||||
const themeSetting = window.Events.getThemeSetting();
|
||||
const newThemeSetting = mapOldThemeToNew(themeSetting);
|
||||
window.Events.setThemeSetting(newThemeSetting);
|
||||
await window.Events.setThemeSetting(newThemeSetting);
|
||||
|
||||
try {
|
||||
initialiseEmojiData(nativeEmojiData);
|
||||
|
@ -281,8 +281,8 @@ async function start() {
|
|||
// tslint:disable-next-line: restrict-plus-operands
|
||||
const launchCount = !prevLaunchCount ? 1 : prevLaunchCount + 1;
|
||||
|
||||
window.setTheme = newTheme => {
|
||||
window.Events.setThemeSetting(newTheme);
|
||||
window.setTheme = async newTheme => {
|
||||
await window.Events.setThemeSetting(newTheme);
|
||||
};
|
||||
|
||||
window.toggleMenuBar = () => {
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import { Dispatch } from 'redux';
|
||||
import { switchHtmlToDarkTheme, switchHtmlToLightTheme } from '../../state/ducks/SessionTheme';
|
||||
import { applyTheme, ThemeStateType } from '../../state/ducks/theme';
|
||||
|
||||
export async function switchThemeTo(theme: ThemeStateType, dispatch: Dispatch | null) {
|
||||
await window.setTheme(theme);
|
||||
|
||||
// for now, do not switch to ocean light nor dark theme as the SessionTheme associated with them is not complete
|
||||
let newTheme: ThemeStateType | null = null;
|
||||
|
||||
switch (theme) {
|
||||
case 'dark':
|
||||
switchHtmlToDarkTheme();
|
||||
newTheme = 'dark';
|
||||
break;
|
||||
case 'light':
|
||||
switchHtmlToLightTheme();
|
||||
newTheme = 'light';
|
||||
break;
|
||||
|
||||
default:
|
||||
window.log.warn('Unsupported theme: ', theme);
|
||||
}
|
||||
|
||||
if (newTheme) {
|
||||
dispatch?.(applyTheme(newTheme));
|
||||
}
|
||||
}
|
|
@ -24,7 +24,7 @@ const darkColorTextSubtle = `${white}99`;
|
|||
const darkColorTextAccent = accentDarkTheme;
|
||||
const darkColorSessionShadow = `0 0 4px 0 ${white}33`;
|
||||
const darkColorComposeViewBg = '#232323';
|
||||
const darkColorSentMessageBg = accentDarkTheme;
|
||||
export const darkColorSentMessageBg = accentDarkTheme;
|
||||
const darkColorClickableHovered = '#414347';
|
||||
const darkColorSessionBorder = `1px solid ${borderDarkThemeColor}`;
|
||||
const darkColorSessionBorderColor = borderDarkThemeColor;
|
||||
|
@ -33,7 +33,7 @@ const darkColorPillDivider = '#353535';
|
|||
const darkColorLastSeenIndicator = accentDarkTheme;
|
||||
const darkColorQuoteBottomBarBg = '#404040';
|
||||
const darkColorCellBackground = '#1b1b1b';
|
||||
const darkColorReceivedMessageBg = '#2d2d2d';
|
||||
export const darkColorReceivedMessageBg = '#2d2d2d';
|
||||
const darkColorReceivedMessageText = white;
|
||||
|
||||
const darkColorPillDividerText = '#a0a0a0';
|
||||
|
@ -170,7 +170,7 @@ const lightColorTextSubtle = `${black}99`;
|
|||
const lightColorTextAccent = accentLightTheme;
|
||||
const lightColorSessionShadow = `0 0 4px 0 ${black}5E`;
|
||||
const lightColorComposeViewBg = '#efefef';
|
||||
const lightColorSentMessageBg = accentLightTheme;
|
||||
export const lightColorSentMessageBg = accentLightTheme;
|
||||
const lightColorClickableHovered = '#dfdfdf';
|
||||
const lightColorSessionBorderColor = borderLightThemeColor;
|
||||
const lightColorSessionBorder = `1px solid ${lightColorSessionBorderColor}`;
|
||||
|
@ -179,7 +179,7 @@ const lightColorPillDivider = `${black}1A`;
|
|||
const lightColorLastSeenIndicator = black;
|
||||
const lightColorQuoteBottomBarBg = '#f0f0f0';
|
||||
const lightColorCellBackground = '#f9f9f9';
|
||||
const lightColorReceivedMessageBg = '#f5f5f5';
|
||||
export const lightColorReceivedMessageBg = '#f5f5f5';
|
||||
const lightColorReceivedMessageText = black;
|
||||
|
||||
const lightColorPillDividerText = '#555555';
|
||||
|
@ -400,3 +400,54 @@ export const SessionTheme = ({ children }: { children: any }) => (
|
|||
{children}
|
||||
</>
|
||||
);
|
||||
|
||||
/**
|
||||
* Just putting those new theme values used in the settings to avoid having conflicts for now.
|
||||
*
|
||||
*/
|
||||
|
||||
type SettingsThemeSwitcherColor = {
|
||||
background: string;
|
||||
border: string;
|
||||
sent: string;
|
||||
received: string;
|
||||
};
|
||||
|
||||
export const OceanBlueDark: SettingsThemeSwitcherColor = {
|
||||
background: '#242735',
|
||||
border: '#3D4A5E',
|
||||
sent: '#57C9FA',
|
||||
received: '#3D4A5D',
|
||||
};
|
||||
export const OceanBlueLight: SettingsThemeSwitcherColor = {
|
||||
background: '#ECFAFB',
|
||||
border: '#5CAACC',
|
||||
sent: '#57C9FA',
|
||||
received: '#B3EDF2',
|
||||
};
|
||||
|
||||
export type PrimaryColorIds =
|
||||
| 'green'
|
||||
| 'blue'
|
||||
| 'yellow'
|
||||
| 'pink'
|
||||
| 'purple'
|
||||
| 'orange'
|
||||
| 'red'
|
||||
| 'blue'
|
||||
| 'blue'
|
||||
| 'blue';
|
||||
|
||||
type PrimaryColorType = { id: PrimaryColorIds; ariaLabel: string; color: string };
|
||||
|
||||
export const getPrimaryColors = (): Array<PrimaryColorType> => {
|
||||
return [
|
||||
{ id: 'green', ariaLabel: window.i18n('primaryColorGreen'), color: '#31F196' },
|
||||
{ id: 'blue', ariaLabel: window.i18n('primaryColorBlue'), color: '#57C9FA' },
|
||||
{ id: 'yellow', ariaLabel: window.i18n('primaryColorYellow'), color: '#FAD657' },
|
||||
{ id: 'pink', ariaLabel: window.i18n('primaryColorPink'), color: '#FF95EF' },
|
||||
{ id: 'purple', ariaLabel: window.i18n('primaryColorPurple'), color: '#C993FF' },
|
||||
{ id: 'orange', ariaLabel: window.i18n('primaryColorOrange'), color: '#FCB159' },
|
||||
{ id: 'red', ariaLabel: window.i18n('primaryColorRed'), color: '#FF9C8E' },
|
||||
];
|
||||
};
|
||||
|
|
|
@ -91,8 +91,8 @@ export const actions = {
|
|||
};
|
||||
|
||||
export const initialSectionState: SectionStateType = {
|
||||
focusedSection: SectionType.Message,
|
||||
focusedSettingsSection: undefined,
|
||||
focusedSection: SectionType.Settings,
|
||||
focusedSettingsSection: SessionSettingCategory.Appearance,
|
||||
isAppFocused: false,
|
||||
overlayMode: undefined,
|
||||
};
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
export const APPLY_THEME = 'APPLY_THEME';
|
||||
|
||||
export type ThemeStateType = 'light' | 'dark';
|
||||
export type ThemeStateType = 'light' | 'dark' | 'ocean-light' | 'ocean-dark';
|
||||
|
||||
export const applyTheme = (theme: ThemeStateType) => {
|
||||
return {
|
||||
type: APPLY_THEME,
|
||||
|
|
|
@ -51,7 +51,6 @@ export const getConversationsCount = createSelector(getConversationLookup, (stat
|
|||
return Object.values(state).length;
|
||||
});
|
||||
|
||||
|
||||
export const getSelectedConversationKey = createSelector(
|
||||
getConversations,
|
||||
(state: ConversationsStateType): string | undefined => {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
export type LocalizerKeys =
|
||||
| 'removePassword'
|
||||
| 'classicDarkThemeTitle'
|
||||
| 'userUnbanFailed'
|
||||
| 'changePassword'
|
||||
| 'saved'
|
||||
|
@ -102,6 +103,7 @@ export type LocalizerKeys =
|
|||
| 'linkDevice'
|
||||
| 'callMissedNotApproved'
|
||||
| 'invalidPubkeyFormat'
|
||||
| 'primaryColorYellow'
|
||||
| 'disappearingMessagesDisabled'
|
||||
| 'spellCheckDescription'
|
||||
| 'clearDataSettingsTitle'
|
||||
|
@ -110,6 +112,7 @@ export type LocalizerKeys =
|
|||
| 'timerOption_30_minutes_abbreviated'
|
||||
| 'pruneSettingDescription'
|
||||
| 'voiceMessage'
|
||||
| 'primaryColorPink'
|
||||
| 'changePasswordTitle'
|
||||
| 'copyMessage'
|
||||
| 'messageDeletionForbidden'
|
||||
|
@ -141,7 +144,7 @@ export type LocalizerKeys =
|
|||
| 'contextMenuNoSuggestions'
|
||||
| 'recoveryPhraseRevealButtonText'
|
||||
| 'banUser'
|
||||
| 'answeredACall'
|
||||
| 'primaryColorBlue'
|
||||
| 'sendMessage'
|
||||
| 'readableListCounterSingular'
|
||||
| 'recoveryPhraseRevealMessage'
|
||||
|
@ -234,6 +237,7 @@ export type LocalizerKeys =
|
|||
| 'failedToAddAsModerator'
|
||||
| 'disabledDisappearingMessages'
|
||||
| 'cannotUpdate'
|
||||
| 'primaryColor'
|
||||
| 'device'
|
||||
| 'replyToMessage'
|
||||
| 'messageDeletedPlaceholder'
|
||||
|
@ -267,8 +271,11 @@ export type LocalizerKeys =
|
|||
| 'resend'
|
||||
| 'copiedToClipboard'
|
||||
| 'closedGroupInviteSuccessTitlePlural'
|
||||
| 'autoUpdateDownloadButtonLabel'
|
||||
| 'groupMembers'
|
||||
| 'primaryColorOrange'
|
||||
| 'dialogClearAllDataDeletionQuestion'
|
||||
| 'oceanDarkThemeTitle'
|
||||
| 'unableToLoadAttachment'
|
||||
| 'cameraPermissionNeededTitle'
|
||||
| 'editMenuRedo'
|
||||
|
@ -279,6 +286,7 @@ export type LocalizerKeys =
|
|||
| 'newMessage'
|
||||
| 'windowMenuClose'
|
||||
| 'mainMenuFile'
|
||||
| 'primaryColorPurple'
|
||||
| 'callMissed'
|
||||
| 'getStarted'
|
||||
| 'unblockUser'
|
||||
|
@ -302,7 +310,8 @@ export type LocalizerKeys =
|
|||
| 'deleteConversationConfirmation'
|
||||
| 'timerOption_6_hours_abbreviated'
|
||||
| 'timerOption_1_week_abbreviated'
|
||||
| 'timerSetTo'
|
||||
| 'removePasswordTitle'
|
||||
| 'unblockGroupToSend'
|
||||
| 'enable'
|
||||
| 'notificationSubtitle'
|
||||
| 'youChangedTheTimer'
|
||||
|
@ -313,6 +322,7 @@ export type LocalizerKeys =
|
|||
| 'notificationForConvo'
|
||||
| 'noNameOrMessage'
|
||||
| 'pinConversationLimitTitle'
|
||||
| 'classicLightThemeTitle'
|
||||
| 'noSearchResults'
|
||||
| 'changeNickname'
|
||||
| 'userUnbanned'
|
||||
|
@ -360,7 +370,7 @@ export type LocalizerKeys =
|
|||
| 'failedResolveOns'
|
||||
| 'showDebugLog'
|
||||
| 'declineRequestMessage'
|
||||
| 'autoUpdateDownloadButtonLabel'
|
||||
| 'primaryColorGreen'
|
||||
| 'dialogClearAllDataDeletionFailedTitleQuestion'
|
||||
| 'autoUpdateDownloadInstructions'
|
||||
| 'dialogClearAllDataDeletionFailedTitle'
|
||||
|
@ -388,6 +398,8 @@ export type LocalizerKeys =
|
|||
| 'deleteForEveryone'
|
||||
| 'createSessionID'
|
||||
| 'multipleLeftTheGroup'
|
||||
| 'answeredACall'
|
||||
| 'oceanLightThemeTitle'
|
||||
| 'enterSessionIDOrONSName'
|
||||
| 'quoteThumbnailAlt'
|
||||
| 'timerOption_1_week'
|
||||
|
@ -440,13 +452,14 @@ export type LocalizerKeys =
|
|||
| 'settingsHeader'
|
||||
| 'autoUpdateNewVersionMessage'
|
||||
| 'oneNonImageAtATimeToast'
|
||||
| 'removePasswordTitle'
|
||||
| 'timerSetTo'
|
||||
| 'iAmSure'
|
||||
| 'primaryColorRed'
|
||||
| 'selectMessage'
|
||||
| 'enterAnOpenGroupURL'
|
||||
| 'delete'
|
||||
| 'changePasswordInvalid'
|
||||
| 'unblockGroupToSend'
|
||||
| 'themesSettingTitle'
|
||||
| 'timerOption_6_hours'
|
||||
| 'confirmPassword'
|
||||
| 'downloadAttachment'
|
||||
|
|
|
@ -56,7 +56,7 @@ declare global {
|
|||
getCallMediaPermissions: () => boolean;
|
||||
toggleMenuBar: () => void;
|
||||
toggleSpellCheck: any;
|
||||
setTheme: (newTheme: string) => any;
|
||||
setTheme: (newTheme: string) => Promise<void>;
|
||||
isDev?: () => boolean;
|
||||
userConfig: any;
|
||||
versionInfo: any;
|
||||
|
|
Loading…
Reference in New Issue