import { RefObject } from 'react'; import React, { useEffect, useRef } from '../../../../lib/teact/teact'; import { getDispatch } from '../../../../lib/teact/teactn'; import { IS_ANDROID, IS_TOUCH_ENV } from '../../../../util/environment'; import windowSize from '../../../../util/windowSize'; import { captureEvents, SwipeDirection } from '../../../../util/captureEvents'; import useFlag from '../../../../hooks/useFlag'; import { preventMessageInputBlur } from '../../helpers/preventMessageInputBlur'; import stopEvent from '../../../../util/stopEvent'; import { REM } from '../../../common/helpers/mediaDimensions'; const ANDROID_KEYBOARD_HIDE_DELAY_MS = 350; const SWIPE_ANIMATION_DURATION = 150; const QUICK_REACTION_DOUBLE_TAP_DELAY = 200; const QUICK_REACTION_AREA_WIDTH = 3 * REM; const QUICK_REACTION_AREA_HEIGHT = Number(REM); const GROUP_MESSAGE_HOVER_ATTRIBUTE = 'data-is-document-group-hover'; export default function useOuterHandlers( selectMessage: (e?: React.MouseEvent, groupedId?: string) => void, containerRef: RefObject, messageId: number, isAlbum: boolean, isInSelectMode: boolean, canReply: boolean, isProtected: boolean, onContextMenu: (e: React.MouseEvent) => void, handleBeforeContextMenu: (e: React.MouseEvent) => void, chatId: string, isContextMenuShown: boolean, contentRef: RefObject, isOwn: boolean, shouldHandleMouseLeave: boolean, ) { const { setReplyingToId, sendDefaultReaction } = getDispatch(); const [isQuickReactionVisible, markQuickReactionVisible, unmarkQuickReactionVisible] = useFlag(); const [isSwiped, markSwiped, unmarkSwiped] = useFlag(); const doubleTapTimeoutRef = useRef(); function handleMouseDown(e: React.MouseEvent) { preventMessageInputBlur(e); handleBeforeContextMenu(e); } function handleMouseMove(e: React.MouseEvent) { const container = contentRef.current; if (!container) return; const { clientX, clientY } = e; const { x, width, y, height, } = container.getBoundingClientRect(); const isVisibleX = Math.abs((isOwn ? (clientX - x) : (x + width - clientX))) < QUICK_REACTION_AREA_WIDTH; const isVisibleY = Math.abs(y + height - clientY) < QUICK_REACTION_AREA_HEIGHT; if (isVisibleX && isVisibleY) { markQuickReactionVisible(); } else { unmarkQuickReactionVisible(); } } function handleSendQuickReaction(e: React.MouseEvent) { const { x, y } = e.currentTarget.getBoundingClientRect(); sendDefaultReaction({ chatId, messageId, x, y, }); } function handleTap(e: React.MouseEvent) { if (IS_ANDROID) { const target = e.target as HTMLDivElement; if (!target.classList.contains('text-content') && !target.classList.contains('Message')) { return; } if (windowSize.getIsKeyboardVisible()) { setTimeout(() => { onContextMenu(e); }, ANDROID_KEYBOARD_HIDE_DELAY_MS); } else { onContextMenu(e); } } } function handleDoubleTap(e: React.MouseEvent) { const { pageX: x, pageY: y } = e; sendDefaultReaction({ chatId, messageId, x, y, }); } function handleClick(e: React.MouseEvent) { if (isInSelectMode) { selectMessage(e); return; } if (!IS_TOUCH_ENV) return; if (doubleTapTimeoutRef.current) { clearInterval(doubleTapTimeoutRef.current); doubleTapTimeoutRef.current = undefined; handleDoubleTap(e); return; } doubleTapTimeoutRef.current = setTimeout(() => { doubleTapTimeoutRef.current = undefined; handleTap(e); }, QUICK_REACTION_DOUBLE_TAP_DELAY); } function handleContextMenu(e: React.MouseEvent) { if (IS_ANDROID) { if ((e.target as HTMLElement).matches('a[href]') || isContextMenuShown) { return; } e.preventDefault(); selectMessage(); } else { onContextMenu(e); } } function handleContainerDoubleClick() { if (IS_TOUCH_ENV) return; setReplyingToId({ messageId }); } function stopPropagation(e: React.MouseEvent) { e.stopPropagation(); } useEffect(() => { if (!IS_TOUCH_ENV || isInSelectMode || !canReply || isContextMenuShown) { return undefined; } let startedAt: number | undefined; return captureEvents(containerRef.current!, { selectorToPreventScroll: '.MessageList', onSwipe: ((e, direction) => { if (direction === SwipeDirection.Left) { if (!startedAt) { startedAt = Date.now(); } markSwiped(); return true; } return false; }), onRelease: () => { if (!startedAt) { return; } setReplyingToId({ messageId }); setTimeout(unmarkSwiped, Math.max(0, SWIPE_ANIMATION_DURATION - (Date.now() - startedAt))); startedAt = undefined; }, }); }, [ containerRef, isInSelectMode, messageId, setReplyingToId, markSwiped, unmarkSwiped, canReply, isContextMenuShown, ]); function handleMouseLeave(e: React.MouseEvent) { unmarkQuickReactionVisible(); if (shouldHandleMouseLeave) handleDocumentGroupMouseLeave(e); } return { handleMouseDown: !isInSelectMode ? handleMouseDown : undefined, handleClick, handleContextMenu: !isInSelectMode ? handleContextMenu : (isProtected ? stopEvent : undefined), handleDoubleClick: !isInSelectMode ? handleContainerDoubleClick : undefined, handleContentDoubleClick: !IS_TOUCH_ENV ? stopPropagation : undefined, handleMouseMove, handleSendQuickReaction, handleMouseLeave, isSwiped, isQuickReactionVisible, handleDocumentGroupMouseEnter, }; } function handleDocumentGroupMouseEnter(e: React.MouseEvent) { const lastGroupElement = getLastElementInDocumentGroup(e.currentTarget); if (lastGroupElement) { lastGroupElement.setAttribute(GROUP_MESSAGE_HOVER_ATTRIBUTE, ''); } } function handleDocumentGroupMouseLeave(e: React.MouseEvent) { const lastGroupElement = getLastElementInDocumentGroup(e.currentTarget); if (lastGroupElement) { lastGroupElement.removeAttribute(GROUP_MESSAGE_HOVER_ATTRIBUTE); } } function getLastElementInDocumentGroup(element: Element) { let current: Element | null = element; do { current = current.nextElementSibling; } while (current && !current.classList.contains('last-in-document-group')); return current; }