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 VideoPlayerPreview, VideoPlayerControls and VideoPopoverMenu component files to TypeScript #37425

Merged
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
7ef3d28
VideoPlayerThumbnail migreated to ts
smelaa Feb 27, 2024
1a70990
Migration of VideoPlayerPreview
smelaa Feb 28, 2024
aed45f8
Addressing review comments
smelaa Feb 28, 2024
0afcaae
Merge branch 'ts-migration-videoplayercontext' into ts-migration-vide…
smelaa Feb 28, 2024
d176eaf
VideoPopovermenu migrated to ts
smelaa Feb 28, 2024
03eae41
ProgressBar migrated to ts
smelaa Feb 29, 2024
2dbfe11
Merge branch 'ts-migration-videoplayercontext' into ts-migration-vide…
smelaa Feb 29, 2024
07947cd
VolumeButton migrated to ts
smelaa Feb 29, 2024
1c8b789
VideoPlayerControls migrated to ts
smelaa Feb 29, 2024
d2567a9
Changing types in VideoPlayerControls to match IconButton
smelaa Feb 29, 2024
528387f
Merging with main
smelaa Mar 1, 2024
361d4a0
Merge branch 'ts-migration-videoplayercontext' into ts-migration-vide…
smelaa Mar 1, 2024
2150628
Wrapping style in StyleProp component
smelaa Mar 1, 2024
4d84ed3
Migrate CategoryPicker to typescript
mateuuszzzzz Feb 12, 2024
b419a09
Merge branch 'ts-migration-videoplayercontext' into ts-migration-vide…
smelaa Mar 6, 2024
c768f5d
Address part of the review
smelaa Mar 13, 2024
6a50480
Address review comments
smelaa Mar 13, 2024
d2258f4
Solve typescript errors
smelaa Mar 13, 2024
fbef252
Merge branch 'main' into ts-migration-videoplayerpreview
smelaa Mar 14, 2024
db92a62
Add anchorref to VideoPopoverMenu
smelaa Mar 14, 2024
bea6377
Adress review comments
smelaa Mar 15, 2024
1a5c507
Address review comments.
smelaa Mar 15, 2024
410ef9f
Remove default value of props which are always set by a caller
smelaa Mar 18, 2024
7edb283
Merge branch 'main' into ts-migration-videoplayerpreview
smelaa Mar 18, 2024
607070c
Merge branch 'main' into ts-migration-videoplayerpreview
smelaa Mar 20, 2024
83a7bfd
Merge branch 'main' into ts-migration-videoplayerpreview
smelaa Mar 20, 2024
b0a2e69
Address review comments
smelaa Mar 20, 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
Original file line number Diff line number Diff line change
@@ -1,44 +1,41 @@
import PropTypes from 'prop-types';
import React, {useEffect, useState} from 'react';
import type {LayoutChangeEvent, ViewStyle} from 'react-native';
import type {GestureStateChangeEvent, GestureUpdateEvent, PanGestureChangeEventPayload, PanGestureHandlerEventPayload} from 'react-native-gesture-handler';
import {Gesture, GestureDetector} from 'react-native-gesture-handler';
import Animated, {runOnJS, useAnimatedStyle, useSharedValue} from 'react-native-reanimated';
import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext';
import useThemeStyles from '@hooks/useThemeStyles';

