diff --git a/native/chat/composed-message.react.js b/native/chat/composed-message.react.js index a78d92b55..37f7d4b32 100644 --- a/native/chat/composed-message.react.js +++ b/native/chat/composed-message.react.js @@ -1,436 +1,431 @@ // @flow import Icon from '@expo/vector-icons/Feather.js'; import invariant from 'invariant'; import * as React from 'react'; import { StyleSheet, View, TouchableOpacity } from 'react-native'; import { useDerivedValue, withTiming, interpolateColor, useAnimatedStyle, } from 'react-native-reanimated'; import { chatMessageItemHasEngagement } from 'lib/shared/chat-message-item-utils.js'; import { getMessageLabel } from 'lib/shared/edit-messages-utils.js'; import { createMessageReply } from 'lib/shared/message-utils.js'; import { assertComposableMessageType } from 'lib/types/message-types.js'; import { clusterEndHeight, composedMessageStyle, avatarOffset, } from './chat-constants.js'; import { useComposedMessageMaxWidth } from './composed-message-width.js'; import { FailedSend } from './failed-send.react.js'; import { InlineEngagement } from './inline-engagement.react.js'; import { MessageEditingContext } from './message-editing-context.react.js'; import { MessageHeader } from './message-header.react.js'; import { useNavigateToSidebar } from './sidebar-navigation.js'; import SwipeableMessage from './swipeable-message.react.js'; import { useContentAndHeaderOpacity, useDeliveryIconOpacity } from './utils.js'; import UserAvatar from '../avatars/user-avatar.react.js'; import CommIcon from '../components/comm-icon.react.js'; import { InputStateContext } from '../input/input-state.js'; import { useColors } from '../themes/colors.js'; import type { ChatComposedMessageInfoItemWithHeight } from '../types/chat-types.js'; -import { - type AnimatedStyleObj, - type ViewStyle, - AnimatedView, -} from '../types/styles.js'; +import { type ViewStyle, AnimatedView } from '../types/styles.js'; import { useNavigateToUserProfileBottomSheet } from '../user-profile/user-profile-utils.js'; type SwipeOptions = 'reply' | 'sidebar' | 'both' | 'none'; type Props = { ...React.ElementConfig, +item: ChatComposedMessageInfoItemWithHeight, +sendFailed: boolean, +focused: boolean, +swipeOptions: SwipeOptions, +shouldDisplayPinIndicator: boolean, +children: React.Node, }; const ConnectedComposedMessage: React.ComponentType = React.memo( function ConnectedComposedMessage(props: Props) { const composedMessageMaxWidth = useComposedMessageMaxWidth(); const colors = useColors(); const inputState = React.useContext(InputStateContext); const navigateToSidebar = useNavigateToSidebar(props.item); const contentAndHeaderOpacity = useContentAndHeaderOpacity(props.item); const deliveryIconOpacity = useDeliveryIconOpacity(props.item); const messageEditingContext = React.useContext(MessageEditingContext); const progress = useDerivedValue(() => { const isThisThread = messageEditingContext?.editState.editedMessage?.threadID === props.item.threadInfo.id; const isHighlighted = messageEditingContext?.editState.editedMessage?.id === props.item.messageInfo.id && isThisThread; return withTiming(isHighlighted ? 1 : 0); }); const editedMessageStyle = useAnimatedStyle(() => { const backgroundColor = interpolateColor( progress.value, [0, 1], ['transparent', `#${props.item.threadInfo.color}40`], ); return { backgroundColor, }; }); assertComposableMessageType(props.item.messageInfo.type); const { item, sendFailed, swipeOptions, shouldDisplayPinIndicator, children, focused, ...viewProps } = props; const { hasBeenEdited, isPinned } = item; const { id, creator } = item.messageInfo; const { isViewer } = creator; const alignStyle = isViewer ? styles.rightChatBubble : styles.leftChatBubble; const containerStyle = React.useMemo(() => { let containerMarginBottom = 5; if (item.endsCluster) { containerMarginBottom += clusterEndHeight; } return { marginBottom: containerMarginBottom }; }, [item.endsCluster]); const messageBoxContainerStyle = React.useMemo( () => [ styles.messageBoxContainer, isViewer ? styles.rightChatContainer : styles.leftChatContainer, ], [isViewer], ); + const deliveryIconAnimatedStyle = useAnimatedStyle(() => ({ + opacity: deliveryIconOpacity.value, + })); + const deliveryIcon = React.useMemo(() => { if (!isViewer) { return undefined; } let deliveryIconName; let deliveryIconColor = `#${item.threadInfo.color}`; const notDeliveredP2PMessages = item?.localMessageInfo?.outboundP2PMessageIDs ?? []; if ( id !== null && id !== undefined && notDeliveredP2PMessages.length === 0 ) { deliveryIconName = 'check-circle'; } else if (sendFailed) { deliveryIconName = 'x-circle'; deliveryIconColor = colors.redText; } else { deliveryIconName = 'circle'; } - const animatedStyle: AnimatedStyleObj = { opacity: deliveryIconOpacity }; - return ( - + ); }, [ colors.redText, - deliveryIconOpacity, + deliveryIconAnimatedStyle, id, isViewer, item?.localMessageInfo?.outboundP2PMessageIDs, item.threadInfo.color, sendFailed, ]); const editInputMessage = inputState?.editInputMessage; const reply = React.useCallback(() => { invariant(editInputMessage, 'editInputMessage should be set in reply'); invariant(item.messageInfo.text, 'text should be set in reply'); editInputMessage({ message: createMessageReply(item.messageInfo.text), mode: 'prepend', }); }, [editInputMessage, item.messageInfo.text]); const triggerReply = swipeOptions === 'reply' || swipeOptions === 'both' ? reply : undefined; const triggerSidebar = swipeOptions === 'sidebar' || swipeOptions === 'both' ? navigateToSidebar : undefined; const navigateToUserProfileBottomSheet = useNavigateToUserProfileBottomSheet(); const onPressAvatar = React.useCallback( () => navigateToUserProfileBottomSheet(item.messageInfo.creator.id), [item.messageInfo.creator.id, navigateToUserProfileBottomSheet], ); const avatar = React.useMemo(() => { if (!isViewer && item.endsCluster) { return ( ); } else if (!isViewer) { return ; } else { return undefined; } }, [ isViewer, item.endsCluster, item.messageInfo.creator.id, onPressAvatar, ]); const pinIconPositioning = isViewer ? 'left' : 'right'; const pinIconName = pinIconPositioning === 'left' ? 'pin-mirror' : 'pin'; const messageBoxTopLevelContainerStyle = pinIconPositioning === 'left' ? styles.rightMessageBoxTopLevelContainerStyle : styles.leftMessageBoxTopLevelContainerStyle; const pinIcon = React.useMemo(() => { if (!isPinned || !shouldDisplayPinIndicator) { return undefined; } return ( ); }, [ isPinned, item.threadInfo.color, pinIconName, shouldDisplayPinIndicator, ]); - const messageBoxStyle = React.useMemo( + const messageBoxStyle = useAnimatedStyle( () => ({ - opacity: contentAndHeaderOpacity, + opacity: contentAndHeaderOpacity.value, maxWidth: composedMessageMaxWidth, }), - [composedMessageMaxWidth, contentAndHeaderOpacity], + [composedMessageMaxWidth], ); const messageBox = React.useMemo( () => ( {pinIcon} {avatar} {children} ), [ avatar, children, isViewer, item.threadInfo.color, messageBoxContainerStyle, messageBoxStyle, messageBoxTopLevelContainerStyle, pinIcon, triggerReply, triggerSidebar, ], ); const label = getMessageLabel(hasBeenEdited, item.threadInfo.id); const inlineEngagement = React.useMemo(() => { if (!chatMessageItemHasEngagement(item, item.threadInfo.id)) { return undefined; } const positioning = isViewer ? 'right' : 'left'; return ( ); }, [label, isViewer, item]); const viewStyle = React.useMemo(() => { const baseStyle: Array = [styles.alignment]; if (__DEV__) { // We don't force view height in dev mode because we // want to measure it in Message to see if it's correct return baseStyle; } if (item.messageShapeType === 'text') { baseStyle.push({ height: item.contentHeight }); } else if (item.messageShapeType === 'multimedia') { const height = item.inlineEngagementHeight ? item.contentHeight + item.inlineEngagementHeight : item.contentHeight; baseStyle.push({ height }); } return baseStyle; }, [ item.contentHeight, item.inlineEngagementHeight, item.messageShapeType, ]); - const messageHeaderStyle = React.useMemo( - () => ({ - opacity: contentAndHeaderOpacity, - }), - [contentAndHeaderOpacity], - ); + const messageHeaderStyle = useAnimatedStyle(() => ({ + opacity: contentAndHeaderOpacity.value, + })); const animatedContainerStyle = React.useMemo( () => [containerStyle, editedMessageStyle], [containerStyle, editedMessageStyle], ); const contentStyle = React.useMemo( () => [styles.content, alignStyle], [alignStyle], ); const failedSend = React.useMemo( () => (sendFailed ? : undefined), [item, sendFailed], ); const composedMessage = React.useMemo(() => { return ( {deliveryIcon} {messageBox} {inlineEngagement} {failedSend} ); }, [ animatedContainerStyle, contentStyle, deliveryIcon, failedSend, focused, inlineEngagement, item, messageBox, messageHeaderStyle, viewProps, viewStyle, ]); return composedMessage; }, ); const styles = StyleSheet.create({ alignment: { marginLeft: composedMessageStyle.marginLeft, marginRight: composedMessageStyle.marginRight, }, avatarContainer: { paddingRight: 8, paddingTop: 4, }, avatarOffset: { width: avatarOffset, }, content: { alignItems: 'center', flexDirection: 'row-reverse', }, icon: { fontSize: 16, textAlign: 'center', }, iconContainer: { marginLeft: 2, width: 16, }, leftChatBubble: { justifyContent: 'flex-end', }, leftChatContainer: { alignItems: 'flex-start', }, leftMessageBoxTopLevelContainerStyle: { flexDirection: 'row-reverse', }, messageBoxContainer: { marginRight: 5, }, pinIconContainer: { marginRight: 4, marginTop: 4, }, rightChatBubble: { justifyContent: 'flex-start', }, rightChatContainer: { alignItems: 'flex-end', }, rightMessageBoxTopLevelContainerStyle: { flexDirection: 'row', }, swipeableContainer: { alignItems: 'flex-end', flexDirection: 'row', }, }); export default ConnectedComposedMessage; diff --git a/native/chat/robotext-message.react.js b/native/chat/robotext-message.react.js index 14c9ad7b6..4b5865406 100644 --- a/native/chat/robotext-message.react.js +++ b/native/chat/robotext-message.react.js @@ -1,252 +1,256 @@ // @flow import invariant from 'invariant'; import * as React from 'react'; import { View } from 'react-native'; +import { useAnimatedStyle } from 'react-native-reanimated'; import { chatMessageItemKey, chatMessageItemHasEngagement, chatMessageItemEngagementTargetMessageInfo, } from 'lib/shared/chat-message-item-utils.js'; import { useCanCreateReactionFromMessage } from 'lib/shared/reaction-utils.js'; import { useCanCreateSidebarFromMessage } from 'lib/shared/sidebar-utils.js'; import { inlineEngagementCenterStyle } from './chat-constants.js'; import type { ChatNavigationProp } from './chat.react.js'; import { InlineEngagement } from './inline-engagement.react.js'; import { InnerRobotextMessage } from './inner-robotext-message.react.js'; import { Timestamp } from './timestamp.react.js'; import { getMessageTooltipKey, useContentAndHeaderOpacity } from './utils.js'; import { ChatContext } from '../chat/chat-context.js'; import { KeyboardContext } from '../keyboard/keyboard-state.js'; import type { AppNavigationProp } from '../navigation/app-navigator.react'; import { OverlayContext } from '../navigation/overlay-context.js'; import { RobotextMessageTooltipModalRouteName } from '../navigation/route-names.js'; import type { NavigationRoute } from '../navigation/route-names.js'; import { useStyles } from '../themes/colors.js'; import { fixedTooltipHeight } from '../tooltip/tooltip.react.js'; import type { ChatRobotextMessageInfoItemWithHeight } from '../types/chat-types.js'; import type { VerticalBounds } from '../types/layout-types.js'; import { AnimatedView } from '../types/styles.js'; type Props = { ...React.ElementConfig, +item: ChatRobotextMessageInfoItemWithHeight, +navigation: | ChatNavigationProp<'MessageList'> | AppNavigationProp<'TogglePinModal'> | ChatNavigationProp<'PinnedMessagesScreen'> | ChatNavigationProp<'MessageSearch'>, +route: | NavigationRoute<'MessageList'> | NavigationRoute<'TogglePinModal'> | NavigationRoute<'PinnedMessagesScreen'> | NavigationRoute<'MessageSearch'>, +focused: boolean, +toggleFocus: (messageKey: string) => void, +verticalBounds: ?VerticalBounds, }; function RobotextMessage(props: Props): React.Node { const { item, navigation, route, focused, toggleFocus, verticalBounds, ...viewProps } = props; let timestamp = null; if (focused || item.startsConversation) { timestamp = ; } const styles = useStyles(unboundStyles); const engagementTargetMessageInfo = chatMessageItemEngagementTargetMessageInfo(item); let inlineEngagement = null; if ( engagementTargetMessageInfo && chatMessageItemHasEngagement(item, item.threadInfo.id) ) { inlineEngagement = ( ); } const chatContext = React.useContext(ChatContext); const keyboardState = React.useContext(KeyboardContext); const key = chatMessageItemKey(item); const onPress = React.useCallback(() => { const didDismiss = keyboardState && keyboardState.dismissKeyboardIfShowing(); if (!didDismiss) { toggleFocus(key); } }, [keyboardState, toggleFocus, key]); const overlayContext = React.useContext(OverlayContext); const viewRef = React.useRef>(); const canCreateSidebarFromMessage = useCanCreateSidebarFromMessage( item.threadInfo, engagementTargetMessageInfo, ); const canCreateReactionFromMessage = useCanCreateReactionFromMessage( item.threadInfo, engagementTargetMessageInfo, ); const visibleEntryIDs = React.useMemo(() => { const result = []; if (item.threadCreatedFromMessage || canCreateSidebarFromMessage) { result.push('sidebar'); } return result; }, [item.threadCreatedFromMessage, canCreateSidebarFromMessage]); const openRobotextTooltipModal = React.useCallback( ( x: number, y: number, width: number, height: number, pageX: number, pageY: number, ) => { invariant( verticalBounds, 'verticalBounds should be present in openRobotextTooltipModal', ); const coordinates = { x: pageX, y: pageY, width, height }; const messageTop = pageY; const messageBottom = pageY + height; const boundsTop = verticalBounds.y; const boundsBottom = verticalBounds.y + verticalBounds.height; const belowMargin = 20; const belowSpace = fixedTooltipHeight + belowMargin; const aboveMargin = 30; const aboveSpace = fixedTooltipHeight + aboveMargin; let margin = 0; if ( messageBottom + belowSpace > boundsBottom && messageTop - aboveSpace > boundsTop ) { margin = aboveMargin; } const currentInputBarHeight = chatContext?.chatInputBarHeights.get(item.threadInfo.id) ?? 0; props.navigation.navigate<'RobotextMessageTooltipModal'>({ name: RobotextMessageTooltipModalRouteName, params: { presentedFrom: props.route.key, initialCoordinates: coordinates, verticalBounds, visibleEntryIDs, tooltipLocation: 'fixed', margin, item, chatInputBarHeight: currentInputBarHeight, }, key: getMessageTooltipKey(item), }); }, [ item, props.navigation, props.route.key, verticalBounds, visibleEntryIDs, chatContext, ], ); const onLongPress = React.useCallback(() => { if (keyboardState && keyboardState.dismissKeyboardIfShowing()) { return; } if (visibleEntryIDs.length === 0 && !canCreateReactionFromMessage) { return; } if (!viewRef.current || !verticalBounds) { return; } if (!focused) { toggleFocus(key); } invariant(overlayContext, 'RobotextMessage should have OverlayContext'); overlayContext.setScrollBlockingModalStatus('open'); viewRef.current?.measure(openRobotextTooltipModal); }, [ focused, key, keyboardState, overlayContext, toggleFocus, verticalBounds, viewRef, visibleEntryIDs, openRobotextTooltipModal, canCreateReactionFromMessage, ]); const onLayout = React.useCallback(() => {}, []); const contentAndHeaderOpacity = useContentAndHeaderOpacity(item); + const contentAndHeaderOpacityStyle = useAnimatedStyle(() => { + return { opacity: contentAndHeaderOpacity.value }; + }); const viewStyle: { height?: number } = {}; if (!__DEV__) { // We don't force view height in dev mode because we // want to measure it in Message to see if it's correct viewStyle.height = item.contentHeight; } return ( - + {timestamp} - + {inlineEngagement} ); } const unboundStyles = { sidebar: { marginTop: inlineEngagementCenterStyle.topOffset, marginBottom: -inlineEngagementCenterStyle.topOffset, alignSelf: 'center', }, }; export { RobotextMessage }; diff --git a/native/chat/utils.js b/native/chat/utils.js index ead351864..3520edb55 100644 --- a/native/chat/utils.js +++ b/native/chat/utils.js @@ -1,422 +1,419 @@ // @flow import invariant from 'invariant'; import * as React from 'react'; -import Animated, { +import { type SharedValue, interpolate, runOnJS, useAnimatedStyle, useDerivedValue, interpolateColor, + Extrapolate, } from 'react-native-reanimated'; import { useLoggedInUserInfo } from 'lib/hooks/account-hooks.js'; import { useThreadChatMentionCandidates } from 'lib/hooks/chat-mention-hooks.js'; import { chatMessageItemKey } from 'lib/shared/chat-message-item-utils.js'; import { colorIsDark } from 'lib/shared/color-utils.js'; import { viewerIsMember } from 'lib/shared/thread-utils.js'; import type { ThreadInfo } from 'lib/types/minimally-encoded-thread-permissions-types.js'; import { clusterEndHeight } from './chat-constants.js'; import { ChatContext, useHeightMeasurer } from './chat-context.js'; import { failedSendHeight } from './failed-send.react.js'; import { useNativeMessageListData } from './message-data.react.js'; import { authorNameHeight } from './message-header.react.js'; import { multimediaMessageItemHeight } from './multimedia-message-utils.js'; import { getUnresolvedSidebarThreadInfo } from './sidebar-navigation.js'; import textMessageSendFailed from './text-message-send-failed.js'; import { timestampHeight } from './timestamp.react.js'; import { listLoadingIndicatorHeight } from '../components/list-loading-indicator-utils.js'; import { KeyboardContext } from '../keyboard/keyboard-state.js'; import { OverlayContext } from '../navigation/overlay-context.js'; import { MultimediaMessageTooltipModalRouteName, RobotextMessageTooltipModalRouteName, TextMessageTooltipModalRouteName, } from '../navigation/route-names.js'; import type { ChatMessageInfoItemWithHeight, ChatMessageItemWithHeight, ChatTextMessageInfoItemWithHeight, } from '../types/chat-types.js'; import type { LayoutCoordinates, VerticalBounds, } from '../types/layout-types.js'; import type { AnimatedViewStyle } from '../types/styles.js'; -const { Node, Extrapolate, interpolateNode, sub } = Animated; - function textMessageItemHeight( item: ChatTextMessageInfoItemWithHeight, ): number { const { messageInfo, contentHeight, startsCluster, endsCluster } = item; const { isViewer } = messageInfo.creator; let height = 5 + contentHeight; // 5 from marginBottom in ComposedMessage if (!isViewer && startsCluster) { height += authorNameHeight; } if (endsCluster) { height += clusterEndHeight; } if (textMessageSendFailed(item)) { height += failedSendHeight; } return height; } function messageItemHeight(item: ChatMessageInfoItemWithHeight): number { let height = 0; if (item.messageShapeType === 'text') { height += textMessageItemHeight(item); } else if (item.messageShapeType === 'multimedia') { height += multimediaMessageItemHeight(item); } else { height += item.contentHeight; } if (item.startsConversation) { height += timestampHeight; } return height; } function chatMessageItemHeight(item: ChatMessageItemWithHeight): number { if (item.itemType === 'loader') { return listLoadingIndicatorHeight; } return messageItemHeight(item); } function useMessageTargetParameters( sourceMessage: ChatMessageInfoItemWithHeight, initialCoordinates: LayoutCoordinates, messageListVerticalBounds: VerticalBounds, currentInputBarHeight: number, targetInputBarHeight: number, sidebarThreadInfo: ?ThreadInfo, ): { +position: number, +color: string, } { const messageListData = useNativeMessageListData({ searching: false, userInfoInputArray: [], threadInfo: sidebarThreadInfo, }); const [messagesWithHeight, setMessagesWithHeight] = React.useState>(null); const measureMessages = useHeightMeasurer(); React.useEffect(() => { if (messageListData) { measureMessages( messageListData, sidebarThreadInfo, setMessagesWithHeight, ); } }, [measureMessages, messageListData, sidebarThreadInfo]); const sourceMessageID = sourceMessage.messageInfo?.id; const targetDistanceFromBottom = React.useMemo(() => { if (!messagesWithHeight) { return 0; } let offset = 0; for (const message of messagesWithHeight) { offset += chatMessageItemHeight(message); if (message.messageInfo && message.messageInfo.id === sourceMessageID) { return offset; } } return ( messageListVerticalBounds.height + chatMessageItemHeight(sourceMessage) ); }, [ messageListVerticalBounds.height, messagesWithHeight, sourceMessage, sourceMessageID, ]); if (!sidebarThreadInfo) { return { position: 0, color: sourceMessage.threadInfo.color, }; } const authorNameComponentHeight = !sourceMessage.messageInfo || sourceMessage.messageInfo.creator.isViewer ? 0 : authorNameHeight; const currentDistanceFromBottom = messageListVerticalBounds.height + messageListVerticalBounds.y - initialCoordinates.y + timestampHeight + authorNameComponentHeight + currentInputBarHeight; return { position: targetDistanceFromBottom + targetInputBarHeight - currentDistanceFromBottom, color: sidebarThreadInfo.color, }; } type AnimatedMessageArgs = { +sourceMessage: ChatMessageInfoItemWithHeight, +initialCoordinates: LayoutCoordinates, +messageListVerticalBounds: VerticalBounds, +progressV2: SharedValue, +targetInputBarHeight: ?number, }; function useAnimatedMessageTooltipButton({ sourceMessage, initialCoordinates, messageListVerticalBounds, progressV2, targetInputBarHeight, }: AnimatedMessageArgs): { +style: AnimatedViewStyle, +threadColorOverride: SharedValue, +isThreadColorDarkOverride: ?boolean, } { const chatContext = React.useContext(ChatContext); invariant(chatContext, 'chatContext should be set'); const { currentTransitionSidebarSourceID, setCurrentTransitionSidebarSourceID, chatInputBarHeights, sidebarAnimationType, setSidebarAnimationType, } = chatContext; const loggedInUserInfo = useLoggedInUserInfo(); const chatMentionCandidates = useThreadChatMentionCandidates( sourceMessage.threadInfo, ); const sidebarThreadInfo = React.useMemo( () => getUnresolvedSidebarThreadInfo({ sourceMessage, loggedInUserInfo, chatMentionCandidates, }), [sourceMessage, loggedInUserInfo, chatMentionCandidates], ); const currentInputBarHeight = chatInputBarHeights.get(sourceMessage.threadInfo.id) ?? 0; const keyboardState = React.useContext(KeyboardContext); const newSidebarAnimationType = !currentInputBarHeight || !targetInputBarHeight || keyboardState?.keyboardShowing || !viewerIsMember(sidebarThreadInfo) ? 'fade_source_message' : 'move_source_message'; React.useEffect(() => { setSidebarAnimationType(newSidebarAnimationType); }, [setSidebarAnimationType, newSidebarAnimationType]); const { position: targetPosition, color: targetColor } = useMessageTargetParameters( sourceMessage, initialCoordinates, messageListVerticalBounds, currentInputBarHeight, targetInputBarHeight ?? currentInputBarHeight, sidebarThreadInfo, ); React.useEffect(() => { return () => setCurrentTransitionSidebarSourceID(null); }, [setCurrentTransitionSidebarSourceID]); const [isThreadColorDarkOverride, setThreadColorDarkOverride] = React.useState(null); const setThreadColorBrightness = React.useCallback(() => { const isSourceThreadDark = colorIsDark(sourceMessage.threadInfo.color); const isTargetThreadDark = colorIsDark(targetColor); if (isSourceThreadDark !== isTargetThreadDark) { setThreadColorDarkOverride(isTargetThreadDark); } }, [sourceMessage.threadInfo.color, targetColor]); const threadColorOverride = useDerivedValue(() => { if ( sourceMessage.messageShapeType !== 'text' || !currentTransitionSidebarSourceID ) { return null; } if (progressV2.value === 1) { runOnJS(setThreadColorBrightness)(); } return interpolateColor( progressV2.value, [0, 1], [`#${targetColor}`, `#${sourceMessage.threadInfo.color}`], ); }, [ currentTransitionSidebarSourceID, setThreadColorBrightness, sourceMessage.messageShapeType, sourceMessage.threadInfo.color, targetColor, ]); const messageContainerStyle = useAnimatedStyle(() => { const bottom = interpolate( progressV2.value, [0.3, 1], [targetPosition, 0], Extrapolate.CLAMP, ); return { bottom: currentTransitionSidebarSourceID ? bottom : 0, opacity: currentTransitionSidebarSourceID && sidebarAnimationType === 'fade_source_message' ? 0 : 1, }; - }, [currentTransitionSidebarSourceID, sidebarAnimationType]); + }, [currentTransitionSidebarSourceID, sidebarAnimationType, targetPosition]); return { style: messageContainerStyle, threadColorOverride, isThreadColorDarkOverride, }; } function getMessageTooltipKey(item: ChatMessageInfoItemWithHeight): string { return `tooltip|${chatMessageItemKey(item)}`; } function isMessageTooltipKey(key: string): boolean { return key.startsWith('tooltip|'); } function useOverlayPosition(item: ChatMessageInfoItemWithHeight) { const overlayContext = React.useContext(OverlayContext); invariant(overlayContext, 'should be set'); for (const overlay of overlayContext.visibleOverlays) { if ( (overlay.routeName === MultimediaMessageTooltipModalRouteName || overlay.routeName === TextMessageTooltipModalRouteName || overlay.routeName === RobotextMessageTooltipModalRouteName) && overlay.routeKey === getMessageTooltipKey(item) ) { - return overlay.position; + return overlay.positionV2; } } return undefined; } function useContentAndHeaderOpacity( item: ChatMessageInfoItemWithHeight, -): number | Node { +): SharedValue { const overlayPosition = useOverlayPosition(item); const chatContext = React.useContext(ChatContext); - return React.useMemo( - () => - overlayPosition && + return useDerivedValue(() => { + return overlayPosition && chatContext?.sidebarAnimationType === 'move_source_message' - ? sub( - 1, - interpolateNode(overlayPosition, { - inputRange: [0.05, 0.06], - outputRange: [0, 1], - extrapolate: Extrapolate.CLAMP, - }), + ? 1 - + interpolate( + overlayPosition.value, + [0.05, 0.06], + [0, 1], + Extrapolate.CLAMP, ) - : 1, - [chatContext?.sidebarAnimationType, overlayPosition], - ); + : 1; + }, [chatContext?.sidebarAnimationType, overlayPosition]); } function useDeliveryIconOpacity( item: ChatMessageInfoItemWithHeight, -): number | Node { +): SharedValue { const overlayPosition = useOverlayPosition(item); const chatContext = React.useContext(ChatContext); - return React.useMemo(() => { + return useDerivedValue(() => { if ( !overlayPosition || !chatContext?.currentTransitionSidebarSourceID || chatContext?.sidebarAnimationType === 'fade_source_message' ) { return 1; } - return interpolateNode(overlayPosition, { - inputRange: [0.05, 0.06, 1], - outputRange: [1, 0, 0], - extrapolate: Extrapolate.CLAMP, - }); + return interpolate( + overlayPosition.value, + [0.05, 0.06, 1], + [1, 0, 0], + Extrapolate.CLAMP, + ); }, [ chatContext?.currentTransitionSidebarSourceID, chatContext?.sidebarAnimationType, overlayPosition, ]); } function modifyItemForResultScreen( item: ChatMessageInfoItemWithHeight, ): ChatMessageInfoItemWithHeight { if (item.messageShapeType === 'robotext') { return item; } if (item.messageShapeType === 'multimedia') { return { ...item, startsConversation: false, startsCluster: true, endsCluster: true, messageInfo: { ...item.messageInfo, creator: { ...item.messageInfo.creator, isViewer: false, }, }, }; } return { ...item, startsConversation: false, startsCluster: true, endsCluster: true, messageInfo: { ...item.messageInfo, creator: { ...item.messageInfo.creator, isViewer: false, }, }, }; } export { chatMessageItemHeight, useAnimatedMessageTooltipButton, messageItemHeight, getMessageTooltipKey, isMessageTooltipKey, useContentAndHeaderOpacity, useDeliveryIconOpacity, modifyItemForResultScreen, };