Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
106 changes: 62 additions & 44 deletions src/components/AutoSubmitModal.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import React, {useMemo, useRef} from 'react';
import {View} from 'react-native';
import useBeforeRemove from '@hooks/useBeforeRemove';
import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
import TransitionTracker from '@libs/Navigation/TransitionTracker';
import colors from '@styles/theme/colors';
import variables from '@styles/variables';
import {dismissASAPSubmitExplanation} from '@userActions/User';
import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import ONYXKEYS from '@src/ONYXKEYS';
import FeatureTrainingModal from './FeatureTrainingModal';
import CenteredModalLayout from './CenteredModalLayout';
import FeatureTrainingContent from './FeatureTrainingContent';
import Icon from './Icon';
import Text from './Text';

Expand All @@ -37,61 +41,75 @@ function AutoSubmitModal() {
[illustrations.PaperAirplane, illustrations.Pencil],
);

// Defer the Onyx write until after the modal close animation finishes. The ref is set in onConfirm
// and consumed in onClose, which FeatureTrainingModal fires from onModalHide (after the close animation completes).
const willShowAgainRef = useRef<boolean | null>(null);

const onConfirm = (willShowAgain: boolean) => {
willShowAgainRef.current = willShowAgain;
};

const onClose = () => {
const persistDismiss = () => {
if (willShowAgainRef.current === null) {
return;
}
dismissASAPSubmitExplanation(!willShowAgainRef.current);

const shouldDismiss = !willShowAgainRef.current;
willShowAgainRef.current = null;

// Defer the Onyx write until after the close transition finishes. This prevents potential checkbox flicker.
TransitionTracker.runAfterTransitions({
callback: () => dismissASAPSubmitExplanation(shouldDismiss),
waitForUpcomingTransition: true,
});
};

useBeforeRemove(persistDismiss);

const handleClose = () => Navigation.goBack();

const onConfirm = (willShowAgain: boolean) => {
willShowAgainRef.current = willShowAgain;
};

return (
<FeatureTrainingModal
title={translate('autoSubmitModal.title')}
description={translate('autoSubmitModal.description')}
confirmText={translate('common.buttonConfirm')}
image={illustrations.ReceiptsStackedOnPin}
contentFitImage="cover"
<CenteredModalLayout
onBackdropPress={handleClose}
width={variables.holdEducationModalWidth}
imageWidth={variables.changePolicyEducationModalIconWidth}
imageHeight={variables.changePolicyEducationModalIconHeight}
illustrationAspectRatio={CONST.ILLUSTRATION_ASPECT_RATIO}
illustrationInnerContainerStyle={[styles.alignItemsCenter, styles.justifyContentCenter, StyleUtils.getBackgroundColorStyle(colors.green700), styles.p8]}
modalInnerContainerStyle={styles.pt0}
illustrationOuterContainerStyle={styles.p0}
shouldShowDismissModalOption={dismissedASAPSubmitExplanation === false}
onConfirm={onConfirm}
onClose={onClose}
titleStyles={[styles.mb1]}
contentInnerContainerStyles={[styles.mb5]}
shouldUseScrollView
contentStyle={[styles.pt0, styles.pb0]}
>
{menuSections.map((section) => (
<View
key={section.titleTranslationKey}
style={[styles.flexRow, styles.alignItemsCenter, styles.mt3]}
>
<Icon
width={variables.menuIconSize}
height={variables.menuIconSize}
src={section.icon}
additionalStyles={[styles.mr4]}
/>
<View style={[styles.flex1, styles.justifyContentCenter]}>
<Text style={[styles.textStrong, styles.mb1]}>{translate(section.titleTranslationKey as TranslationPaths)}</Text>
<Text style={[styles.mutedTextLabel, styles.lh16]}>{translate(section.descriptionTranslationKey as TranslationPaths)}</Text>
<FeatureTrainingContent
title={translate('autoSubmitModal.title')}
description={translate('autoSubmitModal.description')}
confirmText={translate('common.buttonConfirm')}
image={illustrations.ReceiptsStackedOnPin}
contentFitImage="cover"
width={variables.holdEducationModalWidth}
imageWidth={variables.changePolicyEducationModalIconWidth}
imageHeight={variables.changePolicyEducationModalIconHeight}
illustrationAspectRatio={CONST.ILLUSTRATION_ASPECT_RATIO}
illustrationInnerContainerStyle={[styles.alignItemsCenter, styles.justifyContentCenter, StyleUtils.getBackgroundColorStyle(colors.green700), styles.p8]}
illustrationOuterContainerStyle={styles.p0}
shouldShowDismissModalOption={dismissedASAPSubmitExplanation === false}
onConfirm={onConfirm}
onClose={handleClose}
titleStyles={[styles.mb1]}
contentInnerContainerStyles={[styles.mb5]}
shouldUseScrollView
>
{menuSections.map((section) => (
<View
key={section.titleTranslationKey}
style={[styles.flexRow, styles.alignItemsCenter, styles.mt3]}
>
<Icon
width={variables.menuIconSize}
height={variables.menuIconSize}
src={section.icon}
additionalStyles={[styles.mr4]}
/>
<View style={[styles.flex1, styles.justifyContentCenter]}>
<Text style={[styles.textStrong, styles.mb1]}>{translate(section.titleTranslationKey as TranslationPaths)}</Text>
<Text style={[styles.mutedTextLabel, styles.lh16]}>{translate(section.descriptionTranslationKey as TranslationPaths)}</Text>
</View>
</View>
</View>
))}
</FeatureTrainingModal>
))}
</FeatureTrainingContent>
</CenteredModalLayout>
);
}