const propTypes = {
duration: PropTypes.number.isRequired,

position: PropTypes.number.isRequired,

seekPosition: PropTypes.func.isRequired,
type ProgressBarProps = {
duration: number;
smelaa marked this conversation as resolved.
Show resolved Hide resolved
position: number;
seekPosition: (newPosition: number) => void;
};

const defaultProps = {};

function getProgress(currentPosition, maxPosition) {
function getProgress(currentPosition: number, maxPosition: number) {
return Math.min(Math.max((currentPosition / maxPosition) * 100, 0), 100);
smelaa marked this conversation as resolved.
Show resolved Hide resolved
}

function ProgressBar({duration, position, seekPosition}) {
function ProgressBar({duration, position, seekPosition}: ProgressBarProps) {
const styles = useThemeStyles();
const {pauseVideo, playVideo, checkVideoPlaying} = usePlaybackContext();
const [sliderWidth, setSliderWidth] = useState(1);
const [isSliderPressed, setIsSliderPressed] = useState(false);
const progressWidth = useSharedValue(0);
const wasVideoPlayingOnCheck = useSharedValue(false);

const onCheckVideoPlaying = (isPlaying) => {
const onCheckVideoPlaying = (isPlaying: boolean) => {
wasVideoPlayingOnCheck.value = isPlaying;
};

const progressBarInteraction = (event) => {
const progressBarInteraction = (event: GestureUpdateEvent<PanGestureHandlerEventPayload & PanGestureChangeEventPayload> | GestureStateChangeEvent<PanGestureHandlerEventPayload>) => {
const progress = getProgress(event.x, sliderWidth);
progressWidth.value = progress;
runOnJS(seekPosition)((progress * duration) / 100);
};

const onSliderLayout = (e) => {
setSliderWidth(e.nativeEvent.layout.width);
const onSliderLayout = (event: LayoutChangeEvent) => {
setSliderWidth(event.nativeEvent.layout.width);
};

const pan = Gesture.Pan()
Expand Down Expand Up @@ -66,7 +63,7 @@ function ProgressBar({duration, position, seekPosition}) {
progressWidth.value = getProgress(position, duration);
}, [duration, isSliderPressed, position, progressWidth]);

const progressBarStyle = useAnimatedStyle(() => ({width: `${progressWidth.value}%`}));
const progressBarStyle: ViewStyle = useAnimatedStyle(() => ({width: `${progressWidth.value}%`}));

return (
<GestureDetector gesture={pan}>
Expand All @@ -85,8 +82,6 @@ function ProgressBar({duration, position, seekPosition}) {
);
}

ProgressBar.propTypes = propTypes;
ProgressBar.defaultProps = defaultProps;
ProgressBar.displayName = 'ProgressBar';

export default ProgressBar;
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import PropTypes from 'prop-types';
import React, {memo, useCallback, useState} from 'react';
import type {LayoutChangeEvent, StyleProp, ViewStyle} from 'react-native';
import {View} from 'react-native';
import type {GestureStateChangeEvent, GestureUpdateEvent, PanGestureChangeEventPayload, PanGestureHandlerEventPayload} from 'react-native-gesture-handler';
import {Gesture, GestureDetector} from 'react-native-gesture-handler';
import Animated, {runOnJS, useAnimatedStyle, useDerivedValue} from 'react-native-reanimated';
import Hoverable from '@components/Hoverable';
Expand All @@ -10,18 +11,13 @@ import {useVolumeContext} from '@components/VideoPlayerContexts/VolumeContext';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import * as NumberUtils from '@libs/NumberUtils';
import stylePropTypes from '@styles/stylePropTypes';

const propTypes = {
style: stylePropTypes.isRequired,
small: PropTypes.bool,
type VolumeButtonProps = {
style: StyleProp<ViewStyle>;
smelaa marked this conversation as resolved.
Show resolved Hide resolved
small?: boolean;
smelaa marked this conversation as resolved.
Show resolved Hide resolved
};

const defaultProps = {
small: false,
};

const getVolumeIcon = (volume) => {
const getVolumeIcon = (volume: number) => {
if (volume === 0) {
return Expensicons.Mute;
}
Expand All @@ -31,20 +27,20 @@ const getVolumeIcon = (volume) => {
return Expensicons.VolumeHigh;
};

function VolumeButton({style, small}) {
function VolumeButton({style, small = false}: VolumeButtonProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const {updateVolume, volume} = useVolumeContext();
const [sliderHeight, setSliderHeight] = useState(1);
const [volumeIcon, setVolumeIcon] = useState({icon: getVolumeIcon(volume.value)});
const [isSliderBeingUsed, setIsSliderBeingUsed] = useState(false);

const onSliderLayout = useCallback((e) => {
setSliderHeight(e.nativeEvent.layout.height);
const onSliderLayout = useCallback((event: LayoutChangeEvent) => {
setSliderHeight(event.nativeEvent.layout.height);
}, []);

const changeVolumeOnPan = useCallback(
(event) => {
(event: GestureStateChangeEvent<PanGestureHandlerEventPayload> | GestureUpdateEvent<PanGestureHandlerEventPayload & PanGestureChangeEventPayload>) => {
const val = NumberUtils.roundToTwoDecimalPlaces(1 - event.y / sliderHeight);
volume.value = NumberUtils.clamp(val, 0, 1);
},
Expand All @@ -65,7 +61,7 @@ function VolumeButton({style, small}) {

const progressBarStyle = useAnimatedStyle(() => ({height: `${volume.value * 100}%`}));

const updateIcon = useCallback((vol) => {
const updateIcon = useCallback((vol: number) => {
setVolumeIcon({icon: getVolumeIcon(vol)});
}, []);

Expand Down Expand Up @@ -98,7 +94,6 @@ function VolumeButton({style, small}) {
tooltipText={volume.value === 0 ? translate('videoPlayer.unmute') : translate('videoPlayer.mute')}
onPress={() => updateVolume(volume.value === 0 ? 1 : 0)}
src={volumeIcon.icon}
fill={styles.white}
small={small}
smelaa marked this conversation as resolved.
Show resolved Hide resolved
shouldForceRenderingTooltipBelow
/>
Expand All @@ -108,8 +103,6 @@ function VolumeButton({style, small}) {
);
}

VolumeButton.propTypes = propTypes;
VolumeButton.defaultProps = defaultProps;
VolumeButton.displayName = 'VolumeButton';

export default memo(VolumeButton);
Original file line number Diff line number Diff line change
@@ -1,55 +1,42 @@
import PropTypes from 'prop-types';
import type {Video} from 'expo-av';
import type {MutableRefObject} from 'react';
import React, {useCallback, useMemo, useState} from 'react';
import type {GestureResponderEvent, LayoutChangeEvent, StyleProp, ViewStyle} from 'react-native';
import {View} from 'react-native';
import Animated from 'react-native-reanimated';
import * as Expensicons from '@components/Icon/Expensicons';
import refPropTypes from '@components/refPropTypes';
import Text from '@components/Text';
import IconButton from '@components/VideoPlayer/IconButton';
import convertMillisecondsToTime from '@components/VideoPlayer/utils';
import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import stylePropTypes from '@styles/stylePropTypes';
import CONST from '@src/CONST';
import ProgressBar from './ProgressBar';
import VolumeButton from './VolumeButton';

const propTypes = {
duration: PropTypes.number.isRequired,

position: PropTypes.number.isRequired,

url: PropTypes.string.isRequired,

videoPlayerRef: refPropTypes.isRequired,

isPlaying: PropTypes.bool.isRequired,

type VideoPlayerControlsProps = {
duration: number;
smelaa marked this conversation as resolved.
Show resolved Hide resolved
position: number;
url: string;
videoPlayerRef: MutableRefObject<Video>;
isPlaying: boolean;
// Defines if component should have small icons and tighter spacing inline
small: PropTypes.bool,

style: stylePropTypes,

showPopoverMenu: PropTypes.func.isRequired,

togglePlayCurrentVideo: PropTypes.func.isRequired,
};

const defaultProps = {
small: false,
style: undefined,
small?: boolean;
style?: StyleProp<ViewStyle>;
showPopoverMenu: (event?: GestureResponderEvent | KeyboardEvent) => void | Promise<void>;
togglePlayCurrentVideo: (event?: GestureResponderEvent | KeyboardEvent) => void | Promise<void>;
};

function VideoPlayerControls({duration, position, url, videoPlayerRef, isPlaying, small, style, showPopoverMenu, togglePlayCurrentVideo}) {
function VideoPlayerControls({duration, position, url, videoPlayerRef, isPlaying, small = false, style, showPopoverMenu, togglePlayCurrentVideo}: VideoPlayerControlsProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const {updateCurrentlyPlayingURL} = usePlaybackContext();
const [shouldShowTime, setShouldShowTime] = useState(false);
const iconSpacing = small ? styles.mr3 : styles.mr4;

const onLayout = (e) => {
setShouldShowTime(e.nativeEvent.layout.width > CONST.VIDEO_PLAYER.HIDE_TIME_TEXT_WIDTH);
const onLayout = (event: LayoutChangeEvent) => {
setShouldShowTime(event.nativeEvent.layout.width > CONST.VIDEO_PLAYER.HIDE_TIME_TEXT_WIDTH);
};

const enterFullScreenMode = useCallback(() => {
Expand All @@ -58,7 +45,7 @@ function VideoPlayerControls({duration, position, url, videoPlayerRef, isPlaying
}, [updateCurrentlyPlayingURL, url, videoPlayerRef]);

const seekPosition = useCallback(
(newPosition) => {
(newPosition: number) => {
videoPlayerRef.current.setStatusAsync({positionMillis: newPosition});
},
[videoPlayerRef],
Expand Down Expand Up @@ -116,8 +103,6 @@ function VideoPlayerControls({duration, position, url, videoPlayerRef, isPlaying
);
}

VideoPlayerControls.propTypes = propTypes;
VideoPlayerControls.defaultProps = defaultProps;
VideoPlayerControls.displayName = 'VideoPlayerControls';

export default VideoPlayerControls;
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import PropTypes from 'prop-types';
import React from 'react';
import {View} from 'react-native';
import type {GestureResponderEvent} from 'react-native';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
import Image from '@components/Image';
Expand All @@ -13,19 +13,13 @@ import * as ReportUtils from '@libs/ReportUtils';
import variables from '@styles/variables';
import CONST from '@src/CONST';

const propTypes = {
onPress: PropTypes.func.isRequired,

accessibilityLabel: PropTypes.string.isRequired,

thumbnailUrl: PropTypes.string,
};

const defaultProps = {
thumbnailUrl: undefined,
type VideoPlayerThumbnailProps = {
thumbnailUrl: string | undefined;
smelaa marked this conversation as resolved.
Show resolved Hide resolved
onPress: (event?: GestureResponderEvent | KeyboardEvent) => void | Promise<void>;
smelaa marked this conversation as resolved.
Show resolved Hide resolved
accessibilityLabel: string;
};

function VideoPlayerThumbnail({thumbnailUrl, onPress, accessibilityLabel}) {
function VideoPlayerThumbnail({thumbnailUrl, onPress, accessibilityLabel}: VideoPlayerThumbnailProps) {
const styles = useThemeStyles();

return (
Expand All @@ -48,9 +42,7 @@ function VideoPlayerThumbnail({thumbnailUrl, onPress, accessibilityLabel}) {
onPress={onPress}
onPressIn={() => DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()}
onPressOut={() => ControlSelection.unblock()}
onLongPress={(event) =>
showContextMenuForReport(event, anchor, (report && report.reportID) || '', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))
}
onLongPress={(event) => showContextMenuForReport(event, anchor, report?.reportID ?? '', action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))}
shouldUseHapticsOnLongPress
>
<View style={[styles.videoThumbnailPlayButton]}>
Expand All @@ -69,8 +61,6 @@ function VideoPlayerThumbnail({thumbnailUrl, onPress, accessibilityLabel}) {
);
}

VideoPlayerThumbnail.propTypes = propTypes;
VideoPlayerThumbnail.defaultProps = defaultProps;
VideoPlayerThumbnail.displayName = 'VideoPlayerThumbnail';

export default VideoPlayerThumbnail;
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import PropTypes from 'prop-types';
import type {VideoReadyForDisplayEvent} from 'expo-av';
import React, {useEffect, useState} from 'react';
import {View} from 'react-native';
import type {GestureResponderEvent} from 'react-native';
import * as Expensicons from '@components/Icon/Expensicons';
import VideoPlayer from '@components/VideoPlayer';
import IconButton from '@components/VideoPlayer/IconButton';
Expand All @@ -12,30 +13,21 @@ import useWindowDimensions from '@hooks/useWindowDimensions';
import CONST from '@src/CONST';
import VideoPlayerThumbnail from './VideoPlayerThumbnail';

const propTypes = {
videoUrl: PropTypes.string.isRequired,

videoDimensions: PropTypes.shape({
width: PropTypes.number.isRequired,
height: PropTypes.number.isRequired,
}),

videoDuration: PropTypes.number,

thumbnailUrl: PropTypes.string,

fileName: PropTypes.string.isRequired,

onShowModalPress: PropTypes.func.isRequired,
type VideoDimensions = {
width: number;
height: number;
};

const defaultProps = {
videoDimensions: CONST.VIDEO_PLAYER.DEFAULT_VIDEO_DIMENSIONS,
thumbnailUrl: undefined,
videoDuration: 0,
type VideoPlayerPreviewProps = {
videoUrl: string;
smelaa marked this conversation as resolved.
Show resolved Hide resolved
videoDimensions: VideoDimensions;
videoDuration: number;
smelaa marked this conversation as resolved.
Show resolved Hide resolved
thumbnailUrl?: string;
smelaa marked this conversation as resolved.
Show resolved Hide resolved
fileName: string;
onShowModalPress: (event?: GestureResponderEvent | KeyboardEvent) => void | Promise<void>;
};

function VideoPlayerPreview({videoUrl, thumbnailUrl, fileName, videoDimensions, videoDuration, onShowModalPress}) {
function VideoPlayerPreview({videoUrl, thumbnailUrl, fileName, videoDimensions = CONST.VIDEO_PLAYER.DEFAULT_VIDEO_DIMENSIONS, videoDuration = 0, onShowModalPress}: VideoPlayerPreviewProps) {
const styles = useThemeStyles();
smelaa marked this conversation as resolved.
Show resolved Hide resolved
const {translate} = useLocalize();
const {currentlyPlayingURL, updateCurrentlyPlayingURL} = usePlaybackContext();
Expand All @@ -44,8 +36,11 @@ function VideoPlayerPreview({videoUrl, thumbnailUrl, fileName, videoDimensions,
const [measuredDimensions, setMeasuredDimensions] = useState(videoDimensions);
const {thumbnailDimensionsStyles} = useThumbnailDimensions(measuredDimensions.width, measuredDimensions.height);

const onVideoLoaded = (e) => {
setMeasuredDimensions({width: e.srcElement.videoWidth, height: e.srcElement.videoHeight});
// `onVideoLoaded` is passed to VideoPlayerPreview's `Video` element which is displayed only on web.
// VideoReadyForDisplayEvent type is lacking srcElement, that's why it's added here

const onVideoLoaded = (event: VideoReadyForDisplayEvent & {srcElement: HTMLVideoElement}) => {
smelaa marked this conversation as resolved.
Show resolved Hide resolved
setMeasuredDimensions({width: event.srcElement.videoWidth, height: event.srcElement.videoHeight});
smelaa marked this conversation as resolved.
Show resolved Hide resolved
};

const handleOnPress = () => {
Expand Down Expand Up @@ -75,7 +70,7 @@ function VideoPlayerPreview({videoUrl, thumbnailUrl, fileName, videoDimensions,
<VideoPlayer
url={videoUrl}
onOpenInModalButtonPress={onShowModalPress}
onVideoLoaded={onVideoLoaded}
onVideoLoaded={onVideoLoaded as (event: VideoReadyForDisplayEvent) => void}
videoDuration={videoDuration}
shouldUseSmallVideoControls
style={[styles.w100, styles.h100]}
Expand All @@ -94,8 +89,6 @@ function VideoPlayerPreview({videoUrl, thumbnailUrl, fileName, videoDimensions,
);
}

VideoPlayerPreview.propTypes = propTypes;
VideoPlayerPreview.defaultProps = defaultProps;
VideoPlayerPreview.displayName = 'VideoPlayerPreview';

export default VideoPlayerPreview;
Loading
Loading