diff --git a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerOverlay/PlayerOverlayContents.jsx b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerOverlay/PlayerOverlayContents.jsx index 36052be0..a2fcff54 100644 --- a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerOverlay/PlayerOverlayContents.jsx +++ b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerOverlay/PlayerOverlayContents.jsx @@ -18,7 +18,7 @@ import css from './PlayerOverlayContents.module.less'; const SpottableBtn = Spottable('button'); -const Container = SpotlightContainerDecorator({ enterTo: 'default-element' }, 'div'); +const Container = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div'); function PlayerOverlayContents({ type, diff --git a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx index 95189c96..dc6d7c2f 100644 --- a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx +++ b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx @@ -64,7 +64,7 @@ import TabContainerV2 from './PlayerTabContents/v2/TabContainer.v2'; // import ShopNowButton from './PlayerTabContents/v2/ShopNowButton'; const Container = SpotlightContainerDecorator( - { enterTo: 'default-element', preserveld: true }, + { enterTo: 'last-focused', preserveId: true }, 'div' ); @@ -172,6 +172,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props }); const videoPlayer = useRef(null); + const prevModal = usePrevious(panelInfo?.modal); const [playListInfo, setPlayListInfo] = USE_STATE('playListInfo', ''); const [shopNowInfo, setShopNowInfo] = USE_STATE('shopNowInfo'); const [backupInitialIndex, setBackupInitialIndex] = USE_STATE('backupInitialIndex', 0); @@ -369,6 +370,25 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props } }, [panelInfo?.isPaused]); + // Modal 상태 변화 감지 (true → false) + useEffect(() => { + if (prevModal !== undefined && prevModal === true && panelInfo?.modal === false) { + console.log('[PlayerPanel] 🔄 Modal 상태 변화: true → false (전체화면 모드로 복귀)'); + console.log('[PlayerPanel] 🎯 포커스 복원 준비 - lastFocusedTargetId:', panelInfo?.lastFocusedTargetId); + + // DetailPanel에서 복귀 시 포커스 복원 + const lastFocusedTargetId = panelInfo?.lastFocusedTargetId; + + if (lastFocusedTargetId) { + // ShopNowContents가 렌더링될 때까지 대기 후 포커스 복원 + setTimeout(() => { + console.log('[PlayerPanel] 🔍 800ms 후 포커스 복원 시도:', lastFocusedTargetId); + Spotlight.focus(lastFocusedTargetId); + }, 800); + } + } + }, [panelInfo?.modal, prevModal, panelInfo?.lastFocusedTargetId]); + // creating live log params useEffect(() => { if (currentLiveShowInfo && Object.keys(currentLiveShowInfo).length > 0) { @@ -1867,6 +1887,8 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props const [initialEnterV2, setInitialEnterV2] = USE_STATE('initialEnterV2', true); const timerId = useRef(null); const timerIdV2 = useRef(null); + const timerIdTabAutoAdvance = useRef(null); + const prevTabIndexV2 = useRef(null); const showSideContents = useMemo(() => { return ( @@ -1949,45 +1971,76 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props const resetTimerV2 = useCallback( (timeout) => { - // console.log('[TabContainerV2] resetTimerV2 호출', timeout); + console.log('[TabContainerV2] resetTimerV2 호출', timeout); if (timerIdV2.current) { - // console.log('[TabContainerV2] 기존 타이머 클리어'); + console.log('[TabContainerV2] 기존 타이머 클리어'); clearTimerV2(); } if (initialEnterV2) { - // console.log('[TabContainerV2] initialEnterV2 false로 변경'); + console.log('[TabContainerV2] initialEnterV2 false로 변경'); setInitialEnterV2(false); } timerIdV2.current = setTimeout(() => { - // console.log('[TabContainerV2] 타이머 실행 - belowContentsVisible false로 변경'); + console.log('[TabContainerV2] 타이머 실행 - belowContentsVisible false로 변경 (30초 경과)'); setBelowContentsVisible(false); }, timeout); }, [clearTimerV2, initialEnterV2, setInitialEnterV2, setBelowContentsVisible] ); + const clearTimerTabAutoAdvance = useCallback(() => { + clearTimeout(timerIdTabAutoAdvance.current); + timerIdTabAutoAdvance.current = null; + }, []); + + const resetTimerTabAutoAdvance = useCallback( + (timeout) => { + if (timerIdTabAutoAdvance.current) { + clearTimerTabAutoAdvance(); + } + + timerIdTabAutoAdvance.current = setTimeout(() => { + setTabIndexV2(2); + }, timeout); + }, + [clearTimerTabAutoAdvance] + ); + // Redux로 오버레이 숨김 useEffect(() => { if (shouldHideOverlays) { console.log('[PlayerPanel] shouldHideOverlays true - 오버레이 숨김'); setSideContentsVisible(false); + console.log('[setBelowContentsVisible] Redux로 오버레이 숨김 - false로 변경'); setBelowContentsVisible(false); if (videoPlayer.current?.hideControls) { videoPlayer.current.hideControls(); } + // 모든 타이머 클리어 + if (timerId.current) { + clearTimer(); + } + if (timerIdV2.current) { + clearTimerV2(); + } + if (timerIdTabAutoAdvance.current) { + clearTimerTabAutoAdvance(); + } + dispatch(resetPlayerOverlays()); } - }, [shouldHideOverlays, dispatch]); + }, [shouldHideOverlays, dispatch, clearTimer, clearTimerV2, clearTimerTabAutoAdvance]); // Redux로 오버레이 표시 useEffect(() => { if (shouldShowOverlays) { console.log('[PlayerPanel] shouldShowOverlays true - 오버레이 표시'); setSideContentsVisible(true); + console.log('[setBelowContentsVisible] Redux로 오버레이 표시 - true로 변경'); setBelowContentsVisible(true); if (videoPlayer.current?.showControls) { @@ -2001,12 +2054,25 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props // PlayerPanel이 최상단이 될 때 오버레이 표시 (DetailPanel에서 복귀) useEffect(() => { if (isOnTop && !panelInfo.modal && !videoVerticalVisible) { - console.log('[PlayerPanel] isOnTop true - 오버레이 표시'); + console.log('[PlayerPanel] ✅ DetailPanel에서 복귀함! - 오버레이 표시'); setSideContentsVisible(true); + console.log('[setBelowContentsVisible] DetailPanel에서 복귀 - true로 변경'); setBelowContentsVisible(true); // VideoPlayer가 belowContentsVisible prop을 감지해서 자동으로 controls 표시함 + + // DetailPanel에서 복귀 시 포커스 복원 시도 + const lastFocusedTargetId = panelInfo?.lastFocusedTargetId; + console.log('[PlayerPanel] 🎯 DetailPanel 복귀 - lastFocusedTargetId:', lastFocusedTargetId); + + if (lastFocusedTargetId) { + // ShopNowContents가 렌더링될 때까지 잠시 대기 후 포커스 복원 + setTimeout(() => { + console.log('[PlayerPanel] 🔍 500ms 후 포커스 복원 시도:', lastFocusedTargetId); + Spotlight.focus(lastFocusedTargetId); + }, 500); + } } - }, [isOnTop, panelInfo.modal, videoVerticalVisible]); + }, [isOnTop, panelInfo.modal, videoVerticalVisible, panelInfo?.lastFocusedTargetId]); useEffect(() => { // tabContainerVersion === 1일 때만 실행 @@ -2055,54 +2121,71 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props }; }, [sideContentsVisible]); - // TabContainerV2 자동 닫기 + // TabContainerV2 자동 닫기 (tabIndex 1 → 2 변경 감지) useEffect(() => { // tabContainerVersion === 2일 때만 실행 - if (tabContainerVersion !== 2) return; - - // console.log('[TabContainerV2] useEffect 시작', { - // showBelowContents, - // videoVerticalVisible, - // initialEnterV2, - // }); - - const node = document.querySelector(`[data-spotlight-id=${TAB_CONTAINER_V2_SPOTLIGHT_ID}]`); - // console.log('[TabContainerV2] DOM node:', node); - - if (!showBelowContents || !node || videoVerticalVisible) { - // console.log('[TabContainerV2] early return'); + if (tabContainerVersion !== 2) { + prevTabIndexV2.current = tabIndexV2; return; } - // NOTE 탭이 표시될 때마다 타이머 시작 (첫 진입은 30초, 이후에는 REGULAR_TIMEOUT) - resetTimerV2(initialEnterV2 ? INITIAL_TIMEOUT : REGULAR_TIMEOUT); + // tabIndexV2가 1에서 2로 정확하게 변경되는 시점만 감지 + const isTransitionedTo2 = prevTabIndexV2.current === 1 && tabIndexV2 === 2; + prevTabIndexV2.current = tabIndexV2; - const handleEvent = (e) => { - // console.log('[TabContainerV2] 이벤트 발생:', e.type); - resetTimerV2(REGULAR_TIMEOUT); - }; - TARGET_EVENTS.forEach((event) => { - // console.log('[TabContainerV2] 이벤트 리스너 등록:', event); - node.addEventListener(event, handleEvent); - }); + if (!isTransitionedTo2) { + if (timerIdV2.current) { + console.log('[TabContainerV2] 타이머 클리어 - tabIndex가 2가 아님', tabIndexV2); + clearTimerV2(); + } + return; + } + + console.log('[TabContainerV2] tabIndex 1 → 2 감지, 타이머 시작'); + + if (!belowContentsVisible || videoVerticalVisible) { + console.log('[TabContainerV2] early return - belowContentsVisible 또는 videoVerticalVisible 조건 불만족'); + return; + } + + // tabIndex 1 → 2로 변경된 정확한 시점에 30초 타이머 시작 + console.log('[TabContainerV2] 30초 타이머 시작'); + resetTimerV2(REGULAR_TIMEOUT); return () => { - // console.log('[TabContainerV2] cleanup'); - TARGET_EVENTS.forEach((event) => node.removeEventListener(event, handleEvent)); - if (timerIdV2.current) { clearTimerV2(); } }; }, [ - showBelowContents, - videoVerticalVisible, tabContainerVersion, + tabIndexV2, + belowContentsVisible, + videoVerticalVisible, resetTimerV2, - initialEnterV2, clearTimerV2, ]); + // TabIndex 1 자동 다음 단계로 이동 + useEffect(() => { + // tabIndex === 1일 때만 실행 + if (tabIndexV2 !== 1 || !belowContentsVisible || videoVerticalVisible) { + if (timerIdTabAutoAdvance.current) { + clearTimerTabAutoAdvance(); + } + return; + } + + // 10초 후 tabIndex를 2로 변경 + resetTimerTabAutoAdvance(10000); + + return () => { + if (timerIdTabAutoAdvance.current) { + clearTimerTabAutoAdvance(); + } + }; + }, [tabIndexV2, belowContentsVisible, videoVerticalVisible, resetTimerTabAutoAdvance, clearTimerTabAutoAdvance]); + useLayoutEffect(() => { const videoContainer = document.querySelector(`.${css.videoContainer}`); @@ -2383,7 +2466,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props tabIndex={tabIndexV2} onShopNowButtonClick={() => setTabIndexV2(0)} onLiveChannelButtonClick={() => setTabIndexV2(2)} - onLiveNext={handleIndicatorDownClick} + onLiveNext={() => setTabIndexV2(1)} onTabClose={(newTabIndex) => setTabIndexV2(newTabIndex)} tabVisible={belowContentsVisible} /> diff --git a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/ShopNowContents.jsx b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/ShopNowContents.jsx index 26bc5910..c0bb83a3 100644 --- a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/ShopNowContents.jsx +++ b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/ShopNowContents.jsx @@ -151,6 +151,11 @@ export default function ShopNowContents({ const isYouMayLikeStart = shopNowInfo && index === shopNowInfo.length; const handleItemClick = () => { + // 현재 포커스된 요소의 spotlightId 저장 + const currentFocusedElement = Spotlight.getCurrent(); + const currentSpotlightId = currentFocusedElement?.getAttribute('data-spotlight-id'); + console.log('[ShopNowContents] 현재 포커스된 spotlightId:', currentSpotlightId); + // DetailPanel push 전에 VideoPlayer 오버레이 숨김 dispatch(hidePlayerOverlays()); @@ -165,6 +170,7 @@ export default function ShopNowContents({ patnrId, prdtId, launchedFromPlayer: true, + lastFocusedTargetId: currentSpotlightId, // 현재 포커스된 spotlightId 저장 }, }) ); @@ -223,6 +229,11 @@ export default function ShopNowContents({ const { originalPrice, discountedPrice, discountRate } = priceInfoMap[index] || {}; const handleItemClick = () => { + // 현재 포커스된 요소의 spotlightId 저장 + const currentFocusedElement = Spotlight.getCurrent(); + const currentSpotlightId = currentFocusedElement?.getAttribute('data-spotlight-id'); + console.log('[ShopNowContents] 현재 포커스된 spotlightId:', currentSpotlightId); + const params = { tabTitle: tabTitle[tabIndex], productId: prdtId, @@ -253,6 +264,7 @@ export default function ShopNowContents({ patnrId, prdtId, launchedFromPlayer: true, + lastFocusedTargetId: currentSpotlightId, // 현재 포커스된 spotlightId 저장 }, }) ); diff --git a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/YouMayLikeContents.jsx b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/YouMayLikeContents.jsx index 9c8a2421..61e2f2c6 100644 --- a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/YouMayLikeContents.jsx +++ b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/YouMayLikeContents.jsx @@ -2,6 +2,8 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; +import Spotlight from '@enact/spotlight'; + import { pushPanel } from '../../../../actions/panelActions'; import TItemCard, { TYPES } from '../../../../components/TItemCard/TItemCard'; import TVirtualGridList from '../../../../components/TVirtualGridList/TVirtualGridList'; @@ -51,6 +53,11 @@ export default function YouMayLikeContents({ youmaylikeInfos[index]; const handleItemClick = () => { + // 현재 포커스된 요소의 spotlightId 저장 + const currentFocusedElement = Spotlight.getCurrent(); + const currentSpotlightId = currentFocusedElement?.getAttribute('data-spotlight-id'); + console.log('[YouMayLikeContents] 현재 포커스된 spotlightId:', currentSpotlightId); + const params = { tabTitle: 'You May Also Like', productId: prdtId, @@ -79,6 +86,7 @@ export default function YouMayLikeContents({ patnrId, prdtId, launchedFromPlayer: true, + lastFocusedTargetId: currentSpotlightId, // 현재 포커스된 spotlightId 저장 }, }) );