Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TS migration] Migrate VideoPlayerContexts component files to TypeScript #37134

Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
00c5efc
PlaybackContext migrated to ts
smelaa Feb 23, 2024
73707a8
VolumeContext migreated to ts
smelaa Feb 23, 2024
3873457
VideoPopoverMenuContext migrated to ts
smelaa Feb 23, 2024
386d2b6
Update src/components/VideoPlayerContexts/VideoPopoverMenuContext.tsx
smelaa Feb 23, 2024
bbcfcb2
Update src/components/VideoPlayerContexts/PlaybackContext.tsx
smelaa Feb 23, 2024
737a1fa
children type update
smelaa Feb 23, 2024
388c665
Update src/components/VideoPlayerContexts/PlaybackContext.tsx
smelaa Feb 23, 2024
3261c35
adding optional chaining & adding shouldPutLeftPaddingWhenNoIcon to S…
smelaa Feb 23, 2024
09aaad5
reverting || -> ?? change
smelaa Feb 23, 2024
8e911c5
main sync
smelaa Feb 23, 2024
c69e9f9
eslint disable nullish coalescing
smelaa Feb 23, 2024
2add946
Merge branch 'main' into ts-migration-videoplayercontext
smelaa Feb 23, 2024
331abe2
Merge branch 'main' into ts-migration-videoplayercontext and solve co…
smelaa Feb 27, 2024
5728c39
Updateing types
smelaa Feb 27, 2024
90affed
reverting podfile change
smelaa Feb 27, 2024
6f92384
Addressing review comments
smelaa Feb 28, 2024
4108ff4
Optional chaining removed
smelaa Feb 28, 2024
f15e179
PopoverMenuItem reuse
smelaa Feb 28, 2024
ee6d152
addressing reviewer suggestion
smelaa Feb 28, 2024
02e3d19
Merge branch 'main' into ts-migration-videoplayercontext
smelaa Feb 28, 2024
bd9bb4e
Merge branch 'main' into ts-migration-videoplayercontext
smelaa Feb 28, 2024
86443d0
Merge remote-tracking branch 'origin/main' into ts-migration-videopla…
smelaa Feb 29, 2024
e15bec7
Merge branch 'main' into ts-migration-videoplayercontext
smelaa Mar 1, 2024
7a33fec
Merge branch 'main' into ts-migration-videoplayercontext
smelaa Mar 6, 2024
c8df480
base
smelaa Mar 6, 2024
6d9823a
Add comment to eslint-disable-next-line
smelaa Mar 7, 2024
c35ac5d
Change eslint-disable-next-line comment
smelaa Mar 7, 2024
0bd5d80
Merge branch 'main' into ts-migration-videoplayercontext
blazejkustra Mar 11, 2024
a7d0fb4
Fix failing typecheck
blazejkustra Mar 11, 2024
c37fa48
Merge branch 'main' into ts-migration-videoplayercontext
smelaa Mar 12, 2024
a0b14c9
Merge with main and solve conflicts
smelaa Mar 12, 2024
4c579d9
Solving conflict
smelaa Mar 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 4 additions & 32 deletions src/components/PopoverMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type {ImageContentFit} from 'expo-image';
import type {RefObject} from 'react';
import React, {useEffect, useRef, useState} from 'react';
import {View} from 'react-native';
Expand All @@ -10,48 +9,21 @@ import useWindowDimensions from '@hooks/useWindowDimensions';
import CONST from '@src/CONST';
import type {AnchorPosition} from '@src/styles';
import type AnchorAlignment from '@src/types/utils/AnchorAlignment';
import type IconAsset from '@src/types/utils/IconAsset';
import * as Expensicons from './Icon/Expensicons';
import type {MenuItemProps} from './MenuItem';
import MenuItem from './MenuItem';
import PopoverWithMeasuredContent from './PopoverWithMeasuredContent';
import Text from './Text';

