diff --git a/src/components/Attachments/AttachmentView/index.tsx b/src/components/Attachments/AttachmentView/index.tsx index 18283bf821eb8..3e8f552397180 100644 --- a/src/components/Attachments/AttachmentView/index.tsx +++ b/src/components/Attachments/AttachmentView/index.tsx @@ -12,7 +12,7 @@ import Icon from '@components/Icon'; import PerDiemEReceipt from '@components/PerDiemEReceipt'; import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; -import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext'; +import {usePlaybackActionsContext, usePlaybackStateContext} from '@components/VideoPlayerContexts/PlaybackContext'; import useFirstRenderRoute from '@hooks/useFirstRenderRoute'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; @@ -135,7 +135,8 @@ function AttachmentView({ const [transactionFromOnyx] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${getNonEmptyStringOnyxID(transactionID)}`, {canBeMissing: true}); const transaction = transactionProp ?? transactionFromOnyx; const {translate} = useLocalize(); - const {updateCurrentURLAndReportID, currentlyPlayingURL, playVideo} = usePlaybackContext(); + const {currentlyPlayingURL} = usePlaybackStateContext(); + const {updateCurrentURLAndReportID, playVideo} = usePlaybackActionsContext(); const attachmentCarouselPagerContext = useContext(AttachmentCarouselPagerContext); const {onAttachmentError, onTap} = attachmentCarouselPagerContext ?? {}; diff --git a/src/components/VideoPlayer/BaseVideoPlayer.tsx b/src/components/VideoPlayer/BaseVideoPlayer.tsx index f95b7d89bec47..ad93e461345df 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.tsx +++ b/src/components/VideoPlayer/BaseVideoPlayer.tsx @@ -13,7 +13,7 @@ import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import Hoverable from '@components/Hoverable'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import {useFullScreenContext} from '@components/VideoPlayerContexts/FullScreenContext'; -import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext'; +import {usePlaybackActionsContext, usePlaybackStateContext} from '@components/VideoPlayerContexts/PlaybackContext'; import type {PlaybackSpeed} from '@components/VideoPlayerContexts/types'; import {useVideoPopoverMenuContext} from '@components/VideoPlayerContexts/VideoPopoverMenuContext'; import {useVolumeContext} from '@components/VideoPlayerContexts/VolumeContext'; @@ -53,22 +53,8 @@ function BaseVideoPlayer({ onTap, }: VideoPlayerProps & {reportID: string}) { const styles = useThemeStyles(); - const { - pauseVideo, - playVideo, - replayVideo, - currentlyPlayingURL, - sharedElement, - originalParent, - shareVideoPlayerElements, - currentVideoPlayerRef, - currentVideoViewRef, - updateCurrentURLAndReportID, - setCurrentlyPlayingURL, - mountedVideoPlayersRef, - playerStatus, - updatePlayerStatus, - } = usePlaybackContext(); + const {currentlyPlayingURL, sharedElement, originalParent, currentVideoPlayerRef, currentVideoViewRef, mountedVideoPlayersRef, playerStatus} = usePlaybackStateContext(); + const {pauseVideo, playVideo, replayVideo, shareVideoPlayerElements, updateCurrentURLAndReportID, setCurrentlyPlayingURL, updatePlayerStatus} = usePlaybackActionsContext(); const {isFullScreenRef} = useFullScreenContext(); const isOffline = useNetwork().isOffline; diff --git a/src/components/VideoPlayer/VideoPlayerControls/ProgressBar/index.tsx b/src/components/VideoPlayer/VideoPlayerControls/ProgressBar/index.tsx index 24a77c324f0f8..d11a8f8728ce0 100644 --- a/src/components/VideoPlayer/VideoPlayerControls/ProgressBar/index.tsx +++ b/src/components/VideoPlayer/VideoPlayerControls/ProgressBar/index.tsx @@ -4,7 +4,7 @@ import type {GestureStateChangeEvent, GestureUpdateEvent, PanGestureChangeEventP import {Gesture, GestureDetector} from 'react-native-gesture-handler'; import Animated, {useAnimatedStyle, useSharedValue} from 'react-native-reanimated'; import {scheduleOnRN} from 'react-native-worklets'; -import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext'; +import {usePlaybackActionsContext} from '@components/VideoPlayerContexts/PlaybackContext'; import useThemeStyles from '@hooks/useThemeStyles'; type ProgressBarProps = { @@ -24,7 +24,7 @@ function getProgress(currentPosition: number, maxPosition: number): number { function ProgressBar({duration, position, seekPosition}: ProgressBarProps) { const styles = useThemeStyles(); - const {pauseVideo, playVideo, checkIfVideoIsPlaying} = usePlaybackContext(); + const {pauseVideo, playVideo, checkIfVideoIsPlaying} = usePlaybackActionsContext(); const [sliderWidth, setSliderWidth] = useState(1); const [isSliderPressed, setIsSliderPressed] = useState(false); const progressWidth = useSharedValue(0); diff --git a/src/components/VideoPlayer/VideoPlayerControls/index.tsx b/src/components/VideoPlayer/VideoPlayerControls/index.tsx index 3efed7dad0d39..7ffaea5999f09 100644 --- a/src/components/VideoPlayer/VideoPlayerControls/index.tsx +++ b/src/components/VideoPlayer/VideoPlayerControls/index.tsx @@ -8,7 +8,7 @@ import type {ValueOf} from 'type-fest'; import Text from '@components/Text'; import IconButton from '@components/VideoPlayer/IconButton'; import {convertSecondsToTime} from '@components/VideoPlayer/utils'; -import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext'; +import {usePlaybackActionsContext} from '@components/VideoPlayerContexts/PlaybackContext'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -69,7 +69,7 @@ function VideoPlayerControls({ const icons = useMemoizedLazyExpensifyIcons(['ThreeDots', 'Pause', 'Play', 'Fullscreen']); const styles = useThemeStyles(); const {translate} = useLocalize(); - const {updateCurrentURLAndReportID} = usePlaybackContext(); + const {updateCurrentURLAndReportID} = usePlaybackActionsContext(); const [shouldShowTime, setShouldShowTime] = useState(false); const iconSpacing = small ? styles.mr3 : styles.mr4; diff --git a/src/components/VideoPlayerContexts/PlaybackContext/index.tsx b/src/components/VideoPlayerContexts/PlaybackContext/index.tsx index 2f8cff67d9531..53752f5e5a9c1 100644 --- a/src/components/VideoPlayerContexts/PlaybackContext/index.tsx +++ b/src/components/VideoPlayerContexts/PlaybackContext/index.tsx @@ -1,19 +1,20 @@ import type {VideoPlayer, VideoPlayerStatus, VideoView} from 'expo-video'; -import React, {useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react'; +import React, {useCallback, useContext, useEffect, useRef, useState} from 'react'; import type {View} from 'react-native'; import {getReportOrDraftReport, isChatThread} from '@libs/ReportUtils'; import Navigation from '@navigation/Navigation'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; import type {ProtectedCurrentRouteReportID} from './playbackContextReportIDUtils'; import {findURLInReportOrAncestorAttachments, getCurrentRouteReportID, NO_REPORT_ID, NO_REPORT_ID_IN_PARAMS, normalizeReportID} from './playbackContextReportIDUtils'; -import type {OriginalParent, PlaybackContext, PlaybackContextValues} from './types'; +import type {OriginalParent, PlaybackActionsContext, PlaybackActionsContextValues, PlaybackStateContext, PlaybackStateContextValues} from './types'; import usePlaybackContextVideoRefs from './usePlaybackContextVideoRefs'; -const Context = React.createContext(null); +const ContextState = React.createContext(null); +const ContextActions = React.createContext(null); function PlaybackContextProvider({children}: ChildrenProps) { - const [currentlyPlayingURL, setCurrentlyPlayingURL] = useState(null); - const [sharedElement, setSharedElement] = useState(null); + const [currentlyPlayingURL, setCurrentlyPlayingURL] = useState(null); + const [sharedElement, setSharedElement] = useState(null); const [originalParent, setOriginalParent] = useState(null); const [currentRouteReportID, setCurrentRouteReportID] = useState(NO_REPORT_ID); const mountedVideoPlayersRef = useRef([]); @@ -28,7 +29,7 @@ function PlaybackContextProvider({children}: ChildrenProps) { const video = usePlaybackContextVideoRefs(resetContextProperties); - const updateCurrentURLAndReportID: PlaybackContextValues['updateCurrentURLAndReportID'] = useCallback( + const updateCurrentURLAndReportID: PlaybackActionsContextValues['updateCurrentURLAndReportID'] = useCallback( (url, reportID) => { if (!reportID) { return; @@ -69,7 +70,7 @@ function PlaybackContextProvider({children}: ChildrenProps) { playerStatus.current = newStatus; }, []); - const shareVideoPlayerElements: PlaybackContextValues['shareVideoPlayerElements'] = useCallback( + const shareVideoPlayerElements: PlaybackActionsContextValues['shareVideoPlayerElements'] = useCallback( ( videoPlayerRef: VideoPlayer | null, videoViewRef: VideoView | null, @@ -117,55 +118,55 @@ function PlaybackContextProvider({children}: ChildrenProps) { }); }, [currentRouteReportID, currentlyPlayingURL, video, video.resetPlayerData]); - const contextValue: PlaybackContext = useMemo( - () => ({ - updateCurrentURLAndReportID, - currentlyPlayingURL, - currentRouteReportID: normalizeReportID(currentRouteReportID), - originalParent, - sharedElement, - shareVideoPlayerElements, - setCurrentlyPlayingURL, - currentVideoPlayerRef: video.playerRef, - currentVideoViewRef: video.viewRef, - playVideo: video.play, - pauseVideo: video.pause, - replayVideo: video.replay, - stopVideo: video.stop, - checkIfVideoIsPlaying: video.isPlaying, - resetVideoPlayerData: video.resetPlayerData, - mountedVideoPlayersRef, - playerStatus, - updatePlayerStatus, - }), - [ - updateCurrentURLAndReportID, - currentlyPlayingURL, - currentRouteReportID, - originalParent, - sharedElement, - shareVideoPlayerElements, - video.playerRef, - video.viewRef, - video.play, - video.pause, - video.replay, - video.stop, - video.isPlaying, - video.resetPlayerData, - updatePlayerStatus, - ], + // Because of the React Compiler we don't need to memoize it manually + // eslint-disable-next-line react/jsx-no-constructed-context-values + const stateValue: PlaybackStateContext = { + currentlyPlayingURL, + currentRouteReportID: normalizeReportID(currentRouteReportID), + originalParent, + sharedElement, + currentVideoPlayerRef: video.playerRef, + currentVideoViewRef: video.viewRef, + mountedVideoPlayersRef, + playerStatus, + }; + + // Because of the React Compiler we don't need to memoize it manually + // eslint-disable-next-line react/jsx-no-constructed-context-values + const actionsValue: PlaybackActionsContext = { + updateCurrentURLAndReportID, + shareVideoPlayerElements, + setCurrentlyPlayingURL, + playVideo: video.play, + pauseVideo: video.pause, + replayVideo: video.replay, + stopVideo: video.stop, + checkIfVideoIsPlaying: video.isPlaying, + resetVideoPlayerData: video.resetPlayerData, + updatePlayerStatus, + }; + + return ( + + {children} + ); +} - return {children}; +function usePlaybackStateContext() { + const playbackStateContext = useContext(ContextState); + if (!playbackStateContext) { + throw new Error('usePlaybackStateContext must be used within a PlaybackContextProvider'); + } + return playbackStateContext; } -function usePlaybackContext() { - const playbackContext = useContext(Context); - if (!playbackContext) { - throw new Error('usePlaybackContext must be used within a PlaybackContextProvider'); +function usePlaybackActionsContext() { + const playbackActionsContext = useContext(ContextActions); + if (!playbackActionsContext) { + throw new Error('usePlaybackActionsContext must be used within a PlaybackContextProvider'); } - return playbackContext; + return playbackActionsContext; } -export {Context as PlaybackContext, PlaybackContextProvider, usePlaybackContext}; +export {ContextActions as PlaybackActionsContext, ContextState as PlaybackStateContext, PlaybackContextProvider, usePlaybackStateContext, usePlaybackActionsContext}; diff --git a/src/components/VideoPlayerContexts/PlaybackContext/types.ts b/src/components/VideoPlayerContexts/PlaybackContext/types.ts index 05f1788c8c29d..0a4ef1d4f6217 100644 --- a/src/components/VideoPlayerContexts/PlaybackContext/types.ts +++ b/src/components/VideoPlayerContexts/PlaybackContext/types.ts @@ -39,17 +39,7 @@ type VideoElementData = { reportID: string | undefined; }; -/** - * Playback-related context values available throughout the app. - */ -type PlaybackContextValues = { - /** - * Updates the currently tracked video URL and associated report ID. - * @param url The new video URL. - * @param reportID The new report ID. - */ - updateCurrentURLAndReportID: (url: string | undefined, reportID: string | undefined) => void; - +type PlaybackStateContextValues = { /** * The URL of the video currently being played, or null if none. */ @@ -70,6 +60,28 @@ type PlaybackContextValues = { */ sharedElement: View | HTMLDivElement | null; + /** + * Array of currently mounted Video Player instances + */ + mountedVideoPlayersRef: RefObject; + + /** + * Status of the currently used Video Player + */ + playerStatus: RefObject; +}; + +/** + * Playback-related context actions values available throughout the app. + */ +type PlaybackActionsContextValues = { + /** + * Updates the currently tracked video URL and associated report ID. + * @param url The new video URL. + * @param reportID The new report ID. + */ + updateCurrentURLAndReportID: (url: string | undefined, reportID: string | undefined) => void; + /** * Updates shared video player elements across different parts of the UI. * @param playerRef Reference to the VideoPlayer instance. @@ -93,16 +105,6 @@ type PlaybackContextValues = { */ setCurrentlyPlayingURL: React.Dispatch>; - /** - * Array of currently mounted Video Player instances - */ - mountedVideoPlayersRef: RefObject; - - /** - * Status of the currently used Video Player - */ - playerStatus: RefObject; - /** * Updates current videoPlayer status * @param newStatus New videoPlayer status @@ -164,17 +166,23 @@ type PlaybackContextVideoRefs = { }; /** - * Combined playback context with values and video control helpers. + * Combined playback state context with values and video control helpers. */ -type PlaybackContext = PlaybackContextValues & { +type PlaybackStateContext = PlaybackStateContextValues & { + currentVideoPlayerRef: PlaybackContextVideoRefs['playerRef']; + currentVideoViewRef: PlaybackContextVideoRefs['viewRef']; +}; + +/** + * Combined playback actions context with values and video control helpers. + */ +type PlaybackActionsContext = PlaybackActionsContextValues & { resetVideoPlayerData: PlaybackContextVideoRefs['resetPlayerData']; playVideo: PlaybackContextVideoRefs['play']; pauseVideo: PlaybackContextVideoRefs['pause']; replayVideo: PlaybackContextVideoRefs['replay']; stopVideo: PlaybackContextVideoRefs['stop']; checkIfVideoIsPlaying: PlaybackContextVideoRefs['isPlaying']; - currentVideoPlayerRef: PlaybackContextVideoRefs['playerRef']; - currentVideoViewRef: PlaybackContextVideoRefs['viewRef']; }; -export type {PlaybackContextVideoRefs, StopVideo, PlaybackContextValues, PlaybackContext, OriginalParent}; +export type {PlaybackContextVideoRefs, StopVideo, PlaybackStateContextValues, PlaybackActionsContextValues, PlaybackStateContext, PlaybackActionsContext, OriginalParent}; diff --git a/src/components/VideoPlayerContexts/VolumeContext.tsx b/src/components/VideoPlayerContexts/VolumeContext.tsx index 80b2ee52525da..f6cb2a9bc3e94 100644 --- a/src/components/VideoPlayerContexts/VolumeContext.tsx +++ b/src/components/VideoPlayerContexts/VolumeContext.tsx @@ -1,13 +1,13 @@ import React, {useCallback, useContext, useEffect, useMemo} from 'react'; import {useSharedValue} from 'react-native-reanimated'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; -import {usePlaybackContext} from './PlaybackContext'; +import {usePlaybackStateContext} from './PlaybackContext'; import type {VolumeContext} from './types'; const Context = React.createContext(null); function VolumeContextProvider({children}: ChildrenProps) { - const {currentVideoPlayerRef, originalParent} = usePlaybackContext(); + const {currentVideoPlayerRef, originalParent} = usePlaybackStateContext(); const volume = useSharedValue(0); // We need this field to remember the last value before clicking mute const lastNonZeroVolume = useSharedValue(1); diff --git a/src/components/VideoPlayerPreview/index.tsx b/src/components/VideoPlayerPreview/index.tsx index 6253319e72a3b..b63bacc6bfcd3 100644 --- a/src/components/VideoPlayerPreview/index.tsx +++ b/src/components/VideoPlayerPreview/index.tsx @@ -6,7 +6,7 @@ import {View} from 'react-native'; import {useIsOnSearch} from '@components/Search/SearchScopeProvider'; import VideoPlayer from '@components/VideoPlayer'; import IconButton from '@components/VideoPlayer/IconButton'; -import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext'; +import {usePlaybackActionsContext, usePlaybackStateContext} from '@components/VideoPlayerContexts/PlaybackContext'; import useCheckIfRouteHasRemainedUnchanged from '@hooks/useCheckIfRouteHasRemainedUnchanged'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; @@ -52,7 +52,8 @@ function VideoPlayerPreview({videoUrl, thumbnailUrl, reportID, fileName, videoDi const icons = useMemoizedLazyExpensifyIcons(['Expand']); const styles = useThemeStyles(); const {translate} = useLocalize(); - const {currentlyPlayingURL, currentRouteReportID, updateCurrentURLAndReportID} = usePlaybackContext(); + const {currentlyPlayingURL, currentRouteReportID} = usePlaybackStateContext(); + const {updateCurrentURLAndReportID} = usePlaybackActionsContext(); /* This needs to be isSmallScreenWidth because we want to be able to play video in chat (not in attachment modal) when preview is inside an RHP */ // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth diff --git a/src/pages/Search/SearchPage.tsx b/src/pages/Search/SearchPage.tsx index d7b774d98f59c..ee9ed4bc1558a 100644 --- a/src/pages/Search/SearchPage.tsx +++ b/src/pages/Search/SearchPage.tsx @@ -18,7 +18,7 @@ import {ScrollOffsetContext} from '@components/ScrollOffsetContextProvider'; import {useSearchContext} from '@components/Search/SearchContext'; import type {SearchHeaderOptionValue} from '@components/Search/SearchPageHeader/SearchPageHeader'; import type {PaymentData, SearchParams} from '@components/Search/types'; -import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext'; +import {usePlaybackActionsContext} from '@components/VideoPlayerContexts/PlaybackContext'; import useAllTransactions from '@hooks/useAllTransactions'; import useBulkPayOptions from '@hooks/useBulkPayOptions'; import useConfirmModal from '@hooks/useConfirmModal'; @@ -1165,7 +1165,7 @@ function SearchPage({route}: SearchPageProps) { validateFiles(files, Array.from(e.dataTransfer?.items ?? [])); }; - const {resetVideoPlayerData} = usePlaybackContext(); + const {resetVideoPlayerData} = usePlaybackActionsContext(); const metadata = searchResults?.search; const shouldShowFooter = !!metadata?.count || selectedTransactionsKeys.length > 0;