Skip to content

Commit

Permalink
Merge pull request #73 from software-mansion-labs/wave9/welcome-video…
Browse files Browse the repository at this point in the history
…-cdOut

[Wave9] Welcome Video as separate Screen
  • Loading branch information
MaciejSWM authored Mar 4, 2024
2 parents c199bb1 + 80bafbc commit fab0a2e
Show file tree
Hide file tree
Showing 22 changed files with 321 additions and 63 deletions.
1 change: 1 addition & 0 deletions src/NAVIGATORS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ export default {
LEFT_MODAL_NAVIGATOR: 'LeftModalNavigator',
RIGHT_MODAL_NAVIGATOR: 'RightModalNavigator',
ONBOARDING_MODAL_NAVIGATOR: 'OnboardingModalNavigator',
WELCOME_VIDEO_MODAL_NAVIGATOR: 'WelcomeVideoModalNavigator',
FULL_SCREEN_NAVIGATOR: 'FullScreenNavigator',
} as const;
2 changes: 2 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -513,8 +513,10 @@ const ROUTES = {
getRoute: (contentType: string, backTo?: string) => getUrlWithBackToParam(`referral/${contentType}`, backTo),
},
PROCESS_MONEY_REQUEST_HOLD: 'hold-request-educational',
ONBOARDING_ROOT: 'onboarding',
ONBOARDING_PERSONAL_DETAILS: 'onboarding/personal-details',
ONBOARDING_PURPOSE: 'onboarding/purpose',
WELCOME_VIDEO_ROOT: 'onboarding/welcome-video',
} as const;

