diff --git a/app/components/Lightbox.tsx b/app/components/Lightbox.tsx index b1f1ac8afe..8f494031e3 100644 --- a/app/components/Lightbox.tsx +++ b/app/components/Lightbox.tsx @@ -28,6 +28,7 @@ import Fade from "./Fade"; import Button from "./Button"; import CopyToClipboard from "./CopyToClipboard"; import { Separator } from "./Actions"; +import useSwipe from "~/hooks/useSwipe"; export enum LightboxStatus { READY_TO_OPEN, @@ -550,7 +551,8 @@ function Lightbox({ onUpdate, activePos }: Props) { } onSwipeRight={prev} onSwipeLeft={next} - onSwipeUpOrDown={close} + onSwipeUp={close} + onSwipeDown={close} status={status} animation={animation.current} /> @@ -575,7 +577,8 @@ type ImageProps = { onError: () => void; onSwipeRight: () => void; onSwipeLeft: () => void; - onSwipeUpOrDown: () => void; + onSwipeUp: () => void; + onSwipeDown: () => void; status: Status; animation: Animation | null; }; @@ -589,59 +592,21 @@ const Image = forwardRef(function _Image( onError, onSwipeRight, onSwipeLeft, - onSwipeUpOrDown, + onSwipeUp, + onSwipeDown, status, animation, }: ImageProps, ref ) { const { t } = useTranslation(); - const touchXStart = useRef(); - const touchXEnd = useRef(); - const touchYStart = useRef(); - const touchYEnd = useRef(); - const handleTouchStart = (e: React.TouchEvent) => { - touchXStart.current = e.changedTouches[0].screenX; - touchYStart.current = e.changedTouches[0].screenY; - }; - - const handleTouchMove = (e: React.TouchEvent) => { - touchXEnd.current = e.changedTouches[0].screenX; - touchYEnd.current = e.changedTouches[0].screenY; - const dx = touchXEnd.current - (touchXStart.current ?? 0); - const dy = touchYEnd.current - (touchYStart.current ?? 0); - - const swipeRight = dx > 0 && Math.abs(dy) < Math.abs(dx); - if (swipeRight) { - return onSwipeRight(); - } - - const swipeLeft = dx < 0 && Math.abs(dy) < Math.abs(dx); - if (swipeLeft) { - return onSwipeLeft(); - } - - const swipeDown = dy > 0 && Math.abs(dy) > Math.abs(dx); - const swipeUp = dy < 0 && Math.abs(dy) > Math.abs(dx); - if (swipeUp || swipeDown) { - return onSwipeUpOrDown(); - } - }; - - const handleTouchEnd = () => { - touchXStart.current = undefined; - touchXEnd.current = undefined; - touchYStart.current = undefined; - touchYEnd.current = undefined; - }; - - const handleTouchCancel = () => { - touchXStart.current = undefined; - touchXEnd.current = undefined; - touchYStart.current = undefined; - touchYEnd.current = undefined; - }; + const swipeHandlers = useSwipe({ + onSwipeRight, + onSwipeLeft, + onSwipeUp, + onSwipeDown, + }); const [hidden, setHidden] = useState( status.image === null || status.image === ImageStatus.LOADING @@ -673,10 +638,7 @@ const Image = forwardRef(function _Image( alt={alt} animation={animation} onAnimationStart={() => setHidden(false)} - onTouchStart={handleTouchStart} - onTouchMove={handleTouchMove} - onTouchEnd={handleTouchEnd} - onTouchCancel={handleTouchCancel} + {...swipeHandlers} onError={onError} onLoad={onLoad} $hidden={hidden} diff --git a/app/hooks/useSwipe.tsx b/app/hooks/useSwipe.tsx new file mode 100644 index 0000000000..02b38f1be7 --- /dev/null +++ b/app/hooks/useSwipe.tsx @@ -0,0 +1,76 @@ +import { isNumber } from "lodash"; +import { useRef } from "react"; + +type Props = { + onSwipeRight: () => void; + onSwipeLeft: () => void; + onSwipeUp: () => void; + onSwipeDown: () => void; +}; + +export default function useSwipe({ + onSwipeRight, + onSwipeLeft, + onSwipeUp, + onSwipeDown, +}: Props) { + const touchXStart = useRef(); + const touchXEnd = useRef(); + const touchYStart = useRef(); + const touchYEnd = useRef(); + + const resetTouchPoints = () => { + touchXStart.current = undefined; + touchXEnd.current = undefined; + touchYStart.current = undefined; + touchYEnd.current = undefined; + }; + + const onTouchStart = (e: React.TouchEvent) => { + touchXStart.current = e.changedTouches[0].screenX; + touchYStart.current = e.changedTouches[0].screenY; + }; + + const onTouchMove = (e: React.TouchEvent) => { + if (isNumber(touchXStart.current) && isNumber(touchYStart.current)) { + touchXEnd.current = e.changedTouches[0].screenX; + touchYEnd.current = e.changedTouches[0].screenY; + const dx = touchXEnd.current - touchXStart.current; + const dy = touchYEnd.current - touchYStart.current; + + const swipeRight = dx > 0 && Math.abs(dy) < Math.abs(dx); + if (swipeRight) { + resetTouchPoints(); + return onSwipeRight(); + } + + const swipeLeft = dx < 0 && Math.abs(dy) < Math.abs(dx); + if (swipeLeft) { + resetTouchPoints(); + return onSwipeLeft(); + } + + const swipeDown = dy > 0 && Math.abs(dy) > Math.abs(dx); + if (swipeDown) { + resetTouchPoints(); + return onSwipeDown(); + } + + const swipeUp = dy < 0 && Math.abs(dy) > Math.abs(dx); + if (swipeUp) { + resetTouchPoints(); + return onSwipeUp(); + } + } + }; + + const onTouchCancel = () => { + resetTouchPoints(); + }; + + return { + onTouchStart, + onTouchMove, + onTouchCancel, + }; +}