diff --git a/src/NAVIGATORS.ts b/src/NAVIGATORS.ts
index eea357322075..0b4a86c99247 100644
--- a/src/NAVIGATORS.ts
+++ b/src/NAVIGATORS.ts
@@ -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;
diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts
index 2cb615ae0af8..a15ae54cedea 100755
--- a/src/ONYXKEYS.ts
+++ b/src/ONYXKEYS.ts
@@ -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',
@@ -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',
@@ -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;
@@ -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;
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index 5c8cfdcc8a68..c4afee12952e 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -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',
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index 5c5fc6c31092..5ef7f2693512 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -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',
diff --git a/src/components/ExplanationModal.tsx b/src/components/ExplanationModal.tsx
new file mode 100644
index 000000000000..ef49297078d5
--- /dev/null
+++ b/src/components/ExplanationModal.tsx
@@ -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 (
+
+ );
+}
+
+ExplanationModal.displayName = 'ExplanationModal';
+export default ExplanationModal;
diff --git a/src/components/FeatureTrainingModal.tsx b/src/components/FeatureTrainingModal.tsx
index 88099b6d078b..221a582af75d 100644
--- a/src/components/FeatureTrainingModal.tsx
+++ b/src/components/FeatureTrainingModal.tsx
@@ -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;
@@ -73,6 +76,7 @@ function FeatureTrainingModal({
videoAspectRatio: videoAspectRatioProp,
title = '',
description = '',
+ secondaryDescription = '',
shouldShowDismissModalOption = false,
confirmText = '',
onConfirm = () => {},
@@ -199,6 +203,7 @@ function FeatureTrainingModal({
{title}
{description}
+ {secondaryDescription.length > 0 && {secondaryDescription}}
)}
{shouldShowDismissModalOption && (
diff --git a/src/components/OnboardingWelcomeVideo.tsx b/src/components/OnboardingWelcomeVideo.tsx
index c4378a258d5d..47444d133166 100644
--- a/src/components/OnboardingWelcomeVideo.tsx
+++ b/src/components/OnboardingWelcomeVideo.tsx
@@ -17,5 +17,4 @@ function OnboardingWelcomeVideo() {
}
OnboardingWelcomeVideo.displayName = 'OnboardingWelcomeVideo';
-
export default OnboardingWelcomeVideo;
diff --git a/src/components/TestToolMenu.tsx b/src/components/TestToolMenu.tsx
index 2056b4ce4189..15c70f6be5ae 100644
--- a/src/components/TestToolMenu.tsx
+++ b/src/components/TestToolMenu.tsx
@@ -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';
@@ -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 (
@@ -88,6 +94,24 @@ function TestToolMenu({user = USER_DEFAULT, network}: TestToolMenuProps) {
onPress={() => Session.invalidateCredentials()}
/>
+ {/* Navigate to the Explanation Modal. This button is temporary to test Explanation Modal flow without HybridApp native module. */}
+
+
>
);
}
diff --git a/src/languages/en.ts b/src/languages/en.ts
index d5ff9e6c1a5b..3a2ab0700989 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -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.',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 2ecaf37a75d4..bde5edde7898 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -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.',
diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts
index e3115a624680..b1e2e422ac62 100644
--- a/src/libs/API/types.ts
+++ b/src/libs/API/types.ts
@@ -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';
@@ -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',
@@ -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;
diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx
index c9773f104393..fce933cf4ee6 100644
--- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx
+++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx
@@ -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';
@@ -416,6 +417,11 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie
options={screenOptions.fullScreen}
component={DesktopSignInRedirectPage}
/>
+
();
+
+function ExplanationModalNavigator() {
+ return (
+
+
+
+
+
+
+
+ );
+}
+
+ExplanationModalNavigator.displayName = 'ExplanationModalNavigator';
+
+export default ExplanationModalNavigator;
diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.tsx
index 472d2c7d6d29..4194fd6c4c3b 100644
--- a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.tsx
+++ b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.tsx
@@ -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';
@@ -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]);
diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts
index c66472abb3b4..49dcd6f8b246 100644
--- a/src/libs/Navigation/linkingConfig/config.ts
+++ b/src/libs/Navigation/linkingConfig/config.ts
@@ -92,6 +92,14 @@ const config: LinkingOptions['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,
diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts
index 6c4e03aa2018..164dea5cfffa 100644
--- a/src/libs/Navigation/types.ts
+++ b/src/libs/Navigation/types.ts
@@ -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]: {
@@ -944,6 +948,7 @@ type AuthScreensParamList = CentralPaneScreensParamList &
[NAVIGATORS.ONBOARDING_MODAL_NAVIGATOR]: NavigatorScreenParams;
[NAVIGATORS.FEATURE_TRANING_MODAL_NAVIGATOR]: NavigatorScreenParams;
[NAVIGATORS.WELCOME_VIDEO_MODAL_NAVIGATOR]: NavigatorScreenParams;
+ [NAVIGATORS.EXPLANATION_MODAL_NAVIGATOR]: NavigatorScreenParams;
[SCREENS.DESKTOP_SIGN_IN_REDIRECT]: undefined;
[SCREENS.TRANSACTION_RECEIPT]: {
reportID: string;
@@ -990,6 +995,7 @@ export type {
DetailsNavigatorParamList,
EditRequestNavigatorParamList,
EnablePaymentsNavigatorParamList,
+ ExplanationModalNavigatorParamList,
FlagCommentNavigatorParamList,
FullScreenName,
FullScreenNavigatorParamList,
diff --git a/src/libs/actions/Welcome.ts b/src/libs/actions/Welcome.ts
index 31ffcb37ccc7..cee4e24041f1 100644
--- a/src/libs/actions/Welcome.ts
+++ b/src/libs/actions/Welcome.ts
@@ -1,18 +1,31 @@
-import type {OnyxCollection} from 'react-native-onyx';
+import {NativeModules} from 'react-native';
+import type {OnyxCollection, OnyxUpdate} from 'react-native-onyx';
import Onyx from 'react-native-onyx';
+import * as API from '@libs/API';
+import {WRITE_COMMANDS} from '@libs/API/types';
+import Navigation from '@libs/Navigation/Navigation';
+import variables from '@styles/variables';
import type {OnboardingPurposeType} from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
+import ROUTES from '@src/ROUTES';
import type Onboarding from '@src/types/onyx/Onboarding';
import type OnyxPolicy from '@src/types/onyx/Policy';
+import type TryNewDot from '@src/types/onyx/TryNewDot';
let onboarding: Onboarding | [] | undefined;
let isLoadingReportData = true;
+let tryNewDotData: TryNewDot | undefined;
type HasCompletedOnboardingFlowProps = {
onCompleted?: () => void;
onNotCompleted?: () => void;
};
+type HasOpenedForTheFirstTimeFromHybridAppProps = {
+ onFirstTimeInHybridApp?: () => void;
+ onSubsequentRuns?: () => void;
+};
+
let resolveIsReadyPromise: (value?: Promise) => void | undefined;
let isServerDataReadyPromise = new Promise((resolve) => {
resolveIsReadyPromise = resolve;
@@ -23,6 +36,11 @@ let isOnboardingFlowStatusKnownPromise = new Promise((resolve) => {
resolveOnboardingFlowStatus = resolve;
});
+let resolveTryNewDotStatus: (value?: Promise) => void | undefined;
+const tryNewDotStatusPromise = new Promise((resolve) => {
+ resolveTryNewDotStatus = resolve;
+});
+
function onServerDataReady(): Promise {
return isServerDataReadyPromise;
}
@@ -42,6 +60,54 @@ function isOnboardingFlowCompleted({onCompleted, onNotCompleted}: HasCompletedOn
}
/**
+ * Determines whether the application is being launched for the first time by a hybrid app user,
+ * and executes corresponding callback functions.
+ */
+function isFirstTimeHybridAppUser({onFirstTimeInHybridApp, onSubsequentRuns}: HasOpenedForTheFirstTimeFromHybridAppProps) {
+ tryNewDotStatusPromise.then(() => {
+ let completedHybridAppOnboarding = tryNewDotData?.classicRedirect?.completedHybridAppOnboarding;
+ // Backend might return strings instead of booleans
+ if (typeof completedHybridAppOnboarding === 'string') {
+ completedHybridAppOnboarding = completedHybridAppOnboarding === 'true';
+ }
+
+ if (NativeModules.HybridAppModule && !completedHybridAppOnboarding) {
+ onFirstTimeInHybridApp?.();
+ return;
+ }
+
+ onSubsequentRuns?.();
+ });
+}
+
+/**
+ * Handles HybridApp onboarding flow if it's possible and necessary.
+ */
+function handleHybridAppOnboarding() {
+ if (!NativeModules.HybridAppModule) {
+ return;
+ }
+
+ isFirstTimeHybridAppUser({
+ // When user opens New Expensify for the first time from HybridApp we always want to show explanation modal first.
+ onFirstTimeInHybridApp: () => Navigation.navigate(ROUTES.EXPLANATION_MODAL_ROOT),
+ // In other scenarios we need to check if onboarding was completed.
+ onSubsequentRuns: () =>
+ isOnboardingFlowCompleted({
+ onNotCompleted: () =>
+ setTimeout(() => {
+ Navigation.navigate(ROUTES.EXPLANATION_MODAL_ROOT);
+ }, variables.explanationModalDelay),
+ }),
+ });
+}
+
+/**
+ * Check that a few requests have completed so that the welcome action can proceed:
+ *
+ * - Whether we are a first time new expensify user
+ * - Whether we have loaded all policies the server knows about
+ * - Whether we have loaded all reports the server knows about
* Check if onboarding data is ready in order to check if the user has completed onboarding or not
*/
function checkOnboardingDataReady() {
@@ -63,6 +129,17 @@ function checkServerDataReady() {
resolveIsReadyPromise?.();
}
+/**
+ * Check if user completed HybridApp onboarding
+ */
+function checkTryNewDotDataReady() {
+ if (tryNewDotData === undefined) {
+ return;
+ }
+
+ resolveTryNewDotStatus?.();
+}
+
function setOnboardingPurposeSelected(value: OnboardingPurposeType) {
Onyx.set(ONYXKEYS.ONBOARDING_PURPOSE_SELECTED, value ?? null);
}
@@ -75,6 +152,34 @@ function setOnboardingPolicyID(policyID?: string) {
Onyx.set(ONYXKEYS.ONBOARDING_POLICY_ID, policyID ?? null);
}
+function completeHybridAppOnboarding() {
+ const optimisticData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: ONYXKEYS.NVP_TRYNEWDOT,
+ value: {
+ classicRedirect: {
+ completedHybridAppOnboarding: true,
+ },
+ },
+ },
+ ];
+
+ const failureData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: ONYXKEYS.NVP_TRYNEWDOT,
+ value: {
+ classicRedirect: {
+ completedHybridAppOnboarding: false,
+ },
+ },
+ },
+ ];
+
+ API.write(WRITE_COMMANDS.COMPLETE_HYBRID_APP_ONBOARDING, {}, {optimisticData, failureData});
+}
+
Onyx.connect({
key: ONYXKEYS.NVP_ONBOARDING,
callback: (value) => {
@@ -114,6 +219,14 @@ Onyx.connect({
},
});
+Onyx.connect({
+ key: ONYXKEYS.NVP_TRYNEWDOT,
+ callback: (value) => {
+ tryNewDotData = value;
+ checkTryNewDotDataReady();
+ },
+});
+
function resetAllChecks() {
isServerDataReadyPromise = new Promise((resolve) => {
resolveIsReadyPromise = resolve;
@@ -125,4 +238,13 @@ function resetAllChecks() {
isLoadingReportData = true;
}
-export {onServerDataReady, isOnboardingFlowCompleted, setOnboardingPurposeSelected, resetAllChecks, setOnboardingAdminsChatReportID, setOnboardingPolicyID};
+export {
+ onServerDataReady,
+ isOnboardingFlowCompleted,
+ setOnboardingPurposeSelected,
+ resetAllChecks,
+ setOnboardingAdminsChatReportID,
+ setOnboardingPolicyID,
+ completeHybridAppOnboarding,
+ handleHybridAppOnboarding,
+};
diff --git a/src/pages/LogInWithShortLivedAuthTokenPage.tsx b/src/pages/LogInWithShortLivedAuthTokenPage.tsx
index 72425e0e2ca6..8dd6720290b9 100644
--- a/src/pages/LogInWithShortLivedAuthTokenPage.tsx
+++ b/src/pages/LogInWithShortLivedAuthTokenPage.tsx
@@ -16,6 +16,7 @@ import Log from '@libs/Log';
import Navigation from '@libs/Navigation/Navigation';
import type {PublicScreensParamList} from '@libs/Navigation/types';
import * as Session from '@userActions/Session';
+import * as Welcome from '@userActions/Welcome';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Route} from '@src/ROUTES';
@@ -66,6 +67,7 @@ function LogInWithShortLivedAuthTokenPage({route, account}: LogInWithShortLivedA
Navigation.isNavigationReady().then(() => {
const url = NativeModules.HybridAppModule ? Navigation.parseHybridAppUrl(exitTo) : (exitTo as Route);
Navigation.navigate(url);
+ Welcome.handleHybridAppOnboarding();
});
}
// The only dependencies of the effect are based on props.route
diff --git a/src/pages/LogOutPreviousUserPage.tsx b/src/pages/LogOutPreviousUserPage.tsx
index c80b26bbb9e7..ab235c099601 100644
--- a/src/pages/LogOutPreviousUserPage.tsx
+++ b/src/pages/LogOutPreviousUserPage.tsx
@@ -9,6 +9,7 @@ import * as SessionUtils from '@libs/SessionUtils';
import Navigation from '@navigation/Navigation';
import type {AuthScreensParamList} from '@navigation/types';
import * as SessionActions from '@userActions/Session';
+import * as Welcome from '@userActions/Welcome';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
@@ -81,6 +82,7 @@ function LogOutPreviousUserPage({session, route, isAccountLoading}: LogOutPrevio
const exitUrl = NativeModules.HybridAppModule ? Navigation.parseHybridAppUrl(exitTo) : exitTo;
Navigation.goBack();
Navigation.navigate(exitUrl);
+ Welcome.handleHybridAppOnboarding();
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
diff --git a/src/styles/variables.ts b/src/styles/variables.ts
index 954321315da8..ec488cf42744 100644
--- a/src/styles/variables.ts
+++ b/src/styles/variables.ts
@@ -211,6 +211,7 @@ export default {
photoUploadPopoverWidth: 335,
onboardingModalWidth: 500,
welcomeVideoDelay: 1000,
+ explanationModalDelay: 2000,
// The height of the empty list is 14px (2px for borders and 12px for vertical padding)
// This is calculated based on the values specified in the 'getGoogleListViewStyle' function of the 'StyleUtils' utility
diff --git a/src/types/onyx/TryNewDot.ts b/src/types/onyx/TryNewDot.ts
new file mode 100644
index 000000000000..67ea66d255d8
--- /dev/null
+++ b/src/types/onyx/TryNewDot.ts
@@ -0,0 +1,25 @@
+/**
+ * HybridApp NVP
+ */
+type TryNewDot = {
+ /**
+ * This key is mostly used on OldDot. In NewDot, we only use `completedHybridAppOnboarding`.
+ */
+ classicRedirect: {
+ /**
+ * Indicates if transistion from OldDot to NewDot should happen in HybridApp.
+ */
+ dismissed: boolean | string;
+ /**
+ * Indicates timestamp of an action.
+ */
+ timestamp: Date;
+
+ /**
+ * Indicates if explanation modal on NewDot was dismissed.
+ */
+ completedHybridAppOnboarding: boolean;
+ };
+};
+
+export default TryNewDot;
diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts
index 27e32f8e8c9c..ef7dbed25faa 100644
--- a/src/types/onyx/index.ts
+++ b/src/types/onyx/index.ts
@@ -77,6 +77,7 @@ import type Transaction from './Transaction';
import type {TransactionViolation, ViolationName} from './TransactionViolation';
import type TransactionViolations from './TransactionViolation';
import type {TravelSettings} from './TravelSettings';
+import type TryNewDot from './TryNewDot';
import type User from './User';
import type UserLocation from './UserLocation';
import type UserMetadata from './UserMetadata';
@@ -90,6 +91,7 @@ import type WalletTransfer from './WalletTransfer';
import type WorkspaceRateAndUnit from './WorkspaceRateAndUnit';
export type {
+ TryNewDot,
Account,
AccountData,
BankAccount,