Skip to content

Commit

Permalink
merge main
Browse files Browse the repository at this point in the history
  • Loading branch information
DylanDylann committed Mar 28, 2024
2 parents 23375f8 + 9a1f43c commit f8782d5
Show file tree
Hide file tree
Showing 12 changed files with 173 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,14 @@ If you need to enable direct debits from your verified bank account, your bank w
- The ACH CompanyIDs (1270239450, 4270239450 and 2270239450)
- The ACH Originator Name (Expensify)

If using Expensify to process Bill payments, you'll also need to whitelist the ACH IDs from our partner [Stripe](https://support.stripe.com/questions/ach-direct-debit-company-ids-for-stripe?):
- The ACH CompanyIDs (1800948598 and 4270465600)
- The ACH Originator Name (Stripe Payments company)

If using Expensify to process international reimbursements from your USD bank account, you'll also need to whitelist the ACH IDs from our partner CorPay:
- The ACH CompanyIDs (1522304924 and 2522304924)
- The ACH Originator Name (Cambridge Global Payments)

To request to unlock the bank account, go to **Settings > Workspaces > _Workspace Name_ > Bank account** and click **Fix.** This sends a request to our support team to review why the bank account was locked, who will send you a message to confirm that.

Unlocking a bank account can take 4-5 business days to process, to allow for ACH processing time and clawback periods.
Expand Down
2 changes: 2 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import ScrollOffsetContextProvider from './components/ScrollOffsetContextProvide
import ThemeIllustrationsProvider from './components/ThemeIllustrationsProvider';
import ThemeProvider from './components/ThemeProvider';
import ThemeStylesProvider from './components/ThemeStylesProvider';
import {FullScreenContextProvider} from './components/VideoPlayerContexts/FullScreenContext';
import {PlaybackContextProvider} from './components/VideoPlayerContexts/PlaybackContext';
import {VideoPopoverMenuContextProvider} from './components/VideoPlayerContexts/VideoPopoverMenuContext';
import {VolumeContextProvider} from './components/VideoPlayerContexts/VolumeContext';
Expand Down Expand Up @@ -78,6 +79,7 @@ function App({url}: AppProps) {
ActiveElementRoleProvider,
ActiveWorkspaceContextProvider,
PlaybackContextProvider,
FullScreenContextProvider,
VolumeContextProvider,
VideoPopoverMenuContextProvider,
]}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ function CarouselItem({item, onPress, isFocused, isModalHovered}: CarouselItemPr
isHovered={isModalHovered}
isFocused={isFocused}
duration={item.duration}
isUsedInCarousel
/>
</View>

Expand Down
21 changes: 18 additions & 3 deletions src/components/Attachments/AttachmentCarousel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {withOnyx} from 'react-native-onyx';
import type {Attachment, AttachmentSource} from '@components/Attachments/types';
import BlockingView from '@components/BlockingViews/BlockingView';
import * as Illustrations from '@components/Icon/Illustrations';
import {useFullScreenContext} from '@components/VideoPlayerContexts/FullScreenContext';
import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
Expand All @@ -32,6 +33,7 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source,
const theme = useTheme();
const {translate} = useLocalize();
const styles = useThemeStyles();
const {isFullScreenRef} = useFullScreenContext();
const scrollRef = useRef<FlatList>(null);

const canUseTouchScreen = DeviceCapabilities.canUseTouchScreen();
Expand Down Expand Up @@ -76,6 +78,10 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source,
/** Updates the page state when the user navigates between attachments */
const updatePage = useCallback(
({viewableItems}: UpdatePageProps) => {
if (isFullScreenRef.current) {
return;
}

Keyboard.dismiss();

// Since we can have only one item in view at a time, we can use the first item in the array
Expand All @@ -95,12 +101,16 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source,
onNavigate(entry.item);
}
},
[onNavigate],
[isFullScreenRef, onNavigate],
);

/** Increments or decrements the index to get another selected item */
const cycleThroughAttachments = useCallback(
(deltaSlide: number) => {
if (isFullScreenRef.current) {
return;
}

const nextIndex = page + deltaSlide;
const nextItem = attachments[nextIndex];

Expand All @@ -110,7 +120,7 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source,

scrollRef.current.scrollToIndex({index: nextIndex, animated: canUseTouchScreen});
},
[attachments, canUseTouchScreen, page],
[attachments, canUseTouchScreen, isFullScreenRef, page],
);

