Restore ability to message someone from embedded contact
This commit is contained in:
parent
f77175f6b3
commit
302604f67e
18 changed files with 311 additions and 236 deletions
|
@ -1207,15 +1207,15 @@
|
|||
@include font-body-2-bold;
|
||||
|
||||
margin-top: 8px;
|
||||
margin-bottom: -10px;
|
||||
margin-bottom: -8px;
|
||||
margin-left: -12px;
|
||||
margin-right: -12px;
|
||||
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
|
||||
border-bottom-left-radius: 16px;
|
||||
border-bottom-right-radius: 16px;
|
||||
border-bottom-left-radius: 18px;
|
||||
border-bottom-right-radius: 18px;
|
||||
|
||||
@include light-theme {
|
||||
color: $color-ultramarine;
|
||||
|
@ -1235,6 +1235,13 @@
|
|||
}
|
||||
}
|
||||
|
||||
.module-message__send-message-button--no-bottom-left-curve {
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
.module-message__send-message-button--no-bottom-right-curve {
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
|
||||
.module-message__author-avatar-container {
|
||||
align-items: flex-end;
|
||||
display: flex;
|
||||
|
|
|
@ -1,179 +0,0 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import * as React from 'react';
|
||||
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { boolean, number } from '@storybook/addon-knobs';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
|
||||
import type { Props } from './EmbeddedContact';
|
||||
import { EmbeddedContact } from './EmbeddedContact';
|
||||
import { setupI18n } from '../../util/setupI18n';
|
||||
import enMessages from '../../../_locales/en/messages.json';
|
||||
import { ContactFormType } from '../../types/EmbeddedContact';
|
||||
import { IMAGE_GIF } from '../../types/MIME';
|
||||
|
||||
import { fakeAttachment } from '../../test-both/helpers/fakeAttachment';
|
||||
|
||||
const i18n = setupI18n('en', enMessages);
|
||||
|
||||
const story = storiesOf('Components/Conversation/EmbeddedContact', module);
|
||||
|
||||
const createProps = (overrideProps: Partial<Props> = {}): Props => ({
|
||||
contact: overrideProps.contact || {},
|
||||
i18n,
|
||||
isIncoming: boolean('isIncoming', overrideProps.isIncoming || false),
|
||||
onClick: action('onClick'),
|
||||
tabIndex: number('tabIndex', overrideProps.tabIndex || 0),
|
||||
withContentAbove: boolean(
|
||||
'withContentAbove',
|
||||
overrideProps.withContentAbove || false
|
||||
),
|
||||
withContentBelow: boolean(
|
||||
'withContentBelow',
|
||||
overrideProps.withContentBelow || false
|
||||
),
|
||||
});
|
||||
|
||||
const fullContact = {
|
||||
avatar: {
|
||||
avatar: fakeAttachment({
|
||||
path: '/fixtures/giphy-GVNvOUpeYmI7e.gif',
|
||||
contentType: IMAGE_GIF,
|
||||
}),
|
||||
isProfile: true,
|
||||
},
|
||||
email: [
|
||||
{
|
||||
value: 'jerjor@fakemail.com',
|
||||
type: ContactFormType.HOME,
|
||||
},
|
||||
],
|
||||
name: {
|
||||
givenName: 'Jerry',
|
||||
familyName: 'Jordan',
|
||||
prefix: 'Dr.',
|
||||
suffix: 'Jr.',
|
||||
middleName: 'James',
|
||||
displayName: 'Jerry Jordan',
|
||||
},
|
||||
number: [
|
||||
{
|
||||
value: '555-444-2323',
|
||||
type: ContactFormType.HOME,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
story.add('Full Contact', () => {
|
||||
const props = createProps({
|
||||
contact: fullContact,
|
||||
});
|
||||
return <EmbeddedContact {...props} />;
|
||||
});
|
||||
|
||||
story.add('Only Email', () => {
|
||||
const props = createProps({
|
||||
contact: {
|
||||
email: fullContact.email,
|
||||
},
|
||||
});
|
||||
|
||||
return <EmbeddedContact {...props} />;
|
||||
});
|
||||
|
||||
story.add('Given Name', () => {
|
||||
const props = createProps({
|
||||
contact: {
|
||||
name: {
|
||||
givenName: 'Jerry',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return <EmbeddedContact {...props} />;
|
||||
});
|
||||
|
||||
story.add('Organization', () => {
|
||||
const props = createProps({
|
||||
contact: {
|
||||
organization: 'Company 5',
|
||||
},
|
||||
});
|
||||
|
||||
return <EmbeddedContact {...props} />;
|
||||
});
|
||||
|
||||
story.add('Given + Family Name', () => {
|
||||
const props = createProps({
|
||||
contact: {
|
||||
name: {
|
||||
givenName: 'Jerry',
|
||||
familyName: 'FamilyName',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return <EmbeddedContact {...props} />;
|
||||
});
|
||||
|
||||
story.add('Family Name', () => {
|
||||
const props = createProps({
|
||||
contact: {
|
||||
name: {
|
||||
familyName: 'FamilyName',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return <EmbeddedContact {...props} />;
|
||||
});
|
||||
|
||||
story.add('Loading Avatar', () => {
|
||||
const props = createProps({
|
||||
contact: {
|
||||
name: {
|
||||
displayName: 'Jerry Jordan',
|
||||
},
|
||||
avatar: {
|
||||
avatar: fakeAttachment({
|
||||
pending: true,
|
||||
contentType: IMAGE_GIF,
|
||||
}),
|
||||
isProfile: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
return <EmbeddedContact {...props} />;
|
||||
});
|
||||
|
||||
story.add('Incoming', () => {
|
||||
const props = createProps({
|
||||
contact: {
|
||||
name: fullContact.name,
|
||||
},
|
||||
isIncoming: true,
|
||||
});
|
||||
|
||||
// Wrapped in a <div> to provide a background for light color of text
|
||||
return (
|
||||
<div style={{ backgroundColor: 'darkgreen' }}>
|
||||
<EmbeddedContact {...props} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
story.add('Content Above and Below', () => {
|
||||
const props = createProps({
|
||||
withContentAbove: true,
|
||||
withContentBelow: true,
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<div>Content Above</div>
|
||||
<EmbeddedContact {...props} />
|
||||
<div>Content Below</div>
|
||||
</>
|
||||
);
|
||||
});
|
|
@ -20,6 +20,7 @@ import {
|
|||
IMAGE_WEBP,
|
||||
VIDEO_MP4,
|
||||
stringToMIMEType,
|
||||
IMAGE_GIF,
|
||||
} from '../../types/MIME';
|
||||
import { ReadStatus } from '../../messages/MessageReadStatus';
|
||||
import { MessageAudio } from './MessageAudio';
|
||||
|
@ -30,6 +31,7 @@ import { pngUrl } from '../../storybook/Fixtures';
|
|||
import { getDefaultConversation } from '../../test-both/helpers/getDefaultConversation';
|
||||
import { WidthBreakpoint } from '../_util';
|
||||
import { MINUTE } from '../../util/durations';
|
||||
import { ContactFormType } from '../../types/EmbeddedContact';
|
||||
|
||||
import {
|
||||
fakeAttachment,
|
||||
|
@ -37,6 +39,7 @@ import {
|
|||
} from '../../test-both/helpers/fakeAttachment';
|
||||
import { getFakeBadge } from '../../test-both/helpers/getFakeBadge';
|
||||
import { ThemeType } from '../../types/Util';
|
||||
import { UUID } from '../../types/UUID';
|
||||
|
||||
const i18n = setupI18n('en', enMessages);
|
||||
|
||||
|
@ -118,6 +121,7 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
|
|||
select('conversationColor', ConversationColors, ConversationColors[0]),
|
||||
conversationId: text('conversationId', overrideProps.conversationId || ''),
|
||||
conversationType: overrideProps.conversationType || 'direct',
|
||||
contact: overrideProps.contact,
|
||||
deletedForEveryone: overrideProps.deletedForEveryone,
|
||||
deleteMessage: action('deleteMessage'),
|
||||
deleteMessageForEveryone: action('deleteMessageForEveryone'),
|
||||
|
@ -191,6 +195,7 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
|
|||
showForwardMessageModal: action('showForwardMessageModal'),
|
||||
showMessageDetail: action('showMessageDetail'),
|
||||
showVisualAttachment: action('showVisualAttachment'),
|
||||
startConversation: action('startConversation'),
|
||||
status: overrideProps.status || 'sent',
|
||||
text: overrideProps.text || text('text', ''),
|
||||
textDirection: overrideProps.textDirection || TextDirection.Default,
|
||||
|
@ -1516,3 +1521,139 @@ story.add('Story reply', () => {
|
|||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const fullContact = {
|
||||
avatar: {
|
||||
avatar: fakeAttachment({
|
||||
path: '/fixtures/giphy-GVNvOUpeYmI7e.gif',
|
||||
contentType: IMAGE_GIF,
|
||||
}),
|
||||
isProfile: true,
|
||||
},
|
||||
email: [
|
||||
{
|
||||
value: 'jerjor@fakemail.com',
|
||||
type: ContactFormType.HOME,
|
||||
},
|
||||
],
|
||||
name: {
|
||||
givenName: 'Jerry',
|
||||
familyName: 'Jordan',
|
||||
prefix: 'Dr.',
|
||||
suffix: 'Jr.',
|
||||
middleName: 'James',
|
||||
displayName: 'Jerry Jordan',
|
||||
},
|
||||
number: [
|
||||
{
|
||||
value: '555-444-2323',
|
||||
type: ContactFormType.HOME,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
story.add('EmbeddedContact: Full Contact', () => {
|
||||
const props = createProps({
|
||||
contact: fullContact,
|
||||
});
|
||||
return renderBothDirections(props);
|
||||
});
|
||||
|
||||
story.add('EmbeddedContact: 2x Incoming, with Send Message', () => {
|
||||
const props = createProps({
|
||||
contact: {
|
||||
...fullContact,
|
||||
firstNumber: fullContact.number[0].value,
|
||||
uuid: UUID.generate().toString(),
|
||||
},
|
||||
direction: 'incoming',
|
||||
});
|
||||
return renderMany([props, props]);
|
||||
});
|
||||
|
||||
story.add('EmbeddedContact: 2x Outgoing, with Send Message', () => {
|
||||
const props = createProps({
|
||||
contact: {
|
||||
...fullContact,
|
||||
firstNumber: fullContact.number[0].value,
|
||||
uuid: UUID.generate().toString(),
|
||||
},
|
||||
direction: 'outgoing',
|
||||
});
|
||||
return renderMany([props, props]);
|
||||
});
|
||||
|
||||
story.add('EmbeddedContact: Only Email', () => {
|
||||
const props = createProps({
|
||||
contact: {
|
||||
email: fullContact.email,
|
||||
},
|
||||
});
|
||||
|
||||
return renderBothDirections(props);
|
||||
});
|
||||
|
||||
story.add('EmbeddedContact: Given Name', () => {
|
||||
const props = createProps({
|
||||
contact: {
|
||||
name: {
|
||||
givenName: 'Jerry',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return renderBothDirections(props);
|
||||
});
|
||||
|
||||
story.add('EmbeddedContact: Organization', () => {
|
||||
const props = createProps({
|
||||
contact: {
|
||||
organization: 'Company 5',
|
||||
},
|
||||
});
|
||||
|
||||
return renderBothDirections(props);
|
||||
});
|
||||
|
||||
story.add('EmbeddedContact: Given + Family Name', () => {
|
||||
const props = createProps({
|
||||
contact: {
|
||||
name: {
|
||||
givenName: 'Jerry',
|
||||
familyName: 'FamilyName',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return renderBothDirections(props);
|
||||
});
|
||||
|
||||
story.add('EmbeddedContact: Family Name', () => {
|
||||
const props = createProps({
|
||||
contact: {
|
||||
name: {
|
||||
familyName: 'FamilyName',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return renderBothDirections(props);
|
||||
});
|
||||
|
||||
story.add('EmbeddedContact: Loading Avatar', () => {
|
||||
const props = createProps({
|
||||
contact: {
|
||||
name: {
|
||||
displayName: 'Jerry Jordan',
|
||||
},
|
||||
avatar: {
|
||||
avatar: fakeAttachment({
|
||||
pending: true,
|
||||
contentType: IMAGE_GIF,
|
||||
}),
|
||||
isProfile: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
return renderBothDirections(props);
|
||||
});
|
||||
|
|
|
@ -83,6 +83,7 @@ import { getCustomColorStyle } from '../../util/getCustomColorStyle';
|
|||
import { offsetDistanceModifier } from '../../util/popperUtil';
|
||||
import * as KeyboardLayout from '../../services/keyboardLayout';
|
||||
import { StopPropagation } from '../StopPropagation';
|
||||
import type { UUIDStringType } from '../../types/UUID';
|
||||
|
||||
type Trigger = {
|
||||
handleContextClick: (event: React.MouseEvent<HTMLDivElement>) => void;
|
||||
|
@ -279,7 +280,7 @@ export type PropsActions = {
|
|||
clearSelectedMessage: () => unknown;
|
||||
doubleCheckMissingQuoteReference: (messageId: string) => unknown;
|
||||
messageExpanded: (id: string, displayLimit: number) => unknown;
|
||||
checkForAccount: (identifier: string) => unknown;
|
||||
checkForAccount: (phoneNumber: string) => unknown;
|
||||
|
||||
reactToMessage: (
|
||||
id: string,
|
||||
|
@ -293,10 +294,14 @@ export type PropsActions = {
|
|||
deleteMessageForEveryone: (id: string) => void;
|
||||
showMessageDetail: (id: string) => void;
|
||||
|
||||
startConversation: (e164: string, uuid: UUIDStringType) => void;
|
||||
openConversation: (conversationId: string, messageId?: string) => void;
|
||||
showContactDetail: (options: {
|
||||
contact: EmbeddedContactType;
|
||||
signalAccount?: string;
|
||||
signalAccount?: {
|
||||
phoneNumber: string;
|
||||
uuid: UUIDStringType;
|
||||
};
|
||||
}) => void;
|
||||
showContactModal: (contactId: string, conversationId?: string) => void;
|
||||
|
||||
|
@ -501,7 +506,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
const { contact, checkForAccount } = this.props;
|
||||
if (contact && contact.firstNumber && !contact.isNumberOnSignal) {
|
||||
if (contact && contact.firstNumber && !contact.uuid) {
|
||||
checkForAccount(contact.firstNumber);
|
||||
}
|
||||
}
|
||||
|
@ -1336,8 +1341,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
this.getMetadataPlacement() !== MetadataPlacement.NotRendered;
|
||||
|
||||
const otherContent =
|
||||
(contact && contact.firstNumber && contact.isNumberOnSignal) ||
|
||||
withCaption;
|
||||
(contact && contact.firstNumber && contact.uuid) || withCaption;
|
||||
const tabIndex = otherContent ? 0 : -1;
|
||||
|
||||
return (
|
||||
|
@ -1346,7 +1350,18 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
isIncoming={direction === 'incoming'}
|
||||
i18n={i18n}
|
||||
onClick={() => {
|
||||
showContactDetail({ contact, signalAccount: contact.firstNumber });
|
||||
const signalAccount =
|
||||
contact.firstNumber && contact.uuid
|
||||
? {
|
||||
phoneNumber: contact.firstNumber,
|
||||
uuid: contact.uuid,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
showContactDetail({
|
||||
contact,
|
||||
signalAccount,
|
||||
});
|
||||
}}
|
||||
withContentAbove={withContentAbove}
|
||||
withContentBelow={withContentBelow}
|
||||
|
@ -1356,20 +1371,30 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
public renderSendMessageButton(): JSX.Element | null {
|
||||
const { contact, openConversation, i18n } = this.props;
|
||||
const { contact, direction, shouldCollapseBelow, startConversation, i18n } =
|
||||
this.props;
|
||||
const noBottomLeftCurve = direction === 'incoming' && shouldCollapseBelow;
|
||||
const noBottomRightCurve = direction === 'outgoing' && shouldCollapseBelow;
|
||||
|
||||
if (!contact) {
|
||||
return null;
|
||||
}
|
||||
const { firstNumber, isNumberOnSignal } = contact;
|
||||
if (!firstNumber || !isNumberOnSignal) {
|
||||
const { firstNumber, uuid } = contact;
|
||||
if (!firstNumber || !uuid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => openConversation(firstNumber)}
|
||||
className="module-message__send-message-button"
|
||||
onClick={() => startConversation(firstNumber, uuid)}
|
||||
className={classNames(
|
||||
'module-message__send-message-button',
|
||||
noBottomLeftCurve &&
|
||||
'module-message__send-message-button--no-bottom-left-curve',
|
||||
noBottomRightCurve &&
|
||||
'module-message__send-message-button--no-bottom-right-curve'
|
||||
)}
|
||||
>
|
||||
{i18n('sendMessageToContact')}
|
||||
</button>
|
||||
|
@ -2484,7 +2509,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
this.audioButtonRef.current.click();
|
||||
}
|
||||
|
||||
if (contact && contact.firstNumber && contact.isNumberOnSignal) {
|
||||
if (contact && contact.firstNumber && contact.uuid) {
|
||||
openConversation(contact.firstNumber);
|
||||
|
||||
event.preventDefault();
|
||||
|
@ -2492,7 +2517,14 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
if (contact) {
|
||||
showContactDetail({ contact, signalAccount: contact.firstNumber });
|
||||
const signalAccount =
|
||||
contact.firstNumber && contact.uuid
|
||||
? {
|
||||
phoneNumber: contact.firstNumber,
|
||||
uuid: contact.uuid,
|
||||
}
|
||||
: undefined;
|
||||
showContactDetail({ contact, signalAccount });
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
|
|
@ -99,6 +99,7 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
|
|||
),
|
||||
showForwardMessageModal: action('showForwardMessageModal'),
|
||||
showVisualAttachment: action('showVisualAttachment'),
|
||||
startConversation: action('startConversation'),
|
||||
});
|
||||
|
||||
story.add('Delivered Incoming', () => {
|
||||
|
|
|
@ -87,6 +87,7 @@ export type PropsBackboneActions = Pick<
|
|||
| 'showExpiredOutgoingTapToViewToast'
|
||||
| 'showForwardMessageModal'
|
||||
| 'showVisualAttachment'
|
||||
| 'startConversation'
|
||||
>;
|
||||
|
||||
export type PropsReduxActions = Pick<
|
||||
|
@ -297,6 +298,7 @@ export class MessageDetail extends React.Component<Props> {
|
|||
showExpiredOutgoingTapToViewToast,
|
||||
showForwardMessageModal,
|
||||
showVisualAttachment,
|
||||
startConversation,
|
||||
theme,
|
||||
} = this.props;
|
||||
|
||||
|
@ -364,6 +366,7 @@ export class MessageDetail extends React.Component<Props> {
|
|||
log.warn('MessageDetail: deleteMessageForEveryone called!');
|
||||
}}
|
||||
showVisualAttachment={showVisualAttachment}
|
||||
startConversation={startConversation}
|
||||
theme={theme}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -96,6 +96,7 @@ const defaultMessageProps: MessagesProps = {
|
|||
showForwardMessageModal: action('default--showForwardMessageModal'),
|
||||
showMessageDetail: action('default--showMessageDetail'),
|
||||
showVisualAttachment: action('default--showVisualAttachment'),
|
||||
startConversation: action('default--startConversation'),
|
||||
status: 'sent',
|
||||
text: 'This is really interesting.',
|
||||
textDirection: TextDirection.Default,
|
||||
|
|
|
@ -398,6 +398,7 @@ const actions = () => ({
|
|||
downloadNewVersion: action('downloadNewVersion'),
|
||||
|
||||
startCallingLobby: action('startCallingLobby'),
|
||||
startConversation: action('startConversation'),
|
||||
returnToActiveCall: action('returnToActiveCall'),
|
||||
|
||||
contactSupport: action('contactSupport'),
|
||||
|
|
|
@ -253,6 +253,7 @@ const getActions = createSelector(
|
|||
'scrollToQuotedMessage',
|
||||
'showExpiredIncomingTapToViewToast',
|
||||
'showExpiredOutgoingTapToViewToast',
|
||||
'startConversation',
|
||||
|
||||
'showIdentity',
|
||||
|
||||
|
|
|
@ -93,6 +93,7 @@ const getDefaultProps = () => ({
|
|||
downloadNewVersion: action('downloadNewVersion'),
|
||||
showIdentity: action('showIdentity'),
|
||||
startCallingLobby: action('startCallingLobby'),
|
||||
startConversation: action('startConversation'),
|
||||
returnToActiveCall: action('returnToActiveCall'),
|
||||
shouldCollapseAbove: false,
|
||||
shouldCollapseBelow: false,
|
||||
|
|
|
@ -2,15 +2,19 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { ThunkAction } from 'redux-thunk';
|
||||
|
||||
import * as Errors from '../../types/errors';
|
||||
import * as log from '../../logging/log';
|
||||
|
||||
import type { StateType as RootStateType } from '../reducer';
|
||||
import { UUID } from '../../types/UUID';
|
||||
import type { UUIDStringType } from '../../types/UUID';
|
||||
|
||||
import type { NoopActionType } from './noop';
|
||||
|
||||
// State
|
||||
|
||||
export type AccountsStateType = {
|
||||
accounts: Record<string, boolean>;
|
||||
accounts: Record<string, UUIDStringType | undefined>;
|
||||
};
|
||||
|
||||
// Actions
|
||||
|
@ -18,8 +22,8 @@ export type AccountsStateType = {
|
|||
type AccountUpdateActionType = {
|
||||
type: 'accounts/UPDATE';
|
||||
payload: {
|
||||
identifier: string;
|
||||
hasAccount: boolean;
|
||||
phoneNumber: string;
|
||||
uuid?: UUIDStringType;
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -32,14 +36,14 @@ export const actions = {
|
|||
};
|
||||
|
||||
function checkForAccount(
|
||||
identifier: string
|
||||
phoneNumber: string
|
||||
): ThunkAction<
|
||||
void,
|
||||
RootStateType,
|
||||
unknown,
|
||||
AccountUpdateActionType | NoopActionType
|
||||
> {
|
||||
return async dispatch => {
|
||||
return async (dispatch, getState) => {
|
||||
if (!window.textsecure.messaging) {
|
||||
dispatch({
|
||||
type: 'NOOP',
|
||||
|
@ -48,21 +52,50 @@ function checkForAccount(
|
|||
return;
|
||||
}
|
||||
|
||||
let hasAccount = false;
|
||||
const conversation = window.ConversationController.get(phoneNumber);
|
||||
if (conversation && conversation.get('uuid')) {
|
||||
log.error(`checkForAccount: found ${phoneNumber} in existing contacts`);
|
||||
const uuid = conversation.get('uuid');
|
||||
|
||||
dispatch({
|
||||
type: 'accounts/UPDATE',
|
||||
payload: {
|
||||
phoneNumber,
|
||||
uuid,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const state = getState();
|
||||
const existing = Object.prototype.hasOwnProperty.call(
|
||||
state.accounts.accounts,
|
||||
phoneNumber
|
||||
);
|
||||
if (existing) {
|
||||
dispatch({
|
||||
type: 'NOOP',
|
||||
payload: null,
|
||||
});
|
||||
}
|
||||
|
||||
let uuid: UUIDStringType | undefined;
|
||||
|
||||
log.error(`checkForAccount: looking ${phoneNumber} up on server`);
|
||||
try {
|
||||
hasAccount = await window.textsecure.messaging.checkAccountExistence(
|
||||
new UUID(identifier)
|
||||
);
|
||||
} catch (_error) {
|
||||
// Doing nothing with this failed fetch
|
||||
const uuidLookup = await window.textsecure.messaging.getUuidsForE164s([
|
||||
phoneNumber,
|
||||
]);
|
||||
uuid = uuidLookup[phoneNumber] || undefined;
|
||||
} catch (error) {
|
||||
log.error('checkForAccount:', Errors.toLogFormat(error));
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: 'accounts/UPDATE',
|
||||
payload: {
|
||||
identifier,
|
||||
hasAccount,
|
||||
phoneNumber,
|
||||
uuid,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -86,13 +119,13 @@ export function reducer(
|
|||
|
||||
if (action.type === 'accounts/UPDATE') {
|
||||
const { payload } = action;
|
||||
const { identifier, hasAccount } = payload;
|
||||
const { phoneNumber, uuid } = payload;
|
||||
|
||||
return {
|
||||
...state,
|
||||
accounts: {
|
||||
...state.accounts,
|
||||
[identifier]: hasAccount,
|
||||
[phoneNumber]: uuid,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -5,20 +5,23 @@ import { createSelector } from 'reselect';
|
|||
|
||||
import type { StateType } from '../reducer';
|
||||
import type { AccountsStateType } from '../ducks/accounts';
|
||||
import type { UUIDStringType } from '../../types/UUID';
|
||||
|
||||
export const getAccounts = (state: StateType): AccountsStateType =>
|
||||
state.accounts;
|
||||
|
||||
export type AccountSelectorType = (identifier?: string) => boolean;
|
||||
export type AccountSelectorType = (
|
||||
identifier?: string
|
||||
) => UUIDStringType | undefined;
|
||||
export const getAccountSelector = createSelector(
|
||||
getAccounts,
|
||||
(accounts: AccountsStateType): AccountSelectorType => {
|
||||
return (identifier?: string) => {
|
||||
if (!identifier) {
|
||||
return false;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return accounts.accounts[identifier] || false;
|
||||
return accounts.accounts[identifier] || undefined;
|
||||
};
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1443,7 +1443,7 @@ export function getMessagePropStatus(
|
|||
export function getPropsForEmbeddedContact(
|
||||
message: MessageWithUIFieldsType,
|
||||
regionCode: string | undefined,
|
||||
accountSelector: (identifier?: string) => boolean
|
||||
accountSelector: (identifier?: string) => UUIDStringType | undefined
|
||||
): EmbeddedContactType | undefined {
|
||||
const contacts = message.contact;
|
||||
if (!contacts || !contacts.length) {
|
||||
|
@ -1459,7 +1459,7 @@ export function getPropsForEmbeddedContact(
|
|||
getAbsoluteAttachmentPath:
|
||||
window.Signal.Migrations.getAbsoluteAttachmentPath,
|
||||
firstNumber,
|
||||
isNumberOnSignal: accountSelector(firstNumber),
|
||||
uuid: accountSelector(firstNumber),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -56,6 +56,7 @@ const mapStateToProps = (
|
|||
showExpiredOutgoingTapToViewToast,
|
||||
showForwardMessageModal,
|
||||
showVisualAttachment,
|
||||
startConversation,
|
||||
} = props;
|
||||
|
||||
const contactNameColor =
|
||||
|
@ -102,6 +103,7 @@ const mapStateToProps = (
|
|||
showExpiredOutgoingTapToViewToast,
|
||||
showForwardMessageModal,
|
||||
showVisualAttachment,
|
||||
startConversation,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -98,6 +98,7 @@ export type TimelinePropsType = ExternalProps &
|
|||
| 'showIdentity'
|
||||
| 'showMessageDetail'
|
||||
| 'showVisualAttachment'
|
||||
| 'startConversation'
|
||||
| 'unblurAvatar'
|
||||
| 'updateSharedGroups'
|
||||
>;
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
parseAndWriteAvatar,
|
||||
} from '../../types/EmbeddedContact';
|
||||
import { fakeAttachment } from '../../test-both/helpers/fakeAttachment';
|
||||
import { UUID } from '../../types/UUID';
|
||||
|
||||
describe('Contact', () => {
|
||||
const NUMBER = '+12025550099';
|
||||
|
@ -113,7 +114,7 @@ describe('Contact', () => {
|
|||
describe('embeddedContactSelector', () => {
|
||||
const regionCode = '1';
|
||||
const firstNumber = '+1202555000';
|
||||
const isNumberOnSignal = false;
|
||||
const uuid = undefined;
|
||||
const getAbsoluteAttachmentPath = (path: string) => `absolute:${path}`;
|
||||
|
||||
it('eliminates avatar if it has had an attachment download error', () => {
|
||||
|
@ -141,13 +142,13 @@ describe('Contact', () => {
|
|||
organization: 'Somewhere, Inc.',
|
||||
avatar: undefined,
|
||||
firstNumber,
|
||||
isNumberOnSignal,
|
||||
uuid,
|
||||
number: undefined,
|
||||
};
|
||||
const actual = embeddedContactSelector(contact, {
|
||||
regionCode,
|
||||
firstNumber,
|
||||
isNumberOnSignal,
|
||||
uuid,
|
||||
getAbsoluteAttachmentPath,
|
||||
});
|
||||
assert.deepEqual(actual, expected);
|
||||
|
@ -185,19 +186,21 @@ describe('Contact', () => {
|
|||
}),
|
||||
},
|
||||
firstNumber,
|
||||
isNumberOnSignal,
|
||||
uuid,
|
||||
number: undefined,
|
||||
};
|
||||
const actual = embeddedContactSelector(contact, {
|
||||
regionCode,
|
||||
firstNumber,
|
||||
isNumberOnSignal,
|
||||
uuid,
|
||||
getAbsoluteAttachmentPath,
|
||||
});
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
|
||||
it('calculates absolute path', () => {
|
||||
const fullUuid = UUID.generate().toString();
|
||||
|
||||
const contact = {
|
||||
name: {
|
||||
displayName: 'displayName',
|
||||
|
@ -228,13 +231,13 @@ describe('Contact', () => {
|
|||
}),
|
||||
},
|
||||
firstNumber,
|
||||
isNumberOnSignal: true,
|
||||
uuid: fullUuid,
|
||||
number: undefined,
|
||||
};
|
||||
const actual = embeddedContactSelector(contact, {
|
||||
regionCode,
|
||||
firstNumber,
|
||||
isNumberOnSignal: true,
|
||||
uuid: fullUuid,
|
||||
getAbsoluteAttachmentPath,
|
||||
});
|
||||
assert.deepEqual(actual, expected);
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
import type { AttachmentType, migrateDataToFileSystem } from './Attachment';
|
||||
import { toLogFormat } from './errors';
|
||||
import type { LoggerType } from './Logging';
|
||||
import type { UUIDStringType } from './UUID';
|
||||
|
||||
export type EmbeddedContactType = {
|
||||
name?: Name;
|
||||
|
@ -25,7 +26,7 @@ export type EmbeddedContactType = {
|
|||
|
||||
// Populated by selector
|
||||
firstNumber?: string;
|
||||
isNumberOnSignal?: boolean;
|
||||
uuid?: UUIDStringType;
|
||||
};
|
||||
|
||||
type Name = {
|
||||
|
@ -133,16 +134,11 @@ export function embeddedContactSelector(
|
|||
options: {
|
||||
regionCode?: string;
|
||||
firstNumber?: string;
|
||||
isNumberOnSignal?: boolean;
|
||||
uuid?: UUIDStringType;
|
||||
getAbsoluteAttachmentPath: (path: string) => string;
|
||||
}
|
||||
): EmbeddedContactType {
|
||||
const {
|
||||
getAbsoluteAttachmentPath,
|
||||
firstNumber,
|
||||
isNumberOnSignal,
|
||||
regionCode,
|
||||
} = options;
|
||||
const { getAbsoluteAttachmentPath, firstNumber, uuid, regionCode } = options;
|
||||
|
||||
let { avatar } = contact;
|
||||
if (avatar && avatar.avatar) {
|
||||
|
@ -164,7 +160,7 @@ export function embeddedContactSelector(
|
|||
return {
|
||||
...contact,
|
||||
firstNumber,
|
||||
isNumberOnSignal,
|
||||
uuid,
|
||||
avatar,
|
||||
number:
|
||||
contact.number &&
|
||||
|
|
|
@ -173,7 +173,10 @@ type MessageActionsType = {
|
|||
retryDeleteForEveryone: (messageId: string) => unknown;
|
||||
showContactDetail: (options: {
|
||||
contact: EmbeddedContactType;
|
||||
signalAccount?: string;
|
||||
signalAccount?: {
|
||||
phoneNumber: string;
|
||||
uuid: UUIDStringType;
|
||||
};
|
||||
}) => unknown;
|
||||
showContactModal: (contactId: string) => unknown;
|
||||
showSafetyNumber: (contactId: string) => unknown;
|
||||
|
@ -187,6 +190,7 @@ type MessageActionsType = {
|
|||
messageId: string;
|
||||
showSingle?: boolean;
|
||||
}) => unknown;
|
||||
startConversation: (e164: string, uuid: UUIDStringType) => unknown;
|
||||
};
|
||||
|
||||
type MediaType = {
|
||||
|
@ -768,7 +772,10 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
};
|
||||
const showContactDetail = (options: {
|
||||
contact: EmbeddedContactType;
|
||||
signalAccount?: string;
|
||||
signalAccount?: {
|
||||
phoneNumber: string;
|
||||
uuid: UUIDStringType;
|
||||
};
|
||||
}) => {
|
||||
this.showContactDetail(options);
|
||||
};
|
||||
|
@ -866,6 +873,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
};
|
||||
|
||||
const showForwardMessageModal = this.showForwardMessageModal.bind(this);
|
||||
const startConversation = this.startConversation.bind(this);
|
||||
|
||||
return {
|
||||
deleteMessage,
|
||||
|
@ -891,6 +899,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
showIdentity,
|
||||
showMessageDetail,
|
||||
showVisualAttachment,
|
||||
startConversation,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -2368,7 +2377,10 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
signalAccount,
|
||||
}: {
|
||||
contact: EmbeddedContactType;
|
||||
signalAccount?: string;
|
||||
signalAccount?: {
|
||||
phoneNumber: string;
|
||||
uuid: UUIDStringType;
|
||||
};
|
||||
}): void {
|
||||
const view = new Whisper.ReactWrapperView({
|
||||
Component: window.Signal.Components.ContactDetail,
|
||||
|
@ -2378,7 +2390,10 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
hasSignalAccount: Boolean(signalAccount),
|
||||
onSendMessage: () => {
|
||||
if (signalAccount) {
|
||||
this.openConversation(signalAccount);
|
||||
this.startConversation(
|
||||
signalAccount.phoneNumber,
|
||||
signalAccount.uuid
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
@ -2390,6 +2405,19 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
this.listenBack(view);
|
||||
}
|
||||
|
||||
startConversation(e164: string, uuid: UUIDStringType): void {
|
||||
const conversationId = window.ConversationController.ensureContactIds({
|
||||
e164,
|
||||
uuid,
|
||||
});
|
||||
strictAssert(
|
||||
conversationId,
|
||||
`startConversation failed given ${e164}/${uuid} combination`
|
||||
);
|
||||
|
||||
this.openConversation(conversationId);
|
||||
}
|
||||
|
||||
async openConversation(
|
||||
conversationId: string,
|
||||
messageId?: string
|
||||
|
|
Loading…
Reference in a new issue