type PopoverMenuItem = {
/** An icon element displayed on the left side */
icon: IconAsset;

type PopoverMenuItem = MenuItemProps & {
/** Text label */
text: string;

/** A callback triggered when this item is selected */
onSelected: () => void;

/** A description text to show under the title */
description?: string;

/** The fill color to pass into the icon. */
iconFill?: string;

/** Icon Width */
iconWidth?: number;

/** Icon Height */
iconHeight?: number;

/** Icon should be displayed in its own color */
displayInDefaultIconColor?: boolean;

/** Determines how the icon should be resized to fit its container */
contentFit?: ImageContentFit;
onSelected?: () => void;

/** Sub menu items to be rendered after a menu item is selected */
subMenuItems?: PopoverMenuItem[];

/** Determines whether an icon should be displayed on the right side of the menu item. */
shouldShowRightIcon?: boolean;

/** Adds padding to the left of the text when there is no icon. */
shouldPutLeftPaddingWhenNoIcon?: boolean;
};

type PopoverModalProps = Pick<ModalProps, 'animationIn' | 'animationOut' | 'animationInTiming'>;
Expand Down Expand Up @@ -181,7 +153,7 @@ function PopoverMenu({
const onModalHide = () => {
setFocusedIndex(-1);
if (selectedItemIndex.current !== null) {
currentMenuItems[selectedItemIndex.current].onSelected();
currentMenuItems[selectedItemIndex.current].onSelected?.();
selectedItemIndex.current = null;
}
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,52 +1,43 @@
import PropTypes from 'prop-types';
import type {AVPlaybackStatusToSet, Video} from 'expo-av';
import React, {useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react';
import type {View} from 'react-native';
import useCurrentReportID from '@hooks/useCurrentReportID';
import type ChildrenProps from '@src/types/utils/ChildrenProps';
import type {PlaybackContext, StatusCallback} from './types';

const PlaybackContext = React.createContext(null);
const Context = React.createContext<PlaybackContext | null>(null);

function PlaybackContextProvider({children}) {
const [currentlyPlayingURL, setCurrentlyPlayingURL] = useState(null);
const [sharedElement, setSharedElement] = useState(null);
const [originalParent, setOriginalParent] = useState(null);
const currentVideoPlayerRef = useRef(null);
const {currentReportID} = useCurrentReportID();
function PlaybackContextProvider({children}: ChildrenProps) {
const [currentlyPlayingURL, setCurrentlyPlayingURL] = useState<string | null>(null);
const [sharedElement, setSharedElement] = useState<View | null>(null);
const [originalParent, setOriginalParent] = useState<View | null>(null);
const currentVideoPlayerRef = useRef<Video | null>(null);
const {currentReportID} = useCurrentReportID() ?? {};

const pauseVideo = useCallback(() => {
if (!(currentVideoPlayerRef && currentVideoPlayerRef.current && currentVideoPlayerRef.current.setStatusAsync)) {
return;
}
currentVideoPlayerRef.current.setStatusAsync({shouldPlay: false});
currentVideoPlayerRef.current?.setStatusAsync({shouldPlay: false});
}, [currentVideoPlayerRef]);
smelaa marked this conversation as resolved.
Show resolved Hide resolved

const stopVideo = useCallback(() => {
if (!(currentVideoPlayerRef && currentVideoPlayerRef.current && currentVideoPlayerRef.current.stopAsync)) {
return;
}
currentVideoPlayerRef.current.stopAsync({shouldPlay: false});
currentVideoPlayerRef.current?.stopAsync();
smelaa marked this conversation as resolved.
Show resolved Hide resolved
}, [currentVideoPlayerRef]);

const playVideo = useCallback(() => {
if (!(currentVideoPlayerRef && currentVideoPlayerRef.current && currentVideoPlayerRef.current.setStatusAsync)) {
return;
}
currentVideoPlayerRef.current.getStatusAsync().then((status) => {
const newStatus = {shouldPlay: true};
if (status.durationMillis === status.positionMillis) {
currentVideoPlayerRef.current?.getStatusAsync().then((status) => {
const newStatus: AVPlaybackStatusToSet = {shouldPlay: true};
if ('durationMillis' in status && status.durationMillis === status.positionMillis) {
newStatus.positionMillis = 0;
smelaa marked this conversation as resolved.
Show resolved Hide resolved
}
currentVideoPlayerRef.current.setStatusAsync(newStatus);
currentVideoPlayerRef.current?.setStatusAsync(newStatus);
});
}, [currentVideoPlayerRef]);

const unloadVideo = useCallback(() => {
if (!(currentVideoPlayerRef && currentVideoPlayerRef.current && currentVideoPlayerRef.current.unloadAsync)) {
return;
}
currentVideoPlayerRef.current.unloadAsync();
currentVideoPlayerRef.current?.unloadAsync();
}, [currentVideoPlayerRef]);

const updateCurrentlyPlayingURL = useCallback(
(url) => {
(url: string) => {
if (currentlyPlayingURL && url !== currentlyPlayingURL) {
pauseVideo();
}
Expand All @@ -56,7 +47,7 @@ function PlaybackContextProvider({children}) {
);

const shareVideoPlayerElements = useCallback(
(ref, parent, child, isUploading) => {
(ref: Video, parent: View, child: View, isUploading: boolean) => {
currentVideoPlayerRef.current = ref;
setOriginalParent(parent);
setSharedElement(child);
Expand All @@ -69,12 +60,9 @@ function PlaybackContextProvider({children}) {
);

const checkVideoPlaying = useCallback(
(statusCallback) => {
if (!(currentVideoPlayerRef && currentVideoPlayerRef.current && currentVideoPlayerRef.current.getStatusAsync)) {
return;
}
currentVideoPlayerRef.current.getStatusAsync().then((status) => {
statusCallback(status.isPlaying);
(statusCallback: StatusCallback) => {
currentVideoPlayerRef.current?.getStatusAsync().then((status) => {
statusCallback('isPlaying' in status && status.isPlaying);
});
hayata-suenaga marked this conversation as resolved.
Show resolved Hide resolved
},
[currentVideoPlayerRef],
Expand Down Expand Up @@ -110,21 +98,17 @@ function PlaybackContextProvider({children}) {
}),
[updateCurrentlyPlayingURL, currentlyPlayingURL, originalParent, sharedElement, shareVideoPlayerElements, playVideo, pauseVideo, checkVideoPlaying],
);
return <PlaybackContext.Provider value={contextValue}>{children}</PlaybackContext.Provider>;
return <Context.Provider value={contextValue}>{children}</Context.Provider>;
}

function usePlaybackContext() {
const context = useContext(PlaybackContext);
if (context === undefined) {
const playbackContext = useContext(Context);
if (!playbackContext) {
throw new Error('usePlaybackContext must be used within a PlaybackContextProvider');
smelaa marked this conversation as resolved.
Show resolved Hide resolved
}
return context;
return playbackContext;
}

PlaybackContextProvider.displayName = 'PlaybackContextProvider';
PlaybackContextProvider.propTypes = {
/** Actual content wrapped by this component */
children: PropTypes.node.isRequired,
};

export {PlaybackContextProvider, usePlaybackContext};
Original file line number Diff line number Diff line change
@@ -1,38 +1,42 @@
import PropTypes from 'prop-types';
import React, {useCallback, useContext, useMemo, useState} from 'react';
import _ from 'underscore';
import * as Expensicons from '@components/Icon/Expensicons';
import type {PopoverMenuItem} from '@components/PopoverMenu';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL';
import fileDownload from '@libs/fileDownload';
import CONST from '@src/CONST';
import type ChildrenProps from '@src/types/utils/ChildrenProps';
import {usePlaybackContext} from './PlaybackContext';
import type {PlaybackSpeed, VideoPopoverMenuContext} from './types';

const VideoPopoverMenuContext = React.createContext(null);
const Context = React.createContext<VideoPopoverMenuContext | null>(null);

function VideoPopoverMenuContextProvider({children}) {
function VideoPopoverMenuContextProvider({children}: ChildrenProps) {
const {currentVideoPlayerRef, currentlyPlayingURL} = usePlaybackContext();
const {translate} = useLocalize();
const [currentPlaybackSpeed, setCurrentPlaybackSpeed] = useState(CONST.VIDEO_PLAYER.PLAYBACK_SPEEDS[2]);
const [currentPlaybackSpeed, setCurrentPlaybackSpeed] = useState<PlaybackSpeed>(CONST.VIDEO_PLAYER.PLAYBACK_SPEEDS[2]);
const {isOffline} = useNetwork();
const isLocalFile = currentlyPlayingURL && _.some(CONST.ATTACHMENT_LOCAL_URL_PREFIX, (prefix) => currentlyPlayingURL.startsWith(prefix));
const isLocalFile = currentlyPlayingURL && CONST.ATTACHMENT_LOCAL_URL_PREFIX.some((prefix) => currentlyPlayingURL.startsWith(prefix));

const updatePlaybackSpeed = useCallback(
(speed) => {
(speed: PlaybackSpeed) => {
setCurrentPlaybackSpeed(speed);
currentVideoPlayerRef.current.setStatusAsync({rate: speed});
currentVideoPlayerRef.current?.setStatusAsync({rate: speed});
},
[currentVideoPlayerRef],
);

const downloadAttachment = useCallback(() => {
if (currentlyPlayingURL === null) {
return;
}
const sourceURI = addEncryptedAuthTokenToURL(currentlyPlayingURL);
fileDownload(sourceURI);
}, [currentlyPlayingURL]);

const menuItems = useMemo(() => {
const items = [];
const items: PopoverMenuItem[] = [];

if (!isOffline && !isLocalFile) {
items.push({
Expand All @@ -47,37 +51,30 @@ function VideoPopoverMenuContextProvider({children}) {
items.push({
icon: Expensicons.Meter,
text: translate('videoPlayer.playbackSpeed'),
subMenuItems: [
..._.map(CONST.VIDEO_PLAYER.PLAYBACK_SPEEDS, (speed) => ({
icon: currentPlaybackSpeed === speed ? Expensicons.Checkmark : null,
text: speed.toString(),
onSelected: () => {
updatePlaybackSpeed(speed);
},
shouldPutLeftPaddingWhenNoIcon: true,
})),
],
subMenuItems: CONST.VIDEO_PLAYER.PLAYBACK_SPEEDS.map((speed) => ({
icon: currentPlaybackSpeed === speed ? Expensicons.Checkmark : undefined,
text: speed.toString(),
onSelected: () => {
updatePlaybackSpeed(speed);
},
shouldPutLeftPaddingWhenNoIcon: true,
})),
});

return items;
}, [currentPlaybackSpeed, downloadAttachment, translate, updatePlaybackSpeed, isOffline, isLocalFile]);

const contextValue = useMemo(() => ({menuItems, updatePlaybackSpeed}), [menuItems, updatePlaybackSpeed]);
return <VideoPopoverMenuContext.Provider value={contextValue}>{children}</VideoPopoverMenuContext.Provider>;
return <Context.Provider value={contextValue}>{children}</Context.Provider>;
}

function useVideoPopoverMenuContext() {
const context = useContext(VideoPopoverMenuContext);
if (context === undefined) {
const videoPopooverMenuContext = useContext(Context);
if (!videoPopooverMenuContext) {
throw new Error('useVideoPopoverMenuContext must be used within a VideoPopoverMenuContext');
smelaa marked this conversation as resolved.
Show resolved Hide resolved
}
return context;
return videoPopooverMenuContext;
}

VideoPopoverMenuContextProvider.displayName = 'VideoPopoverMenuContextProvider';
VideoPopoverMenuContextProvider.propTypes = {
/** Actual content wrapped by this component */
children: PropTypes.node.isRequired,
};

export {VideoPopoverMenuContextProvider, useVideoPopoverMenuContext};
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import PropTypes from 'prop-types';
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 type {VolumeContext} from './types';

const VolumeContext = React.createContext(null);
const Context = React.createContext<VolumeContext | null>(null);

function VolumeContextProvider({children}) {
function VolumeContextProvider({children}: ChildrenProps) {
const {currentVideoPlayerRef, originalParent} = usePlaybackContext();
const volume = useSharedValue(0);

const updateVolume = useCallback(
(newVolume) => {
(newVolume: number) => {
if (!currentVideoPlayerRef.current) {
return;
}
Expand All @@ -30,21 +31,17 @@ function VolumeContextProvider({children}) {
}, [originalParent, updateVolume, volume.value]);

const contextValue = useMemo(() => ({updateVolume, volume}), [updateVolume, volume]);
return <VolumeContext.Provider value={contextValue}>{children}</VolumeContext.Provider>;
return <Context.Provider value={contextValue}>{children}</Context.Provider>;
}

function useVolumeContext() {
const context = useContext(VolumeContext);
if (context === undefined) {
const volumeContext = useContext(Context);
if (!volumeContext) {
throw new Error('useVolumeContext must be used within a VolumeContextProvider');
smelaa marked this conversation as resolved.
Show resolved Hide resolved
}
return context;
return volumeContext;
}

VolumeContextProvider.displayName = 'VolumeContextProvider';
VolumeContextProvider.propTypes = {
/** Actual content wrapped by this component */
children: PropTypes.node.isRequired,
};

export {VolumeContextProvider, useVolumeContext};
34 changes: 34 additions & 0 deletions src/components/VideoPlayerContexts/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type {Video} from 'expo-av';
import type {MutableRefObject} from 'react';
import type {View} from 'react-native';
import type {SharedValue} from 'react-native-reanimated';
import type {PopoverMenuItem} from '@components/PopoverMenu';
import type CONST from '@src/CONST';

type PlaybackContext = {
updateCurrentlyPlayingURL: (url: string) => void;
currentlyPlayingURL: string | null;
originalParent: View | null;
sharedElement: View | null;
currentVideoPlayerRef: MutableRefObject<Video | null>;
shareVideoPlayerElements: (ref: Video, parent: View, child: View, isUploading: boolean) => void;
playVideo: () => void;
pauseVideo: () => void;
checkVideoPlaying: (statusCallback: StatusCallback) => void;
};

type VolumeContext = {
updateVolume: (newVolume: number) => void;
volume: SharedValue<number>;
};

type VideoPopoverMenuContext = {
menuItems: PopoverMenuItem[];
updatePlaybackSpeed: (speed: PlaybackSpeed) => void;
};

type StatusCallback = (isPlaying: boolean) => void;

type PlaybackSpeed = (typeof CONST.VIDEO_PLAYER.PLAYBACK_SPEEDS)[number];

export type {PlaybackContext, VolumeContext, VideoPopoverMenuContext, StatusCallback, PlaybackSpeed};
1 change: 1 addition & 0 deletions src/libs/fileDownload/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const fileDownload: FileDownload = (url, fileName) => {
// adding href to anchor
link.href = href;
link.style.display = 'none';
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- nullish coalescing doesn't achieve the same result in this case
hayata-suenaga marked this conversation as resolved.
Show resolved Hide resolved
link.download = FileUtils.appendTimeToFileName(fileName || FileUtils.getFileName(url));

// Append to html link element page
Expand Down
2 changes: 1 addition & 1 deletion src/libs/fileDownload/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type {Asset} from 'react-native-image-picker';

type FileDownload = (url: string, fileName: string, successMessage?: string) => Promise<void>;
type FileDownload = (url: string, fileName?: string, successMessage?: string) => Promise<void>;

type ImageResolution = {width: number; height: number};
type GetImageResolution = (url: File | Asset) => Promise<ImageResolution>;
Expand Down
Loading