const extractItemKey = useCallback(
Expand Down Expand Up @@ -145,7 +155,12 @@ function AttachmentCarousel({report, reportActions, parentReportActions, source,
return (
<View
style={[styles.flex1, styles.attachmentCarouselContainer]}
onLayout={({nativeEvent}) => setContainerWidth(PixelRatio.roundToNearestPixel(nativeEvent.layout.width))}
onLayout={({nativeEvent}) => {
if (isFullScreenRef.current) {
return;
}
setContainerWidth(PixelRatio.roundToNearestPixel(nativeEvent.layout.width));
}}
onMouseEnter={() => !canUseTouchScreen && setShouldShowArrows(true)}
onMouseLeave={() => !canUseTouchScreen && setShouldShowArrows(false)}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ type AttachmentViewVideoProps = Pick<AttachmentViewProps, 'duration' | 'isHovere
};

function AttachmentViewVideo({source, isHovered = false, shouldUseSharedVideoElement = false, duration = 0}: AttachmentViewVideoProps) {
const {isSmallScreen} = useWindowDimensions();
const {isSmallScreenWidth} = useWindowDimensions();
const styles = useThemeStyles();

return (
<VideoPlayer
url={source}
shouldUseSharedVideoElement={shouldUseSharedVideoElement && !isSmallScreen}
shouldUseSharedVideoElement={shouldUseSharedVideoElement && !isSmallScreenWidth}
isVideoHovered={isHovered}
videoDuration={duration}
style={[styles.w100, styles.h100]}
Expand Down
94 changes: 47 additions & 47 deletions src/components/VideoPlayer/BaseVideoPlayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,19 @@ import _ from 'underscore';
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 VideoPopoverMenu from '@components/VideoPopoverMenu';
import useNetwork from '@hooks/useNetwork';
import useThemeStyles from '@hooks/useThemeStyles';
import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL';
import * as Browser from '@libs/Browser';
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
import CONST from '@src/CONST';
import {videoPlayerDefaultProps, videoPlayerPropTypes} from './propTypes';
import shouldReplayVideo from './shouldReplayVideo';
import * as VideoUtils from './utils';
import VideoPlayerControls from './VideoPlayerControls';

const isMobileSafari = Browser.isMobileSafari();

function BaseVideoPlayer({
url,
resizeMode,
Expand All @@ -44,8 +42,19 @@ function BaseVideoPlayer({
isVideoHovered,
}) {
const styles = useThemeStyles();
const {pauseVideo, playVideo, currentlyPlayingURL, updateSharedElements, sharedElement, originalParent, shareVideoPlayerElements, currentVideoPlayerRef, updateCurrentlyPlayingURL} =
usePlaybackContext();
const {
pauseVideo,
playVideo,
currentlyPlayingURL,
updateSharedElements,
sharedElement,
originalParent,
shareVideoPlayerElements,
currentVideoPlayerRef,
updateCurrentlyPlayingURL,
videoResumeTryNumber,
} = usePlaybackContext();
const {isFullScreenRef} = useFullScreenContext();
const {isOffline} = useNetwork();
const [duration, setDuration] = useState(videoDuration * 1000);
const [position, setPosition] = useState(0);
Expand All @@ -60,24 +69,21 @@ function BaseVideoPlayer({
const videoPlayerElementParentRef = useRef(null);
const videoPlayerElementRef = useRef(null);
const sharedVideoPlayerParentRef = useRef(null);
const videoResumeTryNumber = useRef(0);
const canUseTouchScreen = DeviceCapabilities.canUseTouchScreen();
const isCurrentlyURLSet = currentlyPlayingURL === url;
const isUploading = _.some(CONST.ATTACHMENT_LOCAL_URL_PREFIX, (prefix) => url.startsWith(prefix));
const shouldUseSharedVideoElementRef = useRef(shouldUseSharedVideoElement);

const [isFullscreen, setIsFullscreen] = useState(false);
const videoStateRef = useRef(null);

const togglePlayCurrentVideo = useCallback(() => {
videoResumeTryNumber.current = 0;
if (!isCurrentlyURLSet) {
updateCurrentlyPlayingURL(url);
} else if (isPlaying && !isFullscreen) {
} else if (isPlaying) {
pauseVideo();
} else if (!isFullscreen) {
} else {
playVideo();
}
}, [isCurrentlyURLSet, isPlaying, pauseVideo, playVideo, updateCurrentlyPlayingURL, url, isFullscreen]);
}, [isCurrentlyURLSet, isPlaying, pauseVideo, playVideo, updateCurrentlyPlayingURL, url, videoResumeTryNumber]);

const showPopoverMenu = (e) => {
setPopoverAnchorPosition({horizontal: e.nativeEvent.pageX, vertical: e.nativeEvent.pageY});
Expand All @@ -99,7 +105,7 @@ function BaseVideoPlayer({
}
videoResumeTryNumber.current -= 1;
},
[playVideo],
[playVideo, videoResumeTryNumber],
);

const handlePlaybackStatusUpdate = useCallback(
Expand All @@ -118,7 +124,7 @@ function BaseVideoPlayer({
setIsBuffering(e.isBuffering || false);
setDuration(currentDuration);
setPosition(currentPositon);

videoStateRef.current = e;
onPlaybackStatusUpdate(e);
},
[onPlaybackStatusUpdate, preventPausingWhenExitingFullscreen, videoDuration],
Expand All @@ -127,22 +133,20 @@ function BaseVideoPlayer({
const handleFullscreenUpdate = useCallback(
(e) => {
onFullscreenUpdate(e);

setIsFullscreen(e.fullscreenUpdate === VideoFullscreenUpdate.PLAYER_DID_PRESENT);

// fix for iOS native and mWeb: when switching to fullscreen and then exiting
// the fullscreen mode while playing, the video pauses
if (!isPlaying || e.fullscreenUpdate !== VideoFullscreenUpdate.PLAYER_DID_DISMISS) {
return;
if (e.fullscreenUpdate === VideoFullscreenUpdate.PLAYER_DID_DISMISS) {
isFullScreenRef.current = false;
// we need to use video state ref to check if video is playing, to catch proper state after exiting fullscreen
// and also fix a bug with fullscreen mode dismissing when handleFullscreenUpdate function changes
if (videoStateRef.current && videoStateRef.current.isPlaying) {
pauseVideo();
playVideo();
videoResumeTryNumber.current = 3;
}
}

if (isMobileSafari) {
pauseVideo();
}
playVideo();
videoResumeTryNumber.current = 3;
},
[isPlaying, onFullscreenUpdate, pauseVideo, playVideo],
[isFullScreenRef, onFullscreenUpdate, pauseVideo, playVideo, videoResumeTryNumber],
);

const bindFunctions = useCallback(() => {
Expand All @@ -156,45 +160,37 @@ function BaseVideoPlayer({
}, [currentVideoPlayerRef, handleFullscreenUpdate, handlePlaybackStatusUpdate]);

useEffect(() => {
if (!isUploading) {
if (!isUploading || !videoPlayerRef.current) {
return;
}

// If we are uploading a new video, we want to immediately set the video player ref.
currentVideoPlayerRef.current = videoPlayerRef.current;
}, [url, currentVideoPlayerRef, isUploading]);

useEffect(() => {
shouldUseSharedVideoElementRef.current = shouldUseSharedVideoElement;
}, [shouldUseSharedVideoElement]);

useEffect(
() => () => {
if (shouldUseSharedVideoElementRef.current) {
return;
}

// If it's not a shared video player, clear the video player ref.
currentVideoPlayerRef.current = null;
},
[currentVideoPlayerRef],
);

// update shared video elements
useEffect(() => {
if (shouldUseSharedVideoElement || url !== currentlyPlayingURL) {
if (shouldUseSharedVideoElement || url !== currentlyPlayingURL || isFullScreenRef.current) {
return;
}
shareVideoPlayerElements(videoPlayerRef.current, videoPlayerElementParentRef.current, videoPlayerElementRef.current, isUploading);
}, [currentlyPlayingURL, shouldUseSharedVideoElement, shareVideoPlayerElements, updateSharedElements, url, isUploading]);
}, [currentlyPlayingURL, shouldUseSharedVideoElement, shareVideoPlayerElements, updateSharedElements, url, isUploading, isFullScreenRef]);

// append shared video element to new parent (used for example in attachment modal)
useEffect(() => {
if (url !== currentlyPlayingURL || !sharedElement || !shouldUseSharedVideoElement) {
if (url !== currentlyPlayingURL || !sharedElement || isFullScreenRef.current) {
return;
}

const newParentRef = sharedVideoPlayerParentRef.current;

if (!shouldUseSharedVideoElement) {
if (newParentRef && newParentRef.childNodes[0] && newParentRef.childNodes[0].remove) {
newParentRef.childNodes[0].remove();
}
return;
}

videoPlayerRef.current = currentVideoPlayerRef.current;
if (currentlyPlayingURL === url) {
newParentRef.appendChild(sharedElement);
Expand All @@ -204,9 +200,10 @@ function BaseVideoPlayer({
if (!originalParent && !newParentRef.childNodes[0]) {
return;
}
newParentRef.childNodes[0].remove();
originalParent.appendChild(sharedElement);
};
}, [bindFunctions, currentVideoPlayerRef, currentlyPlayingURL, originalParent, sharedElement, shouldUseSharedVideoElement, url]);
}, [bindFunctions, currentVideoPlayerRef, currentlyPlayingURL, isFullScreenRef, originalParent, sharedElement, shouldUseSharedVideoElement, url]);

return (
<>
Expand All @@ -222,6 +219,9 @@ function BaseVideoPlayer({
<PressableWithoutFeedback
accessibilityRole="button"
onPress={() => {
if (isFullScreenRef.current) {
return;
}
togglePlayCurrentVideo();
}}
style={styles.flex1}
Expand Down
5 changes: 4 additions & 1 deletion src/components/VideoPlayer/VideoPlayerControls/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import * as Expensicons from '@components/Icon/Expensicons';
import Text from '@components/Text';
import IconButton from '@components/VideoPlayer/IconButton';
import {convertMillisecondsToTime} from '@components/VideoPlayer/utils';
import {useFullScreenContext} from '@components/VideoPlayerContexts/FullScreenContext';
import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContext';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
Expand Down Expand Up @@ -48,6 +49,7 @@ function VideoPlayerControls({duration, position, url, videoPlayerRef, isPlaying
const styles = useThemeStyles();
const {translate} = useLocalize();
const {updateCurrentlyPlayingURL} = usePlaybackContext();
const {isFullScreenRef} = useFullScreenContext();
const [shouldShowTime, setShouldShowTime] = useState(false);
const iconSpacing = small ? styles.mr3 : styles.mr4;

Expand All @@ -56,9 +58,10 @@ function VideoPlayerControls({duration, position, url, videoPlayerRef, isPlaying
};

const enterFullScreenMode = useCallback(() => {
isFullScreenRef.current = true;
updateCurrentlyPlayingURL(url);
videoPlayerRef.current.presentFullscreenPlayer();
}, [updateCurrentlyPlayingURL, url, videoPlayerRef]);
}, [isFullScreenRef, updateCurrentlyPlayingURL, url, videoPlayerRef]);

const seekPosition = useCallback(
(newPosition: number) => {
Expand Down
34 changes: 34 additions & 0 deletions src/components/VideoPlayerContexts/FullScreenContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React, {useCallback, useContext, useMemo, useRef} from 'react';
import type WindowDimensions from '@hooks/useWindowDimensions/types';
import type ChildrenProps from '@src/types/utils/ChildrenProps';
import type {FullScreenContext} from './types';

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

function FullScreenContextProvider({children}: ChildrenProps) {
const isFullScreenRef = useRef(false);
const lockedWindowDimensionsRef = useRef<WindowDimensions | null>(null);

const lockWindowDimensions = useCallback((newWindowDimensions: WindowDimensions) => {
lockedWindowDimensionsRef.current = newWindowDimensions;
}, []);

const unlockWindowDimensions = useCallback(() => {
lockedWindowDimensionsRef.current = null;
}, []);

const contextValue = useMemo(() => ({isFullScreenRef, lockedWindowDimensionsRef, lockWindowDimensions, unlockWindowDimensions}), [lockWindowDimensions, unlockWindowDimensions]);
return <Context.Provider value={contextValue}>{children}</Context.Provider>;
}

function useFullScreenContext() {
const fullscreenContext = useContext(Context);
if (!fullscreenContext) {
throw new Error('useFullScreenContext must be used within a FullScreenContextProvider');
}
return fullscreenContext;
}

FullScreenContextProvider.displayName = 'FullScreenContextProvider';

export {Context as FullScreenContext, FullScreenContextProvider, useFullScreenContext};
Loading

0 comments on commit f8782d5

Please sign in to comment.