diff --git a/native/chat/multimedia-message-tooltip-button.react.js b/native/chat/multimedia-message-tooltip-button.react.js --- a/native/chat/multimedia-message-tooltip-button.react.js +++ b/native/chat/multimedia-message-tooltip-button.react.js @@ -3,12 +3,21 @@ import * as React from 'react'; import Animated from 'react-native-reanimated'; +import { localIDPrefix } from 'lib/shared/message-utils'; +import { useCanCreateReactionFromMessage } from 'lib/shared/reaction-utils'; +import type { SetState } from 'lib/types/hook-types'; + import type { AppNavigationProp } from '../navigation/app-navigator.react'; import type { TooltipRoute } from '../navigation/tooltip.react'; import { useSelector } from '../redux/redux-utils'; import { TooltipInlineEngagement } from './inline-engagement.react'; import { InnerMultimediaMessage } from './inner-multimedia-message.react'; import { MessageHeader } from './message-header.react'; +import { + useSendReaction, + useReactionSelectionPopoverPosition, +} from './reaction-message-utils'; +import ReactionSelectionPopover from './reaction-selection-popover.react'; import SidebarInputBarHeightMeasurer from './sidebar-input-bar-height-measurer.react'; import { useAnimatedMessageTooltipButton } from './utils'; @@ -23,10 +32,12 @@ +route: TooltipRoute<'MultimediaMessageTooltipModal'>, +progress: Node, +isOpeningSidebar: boolean, + +setHideTooltip: SetState, }; function MultimediaMessageTooltipButton(props: Props): React.Node { + const { navigation, progress, isOpeningSidebar, setHideTooltip } = props; + const windowWidth = useSelector(state => state.dimensions.width); - const { progress } = props; const [ sidebarInputBarHeight, @@ -36,7 +47,13 @@ setSidebarInputBarHeight(height); }, []); - const { item, verticalBounds, initialCoordinates } = props.route.params; + const { + item, + verticalBounds, + initialCoordinates, + margin, + } = props.route.params; + const { style: messageContainerStyle } = useAnimatedMessageTooltipButton({ sourceMessage: item, initialCoordinates, @@ -61,8 +78,6 @@ }; }, [initialCoordinates.height, initialCoordinates.x, progress, windowWidth]); - const { navigation, isOpeningSidebar } = props; - const inlineEngagement = React.useMemo(() => { if (!item.threadCreatedFromMessage) { return null; @@ -79,6 +94,46 @@ ); }, [initialCoordinates, isOpeningSidebar, item, progress, windowWidth]); + const { messageInfo, threadInfo, reactions } = item; + const nextLocalID = useSelector(state => state.nextLocalID); + const localID = `${localIDPrefix}${nextLocalID}`; + + const canCreateReactionFromMessage = useCanCreateReactionFromMessage( + threadInfo, + messageInfo, + ); + + const sendReaction = useSendReaction(messageInfo.id, localID, threadInfo.id); + + const reactionSelectionPopoverPosition = useReactionSelectionPopoverPosition({ + initialCoordinates, + verticalBounds, + margin, + }); + + const reactionSelectionPopover = React.useMemo(() => { + if (!canCreateReactionFromMessage) { + return null; + } + + return ( + + ); + }, [ + canCreateReactionFromMessage, + reactionSelectionPopoverPosition, + reactions, + sendReaction, + setHideTooltip, + ]); + return ( + {reactionSelectionPopover} , + +setHideTooltip: SetState, + +reactionSelectionPopoverContainerStyle: ViewStyle, + +sendReaction: ( + reaction: string, + action: 'add_reaction' | 'remove_reaction', + ) => mixed, +}; + +function ReactionSelectionPopover( + props: ReactionSelectionPopoverProps, +): React.Node { + const { + reactions, + setHideTooltip, + reactionSelectionPopoverContainerStyle, + sendReaction, + } = props; + + const styles = useStyles(unboundStyles); + + const containerStyle = React.useMemo( + () => [ + styles.reactionSelectionPopoverContainer, + reactionSelectionPopoverContainerStyle, + ], + [ + reactionSelectionPopoverContainerStyle, + styles.reactionSelectionPopoverContainer, + ], + ); + + const onPressDefaultEmoji = React.useCallback( + (emoji: string) => { + const reactionInput = emoji; + const viewerReacted = !!reactions.get(reactionInput)?.viewerReacted; + const action = viewerReacted ? 'remove_reaction' : 'add_reaction'; + + sendReaction(reactionInput, action); + setHideTooltip(true); + }, + [reactions, sendReaction, setHideTooltip], + ); + + const defaultEmojis = React.useMemo(() => { + const defaultEmojisData = ['❤️', '😆', '😮', '😠', '👍']; + + return defaultEmojisData.map(emoji => ( + onPressDefaultEmoji(emoji)}> + + {emoji} + + + )); + }, [ + onPressDefaultEmoji, + styles.reactionSelectionItemContainer, + styles.reactionSelectionItemEmoji, + ]); + + return {defaultEmojis}; +} + +const unboundStyles = { + reactionSelectionPopoverContainer: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: 'tooltipBackground', + padding: 8, + borderRadius: 8, + flex: 1, + }, + reactionSelectionItemContainer: { + backgroundColor: 'reactionSelectionPopoverItemBackground', + justifyContent: 'center', + alignItems: 'center', + padding: 8, + borderRadius: 20, + width: 40, + height: 40, + marginRight: 12, + }, + reactionSelectionItemEmoji: { + fontSize: 18, + }, +}; + +export default ReactionSelectionPopover; diff --git a/native/chat/robotext-message-tooltip-button.react.js b/native/chat/robotext-message-tooltip-button.react.js --- a/native/chat/robotext-message-tooltip-button.react.js +++ b/native/chat/robotext-message-tooltip-button.react.js @@ -3,11 +3,20 @@ import * as React from 'react'; import Animated from 'react-native-reanimated'; +import { localIDPrefix } from 'lib/shared/message-utils'; +import { useCanCreateReactionFromMessage } from 'lib/shared/reaction-utils'; +import type { SetState } from 'lib/types/hook-types'; + import type { AppNavigationProp } from '../navigation/app-navigator.react'; import type { TooltipRoute } from '../navigation/tooltip.react'; import { useSelector } from '../redux/redux-utils'; import { TooltipInlineEngagement } from './inline-engagement.react'; import { InnerRobotextMessage } from './inner-robotext-message.react'; +import { + useSendReaction, + useReactionSelectionPopoverPosition, +} from './reaction-message-utils'; +import ReactionSelectionPopover from './reaction-selection-popover.react'; import SidebarInputBarHeightMeasurer from './sidebar-input-bar-height-measurer.react'; import { Timestamp } from './timestamp.react'; import { useAnimatedMessageTooltipButton } from './utils'; @@ -21,9 +30,11 @@ +route: TooltipRoute<'RobotextMessageTooltipModal'>, +progress: Node, +isOpeningSidebar: boolean, + +setHideTooltip: SetState, }; function RobotextMessageTooltipButton(props: Props): React.Node { - const { progress } = props; + const { navigation, progress, isOpeningSidebar, setHideTooltip } = props; + const windowWidth = useSelector(state => state.dimensions.width); const [ @@ -34,7 +45,13 @@ setSidebarInputBarHeight(height); }, []); - const { item, verticalBounds, initialCoordinates } = props.route.params; + const { + item, + verticalBounds, + initialCoordinates, + margin, + } = props.route.params; + const { style: messageContainerStyle } = useAnimatedMessageTooltipButton({ sourceMessage: item, initialCoordinates, @@ -59,8 +76,6 @@ }; }, [initialCoordinates.height, initialCoordinates.x, progress, windowWidth]); - const { navigation, isOpeningSidebar } = props; - const inlineEngagement = React.useMemo(() => { if (!item.threadCreatedFromMessage) { return null; @@ -77,6 +92,46 @@ ); }, [initialCoordinates, isOpeningSidebar, item, progress, windowWidth]); + const { messageInfo, threadInfo, reactions } = item; + const nextLocalID = useSelector(state => state.nextLocalID); + const localID = `${localIDPrefix}${nextLocalID}`; + + const canCreateReactionFromMessage = useCanCreateReactionFromMessage( + threadInfo, + messageInfo, + ); + + const sendReaction = useSendReaction(messageInfo.id, localID, threadInfo.id); + + const reactionSelectionPopoverPosition = useReactionSelectionPopoverPosition({ + initialCoordinates, + verticalBounds, + margin, + }); + + const reactionSelectionPopover = React.useMemo(() => { + if (!canCreateReactionFromMessage) { + return null; + } + + return ( + + ); + }, [ + canCreateReactionFromMessage, + reactionSelectionPopoverPosition, + reactions, + sendReaction, + setHideTooltip, + ]); + return ( + {reactionSelectionPopover} {inlineEngagement} diff --git a/native/chat/text-message-tooltip-button.react.js b/native/chat/text-message-tooltip-button.react.js --- a/native/chat/text-message-tooltip-button.react.js +++ b/native/chat/text-message-tooltip-button.react.js @@ -3,6 +3,10 @@ import * as React from 'react'; import Animated from 'react-native-reanimated'; +import { localIDPrefix } from 'lib/shared/message-utils'; +import { useCanCreateReactionFromMessage } from 'lib/shared/reaction-utils'; +import type { SetState } from 'lib/types/hook-types'; + import type { AppNavigationProp } from '../navigation/app-navigator.react'; import type { TooltipRoute } from '../navigation/tooltip.react'; import { useSelector } from '../redux/redux-utils'; @@ -11,6 +15,11 @@ import { MessageHeader } from './message-header.react'; import { MessageListContextProvider } from './message-list-types'; import { MessagePressResponderContext } from './message-press-responder-context'; +import { + useSendReaction, + useReactionSelectionPopoverPosition, +} from './reaction-message-utils'; +import ReactionSelectionPopover from './reaction-selection-popover.react'; import SidebarInputBarHeightMeasurer from './sidebar-input-bar-height-measurer.react'; import { useAnimatedMessageTooltipButton } from './utils'; @@ -23,9 +32,11 @@ +route: TooltipRoute<'TextMessageTooltipModal'>, +progress: Node, +isOpeningSidebar: boolean, + +setHideTooltip: SetState, }; function TextMessageTooltipButton(props: Props): React.Node { - const { progress } = props; + const { navigation, progress, isOpeningSidebar, setHideTooltip } = props; + const windowWidth = useSelector(state => state.dimensions.width); const [ @@ -36,7 +47,13 @@ setSidebarInputBarHeight(height); }, []); - const { item, verticalBounds, initialCoordinates } = props.route.params; + const { + item, + verticalBounds, + initialCoordinates, + margin, + } = props.route.params; + const { style: messageContainerStyle, threadColorOverride, @@ -66,7 +83,6 @@ }, [initialCoordinates.height, initialCoordinates.x, progress, windowWidth]); const threadID = item.threadInfo.id; - const { navigation, isOpeningSidebar } = props; const messagePressResponderContext = React.useMemo( () => ({ @@ -90,6 +106,47 @@ /> ); }, [initialCoordinates, isOpeningSidebar, item, progress, windowWidth]); + + const { messageInfo, threadInfo, reactions } = item; + const nextLocalID = useSelector(state => state.nextLocalID); + const localID = `${localIDPrefix}${nextLocalID}`; + + const canCreateReactionFromMessage = useCanCreateReactionFromMessage( + threadInfo, + messageInfo, + ); + + const sendReaction = useSendReaction(messageInfo.id, localID, threadInfo.id); + + const reactionSelectionPopoverPosition = useReactionSelectionPopoverPosition({ + initialCoordinates, + verticalBounds, + margin, + }); + + const reactionSelectionPopover = React.useMemo(() => { + if (!canCreateReactionFromMessage) { + return null; + } + + return ( + + ); + }, [ + canCreateReactionFromMessage, + reactionSelectionPopoverPosition, + reactions, + sendReaction, + setHideTooltip, + ]); + return ( + {reactionSelectionPopover} diff --git a/native/navigation/tooltip.react.js b/native/navigation/tooltip.react.js --- a/native/navigation/tooltip.react.js +++ b/native/navigation/tooltip.react.js @@ -111,6 +111,7 @@ ...Base, +progress: Node, +isOpeningSidebar: boolean, + +setHideTooltip: SetState, }; type TooltipProps = { ...Base, @@ -510,6 +511,7 @@ ...navAndRouteForFlow, progress: position, isOpeningSidebar, + setHideTooltip, }; const itemsStyles = [styles.items, styles.itemsFixed]; diff --git a/native/themes/colors.js b/native/themes/colors.js --- a/native/themes/colors.js +++ b/native/themes/colors.js @@ -68,6 +68,7 @@ panelSecondaryForegroundBorder: '#CCCCCC', purpleLink: '#7E57C2', purpleButton: '#7E57C2', + reactionSelectionPopoverItemBackground: '#404040', redText: '#F53100', spoiler: '#33332C', tabBarAccent: '#7E57C2', @@ -151,6 +152,7 @@ panelSecondaryForegroundBorder: '#666666', purpleLink: '#AE94DB', purpleButton: '#7E57C2', + reactionSelectionPopoverItemBackground: '#404040', redText: '#F53100', spoiler: '#33332C', tabBarAccent: '#AE94DB',