Skip to content

Commit

Permalink
Show badges on message avatars and on message details screen
Browse files Browse the repository at this point in the history
  • Loading branch information
EvanHahn-Signal committed Nov 16, 2021
1 parent dc1260f commit ca7fb47
Show file tree
Hide file tree
Showing 17 changed files with 125 additions and 43 deletions.
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -152,7 +152,7 @@
"redux-promise-middleware": "6.1.0",
"redux-thunk": "2.3.0",
"redux-ts-utils": "3.2.2",
"reselect": "4.0.0",
"reselect": "4.1.2",
"rimraf": "2.6.2",
"ringrtc": "https://github.com/signalapp/signal-ringrtc-node.git#f8764156753e68eb85220dc1c04a6ced3704bedf",
"rotating-file-stream": "2.1.5",
Expand Down
15 changes: 15 additions & 0 deletions ts/components/conversation/Message.stories.tsx
Expand Up @@ -31,6 +31,8 @@ import { getDefaultConversation } from '../../test-both/helpers/getDefaultConver
import { WidthBreakpoint } from '../_util';

import { fakeAttachment } from '../../test-both/helpers/fakeAttachment';
import { getFakeBadge } from '../../test-both/helpers/getFakeBadge';
import { ThemeType } from '../../types/Util';

const i18n = setupI18n('en', enMessages);

Expand Down Expand Up @@ -95,6 +97,7 @@ const renderAudioAttachment: Props['renderAudioAttachment'] = props => (
const createProps = (overrideProps: Partial<Props> = {}): Props => ({
attachments: overrideProps.attachments,
author: overrideProps.author || getDefaultConversation(),
authorBadge: overrideProps.authorBadge,
reducedMotion: boolean('reducedMotion', false),
bodyRanges: overrideProps.bodyRanges,
canReply: true,
Expand Down Expand Up @@ -176,6 +179,7 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
status: overrideProps.status || 'sent',
text: overrideProps.text || text('text', ''),
textPending: boolean('textPending', overrideProps.textPending || false),
theme: ThemeType.light,
timestamp: number('timestamp', overrideProps.timestamp || Date.now()),
});

Expand Down Expand Up @@ -544,6 +548,17 @@ story.add('Avatar in Group', () => {
return <Message {...props} />;
});

story.add('Badge in Group', () => {
const props = createProps({
authorBadge: getFakeBadge(),
conversationType: 'group',
status: 'sent',
text: 'Hello it is me, the saxophone.',
});

return <Message {...props} />;
});

story.add('Sticker', () => {
const props = createProps({
attachments: [
Expand Down
9 changes: 7 additions & 2 deletions ts/components/conversation/Message.tsx
Expand Up @@ -62,6 +62,7 @@ import type {
LocalizerType,
ThemeType,
} from '../../types/Util';
import type { BadgeType } from '../../badges/types';
import type {
ContactNameColorType,
ConversationColorType,
Expand Down Expand Up @@ -145,6 +146,7 @@ export type PropsData = {
ConversationType,
| 'acceptedMessageRequest'
| 'avatarPath'
| 'badges'
| 'color'
| 'id'
| 'isMe'
Expand All @@ -155,6 +157,7 @@ export type PropsData = {
| 'title'
| 'unblurredAvatarPath'
>;
authorBadge: undefined | BadgeType;
reducedMotion?: boolean;
conversationType: ConversationTypeType;
attachments?: Array<AttachmentType>;
Expand Down Expand Up @@ -203,7 +206,7 @@ export type PropsHousekeeping = {
containerWidthBreakpoint: WidthBreakpoint;
i18n: LocalizerType;
interactionMode: InteractionModeType;
theme?: ThemeType;
theme: ThemeType;
disableMenu?: boolean;
disableScroll?: boolean;
collapseMetadata?: boolean;
Expand Down Expand Up @@ -1187,7 +1190,7 @@ export class Message extends React.PureComponent<Props, State> {
}

public renderAvatar(): JSX.Element | undefined {
const { author, i18n, showContactModal } = this.props;
const { author, authorBadge, i18n, showContactModal, theme } = this.props;

if (!this.hasAvatar()) {
return undefined;
Expand All @@ -1202,6 +1205,7 @@ export class Message extends React.PureComponent<Props, State> {
<Avatar
acceptedMessageRequest={author.acceptedMessageRequest}
avatarPath={author.avatarPath}
badge={authorBadge}
color={author.color}
conversationType="direct"
i18n={i18n}
Expand All @@ -1217,6 +1221,7 @@ export class Message extends React.PureComponent<Props, State> {
profileName={author.profileName}
sharedGroupNames={author.sharedGroupNames}
size={28}
theme={theme}
title={author.title}
unblurredAvatarPath={author.unblurredAvatarPath}
/>
Expand Down
5 changes: 5 additions & 0 deletions ts/components/conversation/MessageDetail.stories.tsx
Expand Up @@ -15,6 +15,8 @@ import { ReadStatus } from '../../messages/MessageReadStatus';
import { getDefaultConversation } from '../../test-both/helpers/getDefaultConversation';
import { setupI18n } from '../../util/setupI18n';
import enMessages from '../../../_locales/en/messages.json';
import { getFakeBadge } from '../../test-both/helpers/getFakeBadge';
import { ThemeType } from '../../types/Util';

const i18n = setupI18n('en', enMessages);

Expand All @@ -25,6 +27,7 @@ const defaultMessage: MessageDataPropsType = {
id: 'some-id',
title: 'Max',
}),
authorBadge: getFakeBadge(),
canReply: true,
canDeleteForEveryone: true,
canDownload: true,
Expand Down Expand Up @@ -59,8 +62,10 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
receivedAt: number('receivedAt', overrideProps.receivedAt || Date.now()),
sentAt: number('sentAt', overrideProps.sentAt || Date.now()),

getPreferredBadge: () => getFakeBadge(),
i18n,
interactionMode: 'keyboard',
theme: ThemeType.light,

showSafetyNumber: action('showSafetyNumber'),

Expand Down
13 changes: 11 additions & 2 deletions ts/components/conversation/MessageDetail.tsx
Expand Up @@ -14,8 +14,9 @@ import type {
PropsData as MessagePropsDataType,
} from './Message';
import { Message } from './Message';
import type { LocalizerType } from '../../types/Util';
import type { LocalizerType, ThemeType } from '../../types/Util';
import type { ConversationType } from '../../state/ducks/conversations';
import type { PreferredBadgeSelectorType } from '../../state/selectors/badges';
import { groupBy } from '../../util/mapUtil';
import type { ContactNameColorType } from '../../types/Colors';
import { SendStatus } from '../../messages/MessageSendState';
Expand All @@ -27,6 +28,7 @@ export type Contact = Pick<
ConversationType,
| 'acceptedMessageRequest'
| 'avatarPath'
| 'badges'
| 'color'
| 'id'
| 'isMe'
Expand Down Expand Up @@ -60,6 +62,8 @@ export type PropsData = {

showSafetyNumber: (contactId: string) => void;
i18n: LocalizerType;
theme: ThemeType;
getPreferredBadge: PreferredBadgeSelectorType;
} & Pick<MessagePropsType, 'interactionMode'>;

export type PropsBackboneActions = Pick<
Expand Down Expand Up @@ -116,10 +120,11 @@ export class MessageDetail extends React.Component<Props> {
}

public renderAvatar(contact: Contact): JSX.Element {
const { i18n } = this.props;
const { getPreferredBadge, i18n, theme } = this.props;
const {
acceptedMessageRequest,
avatarPath,
badges,
color,
isMe,
name,
Expand All @@ -134,13 +139,15 @@ export class MessageDetail extends React.Component<Props> {
<Avatar
acceptedMessageRequest={acceptedMessageRequest}
avatarPath={avatarPath}
badge={getPreferredBadge(badges)}
color={color}
conversationType="direct"
i18n={i18n}
isMe={isMe}
name={name}
phoneNumber={phoneNumber}
profileName={profileName}
theme={theme}
title={title}
sharedGroupNames={sharedGroupNames}
size={AvatarSize.THIRTY_SIX}
Expand Down Expand Up @@ -288,6 +295,7 @@ export class MessageDetail extends React.Component<Props> {
showExpiredOutgoingTapToViewToast,
showForwardMessageModal,
showVisualAttachment,
theme,
} = this.props;

return (
Expand Down Expand Up @@ -350,6 +358,7 @@ export class MessageDetail extends React.Component<Props> {
log.warn('MessageDetail: deleteMessageForEveryone called!');
}}
showVisualAttachment={showVisualAttachment}
theme={theme}
/>
</div>
<table className="module-message-detail__info">
Expand Down
3 changes: 3 additions & 0 deletions ts/components/conversation/Quote.stories.tsx
Expand Up @@ -26,6 +26,7 @@ import { setupI18n } from '../../util/setupI18n';
import enMessages from '../../../_locales/en/messages.json';
import { getDefaultConversation } from '../../test-both/helpers/getDefaultConversation';
import { WidthBreakpoint } from '../_util';
import { ThemeType } from '../../types/Util';

const i18n = setupI18n('en', enMessages);

Expand All @@ -36,6 +37,7 @@ const defaultMessageProps: MessagesProps = {
id: 'some-id',
title: 'Person X',
}),
authorBadge: undefined,
canReply: true,
canDeleteForEveryone: true,
canDownload: true,
Expand Down Expand Up @@ -90,6 +92,7 @@ const defaultMessageProps: MessagesProps = {
showVisualAttachment: action('default--showVisualAttachment'),
status: 'sent',
text: 'This is really interesting.',
theme: ThemeType.light,
timestamp: Date.now(),
};

Expand Down
9 changes: 9 additions & 0 deletions ts/components/conversation/Timeline.stories.tsx
Expand Up @@ -46,6 +46,7 @@ const items: Record<string, TimelineItemType> = {
author: getDefaultConversation({
phoneNumber: '(202) 555-2001',
}),
authorBadge: undefined,
canDeleteForEveryone: false,
canDownload: true,
canReply: true,
Expand All @@ -66,6 +67,7 @@ const items: Record<string, TimelineItemType> = {
type: 'message',
data: {
author: getDefaultConversation({}),
authorBadge: undefined,
canDeleteForEveryone: false,
canDownload: true,
canReply: true,
Expand Down Expand Up @@ -99,6 +101,7 @@ const items: Record<string, TimelineItemType> = {
type: 'message',
data: {
author: getDefaultConversation({}),
authorBadge: undefined,
canDeleteForEveryone: false,
canDownload: true,
canReply: true,
Expand Down Expand Up @@ -188,6 +191,7 @@ const items: Record<string, TimelineItemType> = {
type: 'message',
data: {
author: getDefaultConversation({}),
authorBadge: undefined,
canDeleteForEveryone: false,
canDownload: true,
canReply: true,
Expand All @@ -209,6 +213,7 @@ const items: Record<string, TimelineItemType> = {
type: 'message',
data: {
author: getDefaultConversation({}),
authorBadge: undefined,
canDeleteForEveryone: false,
canDownload: true,
canReply: true,
Expand All @@ -230,6 +235,7 @@ const items: Record<string, TimelineItemType> = {
type: 'message',
data: {
author: getDefaultConversation({}),
authorBadge: undefined,
canDeleteForEveryone: false,
canDownload: true,
canReply: true,
Expand All @@ -251,6 +257,7 @@ const items: Record<string, TimelineItemType> = {
type: 'message',
data: {
author: getDefaultConversation({}),
authorBadge: undefined,
canDeleteForEveryone: false,
canDownload: true,
canReply: true,
Expand All @@ -273,6 +280,7 @@ const items: Record<string, TimelineItemType> = {
type: 'message',
data: {
author: getDefaultConversation({}),
authorBadge: undefined,
canDeleteForEveryone: false,
canDownload: true,
canReply: true,
Expand Down Expand Up @@ -391,6 +399,7 @@ const renderItem = ({
nextItem={undefined}
i18n={i18n}
interactionMode="keyboard"
theme={ThemeType.light}
containerElementRef={containerElementRef}
containerWidthBreakpoint={containerWidthBreakpoint}
conversationId=""
Expand Down
4 changes: 2 additions & 2 deletions ts/components/conversation/Timeline.tsx
@@ -1,7 +1,7 @@
// Copyright 2019-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only

import { debounce, get, isNumber, pick, identity } from 'lodash';
import { debounce, get, isNumber, pick } from 'lodash';
import classNames from 'classnames';
import type { CSSProperties, ReactChild, ReactNode, RefObject } from 'react';
import React from 'react';
Expand Down Expand Up @@ -226,7 +226,7 @@ type StateType = {
const getActions = createSelector(
// It is expensive to pick so many properties out of the `props` object so we
// use `createSelector` to memoize them by the last seen `props` object.
identity,
(props: PropsType) => props,

(props: PropsType): PropsActionsType => {
const unsafe = pick(props, [
Expand Down
2 changes: 2 additions & 0 deletions ts/components/conversation/TimelineItem.stories.tsx
Expand Up @@ -16,6 +16,7 @@ import { CallMode } from '../../types/Calling';
import { AvatarColors } from '../../types/Colors';
import { getDefaultConversation } from '../../test-both/helpers/getDefaultConversation';
import { WidthBreakpoint } from '../_util';
import { ThemeType } from '../../types/Util';

const i18n = setupI18n('en', enMessages);

Expand Down Expand Up @@ -53,6 +54,7 @@ const getDefaultProps = () => ({
id: 'asdf',
isSelected: false,
interactionMode: 'keyboard' as const,
theme: ThemeType.light,
selectMessage: action('selectMessage'),
reactToMessage: action('reactToMessage'),
checkForAccount: action('checkForAccount'),
Expand Down
2 changes: 1 addition & 1 deletion ts/components/conversation/TimelineItem.tsx
Expand Up @@ -148,7 +148,7 @@ type PropsLocalType = {
renderUniversalTimerNotification: () => JSX.Element;
i18n: LocalizerType;
interactionMode: InteractionModeType;
theme?: ThemeType;
theme: ThemeType;
previousItem: undefined | TimelineItemType;
nextItem: undefined | TimelineItemType;
};
Expand Down
9 changes: 9 additions & 0 deletions ts/models/messages.ts
Expand Up @@ -134,6 +134,8 @@ import type { LinkPreviewType } from '../types/message/LinkPreviews';
import * as log from '../logging/log';
import * as Bytes from '../Bytes';
import { computeHash } from '../Crypto';
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
import { getPreferredBadgeSelector } from '../state/selectors/badges';

/* eslint-disable camelcase */
/* eslint-disable more/no-then */
Expand Down Expand Up @@ -395,6 +397,13 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
ourConversationId,
ourNumber: window.textsecure.storage.user.getNumber(),
ourUuid: window.textsecure.storage.user.getCheckedUuid().toString(),
preferredBadgeSelector: (
...args: Parameters<PreferredBadgeSelectorType>
) => {
const state = window.reduxStore.getState();
const preferredBadgeSelector = getPreferredBadgeSelector(state);
return preferredBadgeSelector(...args);
},
regionCode: window.storage.get('regionCode', 'ZZ'),
accountSelector: (identifier?: string) => {
const state = window.reduxStore.getState();
Expand Down
8 changes: 5 additions & 3 deletions ts/state/selectors/badges.ts
Expand Up @@ -50,11 +50,13 @@ export const getBadgesSelector = createSelector(
}
);

export type PreferredBadgeSelectorType = (
conversationBadges: ReadonlyArray<Pick<BadgeType, 'id'>>
) => undefined | BadgeType;

export const getPreferredBadgeSelector = createSelector(
getBadgesById,
badgesById => (
conversationBadges: ReadonlyArray<Pick<BadgeType, 'id'>>
): undefined | BadgeType => {
(badgesById): PreferredBadgeSelectorType => conversationBadges => {
const firstId: undefined | string = conversationBadges[0]?.id;
if (!firstId) {
return undefined;
Expand Down

0 comments on commit ca7fb47

Please sign in to comment.