Restore ability to message someone from embedded contact

This commit is contained in:
Scott Nonnenberg 2022-04-11 17:26:09 -07:00 committed by GitHub
parent f77175f6b3
commit 302604f67e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 311 additions and 236 deletions

View file

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

View file

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

View file

@ -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();

View file

@ -99,6 +99,7 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
),
showForwardMessageModal: action('showForwardMessageModal'),
showVisualAttachment: action('showVisualAttachment'),
startConversation: action('startConversation'),
});
story.add('Delivered Incoming', () => {

View file

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

View file

@ -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,

View file

@ -398,6 +398,7 @@ const actions = () => ({
downloadNewVersion: action('downloadNewVersion'),
startCallingLobby: action('startCallingLobby'),
startConversation: action('startConversation'),
returnToActiveCall: action('returnToActiveCall'),
contactSupport: action('contactSupport'),

View file

@ -253,6 +253,7 @@ const getActions = createSelector(
'scrollToQuotedMessage',
'showExpiredIncomingTapToViewToast',
'showExpiredOutgoingTapToViewToast',
'startConversation',
'showIdentity',

View file

@ -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,