From ff3d7ce2268bcbb135c91cacefc92e5e4295edf0 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 23 Aug 2022 14:08:51 +1000 Subject: [PATCH 1/5] feat: update settings Help page with new items and links --- _locales/en/messages.json | 13 ++--- ts/components/basic/SessionRadio.tsx | 2 +- ts/components/basic/SessionToggle.tsx | 6 ++- .../conversation/SessionEmojiPanel.tsx | 1 + .../settings/SessionSettingListItem.tsx | 49 +++++++++++++------ .../settings/section/CategoryHelp.tsx | 34 +++++++------ ts/types/LocalizerKeys.ts | 5 +- 7 files changed, 69 insertions(+), 41 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 4d97533a8..f3be50a9e 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -39,7 +39,7 @@ "youGotKickedFromGroup": "You were removed from the group.", "unreadMessages": "Unread Messages", "debugLogExplanation": "This log will be saved to your desktop.", - "reportIssue": "Report an issue", + "reportIssue": "Report a Bug", "markAllAsRead": "Mark All as Read", "incomingError": "Error handling incoming message", "media": "Media", @@ -61,7 +61,8 @@ "unableToLoadAttachment": "Sorry, there was an error setting your attachment.", "offline": "Offline", "debugLog": "Debug Log", - "showDebugLog": "Show Debug Log", + "showDebugLog": "Export Logs", + "shareBugDetails": "Share some details to help us resolve your issue. Export your logs, then upload the file though Session's Help Desk.", "goToReleaseNotes": "Go to Release Notes", "goToSupportPage": "Go to Support Page", "about": "About", @@ -358,8 +359,7 @@ "userAddedToModerators": "User added to moderator list", "userRemovedFromModerators": "User removed from moderator list", "orJoinOneOfThese": "Or join one of these...", - "helpUsTranslateSession": "Help us Translate Session", - "translation": "Translation", + "helpUsTranslateSession": "Translate Session", "closedGroupInviteFailTitle": "Group Invitation Failed", "closedGroupInviteFailTitlePlural": "Group Invitations Failed", "closedGroupInviteFailMessage": "Unable to successfully invite a group member", @@ -407,8 +407,9 @@ "recoveryPhraseRevealMessage": "Secure your account by saving your recovery phrase. Reveal your recovery phrase then store it safely to secure it.", "recoveryPhraseRevealButtonText": "Reveal Recovery Phrase", "notificationSubtitle": "Notifications - $setting$", - "surveyTitle": "Take our Session Survey", - "goToOurSurvey": "Go to our survey", + "surveyTitle": "We'd love your Feedback", + "faq": "FAQ", + "support": "Support", "clearAll": "Clear All", "clearDataSettingsTitle": "Clear Data", "messageRequests": "Message Requests", diff --git a/ts/components/basic/SessionRadio.tsx b/ts/components/basic/SessionRadio.tsx index e7ba39e4f..1442a03c3 100644 --- a/ts/components/basic/SessionRadio.tsx +++ b/ts/components/basic/SessionRadio.tsx @@ -35,7 +35,7 @@ const StyledLabel = styled.label` transition: var(--default-duration); padding: calc(var(--filled-size) / 2); - outline-offset: 3px; + outline-offset: 2px; outline: var(--color-text) solid 1px; border: none; margin-top: var(--filled-size); diff --git a/ts/components/basic/SessionToggle.tsx b/ts/components/basic/SessionToggle.tsx index 4abd7f4ff..8b008427d 100644 --- a/ts/components/basic/SessionToggle.tsx +++ b/ts/components/basic/SessionToggle.tsx @@ -11,7 +11,8 @@ const StyledKnob = styled.div<{ active: boolean }>` width: 21px; border-radius: 28px; background-color: white; - box-shadow: -2px 1px 3px rgba(0, 0, 0, 0.15); + box-shadow: ${props => + props.active ? '-2px 1px 3px rgba(0, 0, 0, 0.15)' : '2px 1px 3px rgba(0, 0, 0, 0.15);'}; transition: transform var(--default-duration) ease, background-color var(--default-duration) ease; @@ -29,7 +30,8 @@ const StyledSessionToggle = styled.div<{ active: boolean }>` background-color: rgba(0, 0, 0, 0); transition: var(--default-duration); - background-color: ${props => (props.active ? 'var(--color-accent)' : 'unset')}; + background-color: ${props => + props.active ? 'var(--color-accent)' : 'var(--color-cell-background)'}; border-color: ${props => (props.active ? 'var(--color-accent)' : 'unset')}; `; diff --git a/ts/components/conversation/SessionEmojiPanel.tsx b/ts/components/conversation/SessionEmojiPanel.tsx index 823cadcaa..ec4759ea4 100644 --- a/ts/components/conversation/SessionEmojiPanel.tsx +++ b/ts/components/conversation/SessionEmojiPanel.tsx @@ -15,6 +15,7 @@ export const StyledEmojiPanel = styled.div<{ isModal: boolean; theme: 'light' | z-index: 5; opacity: 0; visibility: hidden; + // this disables the slide-in animation when showing the emoji picker from a right click on a message /* transition: var(--default-duration); */ button:focus { diff --git a/ts/components/settings/SessionSettingListItem.tsx b/ts/components/settings/SessionSettingListItem.tsx index fb27f14ce..11bc76060 100644 --- a/ts/components/settings/SessionSettingListItem.tsx +++ b/ts/components/settings/SessionSettingListItem.tsx @@ -3,6 +3,7 @@ import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/S import { SessionToggle } from '../basic/SessionToggle'; import { SessionConfirmDialogProps } from '../dialog/SessionConfirm'; import styled from 'styled-components'; +import { SessionIconButton } from '../icon'; type ButtonSettingsProps = { title?: string; @@ -37,6 +38,30 @@ const StyledDescriptionContainer = styled(StyledDescription)` align-items: center; `; +const StyledSettingItem = styled.div` + font-size: var(--font-size-md); + padding: var(--margins-lg); + margin-bottom: 20px; + + background: var(--color-cell-background); + color: var(--color-text); + border-bottom: var(--border-session); +`; + +const StyledSettingItemInline = styled(StyledSettingItem)` + display: flex; + align-items: center; + justify-content: space-between; + transition: var(--default-duration); +`; + +const StyledSettingItemClickable = styled(StyledSettingItemInline)` + :hover { + background: var(--color-clickable-hovered); + cursor: pointer; + } +`; + const SettingsTitleAndDescription = (props: { title?: string; description?: string; @@ -75,21 +100,15 @@ export const SessionSettingsItemWrapper = (props: { ); }; -const StyledSettingItem = styled.div` - font-size: var(--font-size-md); - padding: var(--margins-lg); - margin-bottom: 20px; - - background: var(--color-cell-background); - color: var(--color-text); - border-bottom: var(--border-session); -`; - -const StyledSettingItemInline = styled(StyledSettingItem)` - display: flex; - align-items: center; - justify-content: space-between; -`; +export const SessionSettingsTitleWithLink = (props: { title: string; onClick: () => void }) => { + const { onClick, title } = props; + return ( + + + + + ); +}; export const SessionToggleWithDescription = (props: { title?: string; diff --git a/ts/components/settings/section/CategoryHelp.tsx b/ts/components/settings/section/CategoryHelp.tsx index 7147cab66..9bdf4d88b 100644 --- a/ts/components/settings/section/CategoryHelp.tsx +++ b/ts/components/settings/section/CategoryHelp.tsx @@ -2,26 +2,12 @@ import { ipcRenderer, shell } from 'electron'; import React from 'react'; import { SessionButtonColor, SessionButtonType } from '../../basic/SessionButton'; -import { SessionSettingButtonItem } from '../SessionSettingListItem'; +import { SessionSettingButtonItem, SessionSettingsTitleWithLink } from '../SessionSettingListItem'; export const SettingsCategoryHelp = (props: { hasPassword: boolean | null }) => { if (props.hasPassword !== null) { return ( <> - void shell.openExternal('https://getsession.org/survey')} - buttonColor={SessionButtonColor.Primary} - buttonType={SessionButtonType.Square} - buttonText={window.i18n('goToOurSurvey')} - /> - void shell.openExternal('https://crowdin.com/project/session-desktop/')} - buttonColor={SessionButtonColor.Primary} - buttonType={SessionButtonType.Square} - buttonText={window.i18n('translation')} - /> { ipcRenderer.send('show-debug-log'); @@ -29,6 +15,24 @@ export const SettingsCategoryHelp = (props: { hasPassword: boolean | null }) => buttonColor={SessionButtonColor.Primary} buttonType={SessionButtonType.Square} buttonText={window.i18n('showDebugLog')} + title={window.i18n('reportIssue')} + description={window.i18n('shareBugDetails')} + /> + void shell.openExternal('https://getsession.org/survey')} + /> + void shell.openExternal('https://crowdin.com/project/session-desktop/')} + /> + void shell.openExternal('https://getsession.org/faq')} + /> + void shell.openExternal('https://sessionapp.zendesk.com/hc/en-us')} /> ); diff --git a/ts/types/LocalizerKeys.ts b/ts/types/LocalizerKeys.ts index fb30c14c7..41dffda7f 100644 --- a/ts/types/LocalizerKeys.ts +++ b/ts/types/LocalizerKeys.ts @@ -13,6 +13,7 @@ export type LocalizerKeys = | 'requestsPlaceholder' | 'closedGroupInviteFailMessage' | 'noContactsForGroup' + | 'faq' | 'linkVisitWarningMessage' | 'messageRequestAcceptedOurs' | 'anonymous' @@ -59,6 +60,7 @@ export type LocalizerKeys = | 'members' | 'noMessageRequestsPending' | 'sendRecoveryPhraseMessage' + | 'shareBugDetails' | 'timerOption_1_hour' | 'youGotKickedFromGroup' | 'cannotRemoveCreatorFromGroupDesc' @@ -99,7 +101,6 @@ export type LocalizerKeys = | 'lightboxImageAlt' | 'linkDevice' | 'callMissedNotApproved' - | 'goToOurSurvey' | 'invalidPubkeyFormat' | 'disappearingMessagesDisabled' | 'spellCheckDescription' @@ -200,7 +201,6 @@ export type LocalizerKeys = | 'deleteMessages' | 'searchForContactsOnly' | 'spellCheckTitle' - | 'translation' | 'editMenuSelectAll' | 'messageBodyMissing' | 'timerOption_12_hours_abbreviated' @@ -427,6 +427,7 @@ export type LocalizerKeys = | 'timerOption_5_seconds_abbreviated' | 'removeFromModerators' | 'enterRecoveryPhrase' + | 'support' | 'stagedImageAttachment' | 'thisWeek' | 'savedTheFile' From ef10e0f1d9cd99206e66ffba2e0b1c6a40ea6943 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 23 Aug 2022 14:41:24 +1000 Subject: [PATCH 2/5] chore: move typing animation to styled components --- stylesheets/_modules.scss | 86 --------------- stylesheets/_theme_dark.scss | 10 -- .../conversation/TypingAnimation.tsx | 103 ++++++++++++++---- 3 files changed, 80 insertions(+), 119 deletions(-) diff --git a/stylesheets/_modules.scss b/stylesheets/_modules.scss index c747f792d..807eda3f8 100644 --- a/stylesheets/_modules.scss +++ b/stylesheets/_modules.scss @@ -908,92 +908,6 @@ flex-grow: 1; } -// Module: Typing Animation - -.module-typing-animation { - display: inline-flex; - flex-direction: row; - align-items: center; - - height: 8px; - width: 38px; - padding-inline-start: 1px; - padding-inline-end: 1px; -} - -.module-typing-animation__dot { - border-radius: 50%; - background-color: $color-gray-60; - - height: 6px; - width: 6px; - opacity: 0.4; -} - -.module-typing-animation__dot--light { - border-radius: 50%; - background-color: $color-white; - - height: 6px; - width: 6px; - opacity: 0.4; -} - -@keyframes typing-animation-first { - 0% { - opacity: 0.4; - } - 20% { - transform: scale(1.3); - opacity: 1; - } - 40% { - opacity: 0.4; - } -} - -@keyframes typing-animation-second { - 10% { - opacity: 0.4; - } - 30% { - transform: scale(1.3); - opacity: 1; - } - 50% { - opacity: 0.4; - } -} - -@keyframes typing-animation-third { - 20% { - opacity: 0.4; - } - 40% { - transform: scale(1.3); - opacity: 1; - } - 60% { - opacity: 0.4; - } -} - -.module-typing-animation__dot--first { - animation: typing-animation-first 1600ms ease infinite; -} - -.module-typing-animation__dot--second { - animation: typing-animation-second 1600ms ease infinite; -} - -.module-typing-animation__dot--third { - animation: typing-animation-third 1600ms ease infinite; -} - -.module-typing-animation__spacer { - flex-grow: 1; -} - // Module: Attachments .module-attachments { diff --git a/stylesheets/_theme_dark.scss b/stylesheets/_theme_dark.scss index 3791e1fdb..466909ade 100644 --- a/stylesheets/_theme_dark.scss +++ b/stylesheets/_theme_dark.scss @@ -313,16 +313,6 @@ background-color: $color-white-015; } - // Module: Typing Animation - - .module-typing-animation__dot { - background-color: $color-white; - } - - .module-typing-animation__dot--light { - background-color: $color-white; - } - // Module: Attachments .module-attachments { diff --git a/ts/components/conversation/TypingAnimation.tsx b/ts/components/conversation/TypingAnimation.tsx index c24c6c156..0094acbea 100644 --- a/ts/components/conversation/TypingAnimation.tsx +++ b/ts/components/conversation/TypingAnimation.tsx @@ -1,29 +1,86 @@ import React from 'react'; -import classNames from 'classnames'; +import styled from 'styled-components'; + +const StyledTypingContainer = styled.div` + display: inline-flex; + flex-direction: row; + align-items: center; + + height: 8px; + width: 38px; + padding-inline-start: 1px; + padding-inline-end: 1px; +`; + +const StyledTypingDot = styled.div<{ index: number }>` + border-radius: 50%; + background-color: var(--color-text-subtle); + + height: 6px; + width: 6px; + opacity: 0.4; + + @keyframes typing-animation-first { + 0% { + opacity: 0.4; + } + 20% { + transform: scale(1.3); + opacity: 1; + } + 40% { + opacity: 0.4; + } + } + + @keyframes typing-animation-second { + 10% { + opacity: 0.4; + } + 30% { + transform: scale(1.3); + opacity: 1; + } + 50% { + opacity: 0.4; + } + } + + @keyframes typing-animation-third { + 20% { + opacity: 0.4; + } + 40% { + transform: scale(1.3); + opacity: 1; + } + 60% { + opacity: 0.4; + } + } + + animation: ${props => + props.index === 0 + ? 'typing-animation-first' + : props.index === 1 + ? 'typing-animation-second' + : 'typing-animation-third'} + 1600ms ease infinite; +`; + +const StyledSpacer = styled.div` + flex-grow: 1; +`; export const TypingAnimation = () => { return ( -
-
-
-
-
-
-
+ + + + + + + + ); }; From bf20c10f811869ac3d7d089e346614557345edf7 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 23 Aug 2022 14:42:01 +1000 Subject: [PATCH 3/5] fix: make use of useSet to select in memberList --- .../leftpane/overlay/OverlayClosedGroup.tsx | 31 +++-------- .../section/CategoryConversations.tsx | 51 ++++++++++--------- ts/hooks/useSet.ts | 4 +- 3 files changed, 36 insertions(+), 50 deletions(-) diff --git a/ts/components/leftpane/overlay/OverlayClosedGroup.tsx b/ts/components/leftpane/overlay/OverlayClosedGroup.tsx index 8936baf30..8df5d3cd2 100644 --- a/ts/components/leftpane/overlay/OverlayClosedGroup.tsx +++ b/ts/components/leftpane/overlay/OverlayClosedGroup.tsx @@ -17,6 +17,7 @@ import useKey from 'react-use/lib/useKey'; import styled from 'styled-components'; import { SessionSearchInput } from '../../SessionSearchInput'; import { getSearchResults, isSearching } from '../../../state/selectors/search'; +import { useSet } from '../../../hooks/useSet'; const StyledMemberListNoContacts = styled.div` font-family: var(--font-font-mono); @@ -44,28 +45,16 @@ export const OverlayClosedGroup = () => { const privateContactsPubkeys = useSelector(getPrivateContactsPubkeys); const [groupName, setGroupName] = useState(''); const [loading, setLoading] = useState(false); - const [selectedMemberIds, setSelectedMemberIds] = useState>([]); + const { + uniqueValues: selectedMemberIds, + addTo: addToSelected, + removeFrom: removeFromSelected, + } = useSet([]); function closeOverlay() { dispatch(resetOverlayMode()); } - function handleSelectMember(memberId: string) { - if (selectedMemberIds.includes(memberId)) { - return; - } - - setSelectedMemberIds([...selectedMemberIds, memberId]); - } - - function handleUnselectMember(unselectId: string) { - setSelectedMemberIds( - selectedMemberIds.filter(id => { - return id !== unselectId; - }) - ); - } - async function onEnterPressed() { if (loading) { window?.log?.warn('Closed group creation already in progress'); @@ -132,12 +121,8 @@ export const OverlayClosedGroup = () => { pubkey={memberPubkey} isSelected={selectedMemberIds.some(m => m === memberPubkey)} key={memberPubkey} - onSelect={selectedMember => { - handleSelectMember(selectedMember); - }} - onUnselect={unselectedMember => { - handleUnselectMember(unselectedMember); - }} + onSelect={addToSelected} + onUnselect={removeFromSelected} /> ))}
diff --git a/ts/components/settings/section/CategoryConversations.tsx b/ts/components/settings/section/CategoryConversations.tsx index ff00eebf1..fe47ec700 100644 --- a/ts/components/settings/section/CategoryConversations.tsx +++ b/ts/components/settings/section/CategoryConversations.tsx @@ -2,16 +2,14 @@ import React from 'react'; import { useDispatch, useSelector } from 'react-redux'; import useUpdate from 'react-use/lib/useUpdate'; import { SettingsKey } from '../../../data/settings-key'; -import { unblockConvoById } from '../../../interactions/conversationInteractions'; -import { getConversationController } from '../../../session/conversations'; +import { useSet } from '../../../hooks/useSet'; import { ToastUtils } from '../../../session/utils'; import { toggleAudioAutoplay } from '../../../state/ducks/userConfig'; import { getBlockedPubkeys } from '../../../state/selectors/conversations'; import { getAudioAutoplay } from '../../../state/selectors/userConfig'; -import { SessionButtonColor, SessionButtonType } from '../../basic/SessionButton'; +import { MemberListItem } from '../../MemberListItem'; import { - SessionSettingButtonItem, SessionSettingsItemWrapper, SessionToggleWithDescription, } from '../SessionSettingListItem'; @@ -95,31 +93,34 @@ const NoBlockedContacts = () => { ); }; -const BlockedEntry = (props: { blockedEntry: string; title: string }) => { - return ( - { - await unblockConvoById(props.blockedEntry); - }} - /> - ); -}; - const BlockedContactsList = (props: { blockedNumbers: Array }) => { - const blockedEntries = props.blockedNumbers.map(blockedEntry => { - const currentModel = getConversationController().get(blockedEntry); - const title = - currentModel?.getNicknameOrRealUsernameOrPlaceholder() || window.i18n('anonymous'); + const { + uniqueValues: selectedIds, + addTo: addToSelected, + removeFrom: removeFromSelected, + } = useSet([]); - return ; + const blockedEntries = props.blockedNumbers.map(blockedEntry => { + return ( + + ); }); - return <>{blockedEntries}; + return ( + <> + + + ); }; export const CategoryConversations = () => { diff --git a/ts/hooks/useSet.ts b/ts/hooks/useSet.ts index d0030f85e..07f34ab34 100644 --- a/ts/hooks/useSet.ts +++ b/ts/hooks/useSet.ts @@ -1,5 +1,5 @@ import { useCallback, useState } from 'react'; -import _ from 'lodash'; +import { isEqual } from 'lodash'; export function useSet(initialValues: Array = []) { const [uniqueValues, setUniqueValues] = useState>(initialValues); @@ -18,7 +18,7 @@ export function useSet(initialValues: Array = []) { if (!uniqueValues.includes(valueToRemove)) { return; } - setUniqueValues(uniqueValues.filter(v => !_.isEqual(v, valueToRemove))); + setUniqueValues(uniqueValues.filter(v => !isEqual(v, valueToRemove))); }, [uniqueValues, setUniqueValues] ); From f137cad2a0800d24940a4c4e5accd4e59637bb2d Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Wed, 24 Aug 2022 11:37:25 +1000 Subject: [PATCH 4/5] feat: add the BlockedList component in Settings --- stylesheets/_rtl.scss | 3 +- stylesheets/_session.scss | 13 -- ts/components/MemberListItem.tsx | 4 +- ts/components/basic/SessionToggle.tsx | 6 +- ts/components/settings/BlockedList.tsx | 162 ++++++++++++++++++ .../settings/SessionSettingListItem.tsx | 5 +- ts/components/settings/SessionSettings.tsx | 23 ++- .../section/CategoryConversations.tsx | 60 +------ ts/hooks/useSet.ts | 9 +- ts/state/selectors/conversations.ts | 8 - ts/util/blockedNumberController.ts | 23 +++ 11 files changed, 227 insertions(+), 89 deletions(-) create mode 100644 ts/components/settings/BlockedList.tsx diff --git a/stylesheets/_rtl.scss b/stylesheets/_rtl.scss index 63b400bc6..264d2fee0 100644 --- a/stylesheets/_rtl.scss +++ b/stylesheets/_rtl.scss @@ -6,8 +6,7 @@ body.rtl { .group-settings-item, .contact-selection-list, .group-member-list__selection, - .react-contexify__item, - .session-settings-list { + .react-contexify__item { direction: rtl; } diff --git a/stylesheets/_session.scss b/stylesheets/_session.scss index 1e2ae815a..296690068 100644 --- a/stylesheets/_session.scss +++ b/stylesheets/_session.scss @@ -800,19 +800,6 @@ label { display: flex; flex-direction: column; - &-list { - overflow-y: auto; - overflow-x: hidden; - } - - &-view { - flex-grow: 1; - display: flex; - flex-direction: column; - justify-content: space-between; - overflow: hidden; - } - &__password-lock { display: flex; align-items: center; diff --git a/ts/components/MemberListItem.tsx b/ts/components/MemberListItem.tsx index 46a5bf949..02bba9272 100644 --- a/ts/components/MemberListItem.tsx +++ b/ts/components/MemberListItem.tsx @@ -64,6 +64,7 @@ export const MemberListItem = (props: { // this bool is used to make a zombie appear with less opacity than a normal member isZombie?: boolean; inMentions?: boolean; // set to true if we are rendering members but in the Mentions picker + disableBg?: boolean; isAdmin?: boolean; // if true, we add a small crown on top of their avatar onSelect?: (pubkey: string) => void; onUnselect?: (pubkey: string) => void; @@ -77,6 +78,7 @@ export const MemberListItem = (props: { onSelect, onUnselect, inMentions, + disableBg, dataTestId, } = props; @@ -89,7 +91,7 @@ export const MemberListItem = (props: { isSelected ? onUnselect?.(pubkey) : onSelect?.(pubkey); }} style={ - !inMentions + !inMentions && !disableBg ? { backgroundColor: 'var(--color-cell-background)', } diff --git a/ts/components/basic/SessionToggle.tsx b/ts/components/basic/SessionToggle.tsx index 8b008427d..47f98ed96 100644 --- a/ts/components/basic/SessionToggle.tsx +++ b/ts/components/basic/SessionToggle.tsx @@ -22,7 +22,7 @@ const StyledKnob = styled.div<{ active: boolean }>` const StyledSessionToggle = styled.div<{ active: boolean }>` width: 51px; height: 25px; - border: 1.5px solid #e5e5ea; + border: 1px solid #e5e5ea; border-radius: 16px; position: relative; @@ -31,8 +31,8 @@ const StyledSessionToggle = styled.div<{ active: boolean }>` transition: var(--default-duration); background-color: ${props => - props.active ? 'var(--color-accent)' : 'var(--color-cell-background)'}; - border-color: ${props => (props.active ? 'var(--color-accent)' : 'unset')}; + props.active ? 'var(--color-accent)' : 'var(--color-clickable-hovered)'}; + border-color: ${props => (props.active ? 'var(--color-accent)' : 'var(--color-cell-background)')}; `; type Props = { diff --git a/ts/components/settings/BlockedList.tsx b/ts/components/settings/BlockedList.tsx new file mode 100644 index 000000000..9021f1554 --- /dev/null +++ b/ts/components/settings/BlockedList.tsx @@ -0,0 +1,162 @@ +import React, { useState } from 'react'; + +// tslint:disable-next-line: no-submodule-imports +import useUpdate from 'react-use/lib/useUpdate'; +import styled, { CSSProperties } from 'styled-components'; +import { useSet } from '../../hooks/useSet'; +import { ToastUtils } from '../../session/utils'; +import { BlockedNumberController } from '../../util'; +import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton'; +import { SpacerLG } from '../basic/Text'; +import { SessionIconButton } from '../icon'; +import { MemberListItem } from '../MemberListItem'; +import { SettingsTitleAndDescription } from './SessionSettingListItem'; +// tslint:disable: use-simple-attributes + +const BlockedEntriesContainer = styled.div` + flex-shrink: 1; + overflow: auto; + min-height: 40px; + max-height: 100%; + background: var(--blocked-contact-list-bg); +`; + +const BlockedEntriesRoundedContainer = styled.div` + overflow: hidden; + border-radius: 16px; + padding: var(--margins-lg); + background: var(--blocked-contact-list-bg); +`; + +const BlockedContactsSection = styled.div` + display: flex; + flex-direction: column; + min-height: 0; +`; + +const BlockedContactListTitle = styled.div` + display: flex; + justify-content: space-between; + min-height: 45px; + align-items: center; +`; + +const BlockedContactListTitleButtons = styled.div` + display: flex; + align-items: center; +`; + +export const StyledBlockedSettingItem = styled.div<{ clickable: boolean }>` + font-size: var(--font-size-md); + padding: var(--margins-lg); + + background: var(--color-cell-background); + color: var(--color-text); + border-bottom: var(--border-session); + + cursor: ${props => (props.clickable ? 'pointer' : 'unset')}; +`; + +const BlockedEntries = (props: { + blockedNumbers: Array; + selectedIds: Array; + addToSelected: (id: string) => void; + removeFromSelected: (id: string) => void; +}) => { + const { addToSelected, blockedNumbers, removeFromSelected, selectedIds } = props; + return ( + + + {blockedNumbers.map(blockedEntry => { + return ( + + ); + })} + + + ); +}; + +const NoBlockedContacts = () => { + return
{window.i18n('noBlockedContacts')}
; +}; + +export const BlockedContactsList = () => { + const [expanded, setExpanded] = useState(false); + const { + uniqueValues: selectedIds, + addTo: addToSelected, + removeFrom: removeFromSelected, + empty: emptySelected, + } = useSet([]); + + const forceUpdate = useUpdate(); + + const hasAtLeastOneSelected = Boolean(selectedIds.length); + const blockedNumbers = BlockedNumberController.getBlockedNumbers(); + const noBlockedNumbers = !blockedNumbers.length; + + function toggleUnblockList() { + if (blockedNumbers.length) { + setExpanded(!expanded); + } + } + + async function unBlockThoseUsers() { + if (selectedIds.length) { + await BlockedNumberController.unblockAll(selectedIds); + emptySelected(); + ToastUtils.pushToastSuccess('unblocked', window.i18n('unblocked')); + forceUpdate(); + } + } + + return ( + + + + + {noBlockedNumbers ? ( + + ) : ( + + {hasAtLeastOneSelected && expanded ? ( + + ) : null} + + + + + )} + + + {expanded && !noBlockedNumbers ? ( + + ) : null} + + ); +}; diff --git a/ts/components/settings/SessionSettingListItem.tsx b/ts/components/settings/SessionSettingListItem.tsx index 11bc76060..bb4976bda 100644 --- a/ts/components/settings/SessionSettingListItem.tsx +++ b/ts/components/settings/SessionSettingListItem.tsx @@ -38,10 +38,9 @@ const StyledDescriptionContainer = styled(StyledDescription)` align-items: center; `; -const StyledSettingItem = styled.div` +export const StyledSettingItem = styled.div` font-size: var(--font-size-md); padding: var(--margins-lg); - margin-bottom: 20px; background: var(--color-cell-background); color: var(--color-text); @@ -62,7 +61,7 @@ const StyledSettingItemClickable = styled(StyledSettingItemInline)` } `; -const SettingsTitleAndDescription = (props: { +export const SettingsTitleAndDescription = (props: { title?: string; description?: string; childrenDescription?: React.ReactNode; diff --git a/ts/components/settings/SessionSettings.tsx b/ts/components/settings/SessionSettings.tsx index 9c87242f6..7cc054aba 100644 --- a/ts/components/settings/SessionSettings.tsx +++ b/ts/components/settings/SessionSettings.tsx @@ -172,6 +172,21 @@ const SettingInCategory = (props: { } }; +const StyledSettingsView = styled.div` + flex-grow: 1; + display: flex; + flex-direction: column; + justify-content: space-between; + overflow: hidden; +`; + +const StyledSettingsList = styled.div` + overflow-y: auto; + overflow-x: hidden; + display: flex; + flex-direction: column; +`; + export class SessionSettingsView extends React.Component { public settingsViewRef: React.RefObject; @@ -248,23 +263,23 @@ export class SessionSettingsView extends React.Component -
+ {shouldRenderPasswordLock ? ( ) : ( -
+ -
+ )} -
+
); } diff --git a/ts/components/settings/section/CategoryConversations.tsx b/ts/components/settings/section/CategoryConversations.tsx index fe47ec700..530831226 100644 --- a/ts/components/settings/section/CategoryConversations.tsx +++ b/ts/components/settings/section/CategoryConversations.tsx @@ -1,18 +1,16 @@ import React from 'react'; import { useDispatch, useSelector } from 'react-redux'; +// tslint:disable-next-line: no-submodule-imports import useUpdate from 'react-use/lib/useUpdate'; import { SettingsKey } from '../../../data/settings-key'; -import { useSet } from '../../../hooks/useSet'; import { ToastUtils } from '../../../session/utils'; import { toggleAudioAutoplay } from '../../../state/ducks/userConfig'; -import { getBlockedPubkeys } from '../../../state/selectors/conversations'; import { getAudioAutoplay } from '../../../state/selectors/userConfig'; -import { MemberListItem } from '../../MemberListItem'; -import { - SessionSettingsItemWrapper, - SessionToggleWithDescription, -} from '../SessionSettingListItem'; +import { BlockedContactsList } from '../BlockedList'; +// tslint:disable: use-simple-attributes + +import { SessionToggleWithDescription } from '../SessionSettingListItem'; async function toggleCommunitiesPruning() { try { @@ -83,60 +81,14 @@ const AudioMessageAutoPlaySetting = () => { ); }; -const NoBlockedContacts = () => { - return ( - - ); -}; - -const BlockedContactsList = (props: { blockedNumbers: Array }) => { - const { - uniqueValues: selectedIds, - addTo: addToSelected, - removeFrom: removeFromSelected, - } = useSet([]); - - const blockedEntries = props.blockedNumbers.map(blockedEntry => { - return ( - - ); - }); - - return ( - <> - - - ); -}; - export const CategoryConversations = () => { - const blockedNumbers = useSelector(getBlockedPubkeys); - return ( <> - {blockedNumbers?.length ? ( - - ) : ( - - )} + ); }; diff --git a/ts/hooks/useSet.ts b/ts/hooks/useSet.ts index 07f34ab34..2c361ee57 100644 --- a/ts/hooks/useSet.ts +++ b/ts/hooks/useSet.ts @@ -23,5 +23,12 @@ export function useSet(initialValues: Array = []) { [uniqueValues, setUniqueValues] ); - return { uniqueValues, addTo, removeFrom }; + const empty = useCallback(() => { + if (uniqueValues.length) { + setUniqueValues([]); + return; + } + }, [uniqueValues, setUniqueValues]); + + return { uniqueValues, addTo, removeFrom, empty }; } diff --git a/ts/state/selectors/conversations.ts b/ts/state/selectors/conversations.ts index b5cff9693..91148c766 100644 --- a/ts/state/selectors/conversations.ts +++ b/ts/state/selectors/conversations.ts @@ -51,14 +51,6 @@ export const getConversationsCount = createSelector(getConversationLookup, (stat return Object.values(state).length; }); -export const getBlockedPubkeys = createSelector( - // make sure to extends this selector to we are rerun on conversation changes - getConversationLookup, - - (_state): Array => { - return BlockedNumberController.getBlockedNumbers(); - } -); export const getSelectedConversationKey = createSelector( getConversations, diff --git a/ts/util/blockedNumberController.ts b/ts/util/blockedNumberController.ts index 530658fdc..e49a1424e 100644 --- a/ts/util/blockedNumberController.ts +++ b/ts/util/blockedNumberController.ts @@ -87,6 +87,29 @@ export class BlockedNumberController { } } + /** + * Unblock all thope users. + * This will only unblock the primary device of the user. + * + * @param user The user to unblock. + */ + public static async unblockAll(users: Array): Promise { + await this.load(); + let changes = false; + users.forEach(user => { + const toUnblock = PubKey.cast(user); + + if (this.blockedNumbers.has(toUnblock.key)) { + this.blockedNumbers.delete(toUnblock.key); + changes = true; + } + }); + + if (changes) { + await this.saveToDB(BLOCKED_NUMBERS_ID, this.blockedNumbers); + } + } + public static async setBlocked(user: string | PubKey, blocked: boolean): Promise { if (blocked) { return BlockedNumberController.block(user); From 1e202fcdabb6a63020db312f1898aa1f75c87f30 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Fri, 26 Aug 2022 10:40:31 +1000 Subject: [PATCH 5/5] 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 --- _locales/en/messages.json | 13 ++ ts/components/basic/SessionRadio.tsx | 120 +++++++++-- ts/components/leftpane/ActionsPanel.tsx | 31 +-- .../settings/SessionSettingListItem.tsx | 12 +- .../settings/SettingsThemeSwitcher.tsx | 203 ++++++++++++++++++ .../settings/section/CategoryAppearance.tsx | 3 +- ts/mains/main_renderer.tsx | 6 +- ts/session/utils/Theme.tsx | 28 +++ ts/state/ducks/SessionTheme.tsx | 59 ++++- ts/state/ducks/section.tsx | 4 +- ts/state/ducks/theme.tsx | 3 +- ts/state/selectors/conversations.ts | 1 - ts/types/LocalizerKeys.ts | 23 +- ts/window.d.ts | 2 +- 14 files changed, 440 insertions(+), 68 deletions(-) create mode 100644 ts/components/settings/SettingsThemeSwitcher.tsx create mode 100644 ts/session/utils/Theme.tsx diff --git a/_locales/en/messages.json b/_locales/en/messages.json index f3be50a9e..dee03c37e 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -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", diff --git a/ts/components/basic/SessionRadio.tsx b/ts/components/basic/SessionRadio.tsx index 1442a03c3..275dabc47 100644 --- a/ts/components/basic/SessionRadio.tsx +++ b/ts/components/basic/SessionRadio.tsx @@ -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 ( - + { aria-checked={active} checked={active} onChange={clickHandler} + filledSize={filledSize} + outlineOffset={outlineOffset} + selectedColor={selectedColor} /> - + {label} ); }; + +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) { + e.stopPropagation(); + onClick(value); + } + + const filledSize = 31 / 2; + const outlineOffset = 5; + + return ( + + + + + {''} + + + ); +}; diff --git a/ts/components/leftpane/ActionsPanel.tsx b/ts/components/leftpane/ActionsPanel.tsx index f989b986e..0bae85aec 100644 --- a/ts/components/leftpane/ActionsPanel.tsx +++ b/ts/components/leftpane/ActionsPanel.tsx @@ -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 newThemeObject = updatedTheme === 'dark' ? 'dark' : 'light'; - dispatch(applyTheme(newThemeObject)); + const currentTheme = window.Events.getThemeSetting(); + const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; + + 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 diff --git a/ts/components/settings/SessionSettingListItem.tsx b/ts/components/settings/SessionSettingListItem.tsx index bb4976bda..dbe743fe7 100644 --- a/ts/components/settings/SessionSettingListItem.tsx +++ b/ts/components/settings/SessionSettingListItem.tsx @@ -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 ( - {title} + {title} - {description && {description}} + {description && ( + {description} + )} <>{childrenDescription} diff --git a/ts/components/settings/SettingsThemeSwitcher.tsx b/ts/components/settings/SettingsThemeSwitcher.tsx new file mode 100644 index 000000000..7701b8a0b --- /dev/null +++ b/ts/components/settings/SettingsThemeSwitcher.tsx @@ -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 ( + + + + + + ); +}; + +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 = [ + { + 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 ( + + + + + {theme.title} + + + ); + })} + + ); +}; + +export const SettingsThemeSwitcher = () => { + //FIXME store that value somewhere in the theme object + const [selectedAccent, setSelectedAccent] = useState(undefined); + + return ( + + {window.i18n('themesSettingTitle')} + + + + + {window.i18n('primaryColor')} + + + {getPrimaryColors().map(item => { + return ( + { + setSelectedAccent(item.id); + }} + /> + ); + })} + + + ); +}; diff --git a/ts/components/settings/section/CategoryAppearance.tsx b/ts/components/settings/section/CategoryAppearance.tsx index 168946339..d665b607a 100644 --- a/ts/components/settings/section/CategoryAppearance.tsx +++ b/ts/components/settings/section/CategoryAppearance.tsx @@ -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 */} + {isHideMenuBarSupported() && ( { 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 = () => { diff --git a/ts/session/utils/Theme.tsx b/ts/session/utils/Theme.tsx new file mode 100644 index 000000000..34ac87f27 --- /dev/null +++ b/ts/session/utils/Theme.tsx @@ -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)); + } +} diff --git a/ts/state/ducks/SessionTheme.tsx b/ts/state/ducks/SessionTheme.tsx index 8525489ce..2b0898203 100644 --- a/ts/state/ducks/SessionTheme.tsx +++ b/ts/state/ducks/SessionTheme.tsx @@ -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 => { + 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' }, + ]; +}; diff --git a/ts/state/ducks/section.tsx b/ts/state/ducks/section.tsx index 9c0ffcd7f..435cc67a2 100644 --- a/ts/state/ducks/section.tsx +++ b/ts/state/ducks/section.tsx @@ -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, }; diff --git a/ts/state/ducks/theme.tsx b/ts/state/ducks/theme.tsx index 00b9c1edf..8aec1b4c0 100644 --- a/ts/state/ducks/theme.tsx +++ b/ts/state/ducks/theme.tsx @@ -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, diff --git a/ts/state/selectors/conversations.ts b/ts/state/selectors/conversations.ts index 91148c766..ab24bcea0 100644 --- a/ts/state/selectors/conversations.ts +++ b/ts/state/selectors/conversations.ts @@ -51,7 +51,6 @@ export const getConversationsCount = createSelector(getConversationLookup, (stat return Object.values(state).length; }); - export const getSelectedConversationKey = createSelector( getConversations, (state: ConversationsStateType): string | undefined => { diff --git a/ts/types/LocalizerKeys.ts b/ts/types/LocalizerKeys.ts index 41dffda7f..fafa3221b 100644 --- a/ts/types/LocalizerKeys.ts +++ b/ts/types/LocalizerKeys.ts @@ -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' diff --git a/ts/window.d.ts b/ts/window.d.ts index c615de956..8071c232d 100644 --- a/ts/window.d.ts +++ b/ts/window.d.ts @@ -56,7 +56,7 @@ declare global { getCallMediaPermissions: () => boolean; toggleMenuBar: () => void; toggleSpellCheck: any; - setTheme: (newTheme: string) => any; + setTheme: (newTheme: string) => Promise; isDev?: () => boolean; userConfig: any; versionInfo: any;