Expand Down
70 changes: 70 additions & 0 deletions src/components/CenteredModalLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React from 'react';
import type {MouseEvent} from 'react';
import type {DimensionValue, StyleProp, ViewStyle} from 'react-native';
import {View} from 'react-native';
import useBottomSafeSafeAreaPaddingStyle from '@hooks/useBottomSafeSafeAreaPaddingStyle';
import useKeyboardShortcut from '@hooks/useKeyboardShortcut';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import isInLandscapeModeUtil from '@libs/isInLandscapeMode';
import Overlay from '@libs/Navigation/AppNavigator/Navigators/Overlay';
import CONST from '@src/CONST';
import FocusTrapForScreen from './FocusTrap/FocusTrapForScreen';
import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback';

type CenteredModalLayoutProps = {
children: React.ReactNode;

/** Width of the inner card on wide layouts (defaults to featureTrainingModalWidth) */
width?: number;

/** Height of the inner card on wide layout */
height?: DimensionValue;

/** Called when the backdrop is pressed, before navigating back */
onBackdropPress: () => void;

/** Extra styles merged into the safe-area content wrapper */
contentStyle?: StyleProp<ViewStyle>;
};

function CenteredModalLayout({children, width, height, onBackdropPress, contentStyle}: CenteredModalLayoutProps) {
const styles = useThemeStyles();
const {shouldUseNarrowLayout} = useResponsiveLayout();
const {windowWidth, windowHeight} = useWindowDimensions();

const isInLandscapeMode = isInLandscapeModeUtil(windowWidth, windowHeight);
const safeAreaStyle = useBottomSafeSafeAreaPaddingStyle({
addBottomSafeAreaPadding: !isInLandscapeMode,
style: [shouldUseNarrowLayout && styles.pt2, !isInLandscapeMode && styles.pb5, contentStyle],
});

useKeyboardShortcut(CONST.KEYBOARD_SHORTCUTS.ESCAPE, onBackdropPress, {shouldBubble: false});

const handleInnerClick = (e: MouseEvent) => e.stopPropagation();

return (
<>
<Overlay onPress={onBackdropPress} />
<PressableWithoutFeedback
onPress={onBackdropPress}
style={[styles.flex1, styles.alignItemsCenter, styles.getCenteredModalOuterView(shouldUseNarrowLayout)]}
accessible={false}
sentryLabel="CenteredModalLayout-backdrop"
>
<FocusTrapForScreen>
<View
onStartShouldSetResponder={() => true}
onClick={handleInnerClick}
style={styles.getCenteredModalInnerView(shouldUseNarrowLayout, width, height)}
>
<View style={safeAreaStyle}>{children}</View>
</View>
</FocusTrapForScreen>
</PressableWithoutFeedback>
</>
);
}

export default CenteredModalLayout;
Loading
Loading