-
Notifications
You must be signed in to change notification settings - Fork 3k
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
Update "Hold expense" modal copy and icons #53354
Changes from all commits
c2e4b5d
aae0a6e
e7be56d
3dad345
0f6d33b
03e1c1a
6f83d98
2118474
9e8896a
6a1a89a
a7dc72f
d13f17b
270d6c0
7c72d5a
894bee6
80975bd
dee55e1
0a339da
3c67b8f
72adbd7
dd3968e
cc6a58d
1f57dce
788e8c4
27348dc
384881a
c4f55b3
463041c
33050c4
ac35c61
c1490ef
088eb88
70a5ceb
564577c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,23 @@ | ||
import type {VideoReadyForDisplayEvent} from 'expo-av'; | ||
import type {ImageContentFit} from 'expo-image'; | ||
import React, {useCallback, useEffect, useState} from 'react'; | ||
import {InteractionManager, View} from 'react-native'; | ||
import type {StyleProp, ViewStyle} from 'react-native'; | ||
import {GestureHandlerRootView} from 'react-native-gesture-handler'; | ||
import type {MergeExclusive} from 'type-fest'; | ||
import useLocalize from '@hooks/useLocalize'; | ||
import useNetwork from '@hooks/useNetwork'; | ||
import useResponsiveLayout from '@hooks/useResponsiveLayout'; | ||
import useStyleUtils from '@hooks/useStyleUtils'; | ||
import useThemeStyles from '@hooks/useThemeStyles'; | ||
import Navigation from '@libs/Navigation/Navigation'; | ||
import variables from '@styles/variables'; | ||
import * as User from '@userActions/User'; | ||
import {dismissTrackTrainingModal} from '@userActions/User'; | ||
import CONST from '@src/CONST'; | ||
import type IconAsset from '@src/types/utils/IconAsset'; | ||
import Button from './Button'; | ||
import CheckboxWithLabel from './CheckboxWithLabel'; | ||
import ImageSVG from './ImageSVG'; | ||
import Lottie from './Lottie'; | ||
import LottieAnimations from './LottieAnimations'; | ||
import type DotLottieAnimation from './LottieAnimations/types'; | ||
|
@@ -36,26 +41,18 @@ type VideoLoadedEventType = { | |
|
||
type VideoStatus = 'video' | 'animation'; | ||
|
||
type FeatureTrainingModalProps = { | ||
/** Animation to show when video is unavailable. Useful when app is offline */ | ||
animation?: DotLottieAnimation; | ||
type BaseFeatureTrainingModalProps = { | ||
/** The aspect ratio to preserve for the icon, video or animation */ | ||
illustrationAspectRatio?: number; | ||
|
||
/** Style for the inner container of the animation */ | ||
animationInnerContainerStyle?: StyleProp<ViewStyle>; | ||
illustrationInnerContainerStyle?: StyleProp<ViewStyle>; | ||
|
||
/** Style for the outer container of the animation */ | ||
animationOuterContainerStyle?: StyleProp<ViewStyle>; | ||
|
||
/** Additional styles for the animation */ | ||
animationStyle?: StyleProp<ViewStyle>; | ||
|
||
/** URL for the video */ | ||
videoURL: string; | ||
|
||
videoAspectRatio?: number; | ||
illustrationOuterContainerStyle?: StyleProp<ViewStyle>; | ||
|
||
/** Title for the modal */ | ||
title?: string; | ||
title?: string | React.ReactNode; | ||
|
||
/** Describe what is showing */ | ||
description?: string; | ||
|
@@ -81,9 +78,6 @@ type FeatureTrainingModalProps = { | |
/** Link to navigate to when user wants to learn more */ | ||
onHelp?: () => void; | ||
|
||
/** Children to render */ | ||
children?: React.ReactNode; | ||
|
||
/** Styles for the content container */ | ||
contentInnerContainerStyles?: StyleProp<ViewStyle>; | ||
|
||
|
@@ -92,15 +86,46 @@ type FeatureTrainingModalProps = { | |
|
||
/** Styles for the modal inner container */ | ||
modalInnerContainerStyle?: ViewStyle; | ||
|
||
/** Children to show below title and description and above buttons */ | ||
children?: React.ReactNode; | ||
|
||
/** Modal width */ | ||
width?: number; | ||
}; | ||
|
||
type FeatureTrainingModalVideoProps = { | ||
/** Animation to show when video is unavailable. Useful when app is offline */ | ||
animation?: DotLottieAnimation; | ||
|
||
/** Additional styles for the animation */ | ||
animationStyle?: StyleProp<ViewStyle>; | ||
|
||
/** URL for the video */ | ||
videoURL?: string; | ||
}; | ||
|
||
type FeatureTrainingModalSVGProps = { | ||
/** Expensicon for the page */ | ||
image: IconAsset; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, we used |
||
|
||
/** Determines how the image should be resized to fit its container */ | ||
contentFitImage?: ImageContentFit; | ||
}; | ||
|
||
// This page requires either an icon or a video/animation, but not both | ||
type FeatureTrainingModalProps = BaseFeatureTrainingModalProps & MergeExclusive<FeatureTrainingModalVideoProps, FeatureTrainingModalSVGProps>; | ||
|
||
function FeatureTrainingModal({ | ||
animation, | ||
animationStyle, | ||
animationInnerContainerStyle, | ||
animationOuterContainerStyle, | ||
illustrationInnerContainerStyle, | ||
illustrationOuterContainerStyle, | ||
videoURL, | ||
videoAspectRatio: videoAspectRatioProp, | ||
illustrationAspectRatio: illustrationAspectRatioProp, | ||
image, | ||
contentFitImage, | ||
width = variables.onboardingModalWidth, | ||
title = '', | ||
description = '', | ||
secondaryDescription = '', | ||
|
@@ -116,13 +141,14 @@ function FeatureTrainingModal({ | |
modalInnerContainerStyle, | ||
}: FeatureTrainingModalProps) { | ||
const styles = useThemeStyles(); | ||
const StyleUtils = useStyleUtils(); | ||
const {translate} = useLocalize(); | ||
const {onboardingIsMediumOrLargerScreenWidth} = useResponsiveLayout(); | ||
const [isModalVisible, setIsModalVisible] = useState(false); | ||
const [willShowAgain, setWillShowAgain] = useState(true); | ||
const [videoStatus, setVideoStatus] = useState<VideoStatus>('video'); | ||
const [isVideoStatusLocked, setIsVideoStatusLocked] = useState(false); | ||
const [videoAspectRatio, setVideoAspectRatio] = useState(videoAspectRatioProp ?? VIDEO_ASPECT_RATIO); | ||
const [illustrationAspectRatio, setIllustrationAspectRatio] = useState(illustrationAspectRatioProp ?? VIDEO_ASPECT_RATIO); | ||
const {shouldUseNarrowLayout} = useResponsiveLayout(); | ||
const {isOffline} = useNetwork(); | ||
|
||
|
@@ -149,14 +175,14 @@ function FeatureTrainingModal({ | |
} | ||
|
||
if ('naturalSize' in event) { | ||
setVideoAspectRatio(event.naturalSize.width / event.naturalSize.height); | ||
setIllustrationAspectRatio(event.naturalSize.width / event.naturalSize.height); | ||
} else { | ||
setVideoAspectRatio(event.srcElement.videoWidth / event.srcElement.videoHeight); | ||
setIllustrationAspectRatio(event.srcElement.videoWidth / event.srcElement.videoHeight); | ||
} | ||
}; | ||
|
||
const renderIllustration = useCallback(() => { | ||
const aspectRatio = videoAspectRatio || VIDEO_ASPECT_RATIO; | ||
const aspectRatio = illustrationAspectRatio || VIDEO_ASPECT_RATIO; | ||
|
||
return ( | ||
<View | ||
|
@@ -166,11 +192,17 @@ function FeatureTrainingModal({ | |
// for the video until it loads. Also, when | ||
// videoStatus === 'animation' it will | ||
// set the same aspect ratio as the video would. | ||
animationInnerContainerStyle, | ||
!!videoURL && {aspectRatio}, | ||
illustrationInnerContainerStyle, | ||
(!!videoURL || !!image) && {aspectRatio}, | ||
]} | ||
> | ||
{!!videoURL && videoStatus === 'video' ? ( | ||
{!!image && ( | ||
<ImageSVG | ||
src={image} | ||
contentFit={contentFitImage} | ||
/> | ||
)} | ||
{!!videoURL && videoStatus === 'video' && ( | ||
<GestureHandlerRootView> | ||
<VideoPlayer | ||
url={videoURL} | ||
|
@@ -182,7 +214,8 @@ function FeatureTrainingModal({ | |
isLooping | ||
/> | ||
</GestureHandlerRootView> | ||
) : ( | ||
)} | ||
{((!videoURL && !image) || (!!videoURL && videoStatus === 'animation')) && ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The animation can show when:
|
||
<View style={[styles.flex1, styles.alignItemsCenter, styles.justifyContentCenter, !!videoURL && {aspectRatio}, animationStyle]}> | ||
<Lottie | ||
source={animation ?? LottieAnimations.Hands} | ||
|
@@ -196,7 +229,9 @@ function FeatureTrainingModal({ | |
</View> | ||
); | ||
}, [ | ||
videoAspectRatio, | ||
image, | ||
contentFitImage, | ||
illustrationAspectRatio, | ||
styles.w100, | ||
styles.onboardingVideoPlayer, | ||
styles.flex1, | ||
|
@@ -208,14 +243,14 @@ function FeatureTrainingModal({ | |
animationStyle, | ||
animation, | ||
shouldUseNarrowLayout, | ||
animationInnerContainerStyle, | ||
illustrationInnerContainerStyle, | ||
]); | ||
|
||
const toggleWillShowAgain = useCallback(() => setWillShowAgain((prevWillShowAgain) => !prevWillShowAgain), []); | ||
|
||
const closeModal = useCallback(() => { | ||
if (!willShowAgain) { | ||
User.dismissTrackTrainingModal(); | ||
dismissTrackTrainingModal(); | ||
} | ||
setIsModalVisible(false); | ||
InteractionManager.runAfterInteractions(() => { | ||
|
@@ -238,7 +273,6 @@ function FeatureTrainingModal({ | |
onClose={closeModal} | ||
innerContainerStyle={{ | ||
boxShadow: 'none', | ||
borderRadius: 16, | ||
paddingBottom: 20, | ||
paddingTop: onboardingIsMediumOrLargerScreenWidth ? undefined : MODAL_PADDING, | ||
...(onboardingIsMediumOrLargerScreenWidth | ||
|
@@ -252,14 +286,14 @@ function FeatureTrainingModal({ | |
...modalInnerContainerStyle, | ||
}} | ||
> | ||
<View style={[styles.mh100, onboardingIsMediumOrLargerScreenWidth && styles.welcomeVideoNarrowLayout, safeAreaPaddingBottomStyle]}> | ||
<View style={[onboardingIsMediumOrLargerScreenWidth ? {padding: MODAL_PADDING} : {paddingHorizontal: MODAL_PADDING}, animationOuterContainerStyle]}> | ||
<View style={[styles.mh100, onboardingIsMediumOrLargerScreenWidth && StyleUtils.getWidthStyle(width), safeAreaPaddingBottomStyle]}> | ||
<View style={[onboardingIsMediumOrLargerScreenWidth ? {padding: MODAL_PADDING} : {paddingHorizontal: MODAL_PADDING}, illustrationOuterContainerStyle]}> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
{renderIllustration()} | ||
</View> | ||
<View style={[styles.mt5, styles.mh5, contentOuterContainerStyles]}> | ||
{!!title && !!description && ( | ||
<View style={[onboardingIsMediumOrLargerScreenWidth ? [styles.gap1, styles.mb8] : [styles.mb10], contentInnerContainerStyles]}> | ||
<Text style={[styles.textHeadlineH1]}>{title}</Text> | ||
{typeof title === 'string' ? <Text style={[styles.textHeadlineH1]}>{title}</Text> : title} | ||
<Text style={styles.textSupporting}>{description}</Text> | ||
{secondaryDescription.length > 0 && <Text style={[styles.textSupporting, styles.mt4]}>{secondaryDescription}</Text>} | ||
{children} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I renamed all these
animation*
toillustration*
because they are no longer limited to.lottie
animations only but can be images or videos as well.