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"
},
"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",
"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"
},
"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"
},
"allowAccess": {

@ -26,7 +26,7 @@ import { PubKey } from '../session/types';
import { ConversationType } from '../state/ducks/conversations';
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
}

@ -3,17 +3,22 @@ import React from 'react';
import { ActionsPanel, SectionType } from './session/ActionsPanel';
import { LeftPaneMessageSection } from './session/LeftPaneMessageSection';
import { ConversationListItemProps } from './ConversationListItem';
import { SearchResultsProps } from './SearchResults';
import { SearchOptions } from '../types/Search';
import { ConversationType } from '../state/ducks/conversations';
import { openConversationExternal } from '../state/ducks/conversations';
import { LeftPaneContactSection } from './session/LeftPaneContactSection';
import { LeftPaneSettingSection } from './session/LeftPaneSettingSection';
import { SessionTheme } from '../state/ducks/SessionTheme';
import { DefaultTheme } from 'styled-components';
import { SessionSettingCategory } from './session/settings/SessionSettings';
import { SessionOffline } from './session/network/SessionOffline';
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
export type RowRendererParamsType = {
@ -25,135 +30,97 @@ export type RowRendererParamsType = {
style: Object;
};
interface Props {
ourPrimaryConversation: ConversationType;
conversations: Array<ConversationListItemProps>;
contacts: Array<ConversationType>;
unreadMessageCount: number;
searchResults?: SearchResultsProps;
searchTerm: string;
focusedSection: SectionType;
focusSection: (section: SectionType) => void;
type Props = {
isExpired: boolean;
};
openConversationExternal: (id: string, messageId?: string) => void;
showSessionSettingsCategory: (category: SessionSettingCategory) => void;
showSessionViewConversation: () => void;
settingsCategory?: SessionSettingCategory;
updateSearchTerm: (searchTerm: string) => void;
search: (query: string, options: SearchOptions) => void;
clearSearch: () => void;
theme: DefaultTheme;
}
export class LeftPane extends React.Component<Props> {
public constructor(props: any) {
super(props);
this.handleSectionSelected = this.handleSectionSelected.bind(this);
}
const InnerLeftPaneMessageSection = (props: { isExpired: boolean }) => {
const dispatch = useDispatch();
const showSearch = useSelector(isSearching);
const searchTerm = useSelector(getQuery);
const searchResults = showSearch ? useSelector(getSearchResults) : undefined;
const lists = showSearch ? undefined : useSelector(getLeftPaneLists);
const theme = useSelector(getTheme);
// tslint:disable: use-simple-attributes
return (
<>
<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) {
this.props.clearSearch();
this.props.focusSection(section);
if (section === SectionType.Settings) {
this.props.showSessionSettingsCategory(SessionSettingCategory.Appearance);
} else {
this.props.showSessionViewConversation();
}
}
const InnerLeftPaneContactSection = () => {
const dispatch = useDispatch();
const theme = useSelector(getTheme);
const showSearch = useSelector(isSearching);
const lists = showSearch ? undefined : useSelector(getLeftPaneLists);
const directContacts = lists?.contacts || [];
return (
<>
<SessionOffline />
<LeftPaneContactSection
openConversationExternal={(id, messageId) =>
dispatch(openConversationExternal(id, messageId))
}
directContacts={directContacts}
theme={theme}
/>
</>
);
};
public render(): JSX.Element {
return (
<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>
);
}
const LeftPaneSettingsSection = () => {
return <LeftPaneSettingSection />;
};
private renderSection(): JSX.Element | undefined {
switch (this.props.focusedSection) {
case SectionType.Message:
return this.renderMessageSection();
case SectionType.Contact:
return this.renderContactSection();
case SectionType.Settings:
return this.renderSettingSection();
default:
return undefined;
}
}
const LeftPaneSection = (props: { isExpired: boolean }) => {
const focusedSection = useSelector(getFocusedSection);
private renderMessageSection() {
const {
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}
/>
</>
);
if (focusedSection === SectionType.Message) {
return <InnerLeftPaneMessageSection isExpired={props.isExpired} />;
}
private renderContactSection() {
const { openConversationExternal } = this.props;
const directContacts = this.getDirectContactsOnly();
return (
<>
<SessionOffline theme={this.props.theme} />
<LeftPaneContactSection
{...this.props}
openConversationExternal={openConversationExternal}
directContacts={directContacts}
/>
</>
);
if (focusedSection === SectionType.Contact) {
return <InnerLeftPaneContactSection />;
}
private getDirectContactsOnly() {
return this.props.contacts.filter(f => f.type === 'direct');
if (focusedSection === SectionType.Settings) {
return <LeftPaneSettingsSection />;
}
return <></>;
};
private renderSettingSection() {
const { settingsCategory } = this.props;
const category = settingsCategory || SessionSettingCategory.Appearance;
export const LeftPane = (props: Props) => {
const theme = useSelector(getTheme);
return (
<>
<LeftPaneSettingSection {...this.props} settingsCategory={category} />
</>
);
}
}
return (
<SessionTheme theme={theme}>
<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 { connect } from 'react-redux';
import React, { useEffect } from 'react';
import { SessionIconButton, SessionIconSize, SessionIconType } from './icon';
import { Avatar } from '../Avatar';
import { darkTheme, lightTheme } from '../../state/ducks/SessionTheme';
import { SessionToastContainer } from './SessionToastContainer';
import { mapDispatchToProps } from '../../state/actions';
import { ConversationType } from '../../state/ducks/conversations';
import { DefaultTheme } from 'styled-components';
import { StateType } from '../../state/reducer';
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 { syncConfigurationIfNeeded } from '../../session/utils/syncUtils';
import { DAYS } from '../../session/utils/Number';
@ -23,6 +17,18 @@ import {
import { OnionPaths } from '../../session/onions';
import { getMessageQueue } from '../../session/sending';
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
export enum SectionType {
@ -34,33 +40,100 @@ export enum SectionType {
Moon,
}
interface Props {
onSectionSelected: any;
selectedSection: SectionType;
unreadMessageCount: number;
ourPrimaryConversation: ConversationType;
ourNumber: string;
applyTheme?: any;
theme: DefaultTheme;
}
const Section = (props: { type: SectionType; avatarPath?: string }) => {
const ourNumber = useSelector(getOurNumber);
const unreadMessageCount = useSelector(getUnreadMessageCount);
const theme = useSelector(getTheme);
const dispatch = useDispatch();
const { type, avatarPath } = props;
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).
* The panel with buttons to switch between the message/contact/settings/theme views
*/
class ActionsPanelPrivate extends React.Component<Props> {
private syncInterval: NodeJS.Timeout | null = null;
export const ActionsPanel = () => {
const dispatch = useDispatch();
constructor(props: Props) {
super(props);
const ourPrimaryConversation = useSelector(getOurPrimaryConversation);
// 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');
}
// fetch the user saved theme from the db, and apply it on mount.
public componentDidMount() {
// this maxi useEffect is called only once: when the component is mounted.
useEffect(() => {
void window.setClockParams();
if (
window.lokiFeatureFlags.useOnionRequests ||
@ -78,12 +151,14 @@ class ActionsPanelPrivate extends React.Component<Props> {
window.setTheme(theme);
const newThemeObject = theme === 'dark' ? darkTheme : lightTheme;
this.props.applyTheme(newThemeObject);
void this.showResetSessionIDDialogIfNeeded();
dispatch(applyTheme(newThemeObject));
void showResetSessionIDDialogIfNeeded();
// remove existing prekeys, sign prekeys and sessions
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
@ -98,178 +173,29 @@ class ActionsPanelPrivate extends React.Component<Props> {
// trigger a sync message if needed for our other devices
void syncConfiguration();
}, []);
this.syncInterval = global.setInterval(() => {
void syncConfigurationIfNeeded();
}, DAYS * 2);
if (!ourPrimaryConversation) {
window.log.warn('ActionsPanel: ourPrimaryConversation is not set');
return <></>;
}
public componentWillUnmount() {
if (this.syncInterval) {
clearInterval(this.syncInterval);
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;
}
useInterval(() => {
void syncConfigurationIfNeeded();
}, DAYS * 2);
return (
<SessionIconButton
iconSize={SessionIconSize.Medium}
iconType={iconType}
notificationCount={notificationCount}
onClick={handleClick}
isSelected={isSelected}
theme={this.props.theme}
return (
<div className="module-left-pane__sections-container">
<Section
type={SectionType.Profile}
avatarPath={ourPrimaryConversation.avatarPath}
/>
);
};
public render(): JSX.Element {
const {
selectedSection,
unreadMessageCount,
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),
};
<Section type={SectionType.Message} />
<Section type={SectionType.Contact} />
<Section type={SectionType.Settings} />
<SessionToastContainer />
<Section type={SectionType.Moon} />
</div>
);
};
const smart = connect(mapStateToProps, mapDispatchToProps);
export const ActionsPanel = smart(ActionsPanelPrivate);

@ -1,8 +1,6 @@
import React from 'react';
import classNames from 'classnames';
import { LeftPane } from '../LeftPane';
import {
SessionButton,
SessionButtonColor,
@ -10,212 +8,162 @@ import {
} from './SessionButton';
import { SessionIcon, SessionIconSize, SessionIconType } from './icon';
import { SessionSearchInput } from './SessionSearchInput';
import { SessionSettingCategory } from './settings/SessionSettings';
import { DefaultTheme } from 'styled-components';
import { LeftPaneSectionHeader } from './LeftPaneSectionHeader';
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;
showSessionSettingsCategory: (category: SessionSettingCategory) => void;
showSettingsSection: (category: SessionSettingCategory) => void;
theme: DefaultTheme;
}
export interface State {
searchQuery: string;
}
export class LeftPaneSettingSection extends React.Component<Props, State> {
public constructor(props: any) {
super(props);
this.state = {
searchQuery: '',
};
this.setCategory = this.setCategory.bind(this);
this.onDeleteAccount = this.onDeleteAccount.bind(this);
}
public render(): JSX.Element {
return (
<div className="left-pane-setting-section">
{this.renderHeader()}
{this.renderSettings()}
};
const getCategories = () => {
return [
{
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,
},
];
};
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>
);
}
public renderHeader(): JSX.Element | undefined {
return (
<LeftPaneSectionHeader
label={window.i18n('settingsHeader')}
theme={this.props.theme}
/>
);
}
public renderRow(item: any): JSX.Element {
const { settingsCategory } = this.props;
return (
<div
key={item.id}
className={classNames(
'left-pane-setting-category-list-item',
item.id === settingsCategory ? 'active' : ''
<div>
{item.id === focusedSettingsSection && (
<SessionIcon
iconSize={SessionIconSize.Medium}
iconType={SessionIconType.Chevron}
iconRotation={270}
theme={theme}
/>
)}
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>
);
}
public renderCategories(): JSX.Element {
const categories = this.getCategories().filter(item => !item.hidden);
return (
<div className="module-left-pane__list" key={0}>
<div className="left-pane-setting-category-list">
{categories.map(item => this.renderRow(item))}
</div>
</div>
);
};
const LeftPaneSettingsCategories = () => {
const categories = getCategories();
return (
<div className="module-left-pane__list" key={0}>
<div className="left-pane-setting-category-list">
{categories
.filter(m => !m.hidden)
.map(item => {
return <LeftPaneSettingsCategoryRow key={item.id} item={item} />;
})}
</div>
);
}
</div>
);
};
public renderSearch() {
return (
<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>
);
}
const onDeleteAccount = () => {
const title = window.i18n('clearAllData');
public renderSettings(): JSX.Element {
const showSearch = false;
const message = window.i18n('unpairDeviceWarning');
return (
<div className="left-pane-setting-content">
{showSearch && this.renderSearch()}
{this.renderCategories()}
{this.renderBottomButtons()}
</div>
);
}
let messageSub = '';
public renderBottomButtons(): JSX.Element | undefined {
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={this.onDeleteAccount}
/>
<SessionButton
text={showRecoveryPhrase}
buttonType={SessionButtonType.SquareOutline}
buttonColor={SessionButtonColor.White}
onClick={() => window.Whisper.events.trigger('showSeedDialog')}
/>
</div>
);
const identityKey = window.textsecure.storage.get('identityKey');
if (identityKey && identityKey.ed25519KeyPair === undefined) {
messageSub =
"We've updated the way Session IDs are generated, so you will not be able to restore your current Session ID.";
}
public onDeleteAccount() {
const title = window.i18n('clearAllData');
const message = window.i18n('unpairDeviceWarning');
let messageSub = '';
const identityKey = window.textsecure.storage.get('identityKey');
if (identityKey && identityKey.ed25519KeyPair === undefined) {
messageSub =
"We've updated the way Session IDs are generated, so you will not be able to restore your current Session ID.";
}
window.confirmationDialog({
title,
message,
messageSub,
resolve: deleteAccount,
okTheme: 'danger',
});
};
const LeftPaneBottomButtons = () => {
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({
title,
message,
messageSub,
resolve: deleteAccount,
okTheme: 'danger',
});
}
<SessionButton
text={showRecoveryPhrase}
buttonType={SessionButtonType.SquareOutline}
buttonColor={SessionButtonColor.White}
onClick={() => window.Whisper.events.trigger('showSeedDialog')}
/>
</div>
);
};
public getCategories() {
return [
{
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,
},
];
}
export const LeftPaneSettingSection = () => {
const theme = useSelector(getTheme);
public setCategory(category: SessionSettingCategory) {
this.props.showSessionSettingsCategory(category);
}
}
return (
<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 { Provider } from 'react-redux';
import { bindActionCreators } from 'redux';
import { getMessageById } from '../../data/data';
import { ConversationModel } from '../../models/conversation';
import { MessageModel } from '../../models/message';
import { getMessageQueue } from '../../session';
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 { createStore } from '../../state/createStore';
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 {
SessionSettingCategory,
SmartSettingsView,
} from './settings/SessionSettings';
import { LeftPane } from '../LeftPane';
import { SessionMainPanel } from '../SessionMainPanel';
// Workaround: A react component's required properties are filtering up through connect()
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
const FilteredLeftPane = SmartLeftPane as any;
const FilteredSettingsView = SmartSettingsView as any;
type Props = {
focusedSection: number;
};
type State = {
isInitialLoadComplete: boolean;
settingsCategory?: SessionSettingCategory;
isExpired: boolean;
};
export class SessionInboxView extends React.Component<Props, State> {
export class SessionInboxView extends React.Component<any, State> {
private store: any;
constructor(props: any) {
super(props);
this.state = {
isInitialLoadComplete: false,
settingsCategory: undefined,
isExpired: false,
};
this.showSessionSettingsCategory = this.showSessionSettingsCategory.bind(
this
);
this.showSessionViewConversation = this.showSessionViewConversation.bind(
this
);
void this.setupLeftPane();
// 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 <></>;
}
const { settingsCategory } = this.state;
const isSettingsView = settingsCategory !== undefined;
return (
<Provider store={this.store}>
<div className="gutter">
<div className="network-status-container" />
{this.renderLeftPane()}
</div>
{isSettingsView
? this.renderSettings()
: this.renderSessionConversation()}
<SessionMainPanel />
</Provider>
);
}
private renderLeftPane() {
return (
<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>
);
return <LeftPane isExpired={this.state.isExpired} />;
}
private async setupLeftPane() {
@ -155,12 +102,4 @@ export class SessionInboxView extends React.Component<Props, State> {
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 { Flex } from './Flex';
import styled, { ThemeContext } from 'styled-components';
import { noop } from 'lodash';
export enum SessionToastType {
Info = 'info',
@ -18,6 +19,7 @@ type Props = {
icon?: SessionIconType;
description?: string;
closeToast?: any;
onToastClick?: () => void;
};
const TitleDiv = styled.div`
@ -74,7 +76,12 @@ export const SessionToast = (props: Props) => {
}
return (
<Flex container={true} alignItems="center">
// tslint:disable-next-line: use-simple-attributes
<Flex
container={true}
alignItems="center"
onClick={props?.onToastClick || noop}
>
<IconDiv>
<SessionIcon
iconType={toastIcon}

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

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

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

@ -1,5 +1,5 @@
import React from 'react';
import styled, { DefaultTheme } from 'styled-components';
import styled from 'styled-components';
import { useNetwork } from '../../../hooks/useNetwork';
type ContainerProps = {
@ -23,7 +23,7 @@ const OfflineTitle = styled.h3`
const OfflineMessage = styled.div``;
export const SessionOffline = (props: { theme: DefaultTheme }) => {
export const SessionOffline = () => {
const isOnline = useNetwork();
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(
<SessionToast
title={title}
description={description}
type={SessionToastType.Info}
onToastClick={onToastClick}
/>
);
}
@ -151,11 +157,12 @@ export function pushMessageDeleteForbidden() {
);
}
export function pushAudioPermissionNeeded() {
export function pushAudioPermissionNeeded(onClicked: () => void) {
pushToastInfo(
'audioPermissionNeeded',
window.i18n('audioPermissionNeededTitle'),
window.i18n('audioPermissionNeeded')
window.i18n('audioPermissionNeeded'),
onClicked
);
}

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

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

@ -1,26 +1,54 @@
import { SectionType } from '../../components/session/ActionsPanel';
import { SessionSettingCategory } from '../../components/session/settings/SessionSettings';
export const FOCUS_SECTION = 'FOCUS_SECTION';
export const FOCUS_SETTINGS_SECTION = 'FOCUS_SETTINGS_SECTION';
type FocusSectionActionType = {
type: 'FOCUS_SECTION';
payload: SectionType;
};
function focusSection(section: SectionType): FocusSectionActionType {
type FocusSettingsSectionActionType = {
type: 'FOCUS_SETTINGS_SECTION';
payload: SessionSettingCategory;
};
export function showLeftPaneSection(
section: SectionType
): FocusSectionActionType {
return {
type: FOCUS_SECTION,
payload: section,
};
}
type SectionActionTypes =
| FocusSectionActionType
| FocusSettingsSectionActionType;
export function showSettingsSection(
category: SessionSettingCategory
): FocusSettingsSectionActionType {
return {
type: FOCUS_SETTINGS_SECTION,
payload: category,
};
}
export const actions = {
focusSection,
showLeftPaneSection,
showSettingsSection,
};
const initialState = {
focusedSection: SectionType.Message,
focusedSettingsSection: undefined,
};
const initialState = { focusedSection: SectionType.Message };
export type SectionStateType = {
focusedSection: SectionType;
focusedSettingsSection?: SessionSettingCategory;
};
export const reducer = (
@ -30,12 +58,32 @@ export const reducer = (
payload,
}: {
type: string;
payload: SectionType;
payload: SectionActionTypes;
}
): SectionStateType => {
switch (type) {
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:
return state;
}

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

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

@ -28,7 +28,7 @@ export const isSearching = createSelector(
(state: SearchStateType) => {
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 { SectionStateType } from '../ducks/section';
import { SectionType } from '../../components/session/ActionsPanel';
import { SessionSettingCategory } from '../../components/session/settings/SessionSettings';
export const getSection = (state: StateType): SectionStateType => state.section;
@ -10,3 +11,9 @@ export const getFocusedSection = createSelector(
getSection,
(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