Blur avatars of unapproved conversations

This commit is contained in:
Evan Hahn 2021-04-30 14:40:25 -05:00 committed by GitHub
parent bbd7fd3854
commit 05703c2719
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 474 additions and 124 deletions

View file

@ -1,4 +1,4 @@
// Copyright 2020 Signal Messenger, LLC
// Copyright 2020-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React, { ReactPortal } from 'react';
@ -106,14 +106,17 @@ export const ContactModal = ({
aria-label={i18n('close')}
/>
<Avatar
acceptedMessageRequest={contact.acceptedMessageRequest}
avatarPath={contact.avatarPath}
color={contact.color}
conversationType="direct"
i18n={i18n}
name={contact.name}
profileName={contact.profileName}
sharedGroupNames={contact.sharedGroupNames}
size={96}
title={contact.title}
unblurredAvatarPath={contact.unblurredAvatarPath}
/>
<div className="module-contact-modal__name">{contact.title}</div>
<div className="module-about__container">

View file

@ -17,7 +17,7 @@ import { Avatar, AvatarSize } from '../Avatar';
import { InContactsIcon } from '../InContactsIcon';
import { LocalizerType } from '../../types/Util';
import { ColorType } from '../../types/Colors';
import { ConversationType } from '../../state/ducks/conversations';
import { MuteOption, getMuteOptions } from '../../util/getMuteOptions';
import {
ExpirationTimerOptions,
@ -35,33 +35,33 @@ export enum OutgoingCallButtonStyle {
export type PropsDataType = {
conversationTitle?: string;
id: string;
name?: string;
phoneNumber?: string;
profileName?: string;
color?: ColorType;
avatarPath?: string;
type: 'direct' | 'group';
title: string;
acceptedMessageRequest?: boolean;
isVerified?: boolean;
isMe?: boolean;
isArchived?: boolean;
isPinned?: boolean;
isMissingMandatoryProfileSharing?: boolean;
left?: boolean;
markedUnread?: boolean;
groupVersion?: number;
canChangeTimer?: boolean;
expireTimer?: number;
muteExpiresAt?: number;
showBackButton?: boolean;
outgoingCallButtonStyle: OutgoingCallButtonStyle;
};
showBackButton?: boolean;
} & Pick<
ConversationType,
| 'acceptedMessageRequest'
| 'avatarPath'
| 'canChangeTimer'
| 'color'
| 'expireTimer'
| 'groupVersion'
| 'id'
| 'isArchived'
| 'isMe'
| 'isPinned'
| 'isVerified'
| 'left'
| 'markedUnread'
| 'muteExpiresAt'
| 'name'
| 'phoneNumber'
| 'profileName'
| 'sharedGroupNames'
| 'title'
| 'type'
| 'unblurredAvatarPath'
>;
export type PropsActionsType = {
onSetMuteNotifications: (seconds: number) => void;
@ -180,6 +180,7 @@ export class ConversationHeader extends React.Component<PropsType, StateType> {
private renderAvatar(): ReactNode {
const {
acceptedMessageRequest,
avatarPath,
color,
i18n,
@ -188,22 +189,28 @@ export class ConversationHeader extends React.Component<PropsType, StateType> {
name,
phoneNumber,
profileName,
sharedGroupNames,
title,
unblurredAvatarPath,
} = this.props;
return (
<span className="module-ConversationHeader__header__avatar">
<Avatar
acceptedMessageRequest={acceptedMessageRequest}
avatarPath={avatarPath}
color={color}
conversationType={type}
i18n={i18n}
isMe={isMe}
noteToSelf={isMe}
title={title}
name={name}
phoneNumber={phoneNumber}
profileName={profileName}
sharedGroupNames={sharedGroupNames}
size={AvatarSize.THIRTY_TWO}
unblurredAvatarPath={unblurredAvatarPath}
/>
</span>
);

View file

@ -4,6 +4,7 @@
import * as React from 'react';
import { storiesOf } from '@storybook/react';
import { number as numberKnob, text } from '@storybook/addon-knobs';
import { action } from '@storybook/addon-actions';
import { ConversationHero } from './ConversationHero';
import { setup as setupI18n } from '../../../js/modules/i18n';
@ -33,6 +34,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
phoneNumber={getPhoneNumber()}
conversationType="direct"
sharedGroupNames={['NYC Rock Climbers', 'Dinner Party', 'Friends 🌿']}
unblurAvatar={action('unblurAvatar')}
/>
</div>
);
@ -50,6 +52,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
phoneNumber={getPhoneNumber()}
conversationType="direct"
sharedGroupNames={['NYC Rock Climbers', 'Dinner Party']}
unblurAvatar={action('unblurAvatar')}
/>
</div>
);
@ -67,6 +70,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
phoneNumber={getPhoneNumber()}
conversationType="direct"
sharedGroupNames={['NYC Rock Climbers']}
unblurAvatar={action('unblurAvatar')}
/>
</div>
);
@ -84,6 +88,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
phoneNumber={getPhoneNumber()}
conversationType="direct"
sharedGroupNames={[]}
unblurAvatar={action('unblurAvatar')}
/>
</div>
);
@ -101,6 +106,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
phoneNumber={getPhoneNumber()}
conversationType="direct"
sharedGroupNames={[]}
unblurAvatar={action('unblurAvatar')}
/>
</div>
);
@ -118,6 +124,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
phoneNumber={getPhoneNumber()}
conversationType="direct"
sharedGroupNames={[]}
unblurAvatar={action('unblurAvatar')}
/>
</div>
);
@ -134,6 +141,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
phoneNumber={text('phoneNumber', '')}
conversationType="direct"
sharedGroupNames={[]}
unblurAvatar={action('unblurAvatar')}
/>
</div>
);
@ -147,6 +155,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
name={text('groupName', 'NYC Rock Climbers')}
conversationType="group"
membersCount={numberKnob('membersCount', 22)}
unblurAvatar={action('unblurAvatar')}
/>
</div>
);
@ -160,6 +169,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
name={text('groupName', 'NYC Rock Climbers')}
conversationType="group"
membersCount={1}
unblurAvatar={action('unblurAvatar')}
/>
</div>
);
@ -173,6 +183,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
name={text('groupName', 'NYC Rock Climbers')}
conversationType="group"
membersCount={0}
unblurAvatar={action('unblurAvatar')}
/>
</div>
);
@ -186,6 +197,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
name={text('groupName', '')}
conversationType="group"
membersCount={0}
unblurAvatar={action('unblurAvatar')}
/>
</div>
);
@ -199,6 +211,7 @@ storiesOf('Components/Conversation/ConversationHero', module)
title={getTitle()}
conversationType="direct"
phoneNumber={getPhoneNumber()}
unblurAvatar={action('unblurAvatar')}
/>
</div>
);