/**
Expand Down
5 changes: 5 additions & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,12 +238,17 @@ const SCREENS = {
PERSONAL_DETAILS: 'Onboarding_Personal_Details',
PURPOSE: 'Onboarding_Purpose',
},

ONBOARD_ENGAGEMENT: {
ROOT: 'Onboard_Engagement_Root',
MANAGE_TEAMS_EXPENSES: 'Manage_Teams_Expenses',
EXPENSIFY_CLASSIC: 'Expenisfy_Classic',
},

WELCOME_VIDEO: {
ROOT: 'Welcome_Video_Root',
},

I_KNOW_A_TEACHER: 'I_Know_A_Teacher',
INTRO_SCHOOL_PRINCIPAL: 'Intro_School_Principal',
I_AM_A_TEACHER: 'I_Am_A_Teacher',
Expand Down
161 changes: 161 additions & 0 deletions src/components/OnboardingWelcomeVideo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import type {VideoReadyForDisplayEvent} from 'expo-av';
import React, {useCallback, useEffect, useRef, useState} from 'react';
import type {LayoutChangeEvent, LayoutRectangle} from 'react-native';
import {View} from 'react-native';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useOnboardingLayout from '@hooks/useOnboardingLayout';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import Navigation from '@libs/Navigation/Navigation';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import Button from './Button';
import Lottie from './Lottie';
import LottieAnimations from './LottieAnimations';
import Modal from './Modal';
import Text from './Text';
import VideoPlayer from './VideoPlayer';

// Aspect ratio and height of the video.
// Useful before video loads to reserve space.
const VIDEO_ASPECT_RATIO = 484 / 272.25;
const VIDEO_HEIGHT = 320;

const MODAL_PADDING = variables.spacing2;

type VideoLoadedEventType = {
srcElement: {
videoWidth: number;
videoHeight: number;
};
};

type VideoPlaybackStatusEventType = {
isLoaded: boolean;
};

type VideoStatus = 'video' | 'animation';

function OnboardingWelcomeVideo() {
const {translate} = useLocalize();
const styles = useThemeStyles();
const containerDimensions = useRef<LayoutRectangle>({width: 0, height: 0, x: 0, y: 0});
const [isModalVisible, setIsModalVisible] = useState(true);
const {isSmallScreenWidth} = useWindowDimensions();
const {shouldUseNarrowLayout} = useOnboardingLayout();
const [welcomeVideoStatus, setWelcomeVideoStatus] = useState<VideoStatus>('video');
const [isWelcomeVideoStatusLocked, setIsWelcomeVideoStatusLocked] = useState(false);
const [isVideoLoaded, setIsVideoLoaded] = useState(false);
const [videoAspectRatio, setVideoAspectRatio] = useState(VIDEO_ASPECT_RATIO);
const {isOffline} = useNetwork();

useEffect(() => {
if (isWelcomeVideoStatusLocked) {
return;
}

if (isOffline) {
setWelcomeVideoStatus('animation');
setIsWelcomeVideoStatusLocked(true);
} else if (!isOffline && isVideoLoaded) {
setWelcomeVideoStatus('video');
setIsWelcomeVideoStatusLocked(true);
}
}, [isOffline, isVideoLoaded, isWelcomeVideoStatusLocked]);

const storeContainerDimensions = (event: LayoutChangeEvent) => {
containerDimensions.current = event.nativeEvent.layout;
};

const closeModal = useCallback(() => {
setIsModalVisible(false);
Navigation.goBack();
}, []);

const setAspectRatio = (e: VideoReadyForDisplayEvent | VideoLoadedEventType | undefined) => {
if (!e) {
return;
}

// TODO: Figure out why on mobile there's e.naturalSize and on web it's e.srcElement
if ('naturalSize' in e) {
setVideoAspectRatio(e.naturalSize.width / e.naturalSize.height);
} else {
setVideoAspectRatio(e.srcElement.videoWidth / e.srcElement.videoHeight);
}
};

const setVideoStatus = (e: VideoPlaybackStatusEventType) => {
setIsVideoLoaded(e.isLoaded);
};

const getWelcomeVideo = () => {
if (welcomeVideoStatus === 'video') {
const videoWidth = containerDimensions.current.width - 2 * MODAL_PADDING;

return (
<View
style={[
// Prevent layout jumps by reserving height for the video
{height: VIDEO_HEIGHT - 2 * MODAL_PADDING},
]}
>
<VideoPlayer
// Temporary file supplied for testing purposes, to
// be changed when correct one gets uploaded on backend
url="https://d23dyxeqlo5psv.cloudfront.net/big_buck_bunny.mp4"
videoPlayerStyle={[styles.onboardingVideoPlayer, {width: videoWidth, height: videoWidth / videoAspectRatio}]}
onVideoLoaded={setAspectRatio}
onPlaybackStatusUpdate={setVideoStatus}
shouldShowProgressVolumeOnly
shouldPlay
isLooping
/>
</View>
);
}

return (
<Lottie
source={LottieAnimations.Hands}
style={styles.w100}
webStyle={isSmallScreenWidth ? styles.h100 : styles.w100}
autoPlay
loop
/>
);
};

return (
<Modal
isVisible={isModalVisible}
type={shouldUseNarrowLayout ? CONST.MODAL.MODAL_TYPE.CENTERED_SMALL : CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED}
onClose={closeModal}
innerContainerStyle={shouldUseNarrowLayout ? {} : {paddingTop: MODAL_PADDING, paddingBottom: MODAL_PADDING}}
>
<View
style={[shouldUseNarrowLayout ? {width: 500, height: 500} : {}, {maxHeight: '100%'}]}
onLayout={storeContainerDimensions}
>
<View style={shouldUseNarrowLayout ? {padding: MODAL_PADDING} : {paddingHorizontal: MODAL_PADDING}}>{getWelcomeVideo()}</View>
<View style={[shouldUseNarrowLayout ? [styles.mt5, styles.mh8] : [styles.mt3, styles.mh5]]}>
<View style={[shouldUseNarrowLayout ? [styles.gap1, styles.mb8] : [styles.gap2, styles.mb10]]}>
<Text style={styles.textHeroSmall}>{translate('onboarding.welcomeVideo.title')}</Text>
<Text style={styles.textSupporting}>{translate('onboarding.welcomeVideo.description')}</Text>
</View>
<Button
success
pressOnEnter
onPress={closeModal}
text={translate('onboarding.welcomeVideo.button')}
/>
</View>
</View>
</Modal>
);
}

OnboardingWelcomeVideo.displayName = 'OnboardingWelcomeVideo';

export default OnboardingWelcomeVideo;
12 changes: 11 additions & 1 deletion src/components/VideoPlayer/BaseVideoPlayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ function BaseVideoPlayer({
shouldUseSharedVideoElement,
shouldUseSmallVideoControls,
shouldShowVideoControls,
shouldShowProgressVolumeOnly,
onPlaybackStatusUpdate,
onFullscreenUpdate,
shouldPlay,
// TODO: investigate what is the root cause of the bug with unexpected video switching
// isVideoHovered caused a bug with unexpected video switching. We are investigating the root cause of the issue,
// but current workaround is just not to use it here for now. This causes not displaying the video controls when
Expand Down Expand Up @@ -167,6 +169,13 @@ function BaseVideoPlayer({
};
}, [bindFunctions, currentVideoPlayerRef, currentlyPlayingURL, isSmallScreenWidth, originalParent, sharedElement, shouldUseSharedVideoElement, url]);

useEffect(() => {
if (!shouldPlay) {
return;
}
updateCurrentlyPlayingURL(url);
}, [shouldPlay, updateCurrentlyPlayingURL, url]);

return (
<>
<View style={style}>
Expand Down Expand Up @@ -211,7 +220,7 @@ function BaseVideoPlayer({
source={{
uri: sourceURL,
}}
shouldPlay={false}
shouldPlay={shouldPlay}
useNativeControls={false}
resizeMode={resizeMode}
isLooping={isLooping}
Expand All @@ -235,6 +244,7 @@ function BaseVideoPlayer({
small={shouldUseSmallVideoControls}
style={videoControlsStyle}
togglePlayCurrentVideo={togglePlayCurrentVideo}
progressVolumeOnly={shouldShowProgressVolumeOnly}
showPopoverMenu={showPopoverMenu}
/>
)}
Expand Down
86 changes: 47 additions & 39 deletions src/components/VideoPlayer/VideoPlayerControls/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,17 @@ const propTypes = {
showPopoverMenu: PropTypes.func.isRequired,

togglePlayCurrentVideo: PropTypes.func.isRequired,

progressVolumeOnly: PropTypes.bool,
};

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

function VideoPlayerControls({duration, position, url, videoPlayerRef, isPlaying, small, style, showPopoverMenu, togglePlayCurrentVideo}) {
function VideoPlayerControls({duration, position, url, videoPlayerRef, isPlaying, small, style, showPopoverMenu, togglePlayCurrentVideo, progressVolumeOnly}) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const {updateCurrentlyPlayingURL} = usePlaybackContext();
Expand All @@ -68,49 +71,54 @@ function VideoPlayerControls({duration, position, url, videoPlayerRef, isPlaying

return (
<Animated.View
style={[styles.videoPlayerControlsContainer, small ? [styles.p2, styles.pb0] : [styles.p3, styles.pb1], style]}
style={[styles.videoPlayerControlsContainer, small ? [styles.p2, styles.pb0] : [styles.p3, styles.pb1], progressVolumeOnly && [styles.pt2, styles.pb2], style]}
onLayout={onLayout}
>
<View style={[styles.videoPlayerControlsButtonContainer, !small && styles.mb4]}>
<View style={[styles.videoPlayerControlsRow]}>
<IconButton
src={isPlaying ? Expensicons.Pause : Expensicons.Play}
tooltipText={isPlaying ? translate('videoPlayer.pause') : translate('videoPlayer.play')}
onPress={togglePlayCurrentVideo}
style={styles.mr2}
small={small}
/>
{shouldShowTime && (
<View style={[styles.videoPlayerControlsRow]}>
<Text style={[styles.videoPlayerText, styles.videoPlayerTimeComponentWidth]}>{convertMillisecondsToTime(position)}</Text>
<Text style={[styles.videoPlayerText]}>/</Text>
<Text style={[styles.videoPlayerText, styles.videoPlayerTimeComponentWidth]}>{durationFormatted}</Text>
</View>
)}
{!progressVolumeOnly && (
<View style={[styles.videoPlayerControlsButtonContainer, !small && styles.mb4]}>
<View style={[styles.videoPlayerControlsRow]}>
<IconButton
src={isPlaying ? Expensicons.Pause : Expensicons.Play}
tooltipText={isPlaying ? translate('videoPlayer.pause') : translate('videoPlayer.play')}
onPress={togglePlayCurrentVideo}
style={styles.mr2}
small={small}
/>
{shouldShowTime && (
<View style={[styles.videoPlayerControlsRow]}>
<Text style={[styles.videoPlayerText, styles.videoPlayerTimeComponentWidth]}>{convertMillisecondsToTime(position)}</Text>
<Text style={[styles.videoPlayerText]}>/</Text>
<Text style={[styles.videoPlayerText, styles.videoPlayerTimeComponentWidth]}>{durationFormatted}</Text>
</View>
)}
</View>
<View style={[styles.videoPlayerControlsRow]}>
<VolumeButton style={iconSpacing} />
<IconButton
src={Expensicons.Fullscreen}
tooltipText={translate('videoPlayer.fullscreen')}
onPress={enterFullScreenMode}
style={iconSpacing}
small={small}
/>
<IconButton
src={Expensicons.ThreeDots}
tooltipText={translate('common.more')}
onPress={showPopoverMenu}
small={small}
/>
</View>
</View>
<View style={[styles.videoPlayerControlsRow]}>
<VolumeButton style={iconSpacing} />
<IconButton
src={Expensicons.Fullscreen}
tooltipText={translate('videoPlayer.fullscreen')}
onPress={enterFullScreenMode}
style={iconSpacing}
small={small}
/>
<IconButton
src={Expensicons.ThreeDots}
tooltipText={translate('common.more')}
onPress={showPopoverMenu}
small={small}
)}
<View style={styles.videoPlayerControlsRow}>
<View style={[styles.flex1]}>
<ProgressBar
duration={duration}
position={position}
seekPosition={seekPosition}
/>
</View>
</View>
<View style={styles.videoPlayerControlsRow}>
<ProgressBar
duration={duration}
position={position}
seekPosition={seekPosition}
/>
{progressVolumeOnly && <VolumeButton style={styles.ml3} />}
</View>
</Animated.View>
);
Expand Down
5 changes: 5 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1212,6 +1212,11 @@ export default {
},
onboarding: {
welcome: 'Welcome!',
welcomeVideo: {
title: 'Welcome to Expensify',
description: 'Getting paid is as easy as sending a message.',
button: "Let's go",
},
whatsYourName: "What's your name?",
purpose: {
title: "What do you want to do today?",
Expand Down
7 changes: 7 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2711,6 +2711,13 @@ export default {
selectSuggestedAddress: 'Por favor, selecciona una dirección sugerida o usa la ubicación actual.',
},
},
onboarding: {
welcomeVideo: {
title: 'Bienvenido a Expensify',
description: 'Recibir pago es tan fácil como enviar un mensaje.',
button: 'Vamos',
},
},
reportCardLostOrDamaged: {
report: 'Notificar la pérdida / daño de la tarjeta física',
screenTitle: 'Notificar la pérdida o deterioro de la tarjeta',
Expand Down
Loading

0 comments on commit fab0a2e

Please sign in to comment.