import * as React from 'react'; import { FC, useState, useRef, useEffect } from 'react'; import { useStore } from '../../../../services/store'; import { IMedia } from '../../../../types'; import Controls from '../components/Controls'; import MediaContainer from '../components/MediaContainer'; import { style } from 'typestyle'; import ShopThisLookProductList, { ShopThisLookProductListHandle } from '../../../ShopThisLook/ProductList'; import MediaProgress from '../components/MediaProgress'; import useTapNavigation from '../../../../hooks/useTapNavigation'; import useSwipeNavigation from '../../../../hooks/useSwipeNavigation'; import NextIndicator from '../components/NextIndicator'; import { mobilePlayerPackshotHeightPercent, productListZIndex } from '../../../consts'; import usePlayerNavigation, { PLAYER_ANIMATION_DURATION_SEC, } from '../../../../hooks/usePlayerNavigation'; import useWheelNavigation from '../../../../hooks/useWheelNavigation'; import { useDialogFocusTrap } from '../../../../hooks/useDialogFocusTrap'; import { useAccessibilityMessage } from '../../../../hooks/useAccessibilityMessage'; import { usePlayerKeyboardNavigation } from '../../../../hooks/usePlayerKeyboardNavigation'; import Spinner from '../../../Icons/Spinner'; interface MobilePlayerProps { post: IMedia; settings: any; totalPosts: number; onClose: () => void; onPostChange: (post: IMedia | null) => void; classPrefix: string; } const MobilePlayer: FC = ({ post, settings, totalPosts, onClose, onPostChange, classPrefix, }) => { const store = useStore(); const [showList, setShowList] = useState(false); const containerRef = useRef(null); const productListRef = useRef(null); const swipeDirection: 'horizontal' | 'vertical' = settings.player_swipe_direction === 'horizontal' ? 'horizontal' : 'vertical'; const isVertical = swipeDirection === 'vertical'; const { currentDisplayPost, nextDisplayPost, prevDisplayPost, isMuted, isPlaying, isVisible, isClosing, showNextIndicator, slideOffset, isSliding, slideAxis, handleClose, handleNextPost, handlePreviousPost, toggleMute, togglePlay, } = usePlayerNavigation({ post, onClose, onPostChange, swipeDirection }); const medias = store.data.content.medias; const toggleList = () => setShowList((prev) => !prev); // Focus trap keeps Tab navigation inside the dialog. useDialogFocusTrap({ active: isVisible, containerRef }); // Screen-reader live-region announcements when navigating between posts. const { accessibilityMessage, announce } = useAccessibilityMessage(); const isFirstAnnouncementRef = useRef(true); useEffect(() => { if (isFirstAnnouncementRef.current) { isFirstAnnouncementRef.current = false; return; } announce( `Post ${currentDisplayPost.postIndex + 1} of ${medias.length}` + (currentDisplayPost.username ? `, by ${currentDisplayPost.username}` : ''), ); // eslint-disable-next-line react-hooks/exhaustive-deps }, [currentDisplayPost.id]); usePlayerKeyboardNavigation({ containerRef, isVertical, currentPostType: currentDisplayPost.type, handleClose, handleNextPost, handlePreviousPost, togglePlay, toggleMute, }); // Tap navigation is disabled in vertical mode — swipe and wheel handle all navigation there. // In vertical mode, tapping a video will instead toggle play/pause via the video's own onClick. useTapNavigation({ onNext: handleNextPost, onPrevious: handlePreviousPost, disabled: showList || isVertical, containerRef, axis: swipeDirection, }); useSwipeNavigation({ containerRef, onSwipeLeft: handleNextPost, onSwipeRight: handlePreviousPost, onSwipeUp: handleNextPost, onSwipeDown: handlePreviousPost, disabled: showList, threshold: 75, axis: swipeDirection, }); // Mouse-wheel / trackpad scroll navigation (vertical mode only) useWheelNavigation({ containerRef, onScrollDown: handleNextPost, onScrollUp: handlePreviousPost, disabled: showList || !isVertical, }); const animationClass = isClosing ? styles.closing : isVisible ? styles.visible : styles.hidden; const handleOverlayClick = () => productListRef.current?.close(); return (