From 24776c0d5c1c77dde2ce3e85a6d1dc0a566d9701 Mon Sep 17 00:00:00 2001 From: William Grant Date: Thu, 15 Jun 2023 15:17:28 +1000 Subject: [PATCH] feat: moved convo model interaction props into the lastMessage logic this makes it easier to interact with since if an interaction fails we save it to the db as a message --- ts/components/dialog/SessionConfirm.tsx | 10 ++-- .../InteractionItem.tsx | 40 ++++++++-------- .../conversation-list-item/MessageItem.tsx | 7 +-- ts/hooks/useParamSelector.ts | 26 ++-------- ts/interactions/conversationInteractions.ts | 18 +++---- ts/models/conversation.ts | 47 ++++++++++++------- ts/models/conversationAttributes.ts | 8 ++-- ts/node/database_utility.ts | 22 +++++---- ts/node/migration/sessionMigrations.ts | 10 ++-- ts/node/sql.ts | 10 ++-- ts/receiver/queuedJob.ts | 3 +- ts/state/ducks/conversations.ts | 7 ++- 12 files changed, 100 insertions(+), 108 deletions(-) diff --git a/ts/components/dialog/SessionConfirm.tsx b/ts/components/dialog/SessionConfirm.tsx index f086d7c06..95d4b3cf5 100644 --- a/ts/components/dialog/SessionConfirm.tsx +++ b/ts/components/dialog/SessionConfirm.tsx @@ -14,7 +14,7 @@ import { ConversationInteractionType, updateConversationInteractionState, } from '../../interactions/conversationInteractions'; -import { useConversationInteractionPropsById } from '../../hooks/useParamSelector'; +import { useLastMessage } from '../../hooks/useParamSelector'; import styled from 'styled-components'; const StyledMessageText = styled(SessionHtmlRenderer)` @@ -84,7 +84,7 @@ export const SessionConfirm = (props: SessionConfirmDialogProps) => { conversationId, } = props; - const interactionProps = useConversationInteractionPropsById(conversationId); + const lastMessage = useLastMessage(conversationId); const [isLoading, setIsLoading] = useState(false); @@ -130,15 +130,15 @@ export const SessionConfirm = (props: SessionConfirmDialogProps) => { useEffect(() => { if (isLoading) { - if (conversationId && interactionProps?.interactionType) { + if (conversationId && lastMessage?.interactionType) { void updateConversationInteractionState({ conversationId, - type: interactionProps?.interactionType, + type: lastMessage?.interactionType, status: ConversationInteractionStatus.Loading, }); } } - }, [isLoading, conversationId, interactionProps?.interactionType]); + }, [isLoading, conversationId, lastMessage?.interactionType]); return ( ` ${props => props.isError && 'color: var(--danger-color) !important;'} `; -type InteractionItemProps = ConversationInteractionProps & { - lastMessage?: LastMessageType | null; +type InteractionItemProps = { + conversationId: string; + lastMessage: LastMessageType | null; }; export const InteractionItem = (props: InteractionItemProps) => { - const { conversationId, interactionStatus, interactionType, lastMessage } = props; + const { conversationId, lastMessage } = props; const isGroup = !useIsPrivate(conversationId); const isCommunity = useIsPublic(conversationId); - const [storedLastMessageId, setStoredLastMessageId] = useState(lastMessage?.id); + if (!lastMessage) { + return null; + } + + const { interactionType, interactionStatus } = lastMessage; + + if (!interactionType || !interactionStatus) { + return null; + } + const [storedLastMessageText, setStoredLastMessageText] = useState(lastMessage?.text); + const [storedLastMessageInteractionStatus, setStoredLastMessageInteractionStatus] = useState( + lastMessage?.interactionStatus + ); // NOTE we want to reset the interaction state when the last message changes useEffect(() => { if (conversationId) { const convo = getConversationController().get(conversationId); - window.log.debug( - `WIP: storedLastMessageId "${storedLastMessageId}" convo.get('lastMessageId') "${convo.get( - 'lastMessageId' - )}' storedLastMessageText ${storedLastMessageText} text ${text}` - ); - - if (storedLastMessageId !== convo.get('lastMessageId')) { - setStoredLastMessageId(convo.get('lastMessageId')); + if (storedLastMessageInteractionStatus !== convo.get('lastMessageInteractionStatus')) { + setStoredLastMessageInteractionStatus(convo.get('lastMessageInteractionStatus')); setStoredLastMessageText(convo.get('lastMessage')); - void clearConversationInteractionState({ conversationId }); } } - }, [conversationId, interactionStatus, lastMessage?.id]); - - if (isEmpty(conversationId) || isEmpty(interactionType) || isEmpty(interactionStatus)) { - return null; - } + }, [conversationId, interactionStatus, lastMessage?.interactionStatus]); let text = storedLastMessageText || ''; let failText = ''; diff --git a/ts/components/leftpane/conversation-list-item/MessageItem.tsx b/ts/components/leftpane/conversation-list-item/MessageItem.tsx index 9b272a870..a47cc6f52 100644 --- a/ts/components/leftpane/conversation-list-item/MessageItem.tsx +++ b/ts/components/leftpane/conversation-list-item/MessageItem.tsx @@ -3,7 +3,6 @@ import { isEmpty } from 'lodash'; import React from 'react'; import { useSelector } from 'react-redux'; import { - useConversationInteractionPropsById, useHasUnread, useIsPrivate, useIsTyping, @@ -28,10 +27,8 @@ export const MessageItem = () => { const isSearchingMode = useSelector(isSearching); - const interactionProps = useConversationInteractionPropsById(conversationId); - - if (!isConvoTyping && interactionProps) { - return ; + if (!isConvoTyping && lastMessage?.interactionType && lastMessage?.interactionStatus) { + return ; } if (!lastMessage && !isConvoTyping) { diff --git a/ts/hooks/useParamSelector.ts b/ts/hooks/useParamSelector.ts index ba7d37e6d..d3261ca4a 100644 --- a/ts/hooks/useParamSelector.ts +++ b/ts/hooks/useParamSelector.ts @@ -10,7 +10,6 @@ import { StateType } from '../state/reducer'; import { getMessageReactsProps } from '../state/selectors/conversations'; import { isPrivateAndFriend } from '../state/selectors/selectedConversation'; import { CONVERSATION } from '../session/constants'; -import { ConversationInteractionProps } from '../interactions/conversationInteractions'; export function useAvatarPath(convoId: string | undefined) { const convoProps = useConversationPropsById(convoId); @@ -269,29 +268,14 @@ export function useIsTyping(conversationId?: string): boolean { return useConversationPropsById(conversationId)?.isTyping || false; } -export function useLastMessage(convoId: string) { - const convoProps = useConversationPropsById(convoId); - if (!convoProps) { - return null; - } - return convoProps.lastMessage; -} - -export function useConversationInteractionPropsById( - conversationId?: string -): ConversationInteractionProps | null { - if (!conversationId) { +export function useLastMessage(convoId?: string) { + if (!convoId) { return null; } - const convoProps = useConversationPropsById(conversationId); - - if (!convoProps || !convoProps.interactionType || !convoProps.interactionStatus) { + const convoProps = useConversationPropsById(convoId); + if (!convoProps) { return null; } - - const interactionType = convoProps.interactionType; - const interactionStatus = convoProps.interactionStatus; - - return { conversationId, interactionType, interactionStatus }; + return convoProps.lastMessage; } diff --git a/ts/interactions/conversationInteractions.ts b/ts/interactions/conversationInteractions.ts index 02caf8352..436210b84 100644 --- a/ts/interactions/conversationInteractions.ts +++ b/ts/interactions/conversationInteractions.ts @@ -43,7 +43,6 @@ import { encryptProfile } from '../util/crypto/profileEncrypter'; import { ReleasedFeatures } from '../util/releaseFeature'; import { Storage, setLastProfileUpdateTimestamp } from '../util/storage'; import { UserGroupsWrapperActions } from '../webworker/workers/browser/libsession_worker_interface'; -import { ConversationModel } from '../models/conversation'; export enum ConversationInteractionStatus { Start = 'start', @@ -57,12 +56,6 @@ export enum ConversationInteractionType { Leave = 'leave', } -export type ConversationInteractionProps = { - conversationId: string; - interactionType: ConversationInteractionType; - interactionStatus: ConversationInteractionStatus; -}; - export async function copyPublicKeyByConvoId(convoId: string) { if (OpenGroupUtils.isOpenGroupV2(convoId)) { const fromWrapper = await UserGroupsWrapperActions.getCommunityByFullUrl(convoId); @@ -460,7 +453,8 @@ export async function deleteAllMessagesByConvoIdNoConfirmation(conversationId: s // conversation still appears on the conversation list but is empty conversation.set({ lastMessage: null, - lastMessageId: null, + lastMessageInteractionType: null, + lastMessageInteractionStatus: null, }); await conversation.commit(); @@ -708,8 +702,8 @@ export async function updateConversationInteractionState({ }) { const convo = getConversationController().get(conversationId); if (convo) { - convo.set('interactionType', type); - convo.set('interactionStatus', status); + convo.set('lastMessageInteractionType', type); + convo.set('lastMessageInteractionStatus', status); await convo.commit(); window.log.debug( @@ -729,8 +723,8 @@ export async function clearConversationInteractionState({ }) { const convo = getConversationController().get(conversationId); if (convo) { - convo.set('interactionType', undefined); - convo.set('interactionStatus', undefined); + convo.set('lastMessageInteractionType', undefined); + convo.set('lastMessageInteractionStatus', undefined); await convo.commit(); window.log.debug(`WIP: clearConversationInteractionState() for ${conversationId}`); diff --git a/ts/models/conversation.ts b/ts/models/conversation.ts index 4880903c2..99463932a 100644 --- a/ts/models/conversation.ts +++ b/ts/models/conversation.ts @@ -368,13 +368,6 @@ export class ConversationModel extends Backbone.Model { } } - if (this.get('interactionType')) { - toRet.interactionType = this.get('interactionType'); - } - if (this.get('interactionStatus')) { - toRet.interactionStatus = this.get('interactionStatus'); - } - // -- Handle the field stored only in memory for all types of conversation-- const inMemoryConvoInfo = inMemoryConvoInfos.get(this.id); if (inMemoryConvoInfo) { @@ -389,13 +382,15 @@ export class ConversationModel extends Backbone.Model { // -- Handle the last message status, if present -- const lastMessageText = this.get('lastMessage'); if (lastMessageText && lastMessageText.length) { - const lastMessageId = this.get('lastMessageId'); const lastMessageStatus = this.get('lastMessageStatus'); + const lastMessageInteractionType = this.get('lastMessageInteractionType'); + const lastMessageInteractionStatus = this.get('lastMessageInteractionStatus'); toRet.lastMessage = { - id: lastMessageId, status: lastMessageStatus, text: lastMessageText, + interactionType: lastMessageInteractionType, + interactionStatus: lastMessageInteractionStatus, }; } return toRet; @@ -755,10 +750,18 @@ export class ConversationModel extends Backbone.Model { this.set({ lastMessage: messageModel.getNotificationText(), - lastMessageId: messageModel.get('id'), lastMessageStatus: 'sending', active_at: networkTimestamp, }); + + if (messageModel.get('interactionNotification')) { + this.set({ + lastMessageInteractionType: messageModel.get('interactionNotification')?.interactionType, + lastMessageInteractionStatus: messageModel.get('interactionNotification') + ?.interactionStatus, + }); + } + await this.commit(); void this.queueJob(async () => { @@ -1856,7 +1859,10 @@ export class ConversationModel extends Backbone.Model { return; } const lastMessageModel = messages.at(0); - const lastMessageId = lastMessageModel.get('id'); + const lastMessageInteractionType = lastMessageModel.get('interactionNotification') + ?.interactionType; + const lastMessageInteractionStatus = lastMessageModel.get('interactionNotification') + ?.interactionStatus; const lastMessageStatus = lastMessageModel.getMessagePropStatus() || undefined; const lastMessageNotificationText = lastMessageModel.getNotificationText() || undefined; // we just want to set the `status` to `undefined` if there are no `lastMessageNotificationText` @@ -1864,22 +1870,31 @@ export class ConversationModel extends Backbone.Model { !!lastMessageNotificationText && !isEmpty(lastMessageNotificationText) ? { lastMessage: lastMessageNotificationText || '', - lastMessageId, lastMessageStatus, + lastMessageInteractionType, + lastMessageInteractionStatus, } - : { lastMessage: '', lastMessageId: '', lastMessageStatus: undefined }; + : { + lastMessage: '', + lastMessageStatus: undefined, + lastMessageInteractionType: undefined, + lastMessageInteractionStatus: undefined, + }; const existingLastMessageAttribute = this.get('lastMessage'); - const existingLastMessageId = this.get('lastMessageId'); const existingLastMessageStatus = this.get('lastMessageStatus'); + const existingLastMessageInteractionType = this.get('lastMessageInteractionType'); + const existingLastMessageInteractionStatus = this.get('lastMessageInteractionStatus'); if ( lastMessageUpdate.lastMessage !== existingLastMessageAttribute || lastMessageUpdate.lastMessageStatus !== existingLastMessageStatus || - lastMessageUpdate.lastMessageId !== existingLastMessageId + lastMessageUpdate.lastMessageInteractionType !== existingLastMessageInteractionType || + lastMessageUpdate.lastMessageInteractionStatus !== existingLastMessageInteractionStatus ) { if ( - lastMessageUpdate.lastMessageId === existingLastMessageId && lastMessageUpdate.lastMessageStatus === existingLastMessageStatus && + lastMessageUpdate.lastMessageInteractionType === existingLastMessageInteractionType && + lastMessageUpdate.lastMessageInteractionStatus === existingLastMessageInteractionStatus && lastMessageUpdate.lastMessage && lastMessageUpdate.lastMessage.length > 40 && existingLastMessageAttribute && diff --git a/ts/models/conversationAttributes.ts b/ts/models/conversationAttributes.ts index 928832b92..c3b262877 100644 --- a/ts/models/conversationAttributes.ts +++ b/ts/models/conversationAttributes.ts @@ -66,8 +66,9 @@ export interface ConversationAttributes { * The shortening is made in sql.ts directly. */ lastMessage: string | null; - lastMessageId: string | null; lastMessageStatus: LastMessageStatusType; + lastMessageInteractionType: ConversationInteractionType | null; + lastMessageInteractionStatus: ConversationInteractionStatus | null; avatarImageId?: number; // avatar imageID is currently used only for sogs. It's the fileID of the image uploaded and set as the sogs avatar (not only sogs I think, but our profile too?) @@ -108,8 +109,6 @@ export interface ConversationAttributes { markedAsUnread: boolean; // Force the conversation as unread even if all the messages are read. Used to highlight a conversation the user wants to check again later, synced. // the last interaction we had with this conversation e.g. failed to leave a group - interactionType?: ConversationInteractionType; // e.g. Leave - interactionStatus?: ConversationInteractionStatus; // e.g. Error } /** @@ -131,8 +130,9 @@ export const fillConvoAttributesWithDefaults = ( active_at: 0, lastMessage: null, - lastMessageId: null, lastMessageStatus: undefined, + lastMessageInteractionType: null, + lastMessageInteractionStatus: null, triggerNotificationsFor: 'all', // if the settings is not set in the db, this is the default diff --git a/ts/node/database_utility.ts b/ts/node/database_utility.ts index 151452d89..63203a352 100644 --- a/ts/node/database_utility.ts +++ b/ts/node/database_utility.ts @@ -58,8 +58,9 @@ const allowedKeysFormatRowOfConversation = [ 'isKickedFromGroup', 'left', 'lastMessage', - 'lastMessageId', 'lastMessageStatus', + 'lastMessageInteractionType', + 'lastMessageInteractionStatus', 'triggerNotificationsFor', 'unreadCount', 'lastJoinedTimestamp', @@ -76,8 +77,6 @@ const allowedKeysFormatRowOfConversation = [ 'conversationIdOrigin', 'markedAsUnread', 'priority', - 'interactionType', - 'interactionStatus', ]; // tslint:disable: cyclomatic-complexity export function formatRowOfConversation( @@ -139,14 +138,18 @@ export function formatRowOfConversation( convo.lastMessage = null; } - if (!convo.lastMessageId) { - convo.lastMessageId = null; - } - if (!convo.lastMessageStatus) { convo.lastMessageStatus = undefined; } + if (!convo.lastMessageInteractionType) { + convo.lastMessageInteractionType = null; + } + + if (!convo.lastMessageInteractionStatus) { + convo.lastMessageInteractionStatus = null; + } + if (!convo.triggerNotificationsFor) { convo.triggerNotificationsFor = 'all'; } @@ -183,8 +186,9 @@ const allowedKeysOfConversationAttributes = [ 'isKickedFromGroup', 'left', 'lastMessage', - 'lastMessageId', 'lastMessageStatus', + 'lastMessageInteractionType', + 'lastMessageInteractionStatus', 'triggerNotificationsFor', 'lastJoinedTimestamp', 'expireTimer', @@ -200,8 +204,6 @@ const allowedKeysOfConversationAttributes = [ 'conversationIdOrigin', 'markedAsUnread', 'priority', - 'interactionType', - 'interactionStatus', ]; /** diff --git a/ts/node/migration/sessionMigrations.ts b/ts/node/migration/sessionMigrations.ts index 5eedbe78e..1f489912c 100644 --- a/ts/node/migration/sessionMigrations.ts +++ b/ts/node/migration/sessionMigrations.ts @@ -1842,11 +1842,13 @@ function updateToSessionSchemaVersion32(currentVersion: number, db: BetterSqlite console.log(`updateToSessionSchemaVersion${targetVersion}: starting...`); db.transaction(() => { - db.prepare(`ALTER TABLE ${CONVERSATIONS_TABLE} ADD COLUMN interactionType TEXT;`).run(); - - db.prepare(`ALTER TABLE ${CONVERSATIONS_TABLE} ADD COLUMN interactionStatus TEXT;`).run(); + db.prepare( + `ALTER TABLE ${CONVERSATIONS_TABLE} ADD COLUMN lastMessageInteractionType TEXT;` + ).run(); - db.prepare(`ALTER TABLE ${CONVERSATIONS_TABLE} ADD COLUMN lastMessageId TEXT;`).run(); + db.prepare( + `ALTER TABLE ${CONVERSATIONS_TABLE} ADD COLUMN lastMessageInteractionStatus TEXT;` + ).run(); writeSessionSchemaVersion(targetVersion, db); })(); diff --git a/ts/node/sql.ts b/ts/node/sql.ts index ccf7210d4..f8f1a9db3 100644 --- a/ts/node/sql.ts +++ b/ts/node/sql.ts @@ -428,8 +428,9 @@ function saveConversation(data: ConversationAttributes): SaveConversationReturn left, expireTimer, lastMessage, - lastMessageId, lastMessageStatus, + lastMessageInteractionType, + lastMessageInteractionStatus, lastJoinedTimestamp, groupAdmins, isKickedFromGroup, @@ -444,8 +445,6 @@ function saveConversation(data: ConversationAttributes): SaveConversationReturn conversationIdOrigin, priority, markedAsUnread, - interactionType, - interactionStatus, } = formatted; const omited = omit(formatted); @@ -479,8 +478,9 @@ function saveConversation(data: ConversationAttributes): SaveConversationReturn left: toSqliteBoolean(left), expireTimer, lastMessage: shortenedLastMessage, - lastMessageId, lastMessageStatus, + lastMessageInteractionType, + lastMessageInteractionStatus, lastJoinedTimestamp, groupAdmins: groupAdmins && groupAdmins.length ? arrayStrToJson(groupAdmins) : '[]', @@ -496,8 +496,6 @@ function saveConversation(data: ConversationAttributes): SaveConversationReturn displayNameInProfile, conversationIdOrigin, markedAsUnread: toSqliteBoolean(markedAsUnread), - interactionType, - interactionStatus, }); return fetchConvoMemoryDetails(id); diff --git a/ts/receiver/queuedJob.ts b/ts/receiver/queuedJob.ts index f5f8854f8..a85f4767f 100644 --- a/ts/receiver/queuedJob.ts +++ b/ts/receiver/queuedJob.ts @@ -276,7 +276,8 @@ async function handleRegularMessage( conversation.set({ active_at: message.get('sent_at'), lastMessage: message.getNotificationText(), - lastMessageId: message.get('id'), + lastMessageInteractionType: message.get('interactionNotification')?.interactionType, + lastMessageInteractionStatus: message.get('interactionNotification')?.interactionStatus, }); // a new message was received for that conversation. If it was not it should not be hidden anymore await conversation.unhideIfNeeded(false); diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts index ca2536f03..7bec9bd8c 100644 --- a/ts/state/ducks/conversations.ts +++ b/ts/state/ducks/conversations.ts @@ -226,11 +226,13 @@ export type PropsForMessageWithConvoProps = PropsForMessageWithoutConvoProps & { }; export type LastMessageType = { - id: string | null; status: LastMessageStatusType; text: string | null; + interactionType: ConversationInteractionType | null; + interactionStatus: ConversationInteractionStatus | null; }; +// NOTE This is used for failed interactions that are saved as messages to the db and rendered in a conversation export type InteractionNotificationType = { interactionType: ConversationInteractionType; interactionStatus: ConversationInteractionStatus; @@ -282,9 +284,6 @@ export interface ReduxConversationType { didApproveMe?: boolean; isMarkedUnread?: boolean; - - interactionType?: ConversationInteractionType; - interactionStatus?: ConversationInteractionStatus; } export interface NotificationForConvoOption {