Skip to content

Commit

Permalink
Merge pull request #37425 from software-mansion-labs/ts-migration-vid…
Browse files Browse the repository at this point in the history
…eoplayerpreview

[TS migration] Migrate VideoPlayerPreview, VideoPlayerControls and VideoPopoverMenu component files to TypeScript
  • Loading branch information
grgia authored Mar 21, 2024
2 parents 51ef6c1 + b0a2e69 commit 8206580
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 140 deletions.
Original file line number Diff line number Diff line change
@@ -1,44 +1,46 @@
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,
type ProgressBarProps = {
/** Total duration of a video. */
duration: number;

position: PropTypes.number.isRequired,
/** Position of progress pointer on the bar. */
position: number;

seekPosition: PropTypes.func.isRequired,
/** Function to seek to a specific position in the video. */
seekPosition: (newPosition: number) => void;
};

const defaultProps = {};

function getProgress(currentPosition, maxPosition) {
function getProgress(currentPosition: number, maxPosition: number): number {
return Math.min(Math.max((currentPosition / maxPosition) * 100, 0), 100);
}

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 +68,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 +87,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,16 @@ 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 for the volume button. */
style?: StyleProp<ViewStyle>;

const defaultProps = {
small: false,
/** Is button icon small. */
small?: boolean;
};

const getVolumeIcon = (volume) => {
const getVolumeIcon = (volume: number) => {
if (volume === 0) {
return Expensicons.Mute;
}
Expand All @@ -31,20 +30,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 +64,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 +97,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}
shouldForceRenderingTooltipBelow
/>
Expand All @@ -108,8 +106,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,58 @@
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,
type VideoPlayerControlsProps = {
/** Duration of a video. */
duration: number;

position: PropTypes.number.isRequired,
/** Position of progress pointer. */
position: number;

url: PropTypes.string.isRequired,
/** Url of a video. */
url: string;

videoPlayerRef: refPropTypes.isRequired,
/** Ref for video player. */
videoPlayerRef: MutableRefObject<Video>;

isPlaying: PropTypes.bool.isRequired,
/** Is video playing. */
isPlaying: boolean;

// Defines if component should have small icons and tighter spacing inline
small: PropTypes.bool,
/** Defines if component should have small icons and tighter spacing inline. */
small?: boolean;

style: stylePropTypes,
/** Style of video player controls. */
style?: StyleProp<ViewStyle>;

showPopoverMenu: PropTypes.func.isRequired,
/** Function called to show popover menu. */
showPopoverMenu: (event?: GestureResponderEvent | KeyboardEvent) => void | Promise<void>;

togglePlayCurrentVideo: PropTypes.func.isRequired,
/** Function to play and pause the video. */
togglePlayCurrentVideo: (event?: GestureResponderEvent | KeyboardEvent) => void | Promise<void>;
};

const defaultProps = {
small: false,
style: undefined,
};

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 +61,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 +119,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,18 @@ import * as ReportUtils from '@libs/ReportUtils';
import variables from '@styles/variables';
import CONST from '@src/CONST';

const propTypes = {
onPress: PropTypes.func.isRequired,
type VideoPlayerThumbnailProps = {
/** Url of thumbnail image. */
thumbnailUrl?: string;

accessibilityLabel: PropTypes.string.isRequired,
/** Callback executed on thumbnail press. */
onPress: (event?: GestureResponderEvent | KeyboardEvent) => void | Promise<void>;

thumbnailUrl: PropTypes.string,
/** Accessibility label for the thumbnail. */
accessibilityLabel: string;
};

const defaultProps = {
thumbnailUrl: undefined,
};

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

return (
Expand All @@ -48,9 +47,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 +66,6 @@ function VideoPlayerThumbnail({thumbnailUrl, onPress, accessibilityLabel}) {
);
}

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

export default VideoPlayerThumbnail;
Loading

0 comments on commit 8206580

Please sign in to comment.