Modern profile sharing in 1:1 and GroupV1 groups

This commit is contained in:
Scott Nonnenberg 2020-10-16 11:31:57 -07:00
parent 60f2422e2a
commit 04b7a29229
22 changed files with 371 additions and 115 deletions

View file

@ -16,12 +16,15 @@ import {
MessageRequestActions,
Props as MessageRequestActionsProps,
} from './conversation/MessageRequestActions';
import { MandatoryProfileSharingActions } from './conversation/MandatoryProfileSharingActions';
import { countStickers } from './stickers/lib';
import { LocalizerType } from '../types/Util';
import { EmojiPickDataType } from './emoji/EmojiPicker';
export type OwnProps = {
readonly i18n: LocalizerType;
readonly groupVersion?: 1 | 2;
readonly isMissingMandatoryProfileSharing?: boolean;
readonly messageRequestsEnabled?: boolean;
readonly acceptedMessageRequest?: boolean;
readonly compositionApi?: React.MutableRefObject<{
@ -113,7 +116,9 @@ export const CompositionArea = ({
// Message Requests
acceptedMessageRequest,
conversationType,
groupVersion,
isBlocked,
isMissingMandatoryProfileSharing,
messageRequestsEnabled,
name,
onAccept,
@ -326,7 +331,7 @@ export const CompositionArea = ({
};
}, [setLarge]);
if ((!acceptedMessageRequest || isBlocked) && messageRequestsEnabled) {
if (messageRequestsEnabled && (!acceptedMessageRequest || isBlocked)) {
return (
<MessageRequestActions
i18n={i18n}
@ -345,6 +350,28 @@ export const CompositionArea = ({
);
}
// If no message request, but we haven't shared profile yet, we show profile-sharing UI
if (
(conversationType === 'direct' ||
(conversationType === 'group' && groupVersion === 1)) &&
isMissingMandatoryProfileSharing
) {
return (
<MandatoryProfileSharingActions
i18n={i18n}
conversationType={conversationType}
onBlock={onBlock}
onBlockAndDelete={onBlockAndDelete}
onDelete={onDelete}
onAccept={onAccept}
name={name}
profileName={profileName}
phoneNumber={phoneNumber}
title={title}
/>
);
}
return (
<div className="module-composition-area">
<div className="module-composition-area__toggle-large">

View file

@ -0,0 +1,47 @@
import * as React from 'react';
import { storiesOf } from '@storybook/react';
import { text } from '@storybook/addon-knobs';
import { action } from '@storybook/addon-actions';
import {
MandatoryProfileSharingActions,
Props as MandatoryProfileSharingActionsProps,
} from './MandatoryProfileSharingActions';
import { setup as setupI18n } from '../../../js/modules/i18n';
import enMessages from '../../../_locales/en/messages.json';
const i18n = setupI18n('en', enMessages);
const getBaseProps = (
isGroup = false
): MandatoryProfileSharingActionsProps => ({
i18n,
conversationType: isGroup ? 'group' : 'direct',
firstName: text('firstName', 'Cayce'),
title: isGroup
? text('title', 'NYC Rock Climbers')
: text('title', 'Cayce Bollard'),
name: isGroup
? text('name', 'NYC Rock Climbers')
: text('name', 'Cayce Bollard'),
onBlock: action('block'),
onBlockAndDelete: action('onBlockAndDelete'),
onDelete: action('delete'),
onAccept: action('accept'),
});
storiesOf('Components/Conversation/MandatoryProfileSharingActions', module)
.add('Direct', () => {
return (
<div style={{ width: '480px' }}>
<MandatoryProfileSharingActions {...getBaseProps()} />
</div>
);
})
.add('Group', () => {
return (
<div style={{ width: '480px' }}>
<MandatoryProfileSharingActions {...getBaseProps(true)} />
</div>
);
});

View file

@ -0,0 +1,134 @@
import * as React from 'react';
import classNames from 'classnames';
import { ContactName, PropsType as ContactNameProps } from './ContactName';
import {
MessageRequestActionsConfirmation,
MessageRequestState,
Props as MessageRequestActionsConfirmationProps,
} from './MessageRequestActionsConfirmation';
import { Intl } from '../Intl';
import { LocalizerType } from '../../types/Util';
export type Props = {
i18n: LocalizerType;
firstName?: string;
onAccept(): unknown;
} & Omit<ContactNameProps, 'module' | 'i18n'> &
Pick<
MessageRequestActionsConfirmationProps,
'conversationType' | 'onBlock' | 'onBlockAndDelete' | 'onDelete'
>;
export const MandatoryProfileSharingActions = ({
conversationType,
firstName,
i18n,
name,
onAccept,
onBlock,
onBlockAndDelete,
onDelete,
phoneNumber,
profileName,
title,
}: Props): JSX.Element => {
const [mrState, setMrState] = React.useState(MessageRequestState.default);
return (
<>
{mrState !== MessageRequestState.default ? (
<MessageRequestActionsConfirmation
i18n={i18n}
onBlock={onBlock}
onBlockAndDelete={onBlockAndDelete}
onUnblock={() => {
throw new Error(
'Should not be able to unblock from MandatoryProfileSharingActions'
);
}}
onDelete={onDelete}
name={name}
profileName={profileName}
phoneNumber={phoneNumber}
title={title}
conversationType={conversationType}
state={mrState}
onChangeState={setMrState}
/>
) : null}
<div className="module-message-request-actions">
<p className="module-message-request-actions__message">
<Intl
i18n={i18n}
id={`MessageRequests--profile-sharing--${conversationType}`}
components={{
firstName: (
<strong
key="name"
className="module-message-request-actions__message__name"
>
<ContactName
name={name}
profileName={profileName}
phoneNumber={phoneNumber}
title={firstName || title}
i18n={i18n}
/>
</strong>
),
learnMore: (
<a
href="https://support.signal.org/hc/articles/360007459591"
target="_blank"
rel="noreferrer"
className="module-message-request-actions__message__learn-more"
>
{i18n('MessageRequests--learn-more')}
</a>
),
}}
/>
</p>
<div className="module-message-request-actions__buttons">
<button
type="button"
onClick={() => {
setMrState(MessageRequestState.blocking);
}}
tabIndex={0}
className={classNames(
'module-message-request-actions__buttons__button',
'module-message-request-actions__buttons__button--deny'
)}
>
{i18n('MessageRequests--block')}
</button>
<button
type="button"
onClick={() => {
setMrState(MessageRequestState.deleting);
}}
tabIndex={0}
className={classNames(
'module-message-request-actions__buttons__button',
'module-message-request-actions__buttons__button--deny'
)}
>
{i18n('MessageRequests--delete')}
</button>
<button
type="button"
onClick={onAccept}
tabIndex={0}
className={classNames(
'module-message-request-actions__buttons__button',
'module-message-request-actions__buttons__button--accept'
)}
>
{i18n('MessageRequests--continue')}
</button>
</div>
</div>
</>
);
};

View file

@ -45,6 +45,7 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
authorTitle: text('authorTitle', overrideProps.authorTitle || ''),
bodyRanges: overrideProps.bodyRanges,
canReply: true,
canDownload: true,
canDeleteForEveryone: overrideProps.canDeleteForEveryone || false,
clearSelectedMessage: action('clearSelectedMessage'),
collapseMetadata: overrideProps.collapseMetadata,

View file

@ -137,6 +137,7 @@ export type PropsData = {
deletedForEveryone?: boolean;
canReply: boolean;
canDownload: boolean;
canDeleteForEveryone: boolean;
bodyRanges?: BodyRangesType;
};
@ -1159,6 +1160,7 @@ export class Message extends React.PureComponent<Props, State> {
): JSX.Element | null {
const {
attachments,
canDownload,
canReply,
direction,
disableMenu,
@ -1294,7 +1296,7 @@ export class Message extends React.PureComponent<Props, State> {
)}
>
{canReply ? reactButton : null}
{canReply ? downloadButton : null}
{canDownload ? downloadButton : null}
{canReply ? replyButton : null}
{menuButton}
</div>
@ -1328,6 +1330,7 @@ export class Message extends React.PureComponent<Props, State> {
public renderContextMenu(triggerId: string): JSX.Element {
const {
attachments,
canDownload,
canReply,
deleteMessage,
deleteMessageForEveryone,
@ -1349,7 +1352,8 @@ export class Message extends React.PureComponent<Props, State> {
const menu = (
<ContextMenu id={triggerId}>
{!isSticker &&
{canDownload &&
!isSticker &&
!multipleAttachments &&
!isTapToView &&
attachments &&

View file

@ -17,6 +17,7 @@ const defaultMessage: MessageProps = {
authorTitle: 'Max',
canReply: true,
canDeleteForEveryone: true,
canDownload: true,
clearSelectedMessage: () => null,
conversationId: 'my-convo',
conversationType: 'direct',

View file

@ -20,6 +20,7 @@ const defaultMessageProps: MessagesProps = {
authorTitle: 'Person X',
canReply: true,
canDeleteForEveryone: true,
canDownload: true,
clearSelectedMessage: () => null,
conversationId: 'conversationId',
conversationType: 'direct', // override