Skip to content

Commit

Permalink
Merge pull request #39074 from software-mansion-labs/hybrid-app-expla…
Browse files Browse the repository at this point in the history
…nation-modal

[HybridApp] Add explanation modal
  • Loading branch information
AndrewGable authored Jun 27, 2024
2 parents 6501d00 + 26a850b commit 24b7c48
Show file tree
Hide file tree
Showing 22 changed files with 310 additions and 4 deletions.
1 change: 1 addition & 0 deletions src/NAVIGATORS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ export default {
ONBOARDING_MODAL_NAVIGATOR: 'OnboardingModalNavigator',
FEATURE_TRANING_MODAL_NAVIGATOR: 'FeatureTrainingModalNavigator',
WELCOME_VIDEO_MODAL_NAVIGATOR: 'WelcomeVideoModalNavigator',
EXPLANATION_MODAL_NAVIGATOR: 'ExplanationModalNavigator',
FULL_SCREEN_NAVIGATOR: 'FullScreenNavigator',
} as const;
9 changes: 9 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ const ONYXKEYS = {
/** This NVP contains information about whether the onboarding flow was completed or not */
NVP_ONBOARDING: 'nvp_onboarding',

/** This NVP contains data associated with HybridApp */
NVP_TRYNEWDOT: 'nvp_tryNewDot',

/** Contains the user preference for the LHN priority mode */
NVP_PRIORITY_MODE: 'nvp_priorityMode',

Expand Down Expand Up @@ -154,6 +157,8 @@ const ONYXKEYS = {
/** Whether the user has dismissed the hold educational interstitial */
NVP_DISMISSED_HOLD_USE_EXPLANATION: 'nvp_dismissedHoldUseExplanation',

/** Whether the user has seen HybridApp explanation modal */
NVP_SEEN_NEW_USER_MODAL: 'nvp_seen_new_user_modal',
/** Store the state of the subscription */
NVP_PRIVATE_SUBSCRIPTION: 'nvp_private_subscription',

Expand Down Expand Up @@ -631,6 +636,9 @@ type OnyxValuesMapping = {
// NVP_ONBOARDING is an array for old users.
[ONYXKEYS.NVP_ONBOARDING]: Onboarding | [];

// ONYXKEYS.NVP_TRYNEWDOT is HybridApp onboarding data
[ONYXKEYS.NVP_TRYNEWDOT]: OnyxTypes.TryNewDot;

[ONYXKEYS.ACTIVE_CLIENTS]: string[];
[ONYXKEYS.DEVICE_ID]: string;
[ONYXKEYS.IS_SIDEBAR_LOADED]: boolean;
Expand Down Expand Up @@ -673,6 +681,7 @@ type OnyxValuesMapping = {
[ONYXKEYS.NVP_RECENT_WAYPOINTS]: OnyxTypes.RecentWaypoint[];
[ONYXKEYS.NVP_INTRO_SELECTED]: OnyxTypes.IntroSelected;
[ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES]: OnyxTypes.LastSelectedDistanceRates;
[ONYXKEYS.NVP_SEEN_NEW_USER_MODAL]: boolean;
[ONYXKEYS.PUSH_NOTIFICATIONS_ENABLED]: boolean;
[ONYXKEYS.PLAID_DATA]: OnyxTypes.PlaidData;
[ONYXKEYS.IS_PLAID_DISABLED]: boolean;
Expand Down
1 change: 1 addition & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -825,6 +825,7 @@ const ROUTES = {
ONBOARDING_WORK: 'onboarding/work',
ONBOARDING_PURPOSE: 'onboarding/purpose',
WELCOME_VIDEO_ROOT: 'onboarding/welcome-video',
EXPLANATION_MODAL_ROOT: 'onboarding/explanation',

TRANSACTION_RECEIPT: {
route: 'r/:reportID/transaction/:transactionID/receipt',
Expand Down
4 changes: 4 additions & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,10 @@ const SCREENS = {
ROOT: 'Welcome_Video_Root',
},

EXPLANATION_MODAL: {
ROOT: 'Explanation_Modal_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
41 changes: 41 additions & 0 deletions src/components/ExplanationModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React, {useCallback} from 'react';
import useLocalize from '@hooks/useLocalize';
import Navigation from '@libs/Navigation/Navigation';
import variables from '@styles/variables';
import * as Welcome from '@userActions/Welcome';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
import FeatureTrainingModal from './FeatureTrainingModal';

function ExplanationModal() {
const {translate} = useLocalize();

const onConfirm = useCallback(() => {
Welcome.completeHybridAppOnboarding();

// We need to check if standard NewDot onboarding is completed.
Welcome.isOnboardingFlowCompleted({
onNotCompleted: () => {
setTimeout(() => {
Navigation.isNavigationReady().then(() => {
Navigation.navigate(ROUTES.ONBOARDING_ROOT);
});
}, variables.welcomeVideoDelay);
},
});
}, []);

return (
<FeatureTrainingModal
title={translate('onboarding.explanationModal.title')}
description={translate('onboarding.explanationModal.description')}
secondaryDescription={translate('onboarding.explanationModal.secondaryDescription')}
confirmText={translate('footer.getStarted')}
videoURL={CONST.WELCOME_VIDEO_URL}
onConfirm={onConfirm}
/>
);
}

ExplanationModal.displayName = 'ExplanationModal';
export default ExplanationModal;
5 changes: 5 additions & 0 deletions src/components/FeatureTrainingModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ type FeatureTrainingModalProps = {
/** Describe what is showing */
description?: string;

/** Secondary description rendered with additional space */
secondaryDescription?: string;

/** Whether to show `Don't show me this again` option */
shouldShowDismissModalOption?: boolean;

Expand All @@ -73,6 +76,7 @@ function FeatureTrainingModal({
videoAspectRatio: videoAspectRatioProp,
title = '',
description = '',
secondaryDescription = '',
shouldShowDismissModalOption = false,
confirmText = '',
onConfirm = () => {},
Expand Down Expand Up @@ -199,6 +203,7 @@ function FeatureTrainingModal({
<View style={[shouldUseNarrowLayout ? [styles.gap1, styles.mb8] : [styles.mb10]]}>
<Text style={[styles.textHeadlineH1]}>{title}</Text>
<Text style={styles.textSupporting}>{description}</Text>
{secondaryDescription.length > 0 && <Text style={[styles.textSupporting, styles.mt4]}>{secondaryDescription}</Text>}
</View>
)}
{shouldShowDismissModalOption && (
Expand Down
1 change: 0 additions & 1 deletion src/components/OnboardingWelcomeVideo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,4 @@ function OnboardingWelcomeVideo() {
}

OnboardingWelcomeVideo.displayName = 'OnboardingWelcomeVideo';

export default OnboardingWelcomeVideo;
24 changes: 24 additions & 0 deletions src/components/TestToolMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@ import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import * as ApiUtils from '@libs/ApiUtils';
import Navigation from '@libs/Navigation/Navigation';
import variables from '@styles/variables';
import * as Network from '@userActions/Network';
import * as Report from '@userActions/Report';
import * as Session from '@userActions/Session';
import * as User from '@userActions/User';
import CONFIG from '@src/CONFIG';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {Network as NetworkOnyx, User as UserOnyx} from '@src/types/onyx';
import Button from './Button';
import {withNetwork} from './OnyxProvider';
Expand All @@ -30,6 +35,7 @@ const USER_DEFAULT: UserOnyx = {shouldUseStagingServer: undefined, isSubscribedT
function TestToolMenu({user = USER_DEFAULT, network}: TestToolMenuProps) {
const shouldUseStagingServer = user?.shouldUseStagingServer ?? ApiUtils.isUsingStagingApi();
const styles = useThemeStyles();
const {isSmallScreenWidth} = useWindowDimensions();
const {translate} = useLocalize();

return (
Expand Down Expand Up @@ -88,6 +94,24 @@ function TestToolMenu({user = USER_DEFAULT, network}: TestToolMenuProps) {
onPress={() => Session.invalidateCredentials()}
/>
</TestToolRow>
{/* Navigate to the Explanation Modal. This button is temporary to test Explanation Modal flow without HybridApp native module. */}
<TestToolRow title="Explanation modal">
<Button
small
text="Navigate"
onPress={() => {
Navigation.dismissModal();
if (isSmallScreenWidth) {
Navigation.navigate(ROUTES.HOME);
} else {
Report.navigateToConciergeChat();
}
setTimeout(() => {
Navigation.navigate(ROUTES.EXPLANATION_MODAL_ROOT);
}, variables.welcomeVideoDelay);
}}
/>
</TestToolRow>
</>
);
}
Expand Down
6 changes: 6 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1413,6 +1413,12 @@ export default {
notYou: ({user}: NotYouParams) => `Not ${user}?`,
},
onboarding: {
welcome: 'Welcome!',
explanationModal: {
title: 'Welcome to Expensify',
description: 'Request and send money is just as easy as sending a message. The new era of expensing is upon us.',
secondaryDescription: 'To switch back to Expensify Classic, just tap your profile picture > Go to Expensify Classic.',
},
welcomeVideo: {
title: 'Welcome to Expensify',
description: 'One app to handle all your business and personal spend in a chat. Built for your business, your team, and your friends.',
Expand Down
6 changes: 6 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1414,6 +1414,12 @@ export default {
notYou: ({user}: NotYouParams) => `¿No eres ${user}?`,
},
onboarding: {
welcome: '¡Bienvenido!',
explanationModal: {
title: 'Bienvenido a Expensify',
description: 'Recibir pagos es tan fácil como mandar un mensaje',
secondaryDescription: 'Para volver a Expensify Classic, simplemente haz click en tu foto de perfil > Ir a Expensify Classic.',
},
welcomeVideo: {
title: 'Bienvenido a Expensify',
description: 'Una aplicación para gestionar todos tus gastos de empresa y personales en un chat. Pensada para tu empresa, tu equipo y tus amigos.',
Expand Down
3 changes: 3 additions & 0 deletions src/libs/API/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type {ValueOf} from 'type-fest';
import type CONST from '@src/CONST';
import type {EmptyObject} from '@src/types/utils/EmptyObject';
import type * as Parameters from './parameters';
import type SignInUserParams from './parameters/SignInUserParams';
import type UpdateBeneficialOwnersForBankAccountParams from './parameters/UpdateBeneficialOwnersForBankAccountParams';
Expand Down Expand Up @@ -141,6 +142,7 @@ const WRITE_COMMANDS = {
REOPEN_TASK: 'ReopenTask',
COMPLETE_TASK: 'CompleteTask',
COMPLETE_GUIDED_SETUP: 'CompleteGuidedSetup',
COMPLETE_HYBRID_APP_ONBOARDING: 'CompleteHybridAppOnboarding',
SET_NAME_VALUE_PAIR: 'SetNameValuePair',
SET_REPORT_FIELD: 'Report_SetFields',
DELETE_REPORT_FIELD: 'RemoveReportField',
Expand Down Expand Up @@ -361,6 +363,7 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.REOPEN_TASK]: Parameters.ReopenTaskParams;
[WRITE_COMMANDS.COMPLETE_TASK]: Parameters.CompleteTaskParams;
[WRITE_COMMANDS.COMPLETE_GUIDED_SETUP]: Parameters.CompleteGuidedSetupParams;
[WRITE_COMMANDS.COMPLETE_HYBRID_APP_ONBOARDING]: EmptyObject;
[WRITE_COMMANDS.SET_NAME_VALUE_PAIR]: Parameters.SetNameValuePairParams;
[WRITE_COMMANDS.SET_REPORT_FIELD]: Parameters.SetReportFieldParams;
[WRITE_COMMANDS.SET_REPORT_NAME]: Parameters.SetReportNameParams;
Expand Down
6 changes: 6 additions & 0 deletions src/libs/Navigation/AppNavigator/AuthScreens.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import createCustomStackNavigator from './createCustomStackNavigator';
import defaultScreenOptions from './defaultScreenOptions';
import getRootNavigatorScreenOptions from './getRootNavigatorScreenOptions';
import BottomTabNavigator from './Navigators/BottomTabNavigator';
import ExplanationModalNavigator from './Navigators/ExplanationModalNavigator';
import FeatureTrainingModalNavigator from './Navigators/FeatureTrainingModalNavigator';
import FullScreenNavigator from './Navigators/FullScreenNavigator';
import LeftModalNavigator from './Navigators/LeftModalNavigator';
Expand Down Expand Up @@ -416,6 +417,11 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie
options={screenOptions.fullScreen}
component={DesktopSignInRedirectPage}
/>
<RootStack.Screen
name={NAVIGATORS.EXPLANATION_MODAL_NAVIGATOR}
options={onboardingModalScreenOptions}
component={ExplanationModalNavigator}
/>
<RootStack.Screen
name={NAVIGATORS.FEATURE_TRANING_MODAL_NAVIGATOR}
options={onboardingModalScreenOptions}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {createStackNavigator} from '@react-navigation/stack';
import React from 'react';
import {View} from 'react-native';
import NoDropZone from '@components/DragAndDrop/NoDropZone';
import ExplanationModal from '@components/ExplanationModal';
import type {ExplanationModalNavigatorParamList} from '@libs/Navigation/types';
import SCREENS from '@src/SCREENS';

const Stack = createStackNavigator<ExplanationModalNavigatorParamList>();

function ExplanationModalNavigator() {
return (
<NoDropZone>
<View>
<Stack.Navigator screenOptions={{headerShown: false, animationEnabled: true}}>
<Stack.Screen
name={SCREENS.EXPLANATION_MODAL.ROOT}
component={ExplanationModal}
/>
</Stack.Navigator>
</View>
</NoDropZone>
);
}

ExplanationModalNavigator.displayName = 'ExplanationModalNavigator';

export default ExplanationModalNavigator;
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {useNavigation, useNavigationState} from '@react-navigation/native';
import React, {memo, useCallback, useEffect} from 'react';
import {View} from 'react-native';
import {NativeModules, View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
import Icon from '@components/Icon';
Expand Down Expand Up @@ -52,6 +52,11 @@ function BottomTabBar({isLoadingApp = false}: PurposeForUsingExpensifyModalProps
return;
}

// HybridApp has own entry point when we decide whether to display onboarding and explanation modal.
if (NativeModules.HybridAppModule) {
return;
}

Welcome.isOnboardingFlowCompleted({onNotCompleted: () => Navigation.navigate(ROUTES.ONBOARDING_ROOT)});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isLoadingApp]);
Expand Down
8 changes: 8 additions & 0 deletions src/libs/Navigation/linkingConfig/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,14 @@ const config: LinkingOptions<RootStackParamList>['config'] = {
},
},
},
[NAVIGATORS.EXPLANATION_MODAL_NAVIGATOR]: {
screens: {
[SCREENS.EXPLANATION_MODAL.ROOT]: {
path: ROUTES.EXPLANATION_MODAL_ROOT,
exact: true,
},
},
},
[NAVIGATORS.ONBOARDING_MODAL_NAVIGATOR]: {
path: ROUTES.ONBOARDING_ROOT,
initialRouteName: SCREENS.ONBOARDING.PURPOSE,
Expand Down
6 changes: 6 additions & 0 deletions src/libs/Navigation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -872,6 +872,10 @@ type WelcomeVideoModalNavigatorParamList = {
[SCREENS.WELCOME_VIDEO.ROOT]: undefined;
};

type ExplanationModalNavigatorParamList = {
[SCREENS.EXPLANATION_MODAL.ROOT]: undefined;
};

type BottomTabNavigatorParamList = {
[SCREENS.HOME]: {policyID?: string};
[SCREENS.SEARCH.BOTTOM_TAB]: {
Expand Down Expand Up @@ -944,6 +948,7 @@ type AuthScreensParamList = CentralPaneScreensParamList &
[NAVIGATORS.ONBOARDING_MODAL_NAVIGATOR]: NavigatorScreenParams<OnboardingModalNavigatorParamList>;
[NAVIGATORS.FEATURE_TRANING_MODAL_NAVIGATOR]: NavigatorScreenParams<FeatureTrainingNavigatorParamList>;
[NAVIGATORS.WELCOME_VIDEO_MODAL_NAVIGATOR]: NavigatorScreenParams<WelcomeVideoModalNavigatorParamList>;
[NAVIGATORS.EXPLANATION_MODAL_NAVIGATOR]: NavigatorScreenParams<ExplanationModalNavigatorParamList>;
[SCREENS.DESKTOP_SIGN_IN_REDIRECT]: undefined;
[SCREENS.TRANSACTION_RECEIPT]: {
reportID: string;
Expand Down Expand Up @@ -990,6 +995,7 @@ export type {
DetailsNavigatorParamList,
EditRequestNavigatorParamList,
EnablePaymentsNavigatorParamList,
ExplanationModalNavigatorParamList,
FlagCommentNavigatorParamList,
FullScreenName,
FullScreenNavigatorParamList,
Expand Down
Loading

0 comments on commit 24b7c48

Please sign in to comment.