Skip to content

Commit

Permalink
New 'unseenStatus' field for certain secondary message types
Browse files Browse the repository at this point in the history
  • Loading branch information
scottnonnenberg-signal committed Apr 22, 2022
1 parent ed9f54d commit 3a1df01
Show file tree
Hide file tree
Showing 23 changed files with 610 additions and 143 deletions.
25 changes: 25 additions & 0 deletions ts/MessageSeenStatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only

/**
* `SeenStatus` represents either the idea that a message doesn't need to track its seen
* status, or the standard unseen/seen status pair.
*
* Unseen is a lot like unread - except that unseen messages only affect the placement
* of the last seen indicator and the count it shows. Unread messages will affect the
* left pane badging for conversations, as well as the overall badge count on the app.
*/
export enum SeenStatus {
NotApplicable = 0,
Unseen = 1,
Seen = 2,
}

const STATUS_NUMBERS: Record<SeenStatus, number> = {
[SeenStatus.NotApplicable]: 0,
[SeenStatus.Unseen]: 1,
[SeenStatus.Seen]: 2,
};

export const maxSeenStatus = (a: SeenStatus, b: SeenStatus): SeenStatus =>
STATUS_NUMBERS[a] > STATUS_NUMBERS[b] ? a : b;
26 changes: 15 additions & 11 deletions ts/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ import { ReactionSource } from './reactions/ReactionSource';
import { singleProtoJobQueue } from './jobs/singleProtoJobQueue';
import { getInitialState } from './state/getInitialState';
import { conversationJobQueue } from './jobs/conversationJobQueue';
import { SeenStatus } from './MessageSeenStatus';

const MAX_ATTACHMENT_DOWNLOAD_AGE = 3600 * 72 * 1000;

Expand Down Expand Up @@ -3052,22 +3053,24 @@ export async function startApp(): Promise<void> {
}

return new window.Whisper.Message({
source: window.textsecure.storage.user.getNumber(),
sourceUuid: window.textsecure.storage.user.getUuid()?.toString(),
sourceDevice: data.device,
sent_at: timestamp,
serverTimestamp: data.serverTimestamp,
received_at: data.receivedAtCounter,
received_at_ms: data.receivedAtDate,
conversationId: descriptor.id,
timestamp,
type: 'outgoing',
sendStateByConversationId,
unidentifiedDeliveries,
expirationStartTimestamp: Math.min(
data.expirationStartTimestamp || timestamp,
now
),
readStatus: ReadStatus.Read,
received_at_ms: data.receivedAtDate,
received_at: data.receivedAtCounter,
seenStatus: SeenStatus.NotApplicable,
sendStateByConversationId,
sent_at: timestamp,
serverTimestamp: data.serverTimestamp,
source: window.textsecure.storage.user.getNumber(),
sourceDevice: data.device,
sourceUuid: window.textsecure.storage.user.getUuid()?.toString(),
timestamp,
type: 'outgoing',
unidentifiedDeliveries,
} as Partial<MessageAttributesType> as WhatIsThis);
}

Expand Down Expand Up @@ -3316,6 +3319,7 @@ export async function startApp(): Promise<void> {
unidentifiedDeliveryReceived: data.unidentifiedDeliveryReceived,
type: data.message.isStory ? 'story' : 'incoming',
readStatus: ReadStatus.Unread,
seenStatus: SeenStatus.Unseen,
timestamp: data.timestamp,
} as Partial<MessageAttributesType> as WhatIsThis);
}
Expand Down
10 changes: 5 additions & 5 deletions ts/components/conversation/Timeline.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -540,9 +540,9 @@ const useProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
items: overrideProps.items || Object.keys(items),
scrollToIndex: overrideProps.scrollToIndex,
scrollToIndexCounter: 0,
totalUnread: number('totalUnread', overrideProps.totalUnread || 0),
oldestUnreadIndex:
number('oldestUnreadIndex', overrideProps.oldestUnreadIndex || 0) ||
totalUnseen: number('totalUnseen', overrideProps.totalUnseen || 0),
oldestUnseenIndex:
number('oldestUnseenIndex', overrideProps.oldestUnseenIndex || 0) ||
undefined,
invitedContactsForNewlyCreatedGroup:
overrideProps.invitedContactsForNewlyCreatedGroup || [],
Expand Down Expand Up @@ -608,8 +608,8 @@ story.add('Empty (just hero)', () => {

story.add('Last Seen', () => {
const props = useProps({
oldestUnreadIndex: 13,
totalUnread: 2,
oldestUnseenIndex: 13,
totalUnseen: 2,
});

return <Timeline {...props} />;
Expand Down
24 changes: 12 additions & 12 deletions ts/components/conversation/Timeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,10 @@ export type PropsDataType = {
messageLoadingState?: TimelineMessageLoadingState;
isNearBottom?: boolean;
items: ReadonlyArray<string>;
oldestUnreadIndex?: number;
oldestUnseenIndex?: number;
scrollToIndex?: number;
scrollToIndexCounter: number;
totalUnread: number;
totalUnseen: number;
};

type PropsHousekeepingType = {
Expand Down Expand Up @@ -342,7 +342,7 @@ export class Timeline extends React.Component<
items,
loadNewestMessages,
messageLoadingState,
oldestUnreadIndex,
oldestUnseenIndex,
selectMessage,
} = this.props;
const { newestBottomVisibleMessageId } = this.state;
Expand All @@ -358,15 +358,15 @@ export class Timeline extends React.Component<

if (
newestBottomVisibleMessageId &&
isNumber(oldestUnreadIndex) &&
isNumber(oldestUnseenIndex) &&
items.findIndex(item => item === newestBottomVisibleMessageId) <
oldestUnreadIndex
oldestUnseenIndex
) {
if (setFocus) {
const messageId = items[oldestUnreadIndex];
const messageId = items[oldestUnseenIndex];
selectMessage(messageId, id);
} else {
this.scrollToItemIndex(oldestUnreadIndex);
this.scrollToItemIndex(oldestUnseenIndex);
}
} else if (haveNewest) {
this.scrollToBottom(setFocus);
Expand Down Expand Up @@ -790,7 +790,7 @@ export class Timeline extends React.Component<
isSomeoneTyping,
items,
messageLoadingState,
oldestUnreadIndex,
oldestUnseenIndex,
onBlock,
onBlockAndReportSpam,
onDelete,
Expand All @@ -804,7 +804,7 @@ export class Timeline extends React.Component<
reviewMessageRequestNameCollision,
showContactModal,
theme,
totalUnread,
totalUnseen,
unblurAvatar,
unreadCount,
updateSharedGroups,
Expand Down Expand Up @@ -898,17 +898,17 @@ export class Timeline extends React.Component<
}

let unreadIndicatorPlacement: undefined | UnreadIndicatorPlacement;
if (oldestUnreadIndex === itemIndex) {
if (oldestUnseenIndex === itemIndex) {
unreadIndicatorPlacement = UnreadIndicatorPlacement.JustAbove;
messageNodes.push(
<LastSeenIndicator
key="last seen indicator"
count={totalUnread}
count={totalUnseen}
i18n={i18n}
ref={this.lastSeenIndicatorRef}
/>
);
} else if (oldestUnreadIndex === nextItemIndex) {
} else if (oldestUnseenIndex === nextItemIndex) {
unreadIndicatorPlacement = UnreadIndicatorPlacement.JustBelow;
}

Expand Down
5 changes: 4 additions & 1 deletion ts/model-types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { ReactionSource } from './reactions/ReactionSource';

import AccessRequiredEnum = Proto.AccessControl.AccessRequired;
import MemberRoleEnum = Proto.Member.Role;
import { SeenStatus } from './MessageSeenStatus';

export type WhatIsThis = any;

Expand Down Expand Up @@ -219,8 +220,10 @@ export type MessageAttributesType = {

sendHQImages?: boolean;

// Should only be present for incoming messages
// Should only be present for incoming messages and errors
readStatus?: ReadStatus;
// Used for all kinds of notifications, as well as incoming messages
seenStatus?: SeenStatus;

// Should only be present for outgoing messages
sendStateByConversationId?: SendStateByConversationId;
Expand Down

0 comments on commit 3a1df01

Please sign in to comment.