add theme logic to switch between dark/light theme based on redux store

pull/1387/head
Audric Ackermann 5 years ago
parent 6c7f1598b7
commit 1a379d2466
No known key found for this signature in database
GPG Key ID: 999F434D76324AD4

@ -817,16 +817,12 @@
// Get memberlist. This function is not accurate >> // Get memberlist. This function is not accurate >>
// window.getMemberList = window.lokiPublicChatAPI.getListOfMembers(); // window.getMemberList = window.lokiPublicChatAPI.getListOfMembers();
window.setTheme = newTheme => {
window.toggleTheme = () => {
const theme = window.Events.getThemeSetting();
const updatedTheme = theme === 'dark' ? 'light' : 'dark';
$(document.body) $(document.body)
.removeClass('dark-theme') .removeClass('dark-theme')
.removeClass('light-theme') .removeClass('light-theme')
.addClass(`${updatedTheme}-theme`); .addClass(`${newTheme}-theme`);
window.Events.setThemeSetting(updatedTheme); window.Events.setThemeSetting(newTheme);
}; };
window.toggleMenuBar = () => { window.toggleMenuBar = () => {

@ -101,8 +101,6 @@ export class LeftPane extends React.Component<Props, State> {
return this.renderContactSection(); return this.renderContactSection();
case SectionType.Settings: case SectionType.Settings:
return this.renderSettingSection(); return this.renderSettingSection();
case SectionType.Moon:
return window.toggleTheme();
default: default:
return undefined; return undefined;
} }

@ -1,8 +1,11 @@
import React from 'react'; import React from 'react';
import { connect, useDispatch } from 'react-redux';
import { SessionIconButton, SessionIconSize, SessionIconType } from './icon'; import { SessionIconButton, SessionIconSize, SessionIconType } from './icon';
import { Avatar } from '../Avatar'; import { Avatar } from '../Avatar';
import { PropsData as ConversationListItemPropsType } from '../ConversationListItem'; import { PropsData as ConversationListItemPropsType } from '../ConversationListItem';
import { createOrUpdateItem, getItemById } from '../../../js/modules/data'; import { createOrUpdateItem, getItemById } from '../../../js/modules/data';
import { APPLY_THEME } from '../../state/ducks/theme';
import { darkTheme, lightTheme } from '../../state/ducks/SessionTheme';
export enum SectionType { export enum SectionType {
Profile, Profile,
@ -22,9 +25,10 @@ interface Props {
selectedSection: SectionType; selectedSection: SectionType;
conversations: Array<ConversationListItemPropsType> | undefined; conversations: Array<ConversationListItemPropsType> | undefined;
unreadMessageCount: number; unreadMessageCount: number;
dispatch?: any;
} }
export class ActionsPanel extends React.Component<Props, State> { class ActionsPanelPrivate extends React.Component<Props, State> {
private ourConversation: any; private ourConversation: any;
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
@ -130,7 +134,16 @@ export class ActionsPanel extends React.Component<Props, State> {
if (type === SectionType.Profile) { if (type === SectionType.Profile) {
this.editProfileHandle(); this.editProfileHandle();
} else if (type === SectionType.Moon) { } 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 { } else {
onSelect(type); onSelect(type);
} }
@ -239,3 +252,5 @@ export class ActionsPanel extends React.Component<Props, State> {
this.props.onSectionSelected(section); this.props.onSectionSelected(section);
}; };
} }
export const ActionsPanel = connect()(ActionsPanelPrivate);

@ -21,6 +21,8 @@ import { UserUtil } from '../../../util';
import { MultiDeviceProtocol } from '../../../session/protocols'; import { MultiDeviceProtocol } from '../../../session/protocols';
import { ConversationHeaderWithDetails } from '../../conversation/ConversationHeader'; import { ConversationHeaderWithDetails } from '../../conversation/ConversationHeader';
import { SessionRightPanelWithDetails } from './SessionRightPanel'; import { SessionRightPanelWithDetails } from './SessionRightPanel';
import { Theme } from '../../../state/ducks/SessionTheme';
import { DefaultTheme } from 'styled-components';
interface State { interface State {
conversationKey: string; conversationKey: string;
@ -54,9 +56,17 @@ interface State {
// dropZoneFiles?: FileList // dropZoneFiles?: FileList
dropZoneFiles: any; dropZoneFiles: any;
// quoted message
quotedMessageProps?: ReplyingToMessageProps;
}
interface Props {
conversations: any;
theme: DefaultTheme;
} }
export class SessionConversation extends React.Component<any, State> { export class SessionConversation extends React.Component<Props, State> {
private readonly messagesEndRef: React.RefObject<HTMLDivElement>; private readonly messagesEndRef: React.RefObject<HTMLDivElement>;
private readonly messageContainerRef: React.RefObject<HTMLDivElement>; private readonly messageContainerRef: React.RefObject<HTMLDivElement>;
@ -210,7 +220,7 @@ export class SessionConversation extends React.Component<any, State> {
const showMessageDetails = this.state.infoViewState === 'messageDetails'; const showMessageDetails = this.state.infoViewState === 'messageDetails';
return ( return (
<> <Theme theme={this.props.theme}>
<div className="conversation-header">{this.renderHeader()}</div> <div className="conversation-header">{this.renderHeader()}</div>
{/* <SessionProgress {/* <SessionProgress
@ -272,6 +282,10 @@ export class SessionConversation extends React.Component<any, State> {
onMessageFailure={this.onMessageFailure} onMessageFailure={this.onMessageFailure}
onLoadVoiceNoteView={this.onLoadVoiceNoteView} onLoadVoiceNoteView={this.onLoadVoiceNoteView}
onExitVoiceNoteView={this.onExitVoiceNoteView} onExitVoiceNoteView={this.onExitVoiceNoteView}
quotedMessageProps={quotedMessageProps}
removeQuotedMessage={() => {
this.replyToMessage(undefined);
}}
/> />
)} )}
</div> </div>
@ -286,7 +300,7 @@ export class SessionConversation extends React.Component<any, State> {
<SessionRightPanelWithDetails {...groupSettingsProps} /> <SessionRightPanelWithDetails {...groupSettingsProps} />
</div> </div>
)} )}
</> </Theme>
); );
} }

@ -342,19 +342,6 @@ export class SettingsView extends React.Component<SettingsViewProps, State> {
const { Settings } = window.Signal.Types; const { Settings } = window.Signal.Types;
return [ 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', id: 'hide-menu-bar',
title: window.i18n('hideMenuBarTitle'), title: window.i18n('hideMenuBarTitle'),

@ -41,20 +41,17 @@ export const UI = {
// COMMON // COMMON
WHITE: '#FFFFFF', WHITE: '#FFFFFF',
WHITE_PALE: '#AFAFAF', WHITE_PALE: '#AFAFAF',
LIGHT_GREY: '#A0A0A0',
DARK_GREY: '#353535',
BLACK: '#000000',
GREEN: '#00F782', GREEN: '#00F782',
// SEMANTIC COLORS // SEMANTIC COLORS
INFO: '#3F3F3F',
SUCCESS: '#35D388',
ERROR: '#EDD422',
WARNING: '#A0A0A0',
WARNING_ALT: '#FF9D00',
DANGER: '#FF453A', DANGER: '#FF453A',
DANGER_ALT: '#FF4538', DANGER_ALT: '#FF4538',
PRIMARY: '#474646', },
SECONDARY: '#232323',
SPACING: {
marginXs: '5px',
marginSm: '10px',
marginMd: '15px',
marginLg: '20px',
}, },
}; };

@ -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;
}) => <ThemeProvider theme={theme}>{children}</ThemeProvider>;

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

@ -6,6 +6,7 @@ import {
reducer as conversations, reducer as conversations,
} from './ducks/conversations'; } from './ducks/conversations';
import { reducer as user, UserStateType } from './ducks/user'; import { reducer as user, UserStateType } from './ducks/user';
import { reducer as theme, ThemeStateType } from './ducks/theme';
// import { reducer as messages } from './ducks/messages'; // import { reducer as messages } from './ducks/messages';
export type StateType = { export type StateType = {
@ -13,6 +14,7 @@ export type StateType = {
messages: any; messages: any;
conversations: ConversationsStateType; conversations: ConversationsStateType;
user: UserStateType; user: UserStateType;
theme: ThemeStateType;
}; };
export const reducers = { export const reducers = {
@ -22,6 +24,7 @@ export const reducers = {
messages: search, messages: search,
conversations, conversations,
user, user,
theme,
}; };
// Making this work would require that our reducer signature supported AnyAction, not // Making this work would require that our reducer signature supported AnyAction, not

@ -21,7 +21,6 @@ const mapStateToProps = (state: StateType) => {
const leftPaneList = getLeftPaneLists(state); const leftPaneList = getLeftPaneLists(state);
const lists = showSearch ? undefined : leftPaneList; const lists = showSearch ? undefined : leftPaneList;
const searchResults = showSearch ? getSearchResults(state) : undefined; const searchResults = showSearch ? getSearchResults(state) : undefined;
return { return {
...lists, ...lists,
searchTerm: getQuery(state), searchTerm: getQuery(state),
@ -32,6 +31,7 @@ const mapStateToProps = (state: StateType) => {
showArchived: getShowArchived(state), showArchived: getShowArchived(state),
i18n: getIntl(state), i18n: getIntl(state),
unreadMessageCount: leftPaneList.unreadCount, unreadMessageCount: leftPaneList.unreadCount,
theme: state.theme,
}; };
}; };

@ -39,6 +39,7 @@ const mapStateToProps = (state: StateType) => {
return { return {
conversations: state.conversations, conversations: state.conversations,
theme: state.theme,
}; };
}; };

63
ts/styled.d.ts vendored

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

4
ts/window.d.ts vendored

@ -12,7 +12,7 @@ import { LibTextsecure } from '../libtextsecure';
import { ConversationType } from '../js/modules/data'; import { ConversationType } from '../js/modules/data';
import { RecoveryPhraseUtil } from '../libloki/modules/mnemonic'; import { RecoveryPhraseUtil } from '../libloki/modules/mnemonic';
import { ConfirmationDialogParams } from '../background'; 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. 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. If you import anything in global.d.ts, the type system won't work correctly.
@ -89,7 +89,7 @@ declare global {
toggleMediaPermissions: any; toggleMediaPermissions: any;
toggleMenuBar: any; toggleMenuBar: any;
toggleSpellCheck: any; toggleSpellCheck: any;
toggleTheme: any; setTheme: (newTheme: string) => any;
tokenlessFileServerAdnAPI: LokiAppDotNetServerInterface; tokenlessFileServerAdnAPI: LokiAppDotNetServerInterface;
userConfig: any; userConfig: any;
versionInfo: any; versionInfo: any;

Loading…
Cancel
Save