mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2026-04-02 00:07:56 +01:00
Collapsing Items: A few improvements
This commit is contained in:
@@ -3384,9 +3384,21 @@
|
||||
"messageformat": "You updated the group.",
|
||||
"description": "Shown in the conversation history when you update a group"
|
||||
},
|
||||
"icu:collapsedContainer": {
|
||||
"messageformat": "{leadingIcon} {text} {trailingIcon}",
|
||||
"description": "This is how all the elements will be assembled: leading icon will be a timer or person, text is the text summary, and trailingIcon is the up/down chevron showing whether the area is expanded or collapsed"
|
||||
},
|
||||
"icu:collapsedGroupUpdates": {
|
||||
"messageformat": "{count, plural, one {# group update} other {# group updates}}",
|
||||
"description": "Label for a button giving access to a collection of group updates (count will always be 2+)"
|
||||
"description": "Label for a button giving access to a collection of group updates (count will always be 2+"
|
||||
},
|
||||
"icu:multidayCollapse__container": {
|
||||
"messageformat": "{containerDescription} · {dayCountSummary}",
|
||||
"description": "For collapsed multiday sets, this is how the overall summary will be combined with the day count summary."
|
||||
},
|
||||
"icu:multidayCollapse__dayCountSummary": {
|
||||
"messageformat": "{dayCount, plural, one {# day} other {# days}}",
|
||||
"description": "Will be appended to the end of the other collapse strings. dayCount will always be 2+"
|
||||
},
|
||||
"icu:collapsedChatUpdates": {
|
||||
"messageformat": "{count, plural, one {# chat update} other {# chat updates}}",
|
||||
|
||||
@@ -3,14 +3,15 @@
|
||||
|
||||
import * as React from 'react';
|
||||
import type { Meta } from '@storybook/react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import { DurationInSeconds } from '../../util/durations/duration-in-seconds.std.js';
|
||||
import { WidthBreakpoint } from '../_util.std.js';
|
||||
import { tw } from '../../axo/tw.dom.js';
|
||||
import { CollapseSetViewer } from './CollapseSet.dom.js';
|
||||
|
||||
import type { Props } from './CollapseSet.dom.js';
|
||||
import type { RenderItemProps } from '../../state/smart/TimelineItem.preload.js';
|
||||
import { DurationInSeconds } from '../../util/durations/duration-in-seconds.std.js';
|
||||
import { WidthBreakpoint } from '../_util.std.js';
|
||||
import { tw } from '../../axo/tw.dom.js';
|
||||
|
||||
const { i18n } = window.SignalContext;
|
||||
|
||||
@@ -27,17 +28,21 @@ function renderItem({ item }: RenderItemProps) {
|
||||
}
|
||||
|
||||
const defaultProps: Props = {
|
||||
// CollapseSet
|
||||
id: 'id1',
|
||||
type: 'none',
|
||||
messages: undefined,
|
||||
// The rest
|
||||
containerElementRef: React.createRef<HTMLElement | null>(),
|
||||
containerWidthBreakpoint: WidthBreakpoint.Wide,
|
||||
conversationId: 'c1',
|
||||
i18n,
|
||||
id: 'id1',
|
||||
isBlocked: false,
|
||||
isGroup: true,
|
||||
messages: undefined,
|
||||
isSelectMode: false,
|
||||
renderItem,
|
||||
targetedMessage: undefined,
|
||||
type: 'none',
|
||||
toggleDeleteMessagesModal: action('toggleDeleteMessagesModal'),
|
||||
};
|
||||
|
||||
export function GroupWithTwo(): React.JSX.Element {
|
||||
@@ -97,11 +102,11 @@ export function GroupWithTen(): React.JSX.Element {
|
||||
{ id: 'id1', isUnseen: false },
|
||||
{ id: 'id2', isUnseen: false },
|
||||
{ id: 'id3', isUnseen: false },
|
||||
{ id: 'id4', isUnseen: false },
|
||||
{ id: 'id4', isUnseen: false, atDateBoundary: true },
|
||||
{ id: 'id5', isUnseen: false },
|
||||
{ id: 'id6', isUnseen: false },
|
||||
{ id: 'id7', isUnseen: false },
|
||||
{ id: 'id8', isUnseen: false },
|
||||
{ id: 'id8', isUnseen: false, atDateBoundary: true },
|
||||
{ id: 'id9', isUnseen: false },
|
||||
{ id: 'id10', isUnseen: false },
|
||||
],
|
||||
@@ -138,7 +143,21 @@ export function TimerWithTwoZero(): React.JSX.Element {
|
||||
type: 'timer-changes',
|
||||
messages: [
|
||||
{ id: 'id1', isUnseen: false },
|
||||
{ id: 'id2', isUnseen: false },
|
||||
{ id: 'id2', isUnseen: false, atDateBoundary: true },
|
||||
],
|
||||
endingState: DurationInSeconds.fromSeconds(0),
|
||||
};
|
||||
return <CollapseSetViewer {...props} />;
|
||||
}
|
||||
|
||||
export function TimerWithTwoZeroInSelectMode(): React.JSX.Element {
|
||||
const props: Props = {
|
||||
...defaultProps,
|
||||
type: 'timer-changes',
|
||||
isSelectMode: true,
|
||||
messages: [
|
||||
{ id: 'id1', isUnseen: false },
|
||||
{ id: 'id2', isUnseen: false, atDateBoundary: true },
|
||||
],
|
||||
endingState: DurationInSeconds.fromSeconds(0),
|
||||
};
|
||||
@@ -166,11 +185,11 @@ export function TimerWithTenAt1Hr(): React.JSX.Element {
|
||||
{ id: 'id1', isUnseen: false },
|
||||
{ id: 'id2', isUnseen: false },
|
||||
{ id: 'id3', isUnseen: false },
|
||||
{ id: 'id4', isUnseen: false },
|
||||
{ id: 'id4', isUnseen: false, atDateBoundary: true },
|
||||
{ id: 'id5', isUnseen: false },
|
||||
{ id: 'id6', isUnseen: false },
|
||||
{ id: 'id7', isUnseen: false },
|
||||
{ id: 'id8', isUnseen: false },
|
||||
{ id: 'id8', isUnseen: false, atDateBoundary: true },
|
||||
{ id: 'id9', isUnseen: false },
|
||||
{ id: 'id10', isUnseen: false },
|
||||
],
|
||||
@@ -198,7 +217,7 @@ export function GroupWithFourTwoUnseen(): React.JSX.Element {
|
||||
messages: [
|
||||
{ id: 'id1', isUnseen: false },
|
||||
{ id: 'id2', isUnseen: false },
|
||||
{ id: 'id3', isUnseen: true },
|
||||
{ id: 'id3', isUnseen: true, atDateBoundary: true },
|
||||
{ id: 'id4', isUnseen: true },
|
||||
],
|
||||
};
|
||||
@@ -225,7 +244,7 @@ export function GroupWithWithUpdateAfterDelay(): React.JSX.Element {
|
||||
type: 'group-updates',
|
||||
messages: [
|
||||
{ id: 'id1', isUnseen: false },
|
||||
{ id: 'id2', isUnseen: false },
|
||||
{ id: 'id2', isUnseen: false, atDateBoundary: true },
|
||||
{ id: 'id3', isUnseen: true },
|
||||
{ id: 'id4', isUnseen: true },
|
||||
],
|
||||
@@ -237,7 +256,7 @@ export function GroupWithWithUpdateAfterDelay(): React.JSX.Element {
|
||||
type: 'group-updates',
|
||||
messages: [
|
||||
{ id: 'id1', isUnseen: false },
|
||||
{ id: 'id2', isUnseen: false },
|
||||
{ id: 'id2', isUnseen: false, atDateBoundary: true },
|
||||
{ id: 'id3', isUnseen: false },
|
||||
{ id: 'id4', isUnseen: false },
|
||||
{ id: 'id5', isUnseen: true },
|
||||
|
||||
@@ -13,6 +13,7 @@ import { missingCaseError } from '../../util/missingCaseError.std.js';
|
||||
import { AxoSymbol } from '../../axo/AxoSymbol.dom.js';
|
||||
import { tw } from '../../axo/tw.dom.js';
|
||||
import { AxoButton } from '../../axo/AxoButton.dom.js';
|
||||
import { MessageContextMenu } from './MessageContextMenu.dom.js';
|
||||
|
||||
import type { WidthBreakpoint } from '../_util.std.js';
|
||||
import type {
|
||||
@@ -22,6 +23,8 @@ import type {
|
||||
import type { RenderItemProps } from '../../state/smart/TimelineItem.preload.js';
|
||||
import type { LocalizerType } from '../../types/I18N.std.js';
|
||||
import type { TargetedMessageType } from '../../state/selectors/conversations.dom.js';
|
||||
import type { DeleteMessagesPropsType } from '../../state/ducks/globalModals.preload.js';
|
||||
import { I18n } from '../I18n.dom.js';
|
||||
|
||||
export type Props = CollapseSet & {
|
||||
containerElementRef: RefObject<HTMLElement | null>;
|
||||
@@ -30,8 +33,10 @@ export type Props = CollapseSet & {
|
||||
i18n: LocalizerType;
|
||||
isBlocked: boolean;
|
||||
isGroup: boolean;
|
||||
isSelectMode: boolean;
|
||||
renderItem: (props: RenderItemProps) => React.JSX.Element;
|
||||
targetedMessage: TargetedMessageType | undefined;
|
||||
toggleDeleteMessagesModal: (props: DeleteMessagesPropsType) => void;
|
||||
};
|
||||
|
||||
export function CollapseSetViewer(props: Props): React.JSX.Element {
|
||||
@@ -49,6 +54,7 @@ export function CollapseSetViewer(props: Props): React.JSX.Element {
|
||||
messages,
|
||||
renderItem,
|
||||
targetedMessage,
|
||||
toggleDeleteMessagesModal,
|
||||
} = props;
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
const [messageCache, setMessageCache] = useState<
|
||||
@@ -99,16 +105,23 @@ export function CollapseSetViewer(props: Props): React.JSX.Element {
|
||||
|
||||
let oldestOriginallyUnseenIndex;
|
||||
const max = messages?.length;
|
||||
let collapsedCount = 0;
|
||||
let collapsedDayCount = 1;
|
||||
|
||||
for (let i = 0; i < max; i += 1) {
|
||||
const message = messages[i];
|
||||
strictAssert(
|
||||
message,
|
||||
'CollapseSet finding oldestOriginallyUnseenIndex in messages'
|
||||
);
|
||||
|
||||
if (messageCache[message.id]?.isUnseen) {
|
||||
oldestOriginallyUnseenIndex = i;
|
||||
break;
|
||||
}
|
||||
|
||||
collapsedCount += 1 + (message.extraItems ?? 0);
|
||||
collapsedDayCount += message.atDateBoundary ? 1 : 0;
|
||||
}
|
||||
|
||||
// We only want to show the button if we have at least two items
|
||||
@@ -123,11 +136,6 @@ export function CollapseSetViewer(props: Props): React.JSX.Element {
|
||||
0,
|
||||
!shouldShowButton ? 0 : oldestOriginallyUnseenIndex
|
||||
);
|
||||
let collapsedCount = collapsedMessages.length;
|
||||
collapsedMessages.forEach(message => {
|
||||
collapsedCount += message.extraItems ?? 0;
|
||||
});
|
||||
|
||||
const passThroughMessages = messages.slice(
|
||||
!shouldShowButton ? 0 : oldestOriginallyUnseenIndex
|
||||
);
|
||||
@@ -141,10 +149,17 @@ export function CollapseSetViewer(props: Props): React.JSX.Element {
|
||||
<CollapseSetButton
|
||||
{...props}
|
||||
count={collapsedCount}
|
||||
dayCount={collapsedDayCount}
|
||||
isExpanded={isExpanded}
|
||||
onClick={() => {
|
||||
setIsExpanded(value => !value);
|
||||
}}
|
||||
onDelete={() => {
|
||||
toggleDeleteMessagesModal({
|
||||
conversationId,
|
||||
messageIds: collapsedMessages.map(item => item.id),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : undefined}
|
||||
@@ -188,6 +203,7 @@ export function CollapseSetViewer(props: Props): React.JSX.Element {
|
||||
const indexItem = {
|
||||
type: 'none' as const,
|
||||
id: child.id,
|
||||
dayCount: undefined,
|
||||
messages: undefined,
|
||||
};
|
||||
|
||||
@@ -226,6 +242,7 @@ export function CollapseSetViewer(props: Props): React.JSX.Element {
|
||||
const indexItem = {
|
||||
type: 'none' as const,
|
||||
id: child.id,
|
||||
dayCount: undefined,
|
||||
messages: undefined,
|
||||
};
|
||||
|
||||
@@ -255,22 +272,25 @@ export function CollapseSetViewer(props: Props): React.JSX.Element {
|
||||
function CollapseSetButton(
|
||||
props: CollapseSet & {
|
||||
count: number;
|
||||
dayCount: number;
|
||||
isExpanded: boolean;
|
||||
isGroup: boolean;
|
||||
isSelectMode: boolean;
|
||||
i18n: LocalizerType;
|
||||
onClick: () => unknown;
|
||||
onDelete: () => unknown;
|
||||
}
|
||||
): React.JSX.Element {
|
||||
const { count, i18n, isExpanded, onClick, type } = props;
|
||||
|
||||
let leadingIcon;
|
||||
let text;
|
||||
const { count, dayCount, i18n, isExpanded, onClick, onDelete, type } = props;
|
||||
|
||||
strictAssert(
|
||||
type !== 'none',
|
||||
"CollapseSetViewer should never render a 'none' set"
|
||||
"CollapseSetButton should never render a 'none' set"
|
||||
);
|
||||
|
||||
let leadingIcon;
|
||||
let text;
|
||||
|
||||
// Note: no need for labels for these icons, since they have full text descriptions
|
||||
if (type === 'group-updates') {
|
||||
if (props.isGroup) {
|
||||
@@ -301,6 +321,15 @@ function CollapseSetButton(
|
||||
throw missingCaseError(type);
|
||||
}
|
||||
|
||||
if (dayCount > 1) {
|
||||
text = i18n('icu:multidayCollapse__container', {
|
||||
containerDescription: text,
|
||||
dayCountSummary: i18n('icu:multidayCollapse__dayCountSummary', {
|
||||
dayCount,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
const trailingIcon = isExpanded ? (
|
||||
<AxoSymbol.InlineGlyph
|
||||
symbol="chevron-up"
|
||||
@@ -314,10 +343,40 @@ function CollapseSetButton(
|
||||
);
|
||||
|
||||
return (
|
||||
<AxoButton.Root size="lg" variant="secondary" onClick={onClick}>
|
||||
<div className={tw('font-semibold text-label-secondary')}>
|
||||
{leadingIcon} {text} {trailingIcon}
|
||||
</div>
|
||||
</AxoButton.Root>
|
||||
<MessageContextMenu
|
||||
renderer="AxoContextMenu"
|
||||
disabled={props.isSelectMode}
|
||||
i18n={i18n}
|
||||
onDeleteMessage={onDelete}
|
||||
shouldShowAdditional={false}
|
||||
onDebugMessage={null}
|
||||
onDownload={null}
|
||||
onEdit={null}
|
||||
onReplyToMessage={null}
|
||||
onReact={null}
|
||||
onEndPoll={null}
|
||||
onRetryMessageSend={null}
|
||||
onRetryDeleteForEveryone={null}
|
||||
onCopy={null}
|
||||
onSelect={null}
|
||||
onForward={null}
|
||||
onMoreInfo={null}
|
||||
onPinMessage={null}
|
||||
onUnpinMessage={null}
|
||||
>
|
||||
<AxoButton.Root size="md" variant="secondary" onClick={onClick}>
|
||||
<div className={tw('font-semibold text-label-secondary')}>
|
||||
<I18n
|
||||
id="icu:collapsedContainer"
|
||||
i18n={i18n}
|
||||
components={{
|
||||
leadingIcon,
|
||||
text,
|
||||
trailingIcon,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</AxoButton.Root>
|
||||
</MessageContextMenu>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -360,6 +360,7 @@ const renderItem = ({
|
||||
isTargeted={false}
|
||||
isBlocked={false}
|
||||
isGroup={false}
|
||||
isSelectMode={false}
|
||||
i18n={i18n}
|
||||
interactivity={MessageInteractivity.Normal}
|
||||
interactionMode="keyboard"
|
||||
|
||||
@@ -554,12 +554,18 @@ export class Timeline extends React.Component<
|
||||
messageIdToMarkRead &&
|
||||
(itemId || lastIndex === newestBottomVisibleItemIndex)
|
||||
) {
|
||||
markMessageRead(id, messageIdToMarkRead);
|
||||
return;
|
||||
const item = items[newestBottomVisibleItemIndex];
|
||||
if (!item || item.type === 'none') {
|
||||
markMessageRead(id, messageIdToMarkRead);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// We can return early if the newest partially-visible item is not a CollapseSet
|
||||
const newestPartiallyVisibleIndex = newestBottomVisibleItemIndex + 1;
|
||||
const newestPartiallyVisibleIndex = Math.min(
|
||||
lastIndex,
|
||||
newestBottomVisibleItemIndex + 1
|
||||
);
|
||||
const newestPartiallyVisibleItem = items[newestPartiallyVisibleIndex];
|
||||
if (
|
||||
newestPartiallyVisibleItem &&
|
||||
|
||||
@@ -43,6 +43,7 @@ const getDefaultProps = () => ({
|
||||
id: 'asdf',
|
||||
isNextItemCallingNotification: false,
|
||||
isPinned: false,
|
||||
isSelectMode: false,
|
||||
isTargeted: false,
|
||||
isBlocked: false,
|
||||
isGroup: false,
|
||||
|
||||
@@ -215,6 +215,7 @@ type PropsLocalType = {
|
||||
isBlocked: boolean;
|
||||
isGroup: boolean;
|
||||
isNextItemCallingNotification: boolean;
|
||||
isSelectMode: boolean;
|
||||
isTargeted: boolean;
|
||||
scrollToPinnedMessage: (pinMessage: PinMessageData) => void;
|
||||
scrollToPollMessage: (
|
||||
@@ -265,6 +266,7 @@ export const TimelineItem = memo(function TimelineItem({
|
||||
isBlocked,
|
||||
isGroup,
|
||||
isNextItemCallingNotification,
|
||||
isSelectMode,
|
||||
isTargeted,
|
||||
item,
|
||||
onOpenEditNicknameAndNoteModal,
|
||||
@@ -326,8 +328,10 @@ export const TimelineItem = memo(function TimelineItem({
|
||||
conversationId={conversationId}
|
||||
isBlocked={isBlocked}
|
||||
isGroup={isGroup}
|
||||
isSelectMode={isSelectMode}
|
||||
renderItem={renderItem}
|
||||
targetedMessage={targetedMessage}
|
||||
toggleDeleteMessagesModal={reducedProps.toggleDeleteMessagesModal}
|
||||
i18n={i18n}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -139,7 +139,7 @@ export const SmartTimeline = memo(function SmartTimeline({
|
||||
React.useMemo(() => {
|
||||
const result = mapItemsIntoCollapseSets({
|
||||
activeCall,
|
||||
allowMultidayDaySets: true,
|
||||
allowMultidaySets: false,
|
||||
callHistorySelector,
|
||||
callSelector,
|
||||
getCallIdFromEra,
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
getPlatform,
|
||||
} from '../selectors/user.std.js';
|
||||
import {
|
||||
getSelectedMessageIds,
|
||||
getTargetedMessage,
|
||||
getTargetedMessageSource,
|
||||
} from '../selectors/conversations.dom.js';
|
||||
@@ -95,6 +96,7 @@ export const SmartTimelineItem = memo(function SmartTimelineItem(
|
||||
const interactionMode = useSelector(getInteractionMode);
|
||||
const theme = useSelector(getTheme);
|
||||
const platform = useSelector(getPlatform);
|
||||
const selectedMessageIds = useSelector(getSelectedMessageIds);
|
||||
|
||||
const itemFromSelector = useTimelineItem(messageId, conversationId);
|
||||
const previousItem = useTimelineItem(previousMessageId, conversationId);
|
||||
@@ -244,6 +246,7 @@ export const SmartTimelineItem = memo(function SmartTimelineItem(
|
||||
getSharedGroupNames={getSharedGroupNames}
|
||||
isNextItemCallingNotification={isNextItemCallingNotification}
|
||||
isTargeted={isTargeted}
|
||||
isSelectMode={selectedMessageIds != null}
|
||||
renderAudioAttachment={renderAudioAttachment}
|
||||
renderContact={renderContact}
|
||||
renderReactionPicker={renderReactionPicker}
|
||||
|
||||
@@ -32,7 +32,7 @@ describe('util/CollapseSets', () => {
|
||||
const yesterday = now - DAY;
|
||||
const defaultParams = {
|
||||
activeCall: undefined,
|
||||
allowMultidayDaySets: true,
|
||||
allowMultidaySets: true,
|
||||
callHistorySelector: () => undefined,
|
||||
callSelector: () => undefined,
|
||||
getCallIdFromEra: (eraId: string) => eraId,
|
||||
@@ -140,11 +140,13 @@ describe('util/CollapseSets', () => {
|
||||
id: 'id1',
|
||||
isUnseen: false,
|
||||
extraItems: 1,
|
||||
atDateBoundary: false,
|
||||
},
|
||||
{
|
||||
id: 'id2',
|
||||
isUnseen: true,
|
||||
extraItems: undefined,
|
||||
atDateBoundary: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -208,10 +210,12 @@ describe('util/CollapseSets', () => {
|
||||
{
|
||||
id: 'id1',
|
||||
isUnseen: true,
|
||||
atDateBoundary: false,
|
||||
},
|
||||
{
|
||||
id: 'id2',
|
||||
isUnseen: true,
|
||||
atDateBoundary: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -302,10 +306,12 @@ describe('util/CollapseSets', () => {
|
||||
{
|
||||
id: 'id1',
|
||||
isUnseen: false,
|
||||
atDateBoundary: false,
|
||||
},
|
||||
{
|
||||
id: 'id2',
|
||||
isUnseen: true,
|
||||
atDateBoundary: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -440,6 +446,7 @@ describe('util/CollapseSets', () => {
|
||||
{
|
||||
id: 'id1',
|
||||
isUnseen: false,
|
||||
atDateBoundary: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -471,6 +478,7 @@ describe('util/CollapseSets', () => {
|
||||
{
|
||||
id: 'id5',
|
||||
isUnseen: false,
|
||||
atDateBoundary: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -567,6 +575,7 @@ describe('util/CollapseSets', () => {
|
||||
id: 'id1',
|
||||
isUnseen: false,
|
||||
extraItems: 1,
|
||||
atDateBoundary: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -583,6 +592,7 @@ describe('util/CollapseSets', () => {
|
||||
id: 'id3',
|
||||
isUnseen: false,
|
||||
extraItems: undefined,
|
||||
atDateBoundary: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -653,6 +663,7 @@ describe('util/CollapseSets', () => {
|
||||
{
|
||||
id: 'id1',
|
||||
isUnseen: false,
|
||||
atDateBoundary: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -664,10 +675,12 @@ describe('util/CollapseSets', () => {
|
||||
{
|
||||
id: 'id2',
|
||||
isUnseen: false,
|
||||
atDateBoundary: false,
|
||||
},
|
||||
{
|
||||
id: 'id3',
|
||||
isUnseen: false,
|
||||
atDateBoundary: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -794,6 +807,7 @@ describe('util/CollapseSets', () => {
|
||||
id: 'id2',
|
||||
isUnseen: false,
|
||||
extraItems: undefined,
|
||||
atDateBoundary: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -810,21 +824,25 @@ describe('util/CollapseSets', () => {
|
||||
id: 'id4',
|
||||
isUnseen: false,
|
||||
extraItems: undefined,
|
||||
atDateBoundary: false,
|
||||
},
|
||||
{
|
||||
id: 'id5',
|
||||
isUnseen: false,
|
||||
extraItems: undefined,
|
||||
atDateBoundary: false,
|
||||
},
|
||||
{
|
||||
id: 'id6',
|
||||
isUnseen: false,
|
||||
extraItems: undefined,
|
||||
atDateBoundary: true,
|
||||
},
|
||||
{
|
||||
id: 'id7',
|
||||
isUnseen: false,
|
||||
extraItems: undefined,
|
||||
atDateBoundary: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -836,11 +854,13 @@ describe('util/CollapseSets', () => {
|
||||
id: 'id8',
|
||||
isUnseen: false,
|
||||
extraItems: undefined,
|
||||
atDateBoundary: false,
|
||||
},
|
||||
{
|
||||
id: 'id9',
|
||||
isUnseen: false,
|
||||
extraItems: undefined,
|
||||
atDateBoundary: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -947,6 +967,7 @@ describe('util/CollapseSets', () => {
|
||||
id: 'id5',
|
||||
isUnseen: false,
|
||||
extraItems: undefined,
|
||||
atDateBoundary: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1071,6 +1092,7 @@ describe('util/CollapseSets', () => {
|
||||
id: 'id4',
|
||||
isUnseen: false,
|
||||
extraItems: 1,
|
||||
atDateBoundary: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1166,6 +1188,7 @@ describe('util/CollapseSets', () => {
|
||||
id: 'id1',
|
||||
isUnseen: false,
|
||||
extraItems: undefined,
|
||||
atDateBoundary: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1182,6 +1205,7 @@ describe('util/CollapseSets', () => {
|
||||
id: 'id3',
|
||||
isUnseen: false,
|
||||
extraItems: undefined,
|
||||
atDateBoundary: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1307,6 +1331,7 @@ describe('util/CollapseSets', () => {
|
||||
id: 'id2',
|
||||
isUnseen: false,
|
||||
extraItems: undefined,
|
||||
atDateBoundary: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1323,11 +1348,13 @@ describe('util/CollapseSets', () => {
|
||||
id: 'id4',
|
||||
isUnseen: false,
|
||||
extraItems: undefined,
|
||||
atDateBoundary: false,
|
||||
},
|
||||
{
|
||||
id: 'id5',
|
||||
isUnseen: false,
|
||||
extraItems: undefined,
|
||||
atDateBoundary: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1344,6 +1371,7 @@ describe('util/CollapseSets', () => {
|
||||
id: 'id7',
|
||||
isUnseen: false,
|
||||
extraItems: undefined,
|
||||
atDateBoundary: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1360,6 +1388,7 @@ describe('util/CollapseSets', () => {
|
||||
id: 'id9',
|
||||
isUnseen: false,
|
||||
extraItems: undefined,
|
||||
atDateBoundary: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1372,7 +1401,7 @@ describe('util/CollapseSets', () => {
|
||||
const { resultSets, resultScrollToIndex, resultUnseenIndex } =
|
||||
mapItemsIntoCollapseSets({
|
||||
...defaultParams,
|
||||
allowMultidayDaySets: false,
|
||||
allowMultidaySets: false,
|
||||
items,
|
||||
messages,
|
||||
});
|
||||
|
||||
@@ -25,6 +25,7 @@ export type CollapsedMessage = {
|
||||
isUnseen: boolean;
|
||||
// A single group-v2-change message can have more than one change in it
|
||||
extraItems?: number;
|
||||
atDateBoundary?: boolean;
|
||||
};
|
||||
|
||||
export type CollapseSet =
|
||||
@@ -41,8 +42,8 @@ export type CollapseSet =
|
||||
| {
|
||||
type: 'timer-changes';
|
||||
id: string;
|
||||
messages: Array<CollapsedMessage>;
|
||||
endingState: DurationInSeconds | undefined;
|
||||
messages: Array<CollapsedMessage>;
|
||||
}
|
||||
| {
|
||||
type: 'call-events';
|
||||
@@ -119,13 +120,7 @@ export function canCollapseForCallSet(
|
||||
conversationCall?.peekInfo?.eraId != null &&
|
||||
options.getCallIdFromEra(conversationCall.peekInfo.eraId);
|
||||
|
||||
const deviceCount = conversationCall?.peekInfo?.deviceCount ?? 0;
|
||||
|
||||
// Don't group if current call in the converasation, or there are devices in the call
|
||||
if (
|
||||
callHistory.mode === CallMode.Group &&
|
||||
(callId === conversationCallId || deviceCount > 0)
|
||||
) {
|
||||
if (callHistory.mode === CallMode.Group && callId === conversationCallId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -134,7 +129,7 @@ export function canCollapseForCallSet(
|
||||
|
||||
export function mapItemsIntoCollapseSets({
|
||||
activeCall,
|
||||
allowMultidayDaySets,
|
||||
allowMultidaySets,
|
||||
callHistorySelector,
|
||||
callSelector,
|
||||
getCallIdFromEra,
|
||||
@@ -145,7 +140,7 @@ export function mapItemsIntoCollapseSets({
|
||||
scrollToIndex,
|
||||
}: {
|
||||
activeCall: CallStateType | undefined;
|
||||
allowMultidayDaySets: boolean;
|
||||
allowMultidaySets: boolean;
|
||||
callHistorySelector: CallHistorySelectorType;
|
||||
callSelector: CallSelectorType;
|
||||
getCallIdFromEra: (eraId: string) => string;
|
||||
@@ -244,13 +239,13 @@ export function mapItemsIntoCollapseSets({
|
||||
}
|
||||
const canContinueSet =
|
||||
!atDateBoundary ||
|
||||
(allowMultidayDaySets && !isToday && havePreviousCompleteDay);
|
||||
(allowMultidaySets && !isToday && havePreviousCompleteDay);
|
||||
|
||||
// Start a new set if we just crossed the last seen indicator
|
||||
if (i === oldestUnseenIndex) {
|
||||
haveCompleteDay &&= atDateBoundary;
|
||||
|
||||
if (allowMultidayDaySets) {
|
||||
if (allowMultidaySets) {
|
||||
// If we've just terminated a multiday set, we need to split it; everything from
|
||||
// the current day needs to be in its own set.
|
||||
const didSplit = maybeSplitLastCollapseSet({
|
||||
@@ -299,6 +294,7 @@ export function mapItemsIntoCollapseSets({
|
||||
id: currentId,
|
||||
isUnseen: currentMessage.seenStatus === SeenStatus.Unseen,
|
||||
extraItems,
|
||||
atDateBoundary,
|
||||
});
|
||||
} else if (lastCollapseSet.type === 'none') {
|
||||
resultSets.pop();
|
||||
@@ -315,6 +311,7 @@ export function mapItemsIntoCollapseSets({
|
||||
id: currentId,
|
||||
isUnseen: currentMessage.seenStatus === SeenStatus.Unseen,
|
||||
extraItems,
|
||||
atDateBoundary,
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -348,6 +345,7 @@ export function mapItemsIntoCollapseSets({
|
||||
lastCollapseSet.messages.push({
|
||||
id: currentId,
|
||||
isUnseen: currentMessage.seenStatus === SeenStatus.Unseen,
|
||||
atDateBoundary,
|
||||
});
|
||||
lastCollapseSet.endingState =
|
||||
currentMessage.expirationTimerUpdate?.expireTimer;
|
||||
@@ -365,6 +363,7 @@ export function mapItemsIntoCollapseSets({
|
||||
{
|
||||
id: currentId,
|
||||
isUnseen: currentMessage.seenStatus === SeenStatus.Unseen,
|
||||
atDateBoundary,
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -408,6 +407,7 @@ export function mapItemsIntoCollapseSets({
|
||||
lastCollapseSet.messages.push({
|
||||
id: currentId,
|
||||
isUnseen: currentMessage.seenStatus === SeenStatus.Unseen,
|
||||
atDateBoundary,
|
||||
});
|
||||
} else if (lastCollapseSet.type === 'none') {
|
||||
resultSets.pop();
|
||||
@@ -422,6 +422,7 @@ export function mapItemsIntoCollapseSets({
|
||||
{
|
||||
id: currentId,
|
||||
isUnseen: currentMessage.seenStatus === SeenStatus.Unseen,
|
||||
atDateBoundary,
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -441,7 +442,7 @@ export function mapItemsIntoCollapseSets({
|
||||
|
||||
haveCompleteDay &&= atDateBoundary;
|
||||
|
||||
if (allowMultidayDaySets) {
|
||||
if (allowMultidaySets) {
|
||||
// If we've just terminated a multiday set, we need to split it; everything from
|
||||
// the current day needs to be in its own set.
|
||||
const didSplit = maybeSplitLastCollapseSet({
|
||||
@@ -561,6 +562,7 @@ export function maybeSplitLastCollapseSet({
|
||||
currentDayMessages.length > 1 ||
|
||||
(firstCurrentMessage.extraItems ?? 0) > 0
|
||||
) {
|
||||
firstCurrentMessage.atDateBoundary = false;
|
||||
const currentDaySet: CollapseSet = {
|
||||
...lastCollapseSet,
|
||||
id: firstCurrentMessage.id,
|
||||
|
||||
Reference in New Issue
Block a user