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

[Wave9] Welcome Video as separate Screen #73

Merged
merged 32 commits into from
Mar 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
ddd6c4e
add base welcome video modal
cdOut Feb 12, 2024
14f867b
remove symbol and fix light mode
cdOut Feb 13, 2024
27158b9
fix styling for smaller displays
cdOut Feb 13, 2024
063d0d3
fix backdrop for native
cdOut Feb 13, 2024
8e27d62
add video player element with correct styling
cdOut Feb 14, 2024
7971274
add offline support for switching between animation and video
cdOut Feb 15, 2024
f94bc11
add isLooping attrib
cdOut Feb 15, 2024
2a27a6b
add spanish translations
cdOut Feb 15, 2024
bb7baa9
Merge remote-tracking branch 'origin/main' into @cdOut/welcome-video
cdOut Feb 16, 2024
75a2b35
add fixes and updates for video player
cdOut Feb 16, 2024
0659d56
Merge branch 'wave9/onboarding-flow' into @cdOut/welcome-video
MaciejSWM Feb 27, 2024
39ed308
Drop -Modal suffix from name
MaciejSWM Feb 27, 2024
1543ce0
Welcome video as separate screen
MaciejSWM Feb 27, 2024
7f6194c
Add texts; desktop vs mobile styles
MaciejSWM Feb 27, 2024
58e1fb2
Drop useless array
MaciejSWM Feb 28, 2024
e218e36
Merge branch 'wave9/onboarding-flow' into wave9/welcome-video-cdOut
MaciejSWM Feb 28, 2024
0725435
Fix translation key conflict
MaciejSWM Feb 28, 2024
018654e
Fix video aspect ratio mobile+desktop
MaciejSWM Feb 28, 2024
9e9a5ec
Root route for onboarding
MaciejSWM Feb 29, 2024
b956dae
Set max height
MaciejSWM Feb 29, 2024
900527c
Rename methods; clean code
MaciejSWM Feb 29, 2024
e703a14
New navigator - WelcomeVideoNavigator
MaciejSWM Feb 29, 2024
c1d6c96
Drop cardStyle
MaciejSWM Feb 29, 2024
b886fea
Video layout improvements
MaciejSWM Feb 29, 2024
671d923
Separate styles for welcome video navigator
MaciejSWM Mar 1, 2024
76961f8
Rewrite to use Modal
MaciejSWM Mar 1, 2024
3bba5f1
Web styling for modal
MaciejSWM Mar 1, 2024
bb2e9b8
Drop unnecessary styles
MaciejSWM Mar 1, 2024
b43027a
Apply modal dimensions
MaciejSWM Mar 1, 2024
8795e1c
Move stack declarations down
MaciejSWM Mar 4, 2024
1297a40
Drop default path
MaciejSWM Mar 4, 2024
80bafbc
Pixel perfect styles - native and web
MaciejSWM Mar 4, 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
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 = {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not use Type suffix for types if it's not absolutely necessary 😄

srcElement: {
videoWidth: number;
videoHeight: number;
};
};

type VideoPlaybackStatusEventType = {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here the same

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?",
error: {
requiredFirstName: 'Please input your first name to continue',
Expand Down
7 changes: 7 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1209,7 +1209,7 @@
loginForm: 'Formulario de inicio de sesión',
notYou: ({user}: NotYouParams) => `¿No eres ${user}?`,
},
onboarding: {

Check failure on line 1212 in src/languages/es.ts

View workflow job for this annotation

GitHub Actions / typecheck

Property 'welcomeVideo' is missing in type '{ welcome: string; whatsYourName: string; error: { requiredFirstName: string; requiredLasttName: string; }; }' but required in type '{ welcome: string; welcomeVideo: { title: string; description: string; button: string; }; whatsYourName: string; error: { requiredFirstName: string; requiredLasttName: string; }; }'.
welcome: '¡Bienvenido!',
whatsYourName: '¿Cómo te llamas?',
error: {
Expand Down Expand Up @@ -2711,6 +2711,13 @@
selectSuggestedAddress: 'Por favor, selecciona una dirección sugerida o usa la ubicación actual.',
},
},
onboarding: {

Check failure on line 2714 in src/languages/es.ts

View workflow job for this annotation

GitHub Actions / typecheck

An object literal cannot have multiple properties with the same name.

Check failure on line 2714 in src/languages/es.ts

View workflow job for this annotation

GitHub Actions / typecheck

Type '{ welcomeVideo: { title: string; description: string; button: string; }; }' is missing the following properties from type '{ welcome: string; welcomeVideo: { title: string; description: string; button: string; }; whatsYourName: string; error: { requiredFirstName: string; requiredLasttName: string; }; }': welcome, whatsYourName, error
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
Loading