From 513f94cb25e5635763e9fb83c3cc14575b9ab148 Mon Sep 17 00:00:00 2001 From: William Grant Date: Mon, 3 Apr 2023 14:09:05 +0200 Subject: [PATCH] feat: extracted expiry logic from the GenericReadableMessage to a new component This allows use to use the expiry logic for different visible messages not just generic ones --- .../message-item/ExpirableReadableMessage.tsx | 134 ++++++++++++++++++ .../message-item/GenericReadableMessage.tsx | 127 +++-------------- .../message/message-item/ReadableMessage.tsx | 2 +- 3 files changed, 154 insertions(+), 109 deletions(-) create mode 100644 ts/components/conversation/message/message-item/ExpirableReadableMessage.tsx diff --git a/ts/components/conversation/message/message-item/ExpirableReadableMessage.tsx b/ts/components/conversation/message/message-item/ExpirableReadableMessage.tsx new file mode 100644 index 000000000..e35fda5b5 --- /dev/null +++ b/ts/components/conversation/message/message-item/ExpirableReadableMessage.tsx @@ -0,0 +1,134 @@ +import React, { useCallback, useEffect, useState } from 'react'; +import { useDispatch } from 'react-redux'; +import { useInterval } from 'react-use'; +import styled from 'styled-components'; +import { Data } from '../../../../data/data'; +import { MessageModelType } from '../../../../models/messageType'; +import { getConversationController } from '../../../../session/conversations'; +import { messagesExpired } from '../../../../state/ducks/conversations'; +import { getIncrement } from '../../../../util/timer'; +import { ExpireTimer } from '../../ExpireTimer'; +import { ReadableMessage, ReadableMessageProps } from './ReadableMessage'; + +const EXPIRATION_CHECK_MINIMUM = 2000; + +type ExpiringProps = { + isExpired?: boolean; + expirationTimestamp?: number | null; + expirationLength?: number | null; + convoId?: string; + messageId: string; +}; + +function useIsExpired(props: ExpiringProps) { + const { + convoId, + messageId, + expirationLength, + expirationTimestamp, + isExpired: isExpiredProps, + } = props; + + const dispatch = useDispatch(); + + const [isExpired] = useState(isExpiredProps); + + const checkExpired = useCallback(async () => { + const now = Date.now(); + + if (!expirationTimestamp || !expirationLength) { + return; + } + + if (isExpired || now >= expirationTimestamp) { + await Data.removeMessage(messageId); + if (convoId) { + dispatch( + messagesExpired([ + { + conversationKey: convoId, + messageId, + }, + ]) + ); + const convo = getConversationController().get(convoId); + convo?.updateLastMessage(); + } + } + }, [expirationTimestamp, expirationLength, isExpired, messageId, convoId]); + + let checkFrequency: number | null = null; + if (expirationLength) { + const increment = getIncrement(expirationLength || EXPIRATION_CHECK_MINIMUM); + checkFrequency = Math.max(EXPIRATION_CHECK_MINIMUM, increment); + } + + useEffect(() => { + void checkExpired(); + }, []); // check on mount + + useInterval(checkExpired, checkFrequency); // check every 2sec or sooner if needed + + return { isExpired }; +} + +const StyledReadableMessage = styled(ReadableMessage)` + display: flex; + align-items: center; + width: 100%; +`; + +export interface ExpirableReadableMessageProps extends ReadableMessageProps, ExpiringProps { + direction: MessageModelType; +} + +export const ExpirableReadableMessage = (props: ExpirableReadableMessageProps) => { + const { + convoId, + messageId, + direction, + receivedAt, + isUnread, + expirationLength, + expirationTimestamp, + } = props; + + const expiringProps: ExpiringProps = { + convoId, + expirationLength, + messageId: messageId, + expirationTimestamp, + isExpired: props.isExpired, + }; + const { isExpired } = useIsExpired(expiringProps); + const isIncoming = direction === 'incoming'; + + if (isExpired) { + return null; + } + + return ( + + {expirationLength && expirationTimestamp && ( + + )} + {props.children} + {expirationLength && expirationTimestamp && ( + + )} + + ); +}; diff --git a/ts/components/conversation/message/message-item/GenericReadableMessage.tsx b/ts/components/conversation/message/message-item/GenericReadableMessage.tsx index e3d521496..f492c85e9 100644 --- a/ts/components/conversation/message/message-item/GenericReadableMessage.tsx +++ b/ts/components/conversation/message/message-item/GenericReadableMessage.tsx @@ -1,25 +1,20 @@ import classNames from 'classnames'; import React, { useCallback, useEffect, useState } from 'react'; import { contextMenu } from 'react-contexify'; -import { useDispatch, useSelector } from 'react-redux'; +import { useSelector } from 'react-redux'; // tslint:disable-next-line: no-submodule-imports -import useInterval from 'react-use/lib/useInterval'; import _ from 'lodash'; -import { Data } from '../../../../data/data'; import { MessageRenderingProps } from '../../../../models/messageType'; import { getConversationController } from '../../../../session/conversations'; -import { messagesExpired } from '../../../../state/ducks/conversations'; import { getGenericReadableMessageSelectorProps, getIsMessageSelected, isMessageSelectionMode, } from '../../../../state/selectors/conversations'; -import { getIncrement } from '../../../../util/timer'; -import { ExpireTimer } from '../../ExpireTimer'; - import { MessageContentWithStatuses } from '../message-content/MessageContentWithStatus'; import { ReadableMessage } from './ReadableMessage'; import styled, { keyframes } from 'styled-components'; +import { ExpirableReadableMessage } from './ExpirableReadableMEssage'; export type GenericReadableMessageSelectorProps = Pick< MessageRenderingProps, @@ -35,66 +30,6 @@ export type GenericReadableMessageSelectorProps = Pick< | 'isDeleted' >; -type ExpiringProps = { - isExpired?: boolean; - expirationTimestamp?: number | null; - expirationLength?: number | null; - convoId?: string; - messageId: string; -}; -const EXPIRATION_CHECK_MINIMUM = 2000; - -function useIsExpired(props: ExpiringProps) { - const { - convoId, - messageId, - expirationLength, - expirationTimestamp, - isExpired: isExpiredProps, - } = props; - - const dispatch = useDispatch(); - - const [isExpired] = useState(isExpiredProps); - - const checkExpired = useCallback(async () => { - const now = Date.now(); - - if (!expirationTimestamp || !expirationLength) { - return; - } - - if (isExpired || now >= expirationTimestamp) { - await Data.removeMessage(messageId); - if (convoId) { - dispatch( - messagesExpired([ - { - conversationKey: convoId, - messageId, - }, - ]) - ); - const convo = getConversationController().get(convoId); - convo?.updateLastMessage(); - } - } - }, [expirationTimestamp, expirationLength, isExpired, messageId, convoId]); - - let checkFrequency: number | null = null; - if (expirationLength) { - const increment = getIncrement(expirationLength || EXPIRATION_CHECK_MINIMUM); - checkFrequency = Math.max(EXPIRATION_CHECK_MINIMUM, increment); - } - - useEffect(() => { - void checkExpired(); - }, []); // check on mount - useInterval(checkExpired, checkFrequency); // check every 2sec or sooner if needed - - return { isExpired }; -} - type Props = { messageId: string; ctxMenuID: string; @@ -150,15 +85,6 @@ export const GenericReadableMessage = (props: Props) => { getGenericReadableMessageSelectorProps(state as any, props.messageId) ); - const expiringProps: ExpiringProps = { - convoId: msgProps?.convoId, - expirationLength: msgProps?.expirationLength, - messageId: props.messageId, - expirationTimestamp: msgProps?.expirationTimestamp, - isExpired: msgProps?.isExpired, - }; - const { isExpired } = useIsExpired(expiringProps); - const isMessageSelected = useSelector(state => getIsMessageSelected(state as any, props.messageId) ); @@ -207,22 +133,10 @@ export const GenericReadableMessage = (props: Props) => { if (!msgProps) { return null; } - const { - direction, - conversationType, - receivedAt, - isUnread, - expirationLength, - expirationTimestamp, - } = msgProps; - - if (isExpired) { - return null; - } + const { conversationType, receivedAt, isUnread } = msgProps; const selected = isMessageSelected || false; const isGroup = conversationType === 'group'; - const isIncoming = direction === 'incoming'; return ( { isUnread={!!isUnread} key={`readable-message-${messageId}`} > - {expirationLength && expirationTimestamp && ( - - )} - - {expirationLength && expirationTimestamp && ( - + - )} + ); }; diff --git a/ts/components/conversation/message/message-item/ReadableMessage.tsx b/ts/components/conversation/message/message-item/ReadableMessage.tsx index a57cead51..bcaba031e 100644 --- a/ts/components/conversation/message/message-item/ReadableMessage.tsx +++ b/ts/components/conversation/message/message-item/ReadableMessage.tsx @@ -24,7 +24,7 @@ import { import { getIsAppFocused } from '../../../../state/selectors/section'; import { ScrollToLoadedMessageContext } from '../../SessionMessagesListContainer'; -type ReadableMessageProps = { +export type ReadableMessageProps = { children: React.ReactNode; messageId: string; className?: string;