View file

@ -1,21 +1,25 @@
// Copyright 2020 Signal Messenger, LLC
// Copyright 2020-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react';
import { Avatar, Props as AvatarProps } from '../Avatar';
import { Avatar, AvatarBlur, Props as AvatarProps } from '../Avatar';
import { ContactName } from './ContactName';
import { About } from './About';
import { SharedGroupNames } from '../SharedGroupNames';
import { LocalizerType } from '../../types/Util';
import { shouldBlurAvatar } from '../../util/shouldBlurAvatar';
export type Props = {
about?: string;
acceptedMessageRequest?: boolean;
i18n: LocalizerType;
isMe?: boolean;
sharedGroupNames?: Array<string>;
membersCount?: number;
phoneNumber?: string;
onHeightChange?: () => unknown;
unblurAvatar: () => void;
unblurredAvatarPath?: string;
updateSharedGroups?: () => unknown;
} & Omit<AvatarProps, 'onClick' | 'size' | 'noteToSelf'>;
@ -61,6 +65,7 @@ const renderMembershipRow = ({
export const ConversationHero = ({
i18n,
about,
acceptedMessageRequest,
avatarPath,
color,
conversationType,
@ -72,6 +77,8 @@ export const ConversationHero = ({
profileName,
title,
onHeightChange,
unblurAvatar,
unblurredAvatarPath,
updateSharedGroups,
}: Props): JSX.Element => {
const firstRenderRef = React.useRef(true);
@ -106,6 +113,23 @@ export const ConversationHero = ({
]);
/* eslint-enable react-hooks/exhaustive-deps */
let avatarBlur: AvatarBlur;
let avatarOnClick: undefined | (() => void);
if (
shouldBlurAvatar({
acceptedMessageRequest,
avatarPath,
isMe,
sharedGroupNames,
unblurredAvatarPath,
})
) {
avatarBlur = AvatarBlur.BlurPictureWithClickToView;
avatarOnClick = unblurAvatar;
} else {
avatarBlur = AvatarBlur.NoBlur;
}
const phoneNumberOnly = Boolean(
!name && !profileName && conversationType === 'direct'
);
@ -115,11 +139,13 @@ export const ConversationHero = ({
<div className="module-conversation-hero">
<Avatar
i18n={i18n}
blur={avatarBlur}
color={color}
noteToSelf={isMe}
avatarPath={avatarPath}
conversationType={conversationType}
name={name}
onClick={avatarOnClick}
profileName={profileName}
title={title}
size={112}

View file

@ -111,13 +111,17 @@ export type PropsData = {
contact?: ContactType;
author: Pick<
ConversationType,
| 'acceptedMessageRequest'
| 'avatarPath'
| 'color'
| 'id'
| 'isMe'
| 'name'
| 'phoneNumber'
| 'profileName'
| 'sharedGroupNames'
| 'title'
| 'unblurredAvatarPath'
>;
reducedMotion?: boolean;
conversationType: ConversationTypesType;
@ -1159,15 +1163,19 @@ export class Message extends React.Component<Props, State> {
tabIndex={0}
>
<Avatar
acceptedMessageRequest={author.acceptedMessageRequest}
avatarPath={author.avatarPath}
color={author.color}
conversationType="direct"
i18n={i18n}
isMe={author.isMe}
name={author.name}
phoneNumber={author.phoneNumber}
profileName={author.profileName}
title={author.title}
sharedGroupNames={author.sharedGroupNames}
size={28}
title={author.title}
unblurredAvatarPath={author.unblurredAvatarPath}
/>
</button>
</div>

View file

@ -271,6 +271,8 @@ const actions = () => ({
onBlockAndDelete: action('onBlockAndDelete'),
onDelete: action('onDelete'),
onUnblock: action('onUnblock'),
unblurAvatar: action('unblurAvatar'),
});
const renderItem = (id: string) => (
@ -312,6 +314,7 @@ const renderHeroRow = () => (
phoneNumber={getPhoneNumber()}
conversationType="direct"
sharedGroupNames={['NYC Rock Climbers', 'Dinner Party']}
unblurAvatar={action('unblurAvatar')}
/>
);
const renderLoadingRow = () => <TimelineLoadingRow state="loading" />;

View file

@ -81,6 +81,7 @@ type PropsHousekeepingType = {
renderHeroRow: (
id: string,
resizeHeroRow: () => unknown,
unblurAvatar: () => void,
updateSharedGroups: () => unknown
) => JSX.Element;
renderLoadingRow: (id: string) => JSX.Element;
@ -113,6 +114,7 @@ type PropsActionsType = {
onUnblock: () => unknown;
selectMessage: (messageId: string, conversationId: string) => unknown;
clearSelectedMessage: () => unknown;
unblurAvatar: () => void;
updateSharedGroups: () => unknown;
} & MessageActionsType &
SafetyNumberActionsType;
@ -583,6 +585,7 @@ export class Timeline extends React.PureComponent<PropsType, StateType> {
renderLoadingRow,
renderLastSeenIndicator,
renderTypingBubble,
unblurAvatar,
updateSharedGroups,
} = this.props;
const { lastMeasuredWarningHeight } = this.state;
@ -602,7 +605,12 @@ export class Timeline extends React.PureComponent<PropsType, StateType> {
{this.getWarning() ? (
<div style={{ height: lastMeasuredWarningHeight }} />
) : null}
{renderHeroRow(id, this.resizeHeroRow, updateSharedGroups)}
{renderHeroRow(
id,
this.resizeHeroRow,
unblurAvatar,
updateSharedGroups
)}
</div>
);
} else if (!haveOldest && row === 0) {