diff --git a/com.twin.app.shoptime/src/actions/mediaActions.js b/com.twin.app.shoptime/src/actions/mediaActions.js index 32d5ca84..abe44e77 100644 --- a/com.twin.app.shoptime/src/actions/mediaActions.js +++ b/com.twin.app.shoptime/src/actions/mediaActions.js @@ -90,18 +90,18 @@ export const finishMediaPreview = () => (dispatch, getState) => { }; /** - * 강제로 modal MediaPanel을 종료합니다 (스택 어디에 있든) + * 강제로 DetailPanel ProductVideo MediaPanel을 종료합니다 (modal/fullscreen 모두) */ export const finishModalMediaForce = () => (dispatch, getState) => { const panels = getState().panels.panels; - const hasModalMediaPanel = panels.some( - (panel) => panel.name === panel_names.MEDIA_PANEL && panel.panelInfo?.modal + const hasProductVideoPanel = panels.some( + (panel) => + panel.name === panel_names.MEDIA_PANEL && + (panel.panelInfo?.modal || panel.panelInfo?.modalContainerId === 'product-video-player') ); - if (hasModalMediaPanel) { - // console.log('[finishModalMediaForce] Force closing modal MediaPanel'); - + if (hasProductVideoPanel) { if (startMediaFocusTimer) { clearTimeout(startMediaFocusTimer); startMediaFocusTimer = null; diff --git a/com.twin.app.shoptime/src/actions/playActions.js b/com.twin.app.shoptime/src/actions/playActions.js index 12c64e23..463074a6 100644 --- a/com.twin.app.shoptime/src/actions/playActions.js +++ b/com.twin.app.shoptime/src/actions/playActions.js @@ -326,10 +326,10 @@ export const shrinkVideoTo1px = () => (dispatch, getState) => { }, }; - console.log('[HomePanel] shrinkVideoTo1px: saving shrinkInfo', { - shrinkInfo: updatedPlayerState.shrinkInfo, - modalStyle: panelInfo.modalStyle, - }); + // console.log('[HomePanel] shrinkVideoTo1px: saving shrinkInfo', { + // shrinkInfo: updatedPlayerState.shrinkInfo, + // modalStyle: panelInfo.modalStyle, + // }); dispatch( updatePanel({ @@ -359,11 +359,11 @@ export const expandVideoFrom1px = () => (dispatch, getState) => { const panelInfo = shrunkModalPlayerPanel.panelInfo; const shrinkInfo = panelInfo.playerState?.shrinkInfo; - console.log('[HomePanel] expandVideoFrom1px: expanding video', { - hasShrinkInfo: !!shrinkInfo, - hasModalStyle: !!shrinkInfo?.modalStyle, - hasModalContainerId: !!shrinkInfo?.modalContainerId, - }); + // console.log('[HomePanel] expandVideoFrom1px: expanding video', { + // hasShrinkInfo: !!shrinkInfo, + // hasModalStyle: !!shrinkInfo?.modalStyle, + // hasModalContainerId: !!shrinkInfo?.modalContainerId, + // }); const updatedPanelInfo = { ...panelInfo, @@ -378,7 +378,7 @@ export const expandVideoFrom1px = () => (dispatch, getState) => { }), }; - console.log('[HomePanel] expandVideoFrom1px: updated panelInfo shouldShrinkTo1px=false, modalStyle restored, skipModalStyleRecalculation=true'); + // console.log('[HomePanel] expandVideoFrom1px: updated panelInfo shouldShrinkTo1px=false, modalStyle restored, skipModalStyleRecalculation=true'); dispatch( updatePanel({ diff --git a/com.twin.app.shoptime/src/components/VideoPlayer/MediaPlayer.v2.jsx b/com.twin.app.shoptime/src/components/VideoPlayer/MediaPlayer.v2.jsx index eac53ba0..7480d61e 100644 --- a/com.twin.app.shoptime/src/components/VideoPlayer/MediaPlayer.v2.jsx +++ b/com.twin.app.shoptime/src/components/VideoPlayer/MediaPlayer.v2.jsx @@ -112,6 +112,7 @@ const MediaPlayerV2 = forwardRef((props, ref) => { onLoadedData, onLoadedMetadata, onDurationChange, + setApiProvider, // Spotlight spotlightId = 'mediaPlayerV2', @@ -470,6 +471,23 @@ const MediaPlayerV2 = forwardRef((props, ref) => { getVideoNode: () => videoRef.current, }), [play, pause, seek, getMediaState, showControls, hideControls, toggleControls, controlsVisible]); + // ========== setApiProvider 호출 ========== + useEffect(() => { + if (setApiProvider && typeof setApiProvider === 'function') { + setApiProvider({ + play, + pause, + seek, + getMediaState, + showControls, + hideControls, + toggleControls, + areControlsVisible: () => controlsVisible, + getVideoNode: () => videoRef.current, + }); + } + }, [setApiProvider, play, pause, seek, getMediaState, showControls, hideControls, toggleControls]); + // ========== Video Props ========== const videoProps = useMemo(() => { const baseProps = { @@ -671,11 +689,13 @@ MediaPlayerV2.propTypes = { style: PropTypes.object, modalClassName: PropTypes.string, modalScale: PropTypes.number, + setApiProvider: PropTypes.func, // 패널 정보 panelInfo: PropTypes.shape({ modal: PropTypes.bool, modalContainerId: PropTypes.string, + modalClassName: PropTypes.string, isPaused: PropTypes.bool, showUrl: PropTypes.string, thumbnailUrl: PropTypes.string, diff --git a/com.twin.app.shoptime/src/views/DetailPanel/DetailPanel.jsx b/com.twin.app.shoptime/src/views/DetailPanel/DetailPanel.jsx index 3909f40b..42bd6c2a 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/DetailPanel.jsx +++ b/com.twin.app.shoptime/src/views/DetailPanel/DetailPanel.jsx @@ -22,6 +22,7 @@ import { getMainCategoryDetail, getMainYouMayLike, } from '../../actions/mainActions'; +import { finishModalMediaForce } from '../../actions/mediaActions'; import { popPanel, updatePanel, @@ -141,11 +142,19 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) { Spotlight.focus('spotlightId_backBtn'); }, []); + useEffect(() => { + return () => { + dispatch(finishModalMediaForce()); + }; + }, [dispatch]); + const onClick = useCallback( (isCancelClick) => (ev) => { fp.pipe( () => { dispatch(clearAllToasts()); // BuyOption Toast 포함 모든 토스트 제거 + dispatch(pauseFullscreenVideo()); // PLAYER_PANEL 비디오 중지 + dispatch(finishModalMediaForce()); // MEDIA_PANEL(ProductVideo) 강제 종료 dispatch(finishVideoPreview()); dispatch(popPanel(panel_names.DETAIL_PANEL)); }, diff --git a/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx b/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx index 63ca9a53..ca4e8058 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx +++ b/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx @@ -27,7 +27,7 @@ import arrowDown import indicatorDefaultImage from '../../../../assets/images/img-thumb-empty-144@3x.png'; // import { pushPanel } from '../../../actions/panelActions'; -import { minimizeModalMedia } from '../../../actions/mediaActions'; +import { minimizeModalMedia, restoreModalMedia } from '../../../actions/mediaActions'; import { pauseFullscreenVideo } from '../../../actions/playActions'; import { resetShowAllReviews } from '../../../actions/productActions'; import { @@ -197,6 +197,9 @@ export default function ProductAllSection({ // ProductAllSection 초기 로딩 시 Skeleton 표시를 위한 상태 const [isInitialLoading, setIsInitialLoading] = useState(true); + // 스크롤 위치에 따른 MediaPanel 제어 상태 + const [shouldMinimizeMedia, setShouldMinimizeMedia] = useState(false); + useEffect(() => { const toggleQRCode = () => { if (isShowQRCode) { @@ -498,7 +501,7 @@ export default function ProductAllSection({ // ProductVideo V1 전용 - MediaPanel minimize 포함 const handleScrollToImagesV1 = useCallback(() => { // 1. MediaPanel을 1px로 축소하여 포커스 충돌 방지 - dispatch(minimizeModalMedia()); + // dispatch(minimizeModalMedia()); // 2. 스크롤 이동 scrollToSection('scroll-marker-after-video'); @@ -657,6 +660,7 @@ export default function ProductAllSection({ [scrollToSection] ); const scrollPositionRef = useRef(0); + const prevScrollPositionRef = useRef(0); // 이전 스크롤 위치 추적 const handleArrowClickAlternative = useCallback(() => { const currentHeight = scrollPositionRef.current; @@ -677,17 +681,40 @@ export default function ProductAllSection({ } }, [documentHeight, scrollTop]); - const handleScroll = useCallback( + // const handleScroll = useCallback( + // (e) => { + // scrollPositionRef.current = e.scrollTop; + // if (documentHeight) { + // const isAtBottom = + // scrollPositionRef.current + 944 >= + // documentHeight + (youMayAlsoLikelRef.current?.scrollHeight || 0); + // if (isAtBottom !== isBottom) { + // setIsBottom(isAtBottom); + // } + // } + // }, + // [documentHeight, isBottom] + // ); + + // 스크롤 멈추었을 때만 호출 (성능 최적화) + const handleScrollStop = useCallback( (e) => { - scrollPositionRef.current = e.scrollTop; + const currentScrollTop = e.scrollTop; + scrollPositionRef.current = currentScrollTop; + if (documentHeight) { const isAtBottom = - scrollPositionRef.current + 944 >= + currentScrollTop + 944 >= documentHeight + (youMayAlsoLikelRef.current?.scrollHeight || 0); if (isAtBottom !== isBottom) { setIsBottom(isAtBottom); } } + + console.log('📍 [ProductAllSection] 스크롤 멈춤 - 위치:', currentScrollTop); + const shouldMinimize = currentScrollTop > 0; + console.log('📍 [ProductAllSection] setShouldMinimizeMedia 호출:', shouldMinimize); + setShouldMinimizeMedia(shouldMinimize); // state 업데이트만 }, [documentHeight, isBottom] ); @@ -710,6 +737,18 @@ export default function ProductAllSection({ ); }, [hasReviews, hasYouMayAlsoLike]); + // 스크롤 위치에 따른 MediaPanel 제어 (비디오 재생 중에는 자동 제어 안함 - unmount 시에만 정리) + // useEffect(() => { + // console.log('📍 [ProductAllSection] useEffect 실행 - shouldMinimizeMedia:', shouldMinimizeMedia); + // if (shouldMinimizeMedia) { + // console.log('📍 [ProductAllSection] MediaPanel minimize 호출 (true)'); + // dispatch(minimizeModalMedia()); + // } else { + // console.log('📍 [ProductAllSection] MediaPanel restore 호출 (false)'); + // dispatch(restoreModalMedia()); + // } + // }, [shouldMinimizeMedia, dispatch]); + //spot관련 useEffect(() => { if (onReady && isOnRender) { @@ -768,9 +807,11 @@ export default function ProductAllSection({ // }; // }, []); - // 컴포넌트 unmount 시 모든 timer cleanup + // 컴포넌트 unmount 시 timer cleanup useEffect(() => { return () => { + console.log('[ProductAllSection] unmount - cleanup 시작'); + // QR code timer cleanup if (timerRef.current) { clearTimeout(timerRef.current); @@ -788,7 +829,7 @@ export default function ProductAllSection({ scrollToImagesTimeoutRef.current = null; } - // console.log('[ProductAllSection] All timers cleaned up on unmount'); + console.log('[ProductAllSection] cleanup 완료 on unmount'); }; }, []); @@ -1019,7 +1060,8 @@ export default function ProductAllSection({ spotlightId="main-content-scroller" spotlightDisabled={false} spotlightRestrict="none" - onScroll={handleScroll} + // onScroll={handleScroll} + onScrollStop={handleScrollStop} onClick={handleScrollContainerClick} >