Merge pull request #1540 from Bilb/fix-toast-allow-mic

Fix toast allow mic
pull/1543/head
Audric Ackermann 4 years ago committed by GitHub
commit d9d203ffc3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -772,7 +772,7 @@
"description": "Shown if the user attempts to send an audio message without audio permssions turned on" "description": "Shown if the user attempts to send an audio message without audio permssions turned on"
}, },
"audioPermissionNeeded": { "audioPermissionNeeded": {
"message": "Session needs microphone access to send audio messages.", "message": "You can enable microphone access under: Settings (Gear icon) => Privacy",
"description": "Shown if the user attempts to send an audio message without audio permssions turned on", "description": "Shown if the user attempts to send an audio message without audio permssions turned on",
"androidKey": "ConversationActivity_to_send_audio_messages_allow_signal_access_to_your_microphone" "androidKey": "ConversationActivity_to_send_audio_messages_allow_signal_access_to_your_microphone"
}, },

@ -630,7 +630,7 @@
"description": "In Android theme, shown in quote if you or someone else replies to you" "description": "In Android theme, shown in quote if you or someone else replies to you"
}, },
"audioPermissionNeeded": { "audioPermissionNeeded": {
"message": "Pour envoyer des messages audio, autorisez Session à accéder à votre microphone.", "message": "Vous pouvez autoriser l'accès au microphone via: Paramètres (icon roue dentée) => Confidentialité.",
"description": "Shown if the user attempts to send an audio message without audio permssions turned on" "description": "Shown if the user attempts to send an audio message without audio permssions turned on"
}, },
"allowAccess": { "allowAccess": {

@ -26,7 +26,7 @@ import { PubKey } from '../session/types';
import { ConversationType } from '../state/ducks/conversations'; import { ConversationType } from '../state/ducks/conversations';
export interface ConversationListItemProps extends ConversationType { export interface ConversationListItemProps extends ConversationType {
index: number; // used to force a refresh when one conversation is removed on top of the list index?: number; // used to force a refresh when one conversation is removed on top of the list
memberAvatars?: Array<ConversationAvatar>; // this is added by usingClosedConversationDetails memberAvatars?: Array<ConversationAvatar>; // this is added by usingClosedConversationDetails
} }

@ -3,17 +3,22 @@ import React from 'react';
import { ActionsPanel, SectionType } from './session/ActionsPanel'; import { ActionsPanel, SectionType } from './session/ActionsPanel';
import { LeftPaneMessageSection } from './session/LeftPaneMessageSection'; import { LeftPaneMessageSection } from './session/LeftPaneMessageSection';
import { ConversationListItemProps } from './ConversationListItem'; import { openConversationExternal } from '../state/ducks/conversations';
import { SearchResultsProps } from './SearchResults';
import { SearchOptions } from '../types/Search';
import { ConversationType } from '../state/ducks/conversations';
import { LeftPaneContactSection } from './session/LeftPaneContactSection'; import { LeftPaneContactSection } from './session/LeftPaneContactSection';
import { LeftPaneSettingSection } from './session/LeftPaneSettingSection'; import { LeftPaneSettingSection } from './session/LeftPaneSettingSection';
import { SessionTheme } from '../state/ducks/SessionTheme'; import { SessionTheme } from '../state/ducks/SessionTheme';
import { DefaultTheme } from 'styled-components';
import { SessionSettingCategory } from './session/settings/SessionSettings';
import { SessionOffline } from './session/network/SessionOffline'; import { SessionOffline } from './session/network/SessionOffline';
import { SessionExpiredWarning } from './session/network/SessionExpiredWarning'; import { SessionExpiredWarning } from './session/network/SessionExpiredWarning';
import { getFocusedSection } from '../state/selectors/section';
import { useDispatch, useSelector } from 'react-redux';
import { getLeftPaneLists } from '../state/selectors/conversations';
import {
getQuery,
getSearchResults,
isSearching,
} from '../state/selectors/search';
import { clearSearch, search, updateSearchTerm } from '../state/ducks/search';
import { getTheme } from '../state/selectors/theme';
// from https://github.com/bvaughn/react-virtualized/blob/fb3484ed5dcc41bffae8eab029126c0fb8f7abc0/source/List/types.js#L5 // from https://github.com/bvaughn/react-virtualized/blob/fb3484ed5dcc41bffae8eab029126c0fb8f7abc0/source/List/types.js#L5
export type RowRendererParamsType = { export type RowRendererParamsType = {
@ -25,135 +30,97 @@ export type RowRendererParamsType = {
style: Object; style: Object;
}; };
interface Props { type Props = {
ourPrimaryConversation: ConversationType;
conversations: Array<ConversationListItemProps>;
contacts: Array<ConversationType>;
unreadMessageCount: number;
searchResults?: SearchResultsProps;
searchTerm: string;
focusedSection: SectionType;
focusSection: (section: SectionType) => void;
isExpired: boolean; isExpired: boolean;
};
openConversationExternal: (id: string, messageId?: string) => void; const InnerLeftPaneMessageSection = (props: { isExpired: boolean }) => {
showSessionSettingsCategory: (category: SessionSettingCategory) => void; const dispatch = useDispatch();
showSessionViewConversation: () => void;
settingsCategory?: SessionSettingCategory; const showSearch = useSelector(isSearching);
updateSearchTerm: (searchTerm: string) => void; const searchTerm = useSelector(getQuery);
search: (query: string, options: SearchOptions) => void;
clearSearch: () => void; const searchResults = showSearch ? useSelector(getSearchResults) : undefined;
theme: DefaultTheme; const lists = showSearch ? undefined : useSelector(getLeftPaneLists);
} const theme = useSelector(getTheme);
// tslint:disable: use-simple-attributes
export class LeftPane extends React.Component<Props> {
public constructor(props: any) { return (
super(props); <>
this.handleSectionSelected = this.handleSectionSelected.bind(this); <SessionOffline />
} {props.isExpired && <SessionExpiredWarning />}
<LeftPaneMessageSection
theme={theme}
openConversationExternal={(id, messageId) =>
dispatch(openConversationExternal(id, messageId))
}
conversations={lists?.conversations || []}
contacts={lists?.contacts || []}
searchResults={searchResults}
searchTerm={searchTerm}
updateSearchTerm={query => dispatch(updateSearchTerm(query))}
search={(query, options) => dispatch(search(query, options))}
clearSearch={() => dispatch(clearSearch())}
/>
</>
);
};
public handleSectionSelected(section: SectionType) { const InnerLeftPaneContactSection = () => {
this.props.clearSearch(); const dispatch = useDispatch();
this.props.focusSection(section); const theme = useSelector(getTheme);
if (section === SectionType.Settings) { const showSearch = useSelector(isSearching);
this.props.showSessionSettingsCategory(SessionSettingCategory.Appearance);
} else { const lists = showSearch ? undefined : useSelector(getLeftPaneLists);
this.props.showSessionViewConversation();
} const directContacts = lists?.contacts || [];
}
return (
<>
<SessionOffline />
<LeftPaneContactSection
openConversationExternal={(id, messageId) =>
dispatch(openConversationExternal(id, messageId))
}
directContacts={directContacts}
theme={theme}
/>
</>
);
};
public render(): JSX.Element { const LeftPaneSettingsSection = () => {
return ( return <LeftPaneSettingSection />;
<SessionTheme theme={this.props.theme}> };
<div className="module-left-pane-session">
<ActionsPanel
{...this.props}
selectedSection={this.props.focusedSection}
onSectionSelected={this.handleSectionSelected}
/>
<div className="module-left-pane">{this.renderSection()}</div>
</div>
</SessionTheme>
);
}
private renderSection(): JSX.Element | undefined { const LeftPaneSection = (props: { isExpired: boolean }) => {
switch (this.props.focusedSection) { const focusedSection = useSelector(getFocusedSection);
case SectionType.Message:
return this.renderMessageSection();
case SectionType.Contact:
return this.renderContactSection();
case SectionType.Settings:
return this.renderSettingSection();
default:
return undefined;
}
}
private renderMessageSection() { if (focusedSection === SectionType.Message) {
const { return <InnerLeftPaneMessageSection isExpired={props.isExpired} />;
openConversationExternal,
conversations,
contacts,
searchResults,
searchTerm,
updateSearchTerm,
search,
clearSearch,
isExpired,
} = this.props;
return (
<>
<SessionOffline theme={this.props.theme} />
{isExpired && <SessionExpiredWarning theme={this.props.theme} />}
<LeftPaneMessageSection
theme={this.props.theme}
contacts={contacts}
openConversationExternal={openConversationExternal}
conversations={conversations}
searchResults={searchResults}
searchTerm={searchTerm}
updateSearchTerm={updateSearchTerm}
search={search}
clearSearch={clearSearch}
/>
</>
);
} }
private renderContactSection() { if (focusedSection === SectionType.Contact) {
const { openConversationExternal } = this.props; return <InnerLeftPaneContactSection />;
const directContacts = this.getDirectContactsOnly();
return (
<>
<SessionOffline theme={this.props.theme} />
<LeftPaneContactSection
{...this.props}
openConversationExternal={openConversationExternal}
directContacts={directContacts}
/>
</>
);
} }
if (focusedSection === SectionType.Settings) {
private getDirectContactsOnly() { return <LeftPaneSettingsSection />;
return this.props.contacts.filter(f => f.type === 'direct');
} }
return <></>;
};
private renderSettingSection() { export const LeftPane = (props: Props) => {
const { settingsCategory } = this.props; const theme = useSelector(getTheme);
const category = settingsCategory || SessionSettingCategory.Appearance;
return ( return (
<> <SessionTheme theme={theme}>
<LeftPaneSettingSection {...this.props} settingsCategory={category} /> <div className="module-left-pane-session">
</> <ActionsPanel />
); <div className="module-left-pane">
} <LeftPaneSection isExpired={props.isExpired} />
} </div>
</div>
</SessionTheme>
);
};

@ -0,0 +1,22 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { getFocusedSettingsSection } from '../state/selectors/section';
import { SmartSessionConversation } from '../state/smart/SessionConversation';
import { SmartSettingsView } from './session/settings/SessionSettings';
const FilteredSettingsView = SmartSettingsView as any;
export const SessionMainPanel = () => {
const focusedSettingsSection = useSelector(getFocusedSettingsSection);
const isSettingsView = focusedSettingsSection !== undefined;
if (isSettingsView) {
return <FilteredSettingsView category={focusedSettingsSection} />;
}
return (
<div className="session-conversation">
<SmartSessionConversation />
</div>
);
};

@ -1,17 +1,11 @@
import React from 'react'; import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import { SessionIconButton, SessionIconSize, SessionIconType } from './icon'; import { SessionIconButton, SessionIconSize, SessionIconType } from './icon';
import { Avatar } from '../Avatar'; import { Avatar } from '../Avatar';
import { darkTheme, lightTheme } from '../../state/ducks/SessionTheme'; import { darkTheme, lightTheme } from '../../state/ducks/SessionTheme';
import { SessionToastContainer } from './SessionToastContainer'; import { SessionToastContainer } from './SessionToastContainer';
import { mapDispatchToProps } from '../../state/actions';
import { ConversationType } from '../../state/ducks/conversations'; import { ConversationType } from '../../state/ducks/conversations';
import { DefaultTheme } from 'styled-components'; import { DefaultTheme } from 'styled-components';
import { StateType } from '../../state/reducer';
import { ConversationController } from '../../session/conversations'; import { ConversationController } from '../../session/conversations';
import { getFocusedSection } from '../../state/selectors/section';
import { getTheme } from '../../state/selectors/theme';
import { getOurNumber } from '../../state/selectors/user';
import { UserUtils } from '../../session/utils'; import { UserUtils } from '../../session/utils';
import { syncConfigurationIfNeeded } from '../../session/utils/syncUtils'; import { syncConfigurationIfNeeded } from '../../session/utils/syncUtils';
import { DAYS } from '../../session/utils/Number'; import { DAYS } from '../../session/utils/Number';
@ -23,6 +17,18 @@ import {
import { OnionPaths } from '../../session/onions'; import { OnionPaths } from '../../session/onions';
import { getMessageQueue } from '../../session/sending'; import { getMessageQueue } from '../../session/sending';
import { clearSessionsAndPreKeys } from '../../util/accountManager'; import { clearSessionsAndPreKeys } from '../../util/accountManager';
import { useDispatch, useSelector } from 'react-redux';
import { getOurNumber } from '../../state/selectors/user';
import {
getOurPrimaryConversation,
getUnreadMessageCount,
} from '../../state/selectors/conversations';
import { getTheme } from '../../state/selectors/theme';
import { applyTheme } from '../../state/ducks/theme';
import { getFocusedSection } from '../../state/selectors/section';
import { useInterval } from '../../hooks/useInterval';
import { clearSearch } from '../../state/ducks/search';
import { showLeftPaneSection } from '../../state/ducks/section';
// tslint:disable-next-line: no-import-side-effect no-submodule-imports // tslint:disable-next-line: no-import-side-effect no-submodule-imports
export enum SectionType { export enum SectionType {
@ -34,33 +40,100 @@ export enum SectionType {
Moon, Moon,
} }
interface Props { const Section = (props: { type: SectionType; avatarPath?: string }) => {
onSectionSelected: any; const ourNumber = useSelector(getOurNumber);
selectedSection: SectionType; const unreadMessageCount = useSelector(getUnreadMessageCount);
unreadMessageCount: number; const theme = useSelector(getTheme);
ourPrimaryConversation: ConversationType; const dispatch = useDispatch();
ourNumber: string; const { type, avatarPath } = props;
applyTheme?: any;
theme: DefaultTheme; const focusedSection = useSelector(getFocusedSection);
} const isSelected = focusedSection === props.type;
const handleClick = () => {
/* tslint:disable:no-void-expression */
if (type === SectionType.Profile) {
window.showEditProfileDialog();
} else if (type === SectionType.Moon) {
const themeFromSettings = window.Events.getThemeSetting();
const updatedTheme = themeFromSettings === 'dark' ? 'light' : 'dark';
window.setTheme(updatedTheme);
const newThemeObject = updatedTheme === 'dark' ? darkTheme : lightTheme;
dispatch(applyTheme(newThemeObject));
} else {
dispatch(clearSearch());
dispatch(showLeftPaneSection(type));
}
};
if (type === SectionType.Profile) {
const conversation = ConversationController.getInstance().get(ourNumber);
const profile = conversation?.getLokiProfile();
const userName = (profile && profile.displayName) || ourNumber;
return (
<Avatar
avatarPath={avatarPath}
size={28}
onAvatarClick={handleClick}
name={userName}
pubkey={ourNumber}
/>
);
}
let iconType: SessionIconType;
switch (type) {
case SectionType.Message:
iconType = SessionIconType.ChatBubble;
break;
case SectionType.Contact:
iconType = SessionIconType.Users;
break;
case SectionType.Settings:
iconType = SessionIconType.Gear;
break;
case SectionType.Moon:
iconType = SessionIconType.Moon;
break;
default:
iconType = SessionIconType.Moon;
}
return (
<SessionIconButton
iconSize={SessionIconSize.Medium}
iconType={iconType}
notificationCount={unreadMessageCount}
onClick={handleClick}
isSelected={isSelected}
theme={theme}
/>
);
};
const showResetSessionIDDialogIfNeeded = async () => {
const userED25519KeyPairHex = await UserUtils.getUserED25519KeyPair();
if (userED25519KeyPairHex) {
return;
}
window.showResetSessionIdDialog();
};
/** /**
* ActionsPanel is the far left banner (not the left pane). * ActionsPanel is the far left banner (not the left pane).
* The panel with buttons to switch between the message/contact/settings/theme views * The panel with buttons to switch between the message/contact/settings/theme views
*/ */
class ActionsPanelPrivate extends React.Component<Props> { export const ActionsPanel = () => {
private syncInterval: NodeJS.Timeout | null = null; const dispatch = useDispatch();
constructor(props: Props) { const ourPrimaryConversation = useSelector(getOurPrimaryConversation);
super(props);
// we consider people had the time to upgrade, so remove this id from the db // this maxi useEffect is called only once: when the component is mounted.
// it was used to display a dialog when we added the light mode auto-enabled useEffect(() => {
void removeItemById('hasSeenLightModeDialog');
}
// fetch the user saved theme from the db, and apply it on mount.
public componentDidMount() {
void window.setClockParams(); void window.setClockParams();
if ( if (
window.lokiFeatureFlags.useOnionRequests || window.lokiFeatureFlags.useOnionRequests ||
@ -78,12 +151,14 @@ class ActionsPanelPrivate extends React.Component<Props> {
window.setTheme(theme); window.setTheme(theme);
const newThemeObject = theme === 'dark' ? darkTheme : lightTheme; const newThemeObject = theme === 'dark' ? darkTheme : lightTheme;
this.props.applyTheme(newThemeObject); dispatch(applyTheme(newThemeObject));
void this.showResetSessionIDDialogIfNeeded();
void showResetSessionIDDialogIfNeeded();
// remove existing prekeys, sign prekeys and sessions // remove existing prekeys, sign prekeys and sessions
void clearSessionsAndPreKeys(); void clearSessionsAndPreKeys();
// we consider people had the time to upgrade, so remove this id from the db
// it was used to display a dialog when we added the light mode auto-enabled
void removeItemById('hasSeenLightModeDialog');
// Do this only if we created a new Session ID, or if we already received the initial configuration message // Do this only if we created a new Session ID, or if we already received the initial configuration message
@ -98,178 +173,29 @@ class ActionsPanelPrivate extends React.Component<Props> {
// trigger a sync message if needed for our other devices // trigger a sync message if needed for our other devices
void syncConfiguration(); void syncConfiguration();
}, []);
this.syncInterval = global.setInterval(() => { if (!ourPrimaryConversation) {
void syncConfigurationIfNeeded(); window.log.warn('ActionsPanel: ourPrimaryConversation is not set');
}, DAYS * 2); return <></>;
} }
public componentWillUnmount() { useInterval(() => {
if (this.syncInterval) { void syncConfigurationIfNeeded();
clearInterval(this.syncInterval); }, DAYS * 2);
this.syncInterval = null;
}
}
public Section = ({
isSelected,
onSelect,
type,
avatarPath,
notificationCount,
}: {
isSelected: boolean;
onSelect?: (event: SectionType) => void;
type: SectionType;
avatarPath?: string;
notificationCount?: number;
}) => {
const { ourNumber } = this.props;
const handleClick = onSelect
? () => {
/* tslint:disable:no-void-expression */
if (type === SectionType.Profile) {
window.showEditProfileDialog();
} else if (type === SectionType.Moon) {
const theme = window.Events.getThemeSetting();
const updatedTheme = theme === 'dark' ? 'light' : 'dark';
window.setTheme(updatedTheme);
const newThemeObject =
updatedTheme === 'dark' ? darkTheme : lightTheme;
this.props.applyTheme(newThemeObject);
} else {
onSelect(type);
}
/* tslint:enable:no-void-expression */
}
: undefined;
if (type === SectionType.Profile) {
const conversation = ConversationController.getInstance().get(ourNumber);
const profile = conversation?.getLokiProfile();
const userName = (profile && profile.displayName) || ourNumber;
return (
<Avatar
avatarPath={avatarPath}
size={28}
onAvatarClick={handleClick}
name={userName}
pubkey={ourNumber}
/>
);
}
let iconType: SessionIconType;
switch (type) {
case SectionType.Message:
iconType = SessionIconType.ChatBubble;
break;
case SectionType.Contact:
iconType = SessionIconType.Users;
break;
case SectionType.Channel:
iconType = SessionIconType.Globe;
break;
case SectionType.Settings:
iconType = SessionIconType.Gear;
break;
case SectionType.Moon:
iconType = SessionIconType.Moon;
break;
default:
iconType = SessionIconType.Moon;
}
return ( return (
<SessionIconButton <div className="module-left-pane__sections-container">
iconSize={SessionIconSize.Medium} <Section
iconType={iconType} type={SectionType.Profile}
notificationCount={notificationCount} avatarPath={ourPrimaryConversation.avatarPath}
onClick={handleClick}
isSelected={isSelected}
theme={this.props.theme}
/> />
); <Section type={SectionType.Message} />
}; <Section type={SectionType.Contact} />
<Section type={SectionType.Settings} />
public render(): JSX.Element {
const { <SessionToastContainer />
selectedSection, <Section type={SectionType.Moon} />
unreadMessageCount, </div>
ourPrimaryConversation, );
} = this.props;
if (!ourPrimaryConversation) {
window.log.warn('ActionsPanel: ourPrimaryConversation is not set');
return <></>;
}
const isProfilePageSelected = selectedSection === SectionType.Profile;
const isMessagePageSelected = selectedSection === SectionType.Message;
const isContactPageSelected = selectedSection === SectionType.Contact;
const isSettingsPageSelected = selectedSection === SectionType.Settings;
const isMoonPageSelected = selectedSection === SectionType.Moon;
return (
<div className="module-left-pane__sections-container">
<this.Section
type={SectionType.Profile}
avatarPath={ourPrimaryConversation.avatarPath}
isSelected={isProfilePageSelected}
onSelect={this.handleSectionSelect}
/>
<this.Section
type={SectionType.Message}
isSelected={isMessagePageSelected}
onSelect={this.handleSectionSelect}
notificationCount={unreadMessageCount}
/>
<this.Section
type={SectionType.Contact}
isSelected={isContactPageSelected}
onSelect={this.handleSectionSelect}
/>
<this.Section
type={SectionType.Settings}
isSelected={isSettingsPageSelected}
onSelect={this.handleSectionSelect}
/>
<SessionToastContainer />
<this.Section
type={SectionType.Moon}
isSelected={isMoonPageSelected}
onSelect={this.handleSectionSelect}
/>
</div>
);
}
private readonly handleSectionSelect = (section: SectionType): void => {
this.props.onSectionSelected(section);
};
private async showResetSessionIDDialogIfNeeded() {
const userED25519KeyPairHex = await UserUtils.getUserED25519KeyPair();
if (userED25519KeyPairHex) {
return;
}
window.showResetSessionIdDialog();
}
}
const mapStateToProps = (state: StateType) => {
return {
section: getFocusedSection(state),
theme: getTheme(state),
ourNumber: getOurNumber(state),
};
}; };
const smart = connect(mapStateToProps, mapDispatchToProps);
export const ActionsPanel = smart(ActionsPanelPrivate);

@ -1,8 +1,6 @@
import React from 'react'; import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { LeftPane } from '../LeftPane';
import { import {
SessionButton, SessionButton,
SessionButtonColor, SessionButtonColor,
@ -10,212 +8,162 @@ import {
} from './SessionButton'; } from './SessionButton';
import { SessionIcon, SessionIconSize, SessionIconType } from './icon'; import { SessionIcon, SessionIconSize, SessionIconType } from './icon';
import { SessionSearchInput } from './SessionSearchInput';
import { SessionSettingCategory } from './settings/SessionSettings'; import { SessionSettingCategory } from './settings/SessionSettings';
import { DefaultTheme } from 'styled-components'; import { DefaultTheme } from 'styled-components';
import { LeftPaneSectionHeader } from './LeftPaneSectionHeader'; import { LeftPaneSectionHeader } from './LeftPaneSectionHeader';
import { deleteAccount } from '../../util/accountManager'; import { deleteAccount } from '../../util/accountManager';
import { useDispatch, useSelector } from 'react-redux';
import { showSettingsSection } from '../../state/ducks/section';
import { getFocusedSettingsSection } from '../../state/selectors/section';
import { getTheme } from '../../state/selectors/theme';
interface Props { type Props = {
settingsCategory: SessionSettingCategory; settingsCategory: SessionSettingCategory;
showSessionSettingsCategory: (category: SessionSettingCategory) => void; showSettingsSection: (category: SessionSettingCategory) => void;
theme: DefaultTheme; theme: DefaultTheme;
} };
export interface State { const getCategories = () => {
searchQuery: string; return [
} {
id: SessionSettingCategory.Appearance,
export class LeftPaneSettingSection extends React.Component<Props, State> { title: window.i18n('appearanceSettingsTitle'),
public constructor(props: any) { hidden: false,
super(props); },
{
this.state = { id: SessionSettingCategory.Privacy,
searchQuery: '', title: window.i18n('privacySettingsTitle'),
}; hidden: false,
},
this.setCategory = this.setCategory.bind(this); {
this.onDeleteAccount = this.onDeleteAccount.bind(this); id: SessionSettingCategory.Blocked,
} title: window.i18n('blockedSettingsTitle'),
hidden: false,
public render(): JSX.Element { },
return ( {
<div className="left-pane-setting-section"> id: SessionSettingCategory.Permissions,
{this.renderHeader()} title: window.i18n('permissionSettingsTitle'),
{this.renderSettings()} hidden: true,
},
{
id: SessionSettingCategory.Notifications,
title: window.i18n('notificationsSettingsTitle'),
hidden: false,
},
];
};
const LeftPaneSettingsCategoryRow = (props: { item: any }) => {
const { item } = props;
const dispatch = useDispatch();
const theme = useSelector(getTheme);
const focusedSettingsSection = useSelector(getFocusedSettingsSection);
return (
<div
key={item.id}
className={classNames(
'left-pane-setting-category-list-item',
item.id === focusedSettingsSection ? 'active' : ''
)}
role="link"
onClick={() => {
dispatch(showSettingsSection(item.id));
}}
>
<div>
<strong>{item.title}</strong>
</div> </div>
);
}
public renderHeader(): JSX.Element | undefined {
return (
<LeftPaneSectionHeader
label={window.i18n('settingsHeader')}
theme={this.props.theme}
/>
);
}
public renderRow(item: any): JSX.Element { <div>
const { settingsCategory } = this.props; {item.id === focusedSettingsSection && (
return ( <SessionIcon
<div iconSize={SessionIconSize.Medium}
key={item.id} iconType={SessionIconType.Chevron}
className={classNames( iconRotation={270}
'left-pane-setting-category-list-item', theme={theme}
item.id === settingsCategory ? 'active' : '' />
)} )}
role="link"
onClick={() => {
this.setCategory(item.id);
}}
>
<div>
<strong>{item.title}</strong>
</div>
<div>
{item.id === settingsCategory && (
<SessionIcon
iconSize={SessionIconSize.Medium}
iconType={SessionIconType.Chevron}
iconRotation={270}
theme={this.props.theme}
/>
)}
</div>
</div> </div>
); </div>
} );
};
public renderCategories(): JSX.Element {
const categories = this.getCategories().filter(item => !item.hidden); const LeftPaneSettingsCategories = () => {
const categories = getCategories();
return (
<div className="module-left-pane__list" key={0}> return (
<div className="left-pane-setting-category-list"> <div className="module-left-pane__list" key={0}>
{categories.map(item => this.renderRow(item))} <div className="left-pane-setting-category-list">
</div> {categories
.filter(m => !m.hidden)
.map(item => {
return <LeftPaneSettingsCategoryRow key={item.id} item={item} />;
})}
</div> </div>
); </div>
} );
};
public renderSearch() { const onDeleteAccount = () => {
return ( const title = window.i18n('clearAllData');
<div className="left-pane-setting-content">
<div className="left-pane-setting-input-group">
<SessionSearchInput
searchString={this.state.searchQuery}
onChange={() => null}
placeholder=""
theme={this.props.theme}
/>
<div className="left-pane-setting-input-button">
<SessionButton
buttonType={SessionButtonType.Square}
buttonColor={SessionButtonColor.Green}
theme={this.props.theme}
>
<SessionIcon
iconType={SessionIconType.Caret}
iconSize={SessionIconSize.Huge}
theme={this.props.theme}
/>
</SessionButton>
</div>
</div>
</div>
);
}
public renderSettings(): JSX.Element { const message = window.i18n('unpairDeviceWarning');
const showSearch = false;
return ( let messageSub = '';
<div className="left-pane-setting-content">
{showSearch && this.renderSearch()}
{this.renderCategories()}
{this.renderBottomButtons()}
</div>
);
}
public renderBottomButtons(): JSX.Element | undefined { const identityKey = window.textsecure.storage.get('identityKey');
const dangerButtonText = window.i18n('clearAllData'); if (identityKey && identityKey.ed25519KeyPair === undefined) {
const showRecoveryPhrase = window.i18n('showRecoveryPhrase'); messageSub =
"We've updated the way Session IDs are generated, so you will not be able to restore your current Session ID.";
return (
<div className="left-pane-setting-bottom-buttons">
<SessionButton
text={dangerButtonText}
buttonType={SessionButtonType.SquareOutline}
buttonColor={SessionButtonColor.Danger}
onClick={this.onDeleteAccount}
/>
<SessionButton
text={showRecoveryPhrase}
buttonType={SessionButtonType.SquareOutline}
buttonColor={SessionButtonColor.White}
onClick={() => window.Whisper.events.trigger('showSeedDialog')}
/>
</div>
);
} }
public onDeleteAccount() { window.confirmationDialog({
const title = window.i18n('clearAllData'); title,
message,
const message = window.i18n('unpairDeviceWarning'); messageSub,
resolve: deleteAccount,
let messageSub = ''; okTheme: 'danger',
});
const identityKey = window.textsecure.storage.get('identityKey'); };
if (identityKey && identityKey.ed25519KeyPair === undefined) {
messageSub = const LeftPaneBottomButtons = () => {
"We've updated the way Session IDs are generated, so you will not be able to restore your current Session ID."; const dangerButtonText = window.i18n('clearAllData');
} const showRecoveryPhrase = window.i18n('showRecoveryPhrase');
return (
<div className="left-pane-setting-bottom-buttons">
<SessionButton
text={dangerButtonText}
buttonType={SessionButtonType.SquareOutline}
buttonColor={SessionButtonColor.Danger}
onClick={onDeleteAccount}
/>
window.confirmationDialog({ <SessionButton
title, text={showRecoveryPhrase}
message, buttonType={SessionButtonType.SquareOutline}
messageSub, buttonColor={SessionButtonColor.White}
resolve: deleteAccount, onClick={() => window.Whisper.events.trigger('showSeedDialog')}
okTheme: 'danger', />
}); </div>
} );
};
public getCategories() { export const LeftPaneSettingSection = () => {
return [ const theme = useSelector(getTheme);
{
id: SessionSettingCategory.Appearance,
title: window.i18n('appearanceSettingsTitle'),
hidden: false,
},
{
id: SessionSettingCategory.Privacy,
title: window.i18n('privacySettingsTitle'),
hidden: false,
},
{
id: SessionSettingCategory.Blocked,
title: window.i18n('blockedSettingsTitle'),
hidden: false,
},
{
id: SessionSettingCategory.Permissions,
title: window.i18n('permissionSettingsTitle'),
hidden: true,
},
{
id: SessionSettingCategory.Notifications,
title: window.i18n('notificationsSettingsTitle'),
hidden: false,
},
];
}
public setCategory(category: SessionSettingCategory) { return (
this.props.showSessionSettingsCategory(category); <div className="left-pane-setting-section">
} <LeftPaneSectionHeader
} label={window.i18n('settingsHeader')}
theme={theme}
/>
<div className="left-pane-setting-content">
<LeftPaneSettingsCategories />
<LeftPaneBottomButtons />
</div>
</div>
);
};

@ -1,59 +1,33 @@
import React from 'react'; import React from 'react';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { getMessageById } from '../../data/data';
import { ConversationModel } from '../../models/conversation'; import { ConversationModel } from '../../models/conversation';
import { MessageModel } from '../../models/message';
import { getMessageQueue } from '../../session';
import { ConversationController } from '../../session/conversations'; import { ConversationController } from '../../session/conversations';
import { MessageController } from '../../session/messages';
import { OpenGroupMessage } from '../../session/messages/outgoing';
import { RawMessage } from '../../session/types';
import { UserUtils } from '../../session/utils'; import { UserUtils } from '../../session/utils';
import { createStore } from '../../state/createStore'; import { createStore } from '../../state/createStore';
import { actions as conversationActions } from '../../state/ducks/conversations'; import { actions as conversationActions } from '../../state/ducks/conversations';
import { actions as userActions } from '../../state/ducks/user';
import { SmartLeftPane } from '../../state/smart/LeftPane';
import { SmartSessionConversation } from '../../state/smart/SessionConversation';
import { makeLookup } from '../../util'; import { makeLookup } from '../../util';
import { import { LeftPane } from '../LeftPane';
SessionSettingCategory, import { SessionMainPanel } from '../SessionMainPanel';
SmartSettingsView,
} from './settings/SessionSettings';
// Workaround: A react component's required properties are filtering up through connect() // Workaround: A react component's required properties are filtering up through connect()
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363 // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
const FilteredLeftPane = SmartLeftPane as any;
const FilteredSettingsView = SmartSettingsView as any;
type Props = {
focusedSection: number;
};
type State = { type State = {
isInitialLoadComplete: boolean; isInitialLoadComplete: boolean;
settingsCategory?: SessionSettingCategory;
isExpired: boolean; isExpired: boolean;
}; };
export class SessionInboxView extends React.Component<Props, State> { export class SessionInboxView extends React.Component<any, State> {
private store: any; private store: any;
constructor(props: any) { constructor(props: any) {
super(props); super(props);
this.state = { this.state = {
isInitialLoadComplete: false, isInitialLoadComplete: false,
settingsCategory: undefined,
isExpired: false, isExpired: false,
}; };
this.showSessionSettingsCategory = this.showSessionSettingsCategory.bind(
this
);
this.showSessionViewConversation = this.showSessionViewConversation.bind(
this
);
void this.setupLeftPane(); void this.setupLeftPane();
// not reactified yet. this is a callback called once we were able to check for expiration of this Session version // not reactified yet. this is a callback called once we were able to check for expiration of this Session version
@ -71,46 +45,19 @@ export class SessionInboxView extends React.Component<Props, State> {
return <></>; return <></>;
} }
const { settingsCategory } = this.state;
const isSettingsView = settingsCategory !== undefined;
return ( return (
<Provider store={this.store}> <Provider store={this.store}>
<div className="gutter"> <div className="gutter">
<div className="network-status-container" /> <div className="network-status-container" />
{this.renderLeftPane()} {this.renderLeftPane()}
</div> </div>
{isSettingsView <SessionMainPanel />
? this.renderSettings()
: this.renderSessionConversation()}
</Provider> </Provider>
); );
} }
private renderLeftPane() { private renderLeftPane() {
return ( return <LeftPane isExpired={this.state.isExpired} />;
<FilteredLeftPane
showSessionSettingsCategory={this.showSessionSettingsCategory}
showSessionViewConversation={this.showSessionViewConversation}
settingsCategory={this.state.settingsCategory}
isExpired={this.state.isExpired}
/>
);
}
private renderSettings() {
const category =
this.state.settingsCategory || SessionSettingCategory.Appearance;
return <FilteredSettingsView category={category} />;
}
private renderSessionConversation() {
return (
<div className="session-conversation">
<SmartSessionConversation />
</div>
);
} }
private async setupLeftPane() { private async setupLeftPane() {
@ -155,12 +102,4 @@ export class SessionInboxView extends React.Component<Props, State> {
this.setState({ isInitialLoadComplete: true }); this.setState({ isInitialLoadComplete: true });
} }
private showSessionSettingsCategory(category: SessionSettingCategory) {
this.setState({ settingsCategory: category });
}
private showSessionViewConversation() {
this.setState({ settingsCategory: undefined });
}
} }

@ -3,6 +3,7 @@ import React, { useContext } from 'react';
import { SessionIcon, SessionIconSize, SessionIconType } from './icon/'; import { SessionIcon, SessionIconSize, SessionIconType } from './icon/';
import { Flex } from './Flex'; import { Flex } from './Flex';
import styled, { ThemeContext } from 'styled-components'; import styled, { ThemeContext } from 'styled-components';
import { noop } from 'lodash';
export enum SessionToastType { export enum SessionToastType {
Info = 'info', Info = 'info',
@ -18,6 +19,7 @@ type Props = {
icon?: SessionIconType; icon?: SessionIconType;
description?: string; description?: string;
closeToast?: any; closeToast?: any;
onToastClick?: () => void;
}; };
const TitleDiv = styled.div` const TitleDiv = styled.div`
@ -74,7 +76,12 @@ export const SessionToast = (props: Props) => {
} }
return ( return (
<Flex container={true} alignItems="center"> // tslint:disable-next-line: use-simple-attributes
<Flex
container={true}
alignItems="center"
onClick={props?.onToastClick || noop}
>
<IconDiv> <IconDiv>
<SessionIcon <SessionIcon
iconType={toastIcon} iconType={toastIcon}

@ -31,6 +31,8 @@ import { ConversationController } from '../../../session/conversations';
import { ConversationType } from '../../../state/ducks/conversations'; import { ConversationType } from '../../../state/ducks/conversations';
import { SessionMemberListItem } from '../SessionMemberListItem'; import { SessionMemberListItem } from '../SessionMemberListItem';
import autoBind from 'auto-bind'; import autoBind from 'auto-bind';
import { SectionType } from '../ActionsPanel';
import { SessionSettingCategory } from '../settings/SessionSettings';
export interface ReplyingToMessageProps { export interface ReplyingToMessageProps {
convoId: string; convoId: string;
@ -78,6 +80,8 @@ interface Props {
clearAttachments: () => any; clearAttachments: () => any;
removeAttachment: (toRemove: AttachmentType) => void; removeAttachment: (toRemove: AttachmentType) => void;
onChoseAttachments: (newAttachments: Array<File>) => void; onChoseAttachments: (newAttachments: Array<File>) => void;
showLeftPaneSection: (section: SectionType) => void;
showSettingsSection: (category: SessionSettingCategory) => void;
theme: DefaultTheme; theme: DefaultTheme;
} }
@ -931,7 +935,10 @@ export class SessionCompositionBox extends React.Component<Props, State> {
return; return;
} }
ToastUtils.pushAudioPermissionNeeded(); ToastUtils.pushAudioPermissionNeeded(() => {
this.props.showLeftPaneSection(SectionType.Settings);
this.props.showSettingsSection(SessionSettingCategory.Privacy);
});
} }
private onExitVoiceNoteView() { private onExitVoiceNoteView() {

@ -29,8 +29,6 @@ import { MessageView } from '../../MainViewController';
import { pushUnblockToSend } from '../../../session/utils/Toast'; import { pushUnblockToSend } from '../../../session/utils/Toast';
import { MessageDetail } from '../../conversation/MessageDetail'; import { MessageDetail } from '../../conversation/MessageDetail';
import { ConversationController } from '../../../session/conversations'; import { ConversationController } from '../../../session/conversations';
import { PubKey } from '../../../session/types';
import { MessageModel } from '../../../models/message';
import { import {
getMessageById, getMessageById,
getPubkeysInPublicConversation, getPubkeysInPublicConversation,
@ -210,6 +208,7 @@ export class SessionConversation extends React.Component<Props, State> {
selectedConversation, selectedConversation,
selectedConversationKey, selectedConversationKey,
messages, messages,
actions,
} = this.props; } = this.props;
if (!selectedConversation || !messages) { if (!selectedConversation || !messages) {
@ -308,6 +307,8 @@ export class SessionConversation extends React.Component<Props, State> {
onMessageFailure={this.onMessageFailure} onMessageFailure={this.onMessageFailure}
onLoadVoiceNoteView={this.onLoadVoiceNoteView} onLoadVoiceNoteView={this.onLoadVoiceNoteView}
onExitVoiceNoteView={this.onExitVoiceNoteView} onExitVoiceNoteView={this.onExitVoiceNoteView}
showLeftPaneSection={actions.showLeftPaneSection}
showSettingsSection={actions.showSettingsSection}
quotedMessageProps={quotedMessageProps} quotedMessageProps={quotedMessageProps}
removeQuotedMessage={() => { removeQuotedMessage={() => {
void this.replyToMessage(undefined); void this.replyToMessage(undefined);

@ -12,7 +12,7 @@ const SessionExpiredWarningLink = styled.a`
color: black; color: black;
`; `;
export const SessionExpiredWarning = (props: { theme: DefaultTheme }) => { export const SessionExpiredWarning = () => {
return ( return (
<SessionExpiredWarningContainer> <SessionExpiredWarningContainer>
<div>{window.i18n('expiredWarning')}</div> <div>{window.i18n('expiredWarning')}</div>

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import styled, { DefaultTheme } from 'styled-components'; import styled from 'styled-components';
import { useNetwork } from '../../../hooks/useNetwork'; import { useNetwork } from '../../../hooks/useNetwork';
type ContainerProps = { type ContainerProps = {
@ -23,7 +23,7 @@ const OfflineTitle = styled.h3`
const OfflineMessage = styled.div``; const OfflineMessage = styled.div``;
export const SessionOffline = (props: { theme: DefaultTheme }) => { export const SessionOffline = () => {
const isOnline = useNetwork(); const isOnline = useNetwork();
return ( return (

@ -35,12 +35,18 @@ export function pushToastWarning(
); );
} }
export function pushToastInfo(id: string, title: string, description?: string) { export function pushToastInfo(
id: string,
title: string,
description?: string,
onToastClick?: () => void
) {
toast.info( toast.info(
<SessionToast <SessionToast
title={title} title={title}
description={description} description={description}
type={SessionToastType.Info} type={SessionToastType.Info}
onToastClick={onToastClick}
/> />
); );
} }
@ -151,11 +157,12 @@ export function pushMessageDeleteForbidden() {
); );
} }
export function pushAudioPermissionNeeded() { export function pushAudioPermissionNeeded(onClicked: () => void) {
pushToastInfo( pushToastInfo(
'audioPermissionNeeded', 'audioPermissionNeeded',
window.i18n('audioPermissionNeededTitle'), window.i18n('audioPermissionNeededTitle'),
window.i18n('audioPermissionNeeded') window.i18n('audioPermissionNeeded'),
onClicked
); );
} }

@ -426,7 +426,7 @@ function conversationReset({
}; };
} }
function openConversationExternal( export function openConversationExternal(
id: string, id: string,
messageId?: string messageId?: string
): SelectedConversationChangedActionType { ): SelectedConversationChangedActionType {

@ -76,7 +76,7 @@ export const actions = {
updateSearchTerm, updateSearchTerm,
}; };
function search( export function search(
query: string, query: string,
options: SearchOptions options: SearchOptions
): SearchResultsKickoffActionType { ): SearchResultsKickoffActionType {
@ -125,13 +125,13 @@ async function doSearch(
messages: getMessageProps(filteredMessages) || [], messages: getMessageProps(filteredMessages) || [],
}; };
} }
function clearSearch(): ClearSearchActionType { export function clearSearch(): ClearSearchActionType {
return { return {
type: 'SEARCH_CLEAR', type: 'SEARCH_CLEAR',
payload: null, payload: null,
}; };
} }
function updateSearchTerm(query: string): UpdateSearchTermActionType { export function updateSearchTerm(query: string): UpdateSearchTermActionType {
return { return {
type: 'SEARCH_UPDATE', type: 'SEARCH_UPDATE',
payload: { payload: {

@ -1,26 +1,54 @@
import { SectionType } from '../../components/session/ActionsPanel'; import { SectionType } from '../../components/session/ActionsPanel';
import { SessionSettingCategory } from '../../components/session/settings/SessionSettings';
export const FOCUS_SECTION = 'FOCUS_SECTION'; export const FOCUS_SECTION = 'FOCUS_SECTION';
export const FOCUS_SETTINGS_SECTION = 'FOCUS_SETTINGS_SECTION';
type FocusSectionActionType = { type FocusSectionActionType = {
type: 'FOCUS_SECTION'; type: 'FOCUS_SECTION';
payload: SectionType; payload: SectionType;
}; };
function focusSection(section: SectionType): FocusSectionActionType { type FocusSettingsSectionActionType = {
type: 'FOCUS_SETTINGS_SECTION';
payload: SessionSettingCategory;
};
export function showLeftPaneSection(
section: SectionType
): FocusSectionActionType {
return { return {
type: FOCUS_SECTION, type: FOCUS_SECTION,
payload: section, payload: section,
}; };
} }
type SectionActionTypes =
| FocusSectionActionType
| FocusSettingsSectionActionType;
export function showSettingsSection(
category: SessionSettingCategory
): FocusSettingsSectionActionType {
return {
type: FOCUS_SETTINGS_SECTION,
payload: category,
};
}
export const actions = { export const actions = {
focusSection, showLeftPaneSection,
showSettingsSection,
};
const initialState = {
focusedSection: SectionType.Message,
focusedSettingsSection: undefined,
}; };
const initialState = { focusedSection: SectionType.Message };
export type SectionStateType = { export type SectionStateType = {
focusedSection: SectionType; focusedSection: SectionType;
focusedSettingsSection?: SessionSettingCategory;
}; };
export const reducer = ( export const reducer = (
@ -30,12 +58,32 @@ export const reducer = (
payload, payload,
}: { }: {
type: string; type: string;
payload: SectionType; payload: SectionActionTypes;
} }
): SectionStateType => { ): SectionStateType => {
switch (type) { switch (type) {
case FOCUS_SECTION: case FOCUS_SECTION:
return { focusedSection: payload }; // if we change to something else than settings, reset the focused settings section
const castedPayload = (payload as unknown) as SectionType;
if (castedPayload !== SectionType.Settings) {
return {
focusedSection: castedPayload,
focusedSettingsSection: undefined,
};
}
// on click on the gear icon: show the appearance tab by default
return {
...state,
focusedSection: payload,
focusedSettingsSection: SessionSettingCategory.Appearance,
};
case FOCUS_SETTINGS_SECTION:
return {
...state,
focusedSettingsSection: payload,
};
default: default:
return state; return state;
} }

@ -1,6 +1,7 @@
export const APPLY_THEME = 'APPLY_THEME'; export const APPLY_THEME = 'APPLY_THEME';
export type ThemeStateType = typeof lightTheme;
export const applyTheme = (theme: any) => { export const applyTheme = (theme: ThemeStateType) => {
return { return {
type: APPLY_THEME, type: APPLY_THEME,
payload: theme, payload: theme,
@ -8,8 +9,6 @@ export const applyTheme = (theme: any) => {
}; };
import { lightTheme } from './SessionTheme'; import { lightTheme } from './SessionTheme';
export type ThemeStateType = typeof lightTheme;
const initialState = lightTheme; const initialState = lightTheme;
export const reducer = ( export const reducer = (

@ -95,6 +95,7 @@ export const getConversationComparator = createSelector(
_getConversationComparator _getConversationComparator
); );
// export only because we use it in some of our tests
export const _getLeftPaneLists = ( export const _getLeftPaneLists = (
lookup: ConversationLookupType, lookup: ConversationLookupType,
comparator: (left: ConversationType, right: ConversationType) => number, comparator: (left: ConversationType, right: ConversationType) => number,
@ -108,7 +109,7 @@ export const _getLeftPaneLists = (
const sorted = values.sort(comparator); const sorted = values.sort(comparator);
const conversations: Array<ConversationType> = []; const conversations: Array<ConversationType> = [];
const allContacts: Array<ConversationType> = []; const directConversations: Array<ConversationType> = [];
let index = 0; let index = 0;
@ -155,8 +156,8 @@ export const _getLeftPaneLists = (
continue; continue;
} }
if (conversation.activeAt !== undefined) { if (conversation.activeAt !== undefined && conversation.type === 'direct') {
allContacts.push(conversation); directConversations.push(conversation);
} }
if (unreadCount < 9 && conversation.unreadCount > 0) { if (unreadCount < 9 && conversation.unreadCount > 0) {
@ -169,7 +170,7 @@ export const _getLeftPaneLists = (
return { return {
conversations, conversations,
contacts: allContacts, contacts: directConversations,
unreadCount, unreadCount,
}; };
}; };
@ -223,3 +224,10 @@ export const getMe = createSelector(
return lookup[ourNumber]; return lookup[ourNumber];
} }
); );
export const getUnreadMessageCount = createSelector(
getLeftPaneLists,
(state): number => {
return state.unreadCount;
}
);

@ -28,7 +28,7 @@ export const isSearching = createSelector(
(state: SearchStateType) => { (state: SearchStateType) => {
const { query } = state; const { query } = state;
return query && query.trim().length > 1; return Boolean(query && query.trim().length > 1);
} }
); );

@ -3,6 +3,7 @@ import { createSelector } from 'reselect';
import { StateType } from '../reducer'; import { StateType } from '../reducer';
import { SectionStateType } from '../ducks/section'; import { SectionStateType } from '../ducks/section';
import { SectionType } from '../../components/session/ActionsPanel'; import { SectionType } from '../../components/session/ActionsPanel';
import { SessionSettingCategory } from '../../components/session/settings/SessionSettings';
export const getSection = (state: StateType): SectionStateType => state.section; export const getSection = (state: StateType): SectionStateType => state.section;
@ -10,3 +11,9 @@ export const getFocusedSection = createSelector(
getSection, getSection,
(state: SectionStateType): SectionType => state.focusedSection (state: SectionStateType): SectionType => state.focusedSection
); );
export const getFocusedSettingsSection = createSelector(
getSection,
(state: SectionStateType): SessionSettingCategory | undefined =>
state.focusedSettingsSection
);

@ -1,41 +0,0 @@
import { connect } from 'react-redux';
import { LeftPane } from '../../components/LeftPane';
import { StateType } from '../reducer';
import { getQuery, getSearchResults, isSearching } from '../selectors/search';
import { getIntl, getOurNumber } from '../selectors/user';
import {
getLeftPaneLists,
getOurPrimaryConversation,
} from '../selectors/conversations';
import { mapDispatchToProps } from '../actions';
import { getFocusedSection } from '../selectors/section';
import { getTheme } from '../selectors/theme';
// Workaround: A react component's required properties are filtering up through connect()
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/3136k3
const mapStateToProps = (state: StateType) => {
const showSearch = isSearching(state);
const leftPaneList = getLeftPaneLists(state);
const lists = showSearch ? undefined : leftPaneList;
const searchResults = showSearch ? getSearchResults(state) : undefined;
const ourPrimaryConversation = getOurPrimaryConversation(state);
return {
...lists,
ourPrimaryConversation, // used in actionPanel
searchTerm: getQuery(state),
ourNumber: getOurNumber(state),
searchResults,
i18n: getIntl(state),
unreadMessageCount: leftPaneList.unreadCount,
theme: getTheme(state),
focusedSection: getFocusedSection(state),
};
};
const smart = connect(mapStateToProps, mapDispatchToProps);
export const SmartLeftPane = smart(LeftPane);
Loading…
Cancel
Save