From 1a379d2466bc048b704730528bee6989aac75c6c Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Thu, 15 Oct 2020 10:07:38 +1100 Subject: [PATCH] add theme logic to switch between dark/light theme based on redux store --- js/background.js | 10 +- ts/components/LeftPane.tsx | 2 - ts/components/session/ActionsPanel.tsx | 19 ++- .../conversation/SessionConversation.tsx | 20 ++- .../session/settings/SessionSettings.tsx | 13 -- ts/session/constants.ts | 17 +-- ts/state/ducks/SessionTheme.tsx | 139 ++++++++++++++++++ ts/state/ducks/theme.tsx | 31 ++++ ts/state/reducer.ts | 3 + ts/state/smart/LeftPane.tsx | 2 +- ts/state/smart/SessionConversation.tsx | 1 + ts/styled.d.ts | 63 ++++++++ ts/window.d.ts | 4 +- 13 files changed, 284 insertions(+), 40 deletions(-) create mode 100644 ts/state/ducks/SessionTheme.tsx create mode 100644 ts/state/ducks/theme.tsx create mode 100644 ts/styled.d.ts diff --git a/js/background.js b/js/background.js index 7922755cd..8c4d7b9a3 100644 --- a/js/background.js +++ b/js/background.js @@ -817,16 +817,12 @@ // Get memberlist. This function is not accurate >> // window.getMemberList = window.lokiPublicChatAPI.getListOfMembers(); - - window.toggleTheme = () => { - const theme = window.Events.getThemeSetting(); - const updatedTheme = theme === 'dark' ? 'light' : 'dark'; - + window.setTheme = newTheme => { $(document.body) .removeClass('dark-theme') .removeClass('light-theme') - .addClass(`${updatedTheme}-theme`); - window.Events.setThemeSetting(updatedTheme); + .addClass(`${newTheme}-theme`); + window.Events.setThemeSetting(newTheme); }; window.toggleMenuBar = () => { diff --git a/ts/components/LeftPane.tsx b/ts/components/LeftPane.tsx index dbf97a13e..8bd250516 100644 --- a/ts/components/LeftPane.tsx +++ b/ts/components/LeftPane.tsx @@ -101,8 +101,6 @@ export class LeftPane extends React.Component { return this.renderContactSection(); case SectionType.Settings: return this.renderSettingSection(); - case SectionType.Moon: - return window.toggleTheme(); default: return undefined; } diff --git a/ts/components/session/ActionsPanel.tsx b/ts/components/session/ActionsPanel.tsx index a8f1ea8f7..6a4dfa7d9 100644 --- a/ts/components/session/ActionsPanel.tsx +++ b/ts/components/session/ActionsPanel.tsx @@ -1,8 +1,11 @@ import React from 'react'; +import { connect, useDispatch } from 'react-redux'; import { SessionIconButton, SessionIconSize, SessionIconType } from './icon'; import { Avatar } from '../Avatar'; import { PropsData as ConversationListItemPropsType } from '../ConversationListItem'; import { createOrUpdateItem, getItemById } from '../../../js/modules/data'; +import { APPLY_THEME } from '../../state/ducks/theme'; +import { darkTheme, lightTheme } from '../../state/ducks/SessionTheme'; export enum SectionType { Profile, @@ -22,9 +25,10 @@ interface Props { selectedSection: SectionType; conversations: Array | undefined; unreadMessageCount: number; + dispatch?: any; } -export class ActionsPanel extends React.Component { +class ActionsPanelPrivate extends React.Component { private ourConversation: any; constructor(props: Props) { super(props); @@ -130,7 +134,16 @@ export class ActionsPanel extends React.Component { if (type === SectionType.Profile) { this.editProfileHandle(); } else if (type === SectionType.Moon) { - window.toggleTheme(); + const theme = window.Events.getThemeSetting(); + const updatedTheme = theme === 'dark' ? 'light' : 'dark'; + window.setTheme(updatedTheme); + + const newThemeObject = + updatedTheme === 'dark' ? darkTheme : lightTheme; + this.props.dispatch({ + type: APPLY_THEME, + payload: newThemeObject, + }); } else { onSelect(type); } @@ -239,3 +252,5 @@ export class ActionsPanel extends React.Component { this.props.onSectionSelected(section); }; } + +export const ActionsPanel = connect()(ActionsPanelPrivate); diff --git a/ts/components/session/conversation/SessionConversation.tsx b/ts/components/session/conversation/SessionConversation.tsx index 333458787..21a719219 100644 --- a/ts/components/session/conversation/SessionConversation.tsx +++ b/ts/components/session/conversation/SessionConversation.tsx @@ -21,6 +21,8 @@ import { UserUtil } from '../../../util'; import { MultiDeviceProtocol } from '../../../session/protocols'; import { ConversationHeaderWithDetails } from '../../conversation/ConversationHeader'; import { SessionRightPanelWithDetails } from './SessionRightPanel'; +import { Theme } from '../../../state/ducks/SessionTheme'; +import { DefaultTheme } from 'styled-components'; interface State { conversationKey: string; @@ -54,9 +56,17 @@ interface State { // dropZoneFiles?: FileList dropZoneFiles: any; + + // quoted message + quotedMessageProps?: ReplyingToMessageProps; +} + +interface Props { + conversations: any; + theme: DefaultTheme; } -export class SessionConversation extends React.Component { +export class SessionConversation extends React.Component { private readonly messagesEndRef: React.RefObject; private readonly messageContainerRef: React.RefObject; @@ -210,7 +220,7 @@ export class SessionConversation extends React.Component { const showMessageDetails = this.state.infoViewState === 'messageDetails'; return ( - <> +
{this.renderHeader()}
{/* { onMessageFailure={this.onMessageFailure} onLoadVoiceNoteView={this.onLoadVoiceNoteView} onExitVoiceNoteView={this.onExitVoiceNoteView} + quotedMessageProps={quotedMessageProps} + removeQuotedMessage={() => { + this.replyToMessage(undefined); + }} /> )} @@ -286,7 +300,7 @@ export class SessionConversation extends React.Component { )} - +
); } diff --git a/ts/components/session/settings/SessionSettings.tsx b/ts/components/session/settings/SessionSettings.tsx index 694db757a..163fe0046 100644 --- a/ts/components/session/settings/SessionSettings.tsx +++ b/ts/components/session/settings/SessionSettings.tsx @@ -342,19 +342,6 @@ export class SettingsView extends React.Component { const { Settings } = window.Signal.Types; return [ - { - id: 'theme-setting', - title: window.i18n('themeToggleTitle'), - description: window.i18n('themeToggleDescription'), - hidden: true, - comparisonValue: 'light', - type: SessionSettingType.Toggle, - category: SessionSettingCategory.Appearance, - setFn: window.toggleTheme, - content: undefined, - onClick: undefined, - confirmationDialogParams: undefined, - }, { id: 'hide-menu-bar', title: window.i18n('hideMenuBarTitle'), diff --git a/ts/session/constants.ts b/ts/session/constants.ts index 28c774574..d84a84476 100644 --- a/ts/session/constants.ts +++ b/ts/session/constants.ts @@ -41,20 +41,17 @@ export const UI = { // COMMON WHITE: '#FFFFFF', WHITE_PALE: '#AFAFAF', - LIGHT_GREY: '#A0A0A0', - DARK_GREY: '#353535', - BLACK: '#000000', GREEN: '#00F782', // SEMANTIC COLORS - INFO: '#3F3F3F', - SUCCESS: '#35D388', - ERROR: '#EDD422', - WARNING: '#A0A0A0', - WARNING_ALT: '#FF9D00', DANGER: '#FF453A', DANGER_ALT: '#FF4538', - PRIMARY: '#474646', - SECONDARY: '#232323', + }, + + SPACING: { + marginXs: '5px', + marginSm: '10px', + marginMd: '15px', + marginLg: '20px', }, }; diff --git a/ts/state/ducks/SessionTheme.tsx b/ts/state/ducks/SessionTheme.tsx new file mode 100644 index 000000000..34b11f217 --- /dev/null +++ b/ts/state/ducks/SessionTheme.tsx @@ -0,0 +1,139 @@ +import React from 'react'; +// tslint:disable-next-line: no-import-side-effect no-submodule-imports +// import 'reset-css/reset.css'; + +import { DefaultTheme, ThemeProvider } from 'styled-components'; + +const white = '#ffffff'; +const black = '#000000'; +const destructive = '#ff453a'; +const accentLightTheme = '#00e97b'; +const accentDarkTheme = '#00f782'; +const borderLightTheme = '#f1f1f1'; +const borderDarkTheme = '#ffffff0F'; +const borderAvatarColor = '#00000059'; + +const commonThemes = { + fonts: { + sessionFontDefault: 'Public Sans', + sessionFontAccent: 'Loor', + sessionFontMono: 'SpaceMono', + }, +}; + +export const lightTheme: DefaultTheme = { + commonThemes, + colors: { + accent: accentLightTheme, + accentButton: black, + destructive: destructive, + cellBackground: '#fcfcfc', + modalBackground: '#fcfcfc', + fakeChatBubbleBackground: '#f5f5f5', + // input + inputBackground: '#8E8E93331F', + // text + textColor: black, + textColorSubtle: '#a0a0a0', + textColorOpposite: white, + textHighlight: `${black}33`, + // inbox + inboxBackground: white, + // buttons + backgroundPrimary: '#272726', + foregroundPrimary: white, + buttonGreen: '#272726', + // conversation view + composeViewBackground: '#fcfcfc', + composeViewTextFieldBackground: '#ededed', + receivedMessageBackground: '#f5f5f5', + sentMessageBackground: accentLightTheme, + receivedMessageText: black, + sentMessageText: black, + sessionShadow: `0 0 4px 0 ${black}5E`, + sessionShadowColor: `${black}5E`, + // left pane + conversationList: white, + conversationItemHasUnread: '#fcfcfc', + conversationItemSelected: '#f0f0f0', + clickableHovered: '#dfdfdf', + sessionBorder: `1px solid ${borderLightTheme}`, + sessionUnreadBorder: `4px solid ${accentLightTheme}`, + leftpaneOverlayBackground: white, + // scrollbars + scrollBarTrack: '#fcfcfc', + scrollBarThumb: '#474646', + // pill divider: + pillDividerColor: `${black}1A`, + pillDividerTextColor: '#555555', + // context menu + contextMenuBackground: '#f5f5f5', + filterSessionText: 'brightness(0) saturate(100%)', + lastSeenIndicatorColor: '#62656a', + lastSeenIndicatorTextColor: '#070c14', + quoteBottomBarBackground: '#f0f0f0', + }, +}; + +export const darkTheme = { + commonThemes, + colors: { + accent: accentDarkTheme, + accentButton: accentDarkTheme, + destructive: destructive, + cellBackground: '#1b1b1b', + modalBackground: '#101011', + fakeChatBubbleBackground: '#212121', + // input + inputBackground: '#8e8e931F', + // text + textColor: white, + textColorSubtle: '#a0a0a0', + textColorOpposite: black, + textHighlight: `${accentDarkTheme}99`, + // inbox + inboxBackground: 'linear-gradient(180deg, #171717 0%, #121212 100%)', + // buttons + backgroundPrimary: '#474646', + foregroundPrimary: white, + buttonGreen: accentDarkTheme, + // conversation view + composeViewBackground: '#1b1b1b', + composeViewTextFieldBackground: '#141414', + receivedMessageBackground: '#222325', + sentMessageBackground: '#3f4146', + receivedMessageText: white, + sentMessageText: white, + sessionShadow: `0 0 4px 0 ${white}33`, + sessionShadowColor: `${white}33`, + // left pane + conversationList: '#1b1b1b', + conversationItemHasUnread: '#2c2c2c', + conversationItemSelected: '#404040', + clickableHovered: '#414347', + sessionBorder: `1px solid ${borderDarkTheme}`, + sessionUnreadBorder: `4px solid ${accentDarkTheme}`, + leftpaneOverlayBackground: + 'linear-gradient(180deg, #171717 0%, #121212 100%)', + // scrollbars + scrollBarTrack: '#1b1b1b', + scrollBarThumb: '#474646', + // pill divider: + pillDividerColor: '#353535', + pillDividerTextColor: '#a0a0a0', + // context menu + contextMenuBackground: '#212121', + filterSessionText: 'none', + lastSeenIndicatorColor: '#353535', + lastSeenIndicatorTextColor: '#a8a9aa', + quoteBottomBarBackground: '#404040', + }, +}; + +export const Theme = ({ + children, + theme, +}: { + children: any; + theme: DefaultTheme; +}) => {children}; diff --git a/ts/state/ducks/theme.tsx b/ts/state/ducks/theme.tsx new file mode 100644 index 000000000..55a899cd6 --- /dev/null +++ b/ts/state/ducks/theme.tsx @@ -0,0 +1,31 @@ +export const APPLY_THEME = 'APPLY_THEME'; + +export const applyTheme = (theme: any) => { + return { + type: APPLY_THEME, + payload: theme, + }; +}; +import { lightTheme } from './SessionTheme'; + +export type ThemeStateType = typeof lightTheme; + +const initialState = lightTheme; + +export const reducer = ( + state: any = initialState, + { + type, + payload, + }: { + type: string; + payload: ThemeStateType; + } +): ThemeStateType => { + switch (type) { + case APPLY_THEME: + return payload; + default: + return state; + } +}; diff --git a/ts/state/reducer.ts b/ts/state/reducer.ts index a94ae7cf2..d6c9573b6 100644 --- a/ts/state/reducer.ts +++ b/ts/state/reducer.ts @@ -6,6 +6,7 @@ import { reducer as conversations, } from './ducks/conversations'; import { reducer as user, UserStateType } from './ducks/user'; +import { reducer as theme, ThemeStateType } from './ducks/theme'; // import { reducer as messages } from './ducks/messages'; export type StateType = { @@ -13,6 +14,7 @@ export type StateType = { messages: any; conversations: ConversationsStateType; user: UserStateType; + theme: ThemeStateType; }; export const reducers = { @@ -22,6 +24,7 @@ export const reducers = { messages: search, conversations, user, + theme, }; // Making this work would require that our reducer signature supported AnyAction, not diff --git a/ts/state/smart/LeftPane.tsx b/ts/state/smart/LeftPane.tsx index 97a0d73f7..d08e90d35 100644 --- a/ts/state/smart/LeftPane.tsx +++ b/ts/state/smart/LeftPane.tsx @@ -21,7 +21,6 @@ const mapStateToProps = (state: StateType) => { const leftPaneList = getLeftPaneLists(state); const lists = showSearch ? undefined : leftPaneList; const searchResults = showSearch ? getSearchResults(state) : undefined; - return { ...lists, searchTerm: getQuery(state), @@ -32,6 +31,7 @@ const mapStateToProps = (state: StateType) => { showArchived: getShowArchived(state), i18n: getIntl(state), unreadMessageCount: leftPaneList.unreadCount, + theme: state.theme, }; }; diff --git a/ts/state/smart/SessionConversation.tsx b/ts/state/smart/SessionConversation.tsx index f182b7953..0d274fde0 100644 --- a/ts/state/smart/SessionConversation.tsx +++ b/ts/state/smart/SessionConversation.tsx @@ -39,6 +39,7 @@ const mapStateToProps = (state: StateType) => { return { conversations: state.conversations, + theme: state.theme, }; }; diff --git a/ts/styled.d.ts b/ts/styled.d.ts new file mode 100644 index 000000000..7f2ac8231 --- /dev/null +++ b/ts/styled.d.ts @@ -0,0 +1,63 @@ +import 'styled-components'; + +declare module 'styled-components' { + export interface DefaultTheme { + commonThemes: { + fonts: { + sessionFontDefault: string; + sessionFontAccent: string; + sessionFontMono: string; + }; + }; + colors: { + accent: string; + accentButton: string; + destructive: string; + cellBackground: string; + modalBackground: string; + fakeChatBubbleBackground: string; + // input + inputBackground: string; + // text + textColor: string; + textColorSubtle: string; + textColorOpposite: string; + textHighlight: string; + // inbox + inboxBackground: string; + // buttons + backgroundPrimary: string; + foregroundPrimary: string; + buttonGreen: string; + // conversation view + composeViewBackground: string; + composeViewTextFieldBackground: string; + receivedMessageBackground: string; + sentMessageBackground: string; + receivedMessageText: string; + sentMessageText: string; + sessionShadow: string; + sessionShadowColor: string; + // left pane + conversationList: string; + conversationItemHasUnread: string; + conversationItemSelected: string; + clickableHovered: string; + sessionBorder: string; + sessionUnreadBorder: string; + leftpaneOverlayBackground: string; + // scrollbars + scrollBarTrack: string; + scrollBarThumb: string; + // pill divider: + pillDividerColor: string; + pillDividerTextColor: string; + // context menu + contextMenuBackground: string; + filterSessionText: string; + lastSeenIndicatorColor: string; + lastSeenIndicatorTextColor: string; + quoteBottomBarBackground: string; + }; + } +} diff --git a/ts/window.d.ts b/ts/window.d.ts index 3feda5534..8fbca2229 100644 --- a/ts/window.d.ts +++ b/ts/window.d.ts @@ -12,7 +12,7 @@ import { LibTextsecure } from '../libtextsecure'; import { ConversationType } from '../js/modules/data'; import { RecoveryPhraseUtil } from '../libloki/modules/mnemonic'; import { ConfirmationDialogParams } from '../background'; - +import {} from 'styled-components/cssprop'; /* We declare window stuff here instead of global.d.ts because we are importing other declarations. If you import anything in global.d.ts, the type system won't work correctly. @@ -89,7 +89,7 @@ declare global { toggleMediaPermissions: any; toggleMenuBar: any; toggleSpellCheck: any; - toggleTheme: any; + setTheme: (newTheme: string) => any; tokenlessFileServerAdnAPI: LokiAppDotNetServerInterface; userConfig: any; versionInfo: any;