Timeline date headers
This commit is contained in:
parent
0fa069f260
commit
f9440bf594
41 changed files with 1183 additions and 771 deletions
|
@ -1,13 +1,15 @@
|
|||
// Copyright 2019-2021 Signal Messenger, LLC
|
||||
// Copyright 2019-2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { RefObject } from 'react';
|
||||
import type { ReactChild, RefObject } from 'react';
|
||||
import React from 'react';
|
||||
import { omit } from 'lodash';
|
||||
import moment from 'moment';
|
||||
|
||||
import type { LocalizerType, ThemeType } from '../../types/Util';
|
||||
|
||||
import type { InteractionModeType } from '../../state/ducks/conversations';
|
||||
import { TimelineDateHeader } from './TimelineDateHeader';
|
||||
import type {
|
||||
Props as AllMessageProps,
|
||||
PropsActions as MessageActionsType,
|
||||
|
@ -120,7 +122,7 @@ type ProfileChangeNotificationType = {
|
|||
data: ProfileChangeNotificationPropsType;
|
||||
};
|
||||
|
||||
export type TimelineItemType =
|
||||
export type TimelineItemType = (
|
||||
| CallHistoryType
|
||||
| ChatSessionRefreshedType
|
||||
| DeliveryIssueType
|
||||
|
@ -136,7 +138,8 @@ export type TimelineItemType =
|
|||
| UniversalTimerNotificationType
|
||||
| ChangeNumberNotificationType
|
||||
| UnsupportedMessageType
|
||||
| VerificationNotificationType;
|
||||
| VerificationNotificationType
|
||||
) & { timestamp: number };
|
||||
|
||||
type PropsLocalType = {
|
||||
containerElementRef: RefObject<HTMLElement>;
|
||||
|
@ -149,6 +152,7 @@ type PropsLocalType = {
|
|||
renderUniversalTimerNotification: () => JSX.Element;
|
||||
i18n: LocalizerType;
|
||||
interactionMode: InteractionModeType;
|
||||
isOldestTimelineItem: boolean;
|
||||
theme: ThemeType;
|
||||
previousItem: undefined | TimelineItemType;
|
||||
nextItem: undefined | TimelineItemType;
|
||||
|
@ -179,12 +183,14 @@ export class TimelineItem extends React.PureComponent<PropsType> {
|
|||
conversationId,
|
||||
getPreferredBadge,
|
||||
id,
|
||||
isOldestTimelineItem,
|
||||
isSelected,
|
||||
item,
|
||||
i18n,
|
||||
theme,
|
||||
messageSizeChanged,
|
||||
nextItem,
|
||||
previousItem,
|
||||
renderContact,
|
||||
renderUniversalTimerNotification,
|
||||
returnToActiveCall,
|
||||
|
@ -198,8 +204,9 @@ export class TimelineItem extends React.PureComponent<PropsType> {
|
|||
return null;
|
||||
}
|
||||
|
||||
let itemContents: ReactChild;
|
||||
if (item.type === 'message') {
|
||||
return (
|
||||
itemContents = (
|
||||
<Message
|
||||
{...omit(this.props, ['item'])}
|
||||
{...item.data}
|
||||
|
@ -210,101 +217,143 @@ export class TimelineItem extends React.PureComponent<PropsType> {
|
|||
renderingContext="conversation/TimelineItem"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
let notification;
|
||||
|
||||
if (item.type === 'unsupportedMessage') {
|
||||
notification = (
|
||||
<UnsupportedMessage {...this.props} {...item.data} i18n={i18n} />
|
||||
);
|
||||
} else if (item.type === 'callHistory') {
|
||||
notification = (
|
||||
<CallingNotification
|
||||
conversationId={conversationId}
|
||||
i18n={i18n}
|
||||
messageId={id}
|
||||
messageSizeChanged={messageSizeChanged}
|
||||
nextItem={nextItem}
|
||||
returnToActiveCall={returnToActiveCall}
|
||||
startCallingLobby={startCallingLobby}
|
||||
{...item.data}
|
||||
/>
|
||||
);
|
||||
} else if (item.type === 'chatSessionRefreshed') {
|
||||
notification = (
|
||||
<ChatSessionRefreshedNotification
|
||||
{...this.props}
|
||||
{...item.data}
|
||||
i18n={i18n}
|
||||
/>
|
||||
);
|
||||
} else if (item.type === 'deliveryIssue') {
|
||||
notification = (
|
||||
<DeliveryIssueNotification {...item.data} {...this.props} i18n={i18n} />
|
||||
);
|
||||
} else if (item.type === 'linkNotification') {
|
||||
notification = <LinkNotification i18n={i18n} />;
|
||||
} else if (item.type === 'timerNotification') {
|
||||
notification = (
|
||||
<TimerNotification {...this.props} {...item.data} i18n={i18n} />
|
||||
);
|
||||
} else if (item.type === 'universalTimerNotification') {
|
||||
notification = renderUniversalTimerNotification();
|
||||
} else if (item.type === 'changeNumberNotification') {
|
||||
notification = (
|
||||
<ChangeNumberNotification {...this.props} {...item.data} i18n={i18n} />
|
||||
);
|
||||
} else if (item.type === 'safetyNumberNotification') {
|
||||
notification = (
|
||||
<SafetyNumberNotification {...this.props} {...item.data} i18n={i18n} />
|
||||
);
|
||||
} else if (item.type === 'verificationNotification') {
|
||||
notification = (
|
||||
<VerificationNotification {...this.props} {...item.data} i18n={i18n} />
|
||||
);
|
||||
} else if (item.type === 'groupNotification') {
|
||||
notification = (
|
||||
<GroupNotification {...this.props} {...item.data} i18n={i18n} />
|
||||
);
|
||||
} else if (item.type === 'groupV2Change') {
|
||||
notification = (
|
||||
<GroupV2Change
|
||||
renderContact={renderContact}
|
||||
{...item.data}
|
||||
i18n={i18n}
|
||||
/>
|
||||
);
|
||||
} else if (item.type === 'groupV1Migration') {
|
||||
notification = (
|
||||
<GroupV1Migration {...this.props} {...item.data} i18n={i18n} />
|
||||
);
|
||||
} else if (item.type === 'resetSessionNotification') {
|
||||
notification = (
|
||||
<ResetSessionNotification {...this.props} {...item.data} i18n={i18n} />
|
||||
);
|
||||
} else if (item.type === 'profileChange') {
|
||||
notification = (
|
||||
<ProfileChangeNotification {...this.props} {...item.data} i18n={i18n} />
|
||||
);
|
||||
} else {
|
||||
// Weird, yes, but the idea is to get a compile error when we aren't comprehensive
|
||||
// with our if/else checks above, but also log out the type we don't understand if
|
||||
// we encounter it at runtime.
|
||||
const unknownItem: never = item;
|
||||
const asItem = unknownItem as TimelineItemType;
|
||||
throw new Error(`TimelineItem: Unknown type: ${asItem.type}`);
|
||||
let notification;
|
||||
|
||||
if (item.type === 'unsupportedMessage') {
|
||||
notification = (
|
||||
<UnsupportedMessage {...this.props} {...item.data} i18n={i18n} />
|
||||
);
|
||||
} else if (item.type === 'callHistory') {
|
||||
notification = (
|
||||
<CallingNotification
|
||||
conversationId={conversationId}
|
||||
i18n={i18n}
|
||||
messageId={id}
|
||||
messageSizeChanged={messageSizeChanged}
|
||||
nextItem={nextItem}
|
||||
returnToActiveCall={returnToActiveCall}
|
||||
startCallingLobby={startCallingLobby}
|
||||
{...item.data}
|
||||
/>
|
||||
);
|
||||
} else if (item.type === 'chatSessionRefreshed') {
|
||||
notification = (
|
||||
<ChatSessionRefreshedNotification
|
||||
{...this.props}
|
||||
{...item.data}
|
||||
i18n={i18n}
|
||||
/>
|
||||
);
|
||||
} else if (item.type === 'deliveryIssue') {
|
||||
notification = (
|
||||
<DeliveryIssueNotification
|
||||
{...item.data}
|
||||
{...this.props}
|
||||
i18n={i18n}
|
||||
/>
|
||||
);
|
||||
} else if (item.type === 'linkNotification') {
|
||||
notification = <LinkNotification i18n={i18n} />;
|
||||
} else if (item.type === 'timerNotification') {
|
||||
notification = (
|
||||
<TimerNotification {...this.props} {...item.data} i18n={i18n} />
|
||||
);
|
||||
} else if (item.type === 'universalTimerNotification') {
|
||||
notification = renderUniversalTimerNotification();
|
||||
} else if (item.type === 'changeNumberNotification') {
|
||||
notification = (
|
||||
<ChangeNumberNotification
|
||||
{...this.props}
|
||||
{...item.data}
|
||||
i18n={i18n}
|
||||
/>
|
||||
);
|
||||
} else if (item.type === 'safetyNumberNotification') {
|
||||
notification = (
|
||||
<SafetyNumberNotification
|
||||
{...this.props}
|
||||
{...item.data}
|
||||
i18n={i18n}
|
||||
/>
|
||||
);
|
||||
} else if (item.type === 'verificationNotification') {
|
||||
notification = (
|
||||
<VerificationNotification
|
||||
{...this.props}
|
||||
{...item.data}
|
||||
i18n={i18n}
|
||||
/>
|
||||
);
|
||||
} else if (item.type === 'groupNotification') {
|
||||
notification = (
|
||||
<GroupNotification {...this.props} {...item.data} i18n={i18n} />
|
||||
);
|
||||
} else if (item.type === 'groupV2Change') {
|
||||
notification = (
|
||||
<GroupV2Change
|
||||
renderContact={renderContact}
|
||||
{...item.data}
|
||||
i18n={i18n}
|
||||
/>
|
||||
);
|
||||
} else if (item.type === 'groupV1Migration') {
|
||||
notification = (
|
||||
<GroupV1Migration {...this.props} {...item.data} i18n={i18n} />
|
||||
);
|
||||
} else if (item.type === 'resetSessionNotification') {
|
||||
notification = (
|
||||
<ResetSessionNotification
|
||||
{...this.props}
|
||||
{...item.data}
|
||||
i18n={i18n}
|
||||
/>
|
||||
);
|
||||
} else if (item.type === 'profileChange') {
|
||||
notification = (
|
||||
<ProfileChangeNotification
|
||||
{...this.props}
|
||||
{...item.data}
|
||||
i18n={i18n}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
// Weird, yes, but the idea is to get a compile error when we aren't comprehensive
|
||||
// with our if/else checks above, but also log out the type we don't understand
|
||||
// if we encounter it at runtime.
|
||||
const unknownItem: never = item;
|
||||
const asItem = unknownItem as TimelineItemType;
|
||||
throw new Error(`TimelineItem: Unknown type: ${asItem.type}`);
|
||||
}
|
||||
|
||||
itemContents = (
|
||||
<InlineNotificationWrapper
|
||||
id={id}
|
||||
conversationId={conversationId}
|
||||
isSelected={isSelected}
|
||||
selectMessage={selectMessage}
|
||||
>
|
||||
{notification}
|
||||
</InlineNotificationWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<InlineNotificationWrapper
|
||||
id={id}
|
||||
conversationId={conversationId}
|
||||
isSelected={isSelected}
|
||||
selectMessage={selectMessage}
|
||||
>
|
||||
{notification}
|
||||
</InlineNotificationWrapper>
|
||||
);
|
||||
const shouldRenderDateHeader =
|
||||
isOldestTimelineItem ||
|
||||
Boolean(
|
||||
previousItem &&
|
||||
// This comparison avoids strange header behavior for out-of-order messages.
|
||||
item.timestamp > previousItem.timestamp &&
|
||||
!moment(previousItem.timestamp).isSame(item.timestamp, 'day')
|
||||
);
|
||||
if (shouldRenderDateHeader) {
|
||||
return (
|
||||
<>
|
||||
<TimelineDateHeader i18n={i18n} timestamp={item.timestamp} />
|
||||
{itemContents}
|
||||
</>
|
||||
);
|
||||
}
|
||||
return itemContents;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue