Add user badges to typing bubbles, refactor typing logic
This commit is contained in:
parent
ede34ecee3
commit
f4e336836f
13 changed files with 125 additions and 189 deletions
|
@ -41,7 +41,6 @@ export const AnnouncementsOnlyGroupBanner = ({
|
||||||
draftPreview=""
|
draftPreview=""
|
||||||
lastMessage={undefined}
|
lastMessage={undefined}
|
||||||
lastUpdated={undefined}
|
lastUpdated={undefined}
|
||||||
typingContact={undefined}
|
|
||||||
theme={theme}
|
theme={theme}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { getDefaultConversation } from '../test-both/helpers/getDefaultConversat
|
||||||
import { setupI18n } from '../util/setupI18n';
|
import { setupI18n } from '../util/setupI18n';
|
||||||
import enMessages from '../../_locales/en/messages.json';
|
import enMessages from '../../_locales/en/messages.json';
|
||||||
import { StorybookThemeContext } from '../../.storybook/StorybookThemeContext';
|
import { StorybookThemeContext } from '../../.storybook/StorybookThemeContext';
|
||||||
|
import { UUID } from '../types/UUID';
|
||||||
|
|
||||||
const i18n = setupI18n('en', enMessages);
|
const i18n = setupI18n('en', enMessages);
|
||||||
|
|
||||||
|
@ -295,10 +296,7 @@ story.add('Contact checkboxes: disabled', () => (
|
||||||
|
|
||||||
story.add('Conversation: Typing Status', () =>
|
story.add('Conversation: Typing Status', () =>
|
||||||
renderConversation({
|
renderConversation({
|
||||||
typingContact: {
|
typingContactId: UUID.generate().toString(),
|
||||||
...getDefaultConversation(),
|
|
||||||
name: 'Someone Here',
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -269,7 +269,7 @@ export const ConversationList: React.FC<PropsType> = ({
|
||||||
'shouldShowDraft',
|
'shouldShowDraft',
|
||||||
'title',
|
'title',
|
||||||
'type',
|
'type',
|
||||||
'typingContact',
|
'typingContactId',
|
||||||
'unblurredAvatarPath',
|
'unblurredAvatarPath',
|
||||||
'unreadCount',
|
'unreadCount',
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import * as moment from 'moment';
|
import * as moment from 'moment';
|
||||||
import { isBoolean, times } from 'lodash';
|
import { times } from 'lodash';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import { storiesOf } from '@storybook/react';
|
import { storiesOf } from '@storybook/react';
|
||||||
import { text, boolean, number } from '@storybook/addon-knobs';
|
import { text, boolean, number } from '@storybook/addon-knobs';
|
||||||
|
@ -25,6 +25,8 @@ import { TypingBubble } from './TypingBubble';
|
||||||
import { ContactSpoofingType } from '../../util/contactSpoofing';
|
import { ContactSpoofingType } from '../../util/contactSpoofing';
|
||||||
import { ReadStatus } from '../../messages/MessageReadStatus';
|
import { ReadStatus } from '../../messages/MessageReadStatus';
|
||||||
import type { WidthBreakpoint } from '../_util';
|
import type { WidthBreakpoint } from '../_util';
|
||||||
|
import { ThemeType } from '../../types/Util';
|
||||||
|
import { UUID } from '../../types/UUID';
|
||||||
|
|
||||||
const i18n = setupI18n('en', enMessages);
|
const i18n = setupI18n('en', enMessages);
|
||||||
|
|
||||||
|
@ -441,12 +443,14 @@ const renderLoadingRow = () => <TimelineLoadingRow state="loading" />;
|
||||||
const renderTypingBubble = () => (
|
const renderTypingBubble = () => (
|
||||||
<TypingBubble
|
<TypingBubble
|
||||||
acceptedMessageRequest
|
acceptedMessageRequest
|
||||||
|
badge={undefined}
|
||||||
color={getRandomColor()}
|
color={getRandomColor()}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
phoneNumber="+18005552222"
|
phoneNumber="+18005552222"
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={false}
|
isMe={false}
|
||||||
title="title"
|
title="title"
|
||||||
|
theme={ThemeType.light}
|
||||||
sharedGroupNames={[]}
|
sharedGroupNames={[]}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -486,10 +490,7 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
||||||
renderHeroRow,
|
renderHeroRow,
|
||||||
renderLoadingRow,
|
renderLoadingRow,
|
||||||
renderTypingBubble,
|
renderTypingBubble,
|
||||||
typingContact: boolean(
|
typingContactId: overrideProps.typingContactId,
|
||||||
'typingContact',
|
|
||||||
isBoolean(overrideProps.typingContact) ? overrideProps.typingContact : false
|
|
||||||
),
|
|
||||||
|
|
||||||
...actions(),
|
...actions(),
|
||||||
});
|
});
|
||||||
|
@ -561,7 +562,7 @@ story.add('Target Index to Top', () => {
|
||||||
|
|
||||||
story.add('Typing Indicator', () => {
|
story.add('Typing Indicator', () => {
|
||||||
const props = createProps({
|
const props = createProps({
|
||||||
typingContact: true,
|
typingContactId: UUID.generate().toString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
return <Timeline {...props} />;
|
return <Timeline {...props} />;
|
||||||
|
|
|
@ -94,7 +94,7 @@ type PropsHousekeepingType = {
|
||||||
areWeAdmin?: boolean;
|
areWeAdmin?: boolean;
|
||||||
isGroupV1AndDisabled?: boolean;
|
isGroupV1AndDisabled?: boolean;
|
||||||
isIncomingMessageRequest: boolean;
|
isIncomingMessageRequest: boolean;
|
||||||
typingContact?: unknown;
|
typingContactId?: string;
|
||||||
unreadCount?: number;
|
unreadCount?: number;
|
||||||
|
|
||||||
selectedMessageId?: string;
|
selectedMessageId?: string;
|
||||||
|
@ -859,7 +859,7 @@ export class Timeline extends React.PureComponent<PropsType, StateType> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public getRowCount(): number {
|
public getRowCount(): number {
|
||||||
const { oldestUnreadIndex, typingContact } = this.props;
|
const { oldestUnreadIndex, typingContactId } = this.props;
|
||||||
const { items } = this.props;
|
const { items } = this.props;
|
||||||
const itemsCount = items && items.length ? items.length : 0;
|
const itemsCount = items && items.length ? items.length : 0;
|
||||||
|
|
||||||
|
@ -870,7 +870,7 @@ export class Timeline extends React.PureComponent<PropsType, StateType> {
|
||||||
extraRows += 1;
|
extraRows += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typingContact) {
|
if (typingContactId) {
|
||||||
extraRows += 1;
|
extraRows += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1033,7 +1033,7 @@ export class Timeline extends React.PureComponent<PropsType, StateType> {
|
||||||
resetCounter,
|
resetCounter,
|
||||||
scrollToBottomCounter,
|
scrollToBottomCounter,
|
||||||
scrollToIndex,
|
scrollToIndex,
|
||||||
typingContact,
|
typingContactId,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
// We recompute the hero row's height if:
|
// We recompute the hero row's height if:
|
||||||
|
@ -1097,7 +1097,7 @@ export class Timeline extends React.PureComponent<PropsType, StateType> {
|
||||||
if (
|
if (
|
||||||
items !== prevProps.items ||
|
items !== prevProps.items ||
|
||||||
oldestUnreadIndex !== prevProps.oldestUnreadIndex ||
|
oldestUnreadIndex !== prevProps.oldestUnreadIndex ||
|
||||||
Boolean(typingContact) !== Boolean(prevProps.typingContact)
|
Boolean(typingContactId) !== Boolean(prevProps.typingContactId)
|
||||||
) {
|
) {
|
||||||
const { atTop } = this.state;
|
const { atTop } = this.state;
|
||||||
|
|
||||||
|
@ -1135,13 +1135,13 @@ export class Timeline extends React.PureComponent<PropsType, StateType> {
|
||||||
const rowsIterator = Timeline.getEphemeralRows({
|
const rowsIterator = Timeline.getEphemeralRows({
|
||||||
items,
|
items,
|
||||||
oldestUnreadIndex,
|
oldestUnreadIndex,
|
||||||
typingContact: Boolean(typingContact),
|
hasTypingContact: Boolean(typingContactId),
|
||||||
haveOldest,
|
haveOldest,
|
||||||
});
|
});
|
||||||
const prevRowsIterator = Timeline.getEphemeralRows({
|
const prevRowsIterator = Timeline.getEphemeralRows({
|
||||||
items: prevProps.items,
|
items: prevProps.items,
|
||||||
oldestUnreadIndex: prevProps.oldestUnreadIndex,
|
oldestUnreadIndex: prevProps.oldestUnreadIndex,
|
||||||
typingContact: Boolean(prevProps.typingContact),
|
hasTypingContact: Boolean(prevProps.typingContactId),
|
||||||
haveOldest: prevProps.haveOldest,
|
haveOldest: prevProps.haveOldest,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1578,13 +1578,13 @@ export class Timeline extends React.PureComponent<PropsType, StateType> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static *getEphemeralRows({
|
private static *getEphemeralRows({
|
||||||
items,
|
hasTypingContact,
|
||||||
typingContact,
|
|
||||||
oldestUnreadIndex,
|
|
||||||
haveOldest,
|
haveOldest,
|
||||||
|
items,
|
||||||
|
oldestUnreadIndex,
|
||||||
}: {
|
}: {
|
||||||
items: ReadonlyArray<string>;
|
items: ReadonlyArray<string>;
|
||||||
typingContact: boolean;
|
hasTypingContact: boolean;
|
||||||
oldestUnreadIndex?: number;
|
oldestUnreadIndex?: number;
|
||||||
haveOldest: boolean;
|
haveOldest: boolean;
|
||||||
}): Iterator<string> {
|
}): Iterator<string> {
|
||||||
|
@ -1597,7 +1597,7 @@ export class Timeline extends React.PureComponent<PropsType, StateType> {
|
||||||
yield `item:${items[i]}`;
|
yield `item:${items[i]}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typingContact) {
|
if (hasTypingContact) {
|
||||||
yield 'typing-contact';
|
yield 'typing-contact';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,8 @@ import enMessages from '../../../_locales/en/messages.json';
|
||||||
import type { Props } from './TypingBubble';
|
import type { Props } from './TypingBubble';
|
||||||
import { TypingBubble } from './TypingBubble';
|
import { TypingBubble } from './TypingBubble';
|
||||||
import { AvatarColors } from '../../types/Colors';
|
import { AvatarColors } from '../../types/Colors';
|
||||||
|
import { getFakeBadge } from '../../test-both/helpers/getFakeBadge';
|
||||||
|
import { ThemeType } from '../../types/Util';
|
||||||
|
|
||||||
const i18n = setupI18n('en', enMessages);
|
const i18n = setupI18n('en', enMessages);
|
||||||
|
|
||||||
|
@ -17,6 +19,7 @@ const story = storiesOf('Components/Conversation/TypingBubble', module);
|
||||||
|
|
||||||
const createProps = (overrideProps: Partial<Props> = {}): Props => ({
|
const createProps = (overrideProps: Partial<Props> = {}): Props => ({
|
||||||
acceptedMessageRequest: true,
|
acceptedMessageRequest: true,
|
||||||
|
badge: overrideProps.badge,
|
||||||
isMe: false,
|
isMe: false,
|
||||||
i18n,
|
i18n,
|
||||||
color: select(
|
color: select(
|
||||||
|
@ -33,6 +36,7 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
|
||||||
overrideProps.conversationType || 'direct'
|
overrideProps.conversationType || 'direct'
|
||||||
),
|
),
|
||||||
sharedGroupNames: [],
|
sharedGroupNames: [],
|
||||||
|
theme: ThemeType.light,
|
||||||
});
|
});
|
||||||
|
|
||||||
story.add('Direct', () => {
|
story.add('Direct', () => {
|
||||||
|
@ -46,3 +50,12 @@ story.add('Group', () => {
|
||||||
|
|
||||||
return <TypingBubble {...props} />;
|
return <TypingBubble {...props} />;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
story.add('Group (with badge)', () => {
|
||||||
|
const props = createProps({
|
||||||
|
badge: getFakeBadge(),
|
||||||
|
conversationType: 'group',
|
||||||
|
});
|
||||||
|
|
||||||
|
return <TypingBubble {...props} />;
|
||||||
|
});
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
// Copyright 2018-2021 Signal Messenger, LLC
|
// Copyright 2018-2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import type { ReactElement } from 'react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { TypingAnimation } from './TypingAnimation';
|
import { TypingAnimation } from './TypingAnimation';
|
||||||
import { Avatar } from '../Avatar';
|
import { Avatar } from '../Avatar';
|
||||||
|
|
||||||
import type { LocalizerType } from '../../types/Util';
|
import type { LocalizerType, ThemeType } from '../../types/Util';
|
||||||
import type { ConversationType } from '../../state/ducks/conversations';
|
import type { ConversationType } from '../../state/ducks/conversations';
|
||||||
|
import type { BadgeType } from '../../badges/types';
|
||||||
|
|
||||||
export type Props = Pick<
|
export type Props = Pick<
|
||||||
ConversationType,
|
ConversationType,
|
||||||
|
@ -22,76 +24,69 @@ export type Props = Pick<
|
||||||
| 'sharedGroupNames'
|
| 'sharedGroupNames'
|
||||||
| 'title'
|
| 'title'
|
||||||
> & {
|
> & {
|
||||||
|
badge: undefined | BadgeType;
|
||||||
conversationType: 'group' | 'direct';
|
conversationType: 'group' | 'direct';
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
|
theme: ThemeType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class TypingBubble extends React.PureComponent<Props> {
|
export function TypingBubble({
|
||||||
public renderAvatar(): JSX.Element | null {
|
acceptedMessageRequest,
|
||||||
const {
|
avatarPath,
|
||||||
acceptedMessageRequest,
|
badge,
|
||||||
avatarPath,
|
color,
|
||||||
color,
|
conversationType,
|
||||||
conversationType,
|
i18n,
|
||||||
i18n,
|
isMe,
|
||||||
isMe,
|
name,
|
||||||
name,
|
phoneNumber,
|
||||||
phoneNumber,
|
profileName,
|
||||||
profileName,
|
sharedGroupNames,
|
||||||
sharedGroupNames,
|
theme,
|
||||||
title,
|
title,
|
||||||
} = this.props;
|
}: Props): ReactElement {
|
||||||
|
const isGroup = conversationType === 'group';
|
||||||
|
|
||||||
if (conversationType !== 'group') {
|
return (
|
||||||
return null;
|
<div
|
||||||
}
|
className={classNames(
|
||||||
|
'module-message',
|
||||||
return (
|
'module-message--incoming',
|
||||||
<div className="module-message__author-avatar-container">
|
isGroup ? 'module-message--group' : null
|
||||||
<Avatar
|
)}
|
||||||
acceptedMessageRequest={acceptedMessageRequest}
|
>
|
||||||
avatarPath={avatarPath}
|
{isGroup && (
|
||||||
color={color}
|
<div className="module-message__author-avatar-container">
|
||||||
conversationType="direct"
|
<Avatar
|
||||||
i18n={i18n}
|
acceptedMessageRequest={acceptedMessageRequest}
|
||||||
isMe={isMe}
|
avatarPath={avatarPath}
|
||||||
name={name}
|
badge={badge}
|
||||||
phoneNumber={phoneNumber}
|
color={color}
|
||||||
profileName={profileName}
|
conversationType="direct"
|
||||||
title={title}
|
i18n={i18n}
|
||||||
sharedGroupNames={sharedGroupNames}
|
isMe={isMe}
|
||||||
size={28}
|
name={name}
|
||||||
/>
|
phoneNumber={phoneNumber}
|
||||||
</div>
|
profileName={profileName}
|
||||||
);
|
theme={theme}
|
||||||
}
|
title={title}
|
||||||
|
sharedGroupNames={sharedGroupNames}
|
||||||
public override render(): JSX.Element {
|
size={28}
|
||||||
const { i18n, conversationType } = this.props;
|
/>
|
||||||
const isGroup = conversationType === 'group';
|
</div>
|
||||||
|
)}
|
||||||
return (
|
<div className="module-message__container-outer">
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'module-message',
|
'module-message__container',
|
||||||
'module-message--incoming',
|
'module-message__container--incoming'
|
||||||
isGroup ? 'module-message--group' : null
|
)}
|
||||||
)}
|
>
|
||||||
>
|
<div className="module-message__typing-container">
|
||||||
{this.renderAvatar()}
|
<TypingAnimation color="light" i18n={i18n} />
|
||||||
<div className="module-message__container-outer">
|
|
||||||
<div
|
|
||||||
className={classNames(
|
|
||||||
'module-message__container',
|
|
||||||
'module-message__container--incoming'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="module-message__typing-container">
|
|
||||||
<TypingAnimation color="light" i18n={i18n} />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
</div>
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@ export type PropsData = Pick<
|
||||||
| 'shouldShowDraft'
|
| 'shouldShowDraft'
|
||||||
| 'title'
|
| 'title'
|
||||||
| 'type'
|
| 'type'
|
||||||
| 'typingContact'
|
| 'typingContactId'
|
||||||
| 'unblurredAvatarPath'
|
| 'unblurredAvatarPath'
|
||||||
| 'unreadCount'
|
| 'unreadCount'
|
||||||
> & {
|
> & {
|
||||||
|
@ -94,7 +94,7 @@ export const ConversationListItem: FunctionComponent<Props> = React.memo(
|
||||||
theme,
|
theme,
|
||||||
title,
|
title,
|
||||||
type,
|
type,
|
||||||
typingContact,
|
typingContactId,
|
||||||
unblurredAvatarPath,
|
unblurredAvatarPath,
|
||||||
unreadCount,
|
unreadCount,
|
||||||
}) {
|
}) {
|
||||||
|
@ -121,7 +121,7 @@ export const ConversationListItem: FunctionComponent<Props> = React.memo(
|
||||||
{i18n('ConversationListItem--message-request')}
|
{i18n('ConversationListItem--message-request')}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
} else if (typingContact) {
|
} else if (typingContactId) {
|
||||||
messageText = <TypingAnimation i18n={i18n} />;
|
messageText = <TypingAnimation i18n={i18n} />;
|
||||||
} else if (shouldShowDraft && draftPreview) {
|
} else if (shouldShowDraft && draftPreview) {
|
||||||
messageText = (
|
messageText = (
|
||||||
|
|
|
@ -1400,9 +1400,6 @@ export class ConversationModel extends window.Backbone
|
||||||
const typingMostRecent = window._.first(
|
const typingMostRecent = window._.first(
|
||||||
window._.sortBy(typingValues, 'timestamp')
|
window._.sortBy(typingValues, 'timestamp')
|
||||||
);
|
);
|
||||||
const typingContact = typingMostRecent
|
|
||||||
? window.ConversationController.get(typingMostRecent.senderId)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
const timestamp = this.get('timestamp')!;
|
const timestamp = this.get('timestamp')!;
|
||||||
|
@ -1440,7 +1437,7 @@ export class ConversationModel extends window.Backbone
|
||||||
|
|
||||||
// TODO: DESKTOP-720
|
// TODO: DESKTOP-720
|
||||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||||
const result: ConversationType = {
|
return {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
uuid: this.get('uuid'),
|
uuid: this.get('uuid'),
|
||||||
e164: this.get('e164'),
|
e164: this.get('e164'),
|
||||||
|
@ -1521,6 +1518,7 @@ export class ConversationModel extends window.Backbone
|
||||||
sortedGroupMembers,
|
sortedGroupMembers,
|
||||||
timestamp,
|
timestamp,
|
||||||
title: this.getTitle()!,
|
title: this.getTitle()!,
|
||||||
|
typingContactId: typingMostRecent?.senderId,
|
||||||
searchableTitle: isMe(this.attributes)
|
searchableTitle: isMe(this.attributes)
|
||||||
? window.i18n('noteToSelf')
|
? window.i18n('noteToSelf')
|
||||||
: this.getTitle(),
|
: this.getTitle(),
|
||||||
|
@ -1537,18 +1535,7 @@ export class ConversationModel extends window.Backbone
|
||||||
sharedGroupNames: [],
|
sharedGroupNames: [],
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (typingContact) {
|
|
||||||
// We don't want to call .format() on our own conversation
|
|
||||||
if (typingContact.id === this.id) {
|
|
||||||
result.typingContact = result;
|
|
||||||
} else {
|
|
||||||
result.typingContact = typingContact.format();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* eslint-enable @typescript-eslint/no-non-null-assertion */
|
/* eslint-enable @typescript-eslint/no-non-null-assertion */
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateE164(e164?: string | null): void {
|
updateE164(e164?: string | null): void {
|
||||||
|
|
|
@ -181,17 +181,7 @@ export type ConversationType = {
|
||||||
unreadCount?: number;
|
unreadCount?: number;
|
||||||
isSelected?: boolean;
|
isSelected?: boolean;
|
||||||
isFetchingUUID?: boolean;
|
isFetchingUUID?: boolean;
|
||||||
typingContact?: {
|
typingContactId?: string;
|
||||||
acceptedMessageRequest: boolean;
|
|
||||||
avatarPath?: string;
|
|
||||||
color?: AvatarColorType;
|
|
||||||
isMe: boolean;
|
|
||||||
name?: string;
|
|
||||||
phoneNumber?: string;
|
|
||||||
profileName?: string;
|
|
||||||
sharedGroupNames: Array<string>;
|
|
||||||
title: string;
|
|
||||||
} | null;
|
|
||||||
recentMediaItems?: Array<MediaItemType>;
|
recentMediaItems?: Array<MediaItemType>;
|
||||||
profileSharing?: boolean;
|
profileSharing?: boolean;
|
||||||
|
|
||||||
|
|
|
@ -298,7 +298,7 @@ const mapStateToProps = (state: StateType, props: ExternalProps) => {
|
||||||
...pick(conversation, [
|
...pick(conversation, [
|
||||||
'areWeAdmin',
|
'areWeAdmin',
|
||||||
'unreadCount',
|
'unreadCount',
|
||||||
'typingContact',
|
'typingContactId',
|
||||||
'isGroupV1AndDisabled',
|
'isGroupV1AndDisabled',
|
||||||
]),
|
]),
|
||||||
isIncomingMessageRequest: Boolean(
|
isIncomingMessageRequest: Boolean(
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2019-2020 Signal Messenger, LLC
|
// Copyright 2019-2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
@ -7,8 +7,9 @@ import { TypingBubble } from '../../components/conversation/TypingBubble';
|
||||||
import { strictAssert } from '../../util/assert';
|
import { strictAssert } from '../../util/assert';
|
||||||
import type { StateType } from '../reducer';
|
import type { StateType } from '../reducer';
|
||||||
|
|
||||||
import { getIntl } from '../selectors/user';
|
import { getIntl, getTheme } from '../selectors/user';
|
||||||
import { getConversationSelector } from '../selectors/conversations';
|
import { getConversationSelector } from '../selectors/conversations';
|
||||||
|
import { getPreferredBadgeSelector } from '../selectors/badges';
|
||||||
|
|
||||||
type ExternalProps = {
|
type ExternalProps = {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -17,17 +18,21 @@ type ExternalProps = {
|
||||||
const mapStateToProps = (state: StateType, props: ExternalProps) => {
|
const mapStateToProps = (state: StateType, props: ExternalProps) => {
|
||||||
const { id } = props;
|
const { id } = props;
|
||||||
|
|
||||||
const conversation = getConversationSelector(state)(id);
|
const conversationSelector = getConversationSelector(state);
|
||||||
|
const conversation = conversationSelector(id);
|
||||||
if (!conversation) {
|
if (!conversation) {
|
||||||
throw new Error(`Did not find conversation ${id} in state!`);
|
throw new Error(`Did not find conversation ${id} in state!`);
|
||||||
}
|
}
|
||||||
|
|
||||||
strictAssert(conversation.typingContact, 'Missing typingContact');
|
strictAssert(conversation.typingContactId, 'Missing typing contact ID');
|
||||||
|
const typingContact = conversationSelector(conversation.typingContactId);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...conversation.typingContact,
|
...typingContact,
|
||||||
|
badge: getPreferredBadgeSelector(state)(typingContact.badges),
|
||||||
conversationType: conversation.type,
|
conversationType: conversation.type,
|
||||||
i18n: getIntl(state),
|
i18n: getIntl(state),
|
||||||
|
theme: getTheme(state),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1254,11 +1254,7 @@ describe('both/state/selectors/conversations', () => {
|
||||||
title: 'No timestamp',
|
title: 'No timestamp',
|
||||||
unreadCount: 1,
|
unreadCount: 1,
|
||||||
isSelected: false,
|
isSelected: false,
|
||||||
typingContact: {
|
typingContactId: UUID.generate().toString(),
|
||||||
...getDefaultConversation(),
|
|
||||||
name: 'Someone There',
|
|
||||||
phoneNumber: '+18005551111',
|
|
||||||
},
|
|
||||||
|
|
||||||
acceptedMessageRequest: true,
|
acceptedMessageRequest: true,
|
||||||
}),
|
}),
|
||||||
|
@ -1279,11 +1275,7 @@ describe('both/state/selectors/conversations', () => {
|
||||||
title: 'B',
|
title: 'B',
|
||||||
unreadCount: 1,
|
unreadCount: 1,
|
||||||
isSelected: false,
|
isSelected: false,
|
||||||
typingContact: {
|
typingContactId: UUID.generate().toString(),
|
||||||
...getDefaultConversation(),
|
|
||||||
name: 'Someone There',
|
|
||||||
phoneNumber: '+18005551111',
|
|
||||||
},
|
|
||||||
|
|
||||||
acceptedMessageRequest: true,
|
acceptedMessageRequest: true,
|
||||||
}),
|
}),
|
||||||
|
@ -1304,11 +1296,7 @@ describe('both/state/selectors/conversations', () => {
|
||||||
title: 'C',
|
title: 'C',
|
||||||
unreadCount: 1,
|
unreadCount: 1,
|
||||||
isSelected: false,
|
isSelected: false,
|
||||||
typingContact: {
|
typingContactId: UUID.generate().toString(),
|
||||||
...getDefaultConversation(),
|
|
||||||
name: 'Someone There',
|
|
||||||
phoneNumber: '+18005551111',
|
|
||||||
},
|
|
||||||
|
|
||||||
acceptedMessageRequest: true,
|
acceptedMessageRequest: true,
|
||||||
}),
|
}),
|
||||||
|
@ -1329,11 +1317,7 @@ describe('both/state/selectors/conversations', () => {
|
||||||
title: 'A',
|
title: 'A',
|
||||||
unreadCount: 1,
|
unreadCount: 1,
|
||||||
isSelected: false,
|
isSelected: false,
|
||||||
typingContact: {
|
typingContactId: UUID.generate().toString(),
|
||||||
...getDefaultConversation(),
|
|
||||||
name: 'Someone There',
|
|
||||||
phoneNumber: '+18005551111',
|
|
||||||
},
|
|
||||||
|
|
||||||
acceptedMessageRequest: true,
|
acceptedMessageRequest: true,
|
||||||
}),
|
}),
|
||||||
|
@ -1354,11 +1338,7 @@ describe('both/state/selectors/conversations', () => {
|
||||||
title: 'First!',
|
title: 'First!',
|
||||||
unreadCount: 1,
|
unreadCount: 1,
|
||||||
isSelected: false,
|
isSelected: false,
|
||||||
typingContact: {
|
typingContactId: UUID.generate().toString(),
|
||||||
...getDefaultConversation(),
|
|
||||||
name: 'Someone There',
|
|
||||||
phoneNumber: '+18005551111',
|
|
||||||
},
|
|
||||||
|
|
||||||
acceptedMessageRequest: true,
|
acceptedMessageRequest: true,
|
||||||
}),
|
}),
|
||||||
|
@ -1400,11 +1380,7 @@ describe('both/state/selectors/conversations', () => {
|
||||||
title: 'Pin Two',
|
title: 'Pin Two',
|
||||||
unreadCount: 1,
|
unreadCount: 1,
|
||||||
isSelected: false,
|
isSelected: false,
|
||||||
typingContact: {
|
typingContactId: UUID.generate().toString(),
|
||||||
...getDefaultConversation(),
|
|
||||||
name: 'Someone There',
|
|
||||||
phoneNumber: '+18005551111',
|
|
||||||
},
|
|
||||||
|
|
||||||
acceptedMessageRequest: true,
|
acceptedMessageRequest: true,
|
||||||
}),
|
}),
|
||||||
|
@ -1426,11 +1402,7 @@ describe('both/state/selectors/conversations', () => {
|
||||||
title: 'Pin Three',
|
title: 'Pin Three',
|
||||||
unreadCount: 1,
|
unreadCount: 1,
|
||||||
isSelected: false,
|
isSelected: false,
|
||||||
typingContact: {
|
typingContactId: UUID.generate().toString(),
|
||||||
...getDefaultConversation(),
|
|
||||||
name: 'Someone There',
|
|
||||||
phoneNumber: '+18005551111',
|
|
||||||
},
|
|
||||||
|
|
||||||
acceptedMessageRequest: true,
|
acceptedMessageRequest: true,
|
||||||
}),
|
}),
|
||||||
|
@ -1452,11 +1424,7 @@ describe('both/state/selectors/conversations', () => {
|
||||||
title: 'Pin One',
|
title: 'Pin One',
|
||||||
unreadCount: 1,
|
unreadCount: 1,
|
||||||
isSelected: false,
|
isSelected: false,
|
||||||
typingContact: {
|
typingContactId: UUID.generate().toString(),
|
||||||
...getDefaultConversation(),
|
|
||||||
name: 'Someone There',
|
|
||||||
phoneNumber: '+18005551111',
|
|
||||||
},
|
|
||||||
|
|
||||||
acceptedMessageRequest: true,
|
acceptedMessageRequest: true,
|
||||||
}),
|
}),
|
||||||
|
@ -1495,11 +1463,7 @@ describe('both/state/selectors/conversations', () => {
|
||||||
title: 'Pin Two',
|
title: 'Pin Two',
|
||||||
unreadCount: 1,
|
unreadCount: 1,
|
||||||
isSelected: false,
|
isSelected: false,
|
||||||
typingContact: {
|
typingContactId: UUID.generate().toString(),
|
||||||
...getDefaultConversation(),
|
|
||||||
name: 'Someone There',
|
|
||||||
phoneNumber: '+18005551111',
|
|
||||||
},
|
|
||||||
|
|
||||||
acceptedMessageRequest: true,
|
acceptedMessageRequest: true,
|
||||||
}),
|
}),
|
||||||
|
@ -1520,11 +1484,7 @@ describe('both/state/selectors/conversations', () => {
|
||||||
title: 'Pin Three',
|
title: 'Pin Three',
|
||||||
unreadCount: 1,
|
unreadCount: 1,
|
||||||
isSelected: false,
|
isSelected: false,
|
||||||
typingContact: {
|
typingContactId: UUID.generate().toString(),
|
||||||
...getDefaultConversation(),
|
|
||||||
name: 'Someone There',
|
|
||||||
phoneNumber: '+18005551111',
|
|
||||||
},
|
|
||||||
|
|
||||||
acceptedMessageRequest: true,
|
acceptedMessageRequest: true,
|
||||||
}),
|
}),
|
||||||
|
@ -1545,11 +1505,7 @@ describe('both/state/selectors/conversations', () => {
|
||||||
title: 'Pin One',
|
title: 'Pin One',
|
||||||
unreadCount: 1,
|
unreadCount: 1,
|
||||||
isSelected: false,
|
isSelected: false,
|
||||||
typingContact: {
|
typingContactId: UUID.generate().toString(),
|
||||||
...getDefaultConversation(),
|
|
||||||
name: 'Someone There',
|
|
||||||
phoneNumber: '+18005551111',
|
|
||||||
},
|
|
||||||
|
|
||||||
acceptedMessageRequest: true,
|
acceptedMessageRequest: true,
|
||||||
}),
|
}),
|
||||||
|
@ -1571,11 +1527,7 @@ describe('both/state/selectors/conversations', () => {
|
||||||
title: 'Pin One',
|
title: 'Pin One',
|
||||||
unreadCount: 1,
|
unreadCount: 1,
|
||||||
isSelected: false,
|
isSelected: false,
|
||||||
typingContact: {
|
typingContactId: UUID.generate().toString(),
|
||||||
...getDefaultConversation(),
|
|
||||||
name: 'Someone There',
|
|
||||||
phoneNumber: '+18005551111',
|
|
||||||
},
|
|
||||||
|
|
||||||
acceptedMessageRequest: true,
|
acceptedMessageRequest: true,
|
||||||
}),
|
}),
|
||||||
|
@ -1596,11 +1548,7 @@ describe('both/state/selectors/conversations', () => {
|
||||||
title: 'Pin One',
|
title: 'Pin One',
|
||||||
unreadCount: 1,
|
unreadCount: 1,
|
||||||
isSelected: false,
|
isSelected: false,
|
||||||
typingContact: {
|
typingContactId: UUID.generate().toString(),
|
||||||
...getDefaultConversation(),
|
|
||||||
name: 'Someone There',
|
|
||||||
phoneNumber: '+18005551111',
|
|
||||||
},
|
|
||||||
|
|
||||||
acceptedMessageRequest: true,
|
acceptedMessageRequest: true,
|
||||||
}),
|
}),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue