From c40807257650a6ce4c86d22e8f9be0b59d4ca59b Mon Sep 17 00:00:00 2001 From: Chris Svenningsen Date: Wed, 28 Oct 2020 15:54:32 -0700 Subject: [PATCH] Mark conversation as unread Co-authored-by: Sidney Keese --- _locales/en/messages.json | 14 ++++- js/views/inbox_view.js | 2 + protos/SignalStorage.proto | 21 ++++--- .../ConversationListItem.stories.tsx | 29 +++++++--- ts/components/ConversationListItem.tsx | 29 +++++----- ts/components/LeftPane.stories.tsx | 7 +++ ts/components/SearchResults.stories.tsx | 4 ++ .../ConversationHeader.stories.tsx | 1 + .../conversation/ConversationHeader.tsx | 55 +++++++++++-------- .../ProfileChangeNotification.stories.tsx | 2 + ts/model-types.d.ts | 1 + ts/models/conversations.ts | 9 ++- ts/services/storageRecordOps.ts | 11 ++++ ts/state/ducks/conversations.ts | 1 + ts/test/components/LeftPane_test.tsx | 9 +++ ts/test/state/selectors/conversations_test.ts | 8 +++ ts/textsecure.d.ts | 4 ++ ts/util/lint/exceptions.json | 8 +-- ts/views/conversation_view.ts | 15 +++++ ts/window.d.ts | 1 + 20 files changed, 169 insertions(+), 62 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 95e36d7d3..6345e680d 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -210,11 +210,15 @@ "description": "Shown at the top of the archived conversations list in the left pane" }, "archiveConversation": { - "message": "Archive Conversation", + "message": "Archive", "description": "Shown in menu for conversation, and moves conversation out of main conversation list" }, + "markUnread": { + "message": "Mark as unread", + "description": "Shown in menu for conversation, and marks conversation as unread" + }, "moveConversationToInbox": { - "message": "Move Conversation to Inbox", + "message": "Unarchive", "description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" }, "pinConversation": { @@ -1039,7 +1043,7 @@ "description": "Shown on the drop-down menu for an individual message, deletes single message for everyone" }, "deleteMessages": { - "message": "Delete messages", + "message": "Delete", "description": "Menu item for deleting messages, title case." }, "deleteConversationConfirmation": { @@ -2361,6 +2365,10 @@ "message": "Conversation returned to inbox", "description": "A toast that shows up when the user unarchives a conversation" }, + "conversationMarkedUnread": { + "message": "Conversation marked unread", + "description": "A toast that shows up when user marks a conversation as unread" + }, "StickerCreator--title": { "message": "Sticker pack creator", "description": "The title of the Sticker Pack Creator window" diff --git a/js/views/inbox_view.js b/js/views/inbox_view.js index 12469d24c..8e2c31b13 100644 --- a/js/views/inbox_view.js +++ b/js/views/inbox_view.js @@ -205,6 +205,8 @@ 'private' ); + conversation.setMarkedUnread(false); + const { openConversationExternal } = window.reduxActions.conversations; if (openConversationExternal) { openConversationExternal(id, messageId); diff --git a/protos/SignalStorage.proto b/protos/SignalStorage.proto index beff99d34..8f282dffc 100644 --- a/protos/SignalStorage.proto +++ b/protos/SignalStorage.proto @@ -73,20 +73,23 @@ message ContactRecord { optional bool blocked = 9; optional bool whitelisted = 10; optional bool archived = 11; + optional bool markedUnread = 12; } message GroupV1Record { - optional bytes id = 1; - optional bool blocked = 2; - optional bool whitelisted = 3; - optional bool archived = 4; + optional bytes id = 1; + optional bool blocked = 2; + optional bool whitelisted = 3; + optional bool archived = 4; + optional bool markedUnread = 5; } message GroupV2Record { - optional bytes masterKey = 1; - optional bool blocked = 2; - optional bool whitelisted = 3; - optional bool archived = 4; + optional bytes masterKey = 1; + optional bool blocked = 2; + optional bool whitelisted = 3; + optional bool archived = 4; + optional bool markedUnread = 5; } message AccountRecord { @@ -112,7 +115,7 @@ message AccountRecord { optional bool sealedSenderIndicators = 7; optional bool typingIndicators = 8; optional bool proxiedLinkPreviews = 9; - optional bool noteToSelfUnread = 10; + optional bool noteToSelfMarkedUnread = 10; optional bool linkPreviews = 11; repeated PinnedConversation pinnedConversations = 14; } diff --git a/ts/components/ConversationListItem.stories.tsx b/ts/components/ConversationListItem.stories.tsx index 7e886efcd..448d7182b 100644 --- a/ts/components/ConversationListItem.stories.tsx +++ b/ts/components/ConversationListItem.stories.tsx @@ -37,6 +37,7 @@ const createProps = (overrideProps: Partial = {}): Props => ({ name: overrideProps.name || 'Some Person', type: overrideProps.type || 'direct', onClick: action('onClick'), + markedUnread: boolean('markedUnread', overrideProps.markedUnread || false), lastMessage: overrideProps.lastMessage || { text: text('lastMessage.text', 'Hi there!'), status: select( @@ -137,18 +138,32 @@ story.add('Message Request', () => { story.add('Unread', () => { const counts = [4, 10, 250]; + const defaultProps = createProps({ + lastMessage: { + text: 'Hey there!', + status: 'delivered', + }, + }); - return counts.map(unreadCount => { - const props = createProps({ - lastMessage: { - text: 'Hey there!', - status: 'delivered', - }, + const items = counts.map(unreadCount => { + const props = { + ...defaultProps, unreadCount, - }); + }; return ; }); + + const markedUnreadProps = { + ...defaultProps, + markedUnread: true, + }; + + const markedUnreadItem = [ + , + ]; + + return [...items, ...markedUnreadItem]; }); story.add('Selected', () => { diff --git a/ts/components/ConversationListItem.tsx b/ts/components/ConversationListItem.tsx index fe6e12e37..06fda06df 100644 --- a/ts/components/ConversationListItem.tsx +++ b/ts/components/ConversationListItem.tsx @@ -37,6 +37,7 @@ export type PropsData = { lastUpdated: number; unreadCount?: number; + markedUnread: boolean; isSelected: boolean; acceptedMessageRequest?: boolean; @@ -93,13 +94,19 @@ export class ConversationListItem extends React.PureComponent { ); } + isUnread(): boolean { + const { markedUnread, unreadCount } = this.props; + + return (isNumber(unreadCount) && unreadCount > 0) || markedUnread; + } + public renderUnread(): JSX.Element | null { const { unreadCount } = this.props; - if (isNumber(unreadCount) && unreadCount > 0) { + if (this.isUnread()) { return (
- {unreadCount} + {unreadCount || ''}
); } @@ -109,7 +116,6 @@ export class ConversationListItem extends React.PureComponent { public renderHeader(): JSX.Element { const { - unreadCount, i18n, isMe, lastUpdated, @@ -119,14 +125,12 @@ export class ConversationListItem extends React.PureComponent { title, } = this.props; - const withUnread = isNumber(unreadCount) && unreadCount > 0; - return (
{
{ timestamp={lastUpdated} extended={false} module="module-conversation-list-item__header__timestamp" - withUnread={withUnread} + withUnread={this.isUnread()} i18n={i18n} />
@@ -172,14 +176,12 @@ export class ConversationListItem extends React.PureComponent { muteExpiresAt, shouldShowDraft, typingContact, - unreadCount, } = this.props; if (!lastMessage && !typingContact) { return null; } const messageBody = lastMessage ? lastMessage.text : ''; - const withUnread = isNumber(unreadCount) && unreadCount > 0; const showingDraft = shouldShowDraft && draftPreview; const deletedForEveryone = Boolean( lastMessage && lastMessage.deletedForEveryone @@ -192,7 +194,7 @@ export class ConversationListItem extends React.PureComponent { dir="auto" className={classNames( 'module-conversation-list-item__message__text', - withUnread + this.isUnread() ? 'module-conversation-list-item__message__text--has-unread' : null )} @@ -249,8 +251,7 @@ export class ConversationListItem extends React.PureComponent { /* eslint-enable no-nested-ternary */ public render(): JSX.Element { - const { unreadCount, onClick, id, isSelected, style } = this.props; - const withUnread = isNumber(unreadCount) && unreadCount > 0; + const { id, isSelected, onClick, style } = this.props; return (