From 0c1b3416c6c77d0640ca73a1c1dfa9878c59aa73 Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Tue, 9 Jul 2024 17:38:46 +0200 Subject: [PATCH 1/3] Propagate nvp_tryNewDot to OldDot --- src/components/HybridAppMiddleware.tsx | 28 ++++++++++++++++++++++++++ src/libs/actions/Welcome.ts | 2 +- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/components/HybridAppMiddleware.tsx b/src/components/HybridAppMiddleware.tsx index 5c6934f4fc3d..48ce2e5a6154 100644 --- a/src/components/HybridAppMiddleware.tsx +++ b/src/components/HybridAppMiddleware.tsx @@ -2,6 +2,8 @@ import {useNavigation} from '@react-navigation/native'; import type {StackNavigationProp} from '@react-navigation/stack'; import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {NativeModules} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; import useSplashScreen from '@hooks/useSplashScreen'; import BootSplash from '@libs/BootSplash'; import Log from '@libs/Log'; @@ -9,7 +11,9 @@ import Navigation from '@libs/Navigation/Navigation'; import type {RootStackParamList} from '@libs/Navigation/types'; import * as Welcome from '@userActions/Welcome'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; +import type {TryNewDot} from '@src/types/onyx'; type HybridAppMiddlewareProps = { children: React.ReactNode; @@ -24,6 +28,16 @@ const HybridAppMiddlewareContext = React.createContext {}, }); +const onboardingStatusSelector = (tryNewDot: OnyxEntry) => { + let completedHybridAppOnboarding = tryNewDot?.classicRedirect?.completedHybridAppOnboarding; + + if (typeof completedHybridAppOnboarding === 'string') { + completedHybridAppOnboarding = completedHybridAppOnboarding === 'true'; + } + + return completedHybridAppOnboarding; +}; + /* * HybridAppMiddleware is responsible for handling BootSplash visibility correctly. * It is crucial to make transitions between OldDot and NewDot look smooth. @@ -33,6 +47,20 @@ function HybridAppMiddleware(props: HybridAppMiddlewareProps) { const [startedTransition, setStartedTransition] = useState(false); const [finishedTransition, setFinishedTransition] = useState(false); const navigation = useNavigation>(); + const [completedHybridAppOnboarding] = useOnyx(ONYXKEYS.NVP_TRYNEWDOT, {selector: onboardingStatusSelector}); + + /** + * This useEffect tracks changes of `nvp_tryNewDot` value. + * We propagate it from OldDot to NewDot with native method due to limitations of old app. + */ + useEffect(() => { + if (completedHybridAppOnboarding === undefined) { + return; + } + + Log.info(`[HybridApp] Onboarding status has changed. Propagating new value to OldDot`, true, {completedHybridAppOnboarding}); + NativeModules.HybridAppModule.completeOnboarding(completedHybridAppOnboarding); + }, [completedHybridAppOnboarding]); /* * Handles navigation during transition from OldDot. For ordinary NewDot app it is just pure navigation. diff --git a/src/libs/actions/Welcome.ts b/src/libs/actions/Welcome.ts index a90c386d02b6..0c3224ee37d9 100644 --- a/src/libs/actions/Welcome.ts +++ b/src/libs/actions/Welcome.ts @@ -95,7 +95,7 @@ function handleHybridAppOnboarding() { isOnboardingFlowCompleted({ onNotCompleted: () => setTimeout(() => { - Navigation.navigate(ROUTES.EXPLANATION_MODAL_ROOT); + Navigation.navigate(ROUTES.ONBOARDING_ROOT); }, variables.explanationModalDelay), }), }); From b5fd3475cb97e1e2d293bd4db43bb579f9e9380f Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Tue, 9 Jul 2024 17:49:51 +0200 Subject: [PATCH 2/3] Update types --- src/types/modules/react-native.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/types/modules/react-native.d.ts b/src/types/modules/react-native.d.ts index a2e271a3839d..2efab8e90cfc 100644 --- a/src/types/modules/react-native.d.ts +++ b/src/types/modules/react-native.d.ts @@ -12,6 +12,7 @@ import type StartupTimer from '@libs/StartupTimer/types'; type HybridAppModule = { closeReactNativeApp: () => void; + completeOnboarding: (status: boolean) => void; exitApp: () => void; }; From e5f8073d69042f11cc74af9142a3254477faaf47 Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Thu, 11 Jul 2024 17:13:09 +0200 Subject: [PATCH 3/3] Fix merge problems --- src/components/HybridAppMiddleware.tsx | 136 ------------------ .../HybridAppMiddleware/index.ios.tsx | 26 ++++ src/components/HybridAppMiddleware/index.tsx | 26 ++++ 3 files changed, 52 insertions(+), 136 deletions(-) delete mode 100644 src/components/HybridAppMiddleware.tsx diff --git a/src/components/HybridAppMiddleware.tsx b/src/components/HybridAppMiddleware.tsx deleted file mode 100644 index 48ce2e5a6154..000000000000 --- a/src/components/HybridAppMiddleware.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import {useNavigation} from '@react-navigation/native'; -import type {StackNavigationProp} from '@react-navigation/stack'; -import React, {useCallback, useEffect, useMemo, useState} from 'react'; -import {NativeModules} from 'react-native'; -import type {OnyxEntry} from 'react-native-onyx'; -import {useOnyx} from 'react-native-onyx'; -import useSplashScreen from '@hooks/useSplashScreen'; -import BootSplash from '@libs/BootSplash'; -import Log from '@libs/Log'; -import Navigation from '@libs/Navigation/Navigation'; -import type {RootStackParamList} from '@libs/Navigation/types'; -import * as Welcome from '@userActions/Welcome'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import type {Route} from '@src/ROUTES'; -import type {TryNewDot} from '@src/types/onyx'; - -type HybridAppMiddlewareProps = { - children: React.ReactNode; -}; - -type HybridAppMiddlewareContextType = { - navigateToExitUrl: (exitUrl: Route) => void; - showSplashScreenOnNextStart: () => void; -}; -const HybridAppMiddlewareContext = React.createContext({ - navigateToExitUrl: () => {}, - showSplashScreenOnNextStart: () => {}, -}); - -const onboardingStatusSelector = (tryNewDot: OnyxEntry) => { - let completedHybridAppOnboarding = tryNewDot?.classicRedirect?.completedHybridAppOnboarding; - - if (typeof completedHybridAppOnboarding === 'string') { - completedHybridAppOnboarding = completedHybridAppOnboarding === 'true'; - } - - return completedHybridAppOnboarding; -}; - -/* - * HybridAppMiddleware is responsible for handling BootSplash visibility correctly. - * It is crucial to make transitions between OldDot and NewDot look smooth. - */ -function HybridAppMiddleware(props: HybridAppMiddlewareProps) { - const {isSplashHidden, setIsSplashHidden} = useSplashScreen(); - const [startedTransition, setStartedTransition] = useState(false); - const [finishedTransition, setFinishedTransition] = useState(false); - const navigation = useNavigation>(); - const [completedHybridAppOnboarding] = useOnyx(ONYXKEYS.NVP_TRYNEWDOT, {selector: onboardingStatusSelector}); - - /** - * This useEffect tracks changes of `nvp_tryNewDot` value. - * We propagate it from OldDot to NewDot with native method due to limitations of old app. - */ - useEffect(() => { - if (completedHybridAppOnboarding === undefined) { - return; - } - - Log.info(`[HybridApp] Onboarding status has changed. Propagating new value to OldDot`, true, {completedHybridAppOnboarding}); - NativeModules.HybridAppModule.completeOnboarding(completedHybridAppOnboarding); - }, [completedHybridAppOnboarding]); - - /* - * Handles navigation during transition from OldDot. For ordinary NewDot app it is just pure navigation. - */ - const navigateToExitUrl = useCallback((exitUrl: Route) => { - if (NativeModules.HybridAppModule) { - setStartedTransition(true); - Log.info(`[HybridApp] Started transition to ${exitUrl}`, true); - } - - Navigation.navigate(exitUrl); - }, []); - - /** - * This function only affects iOS. If during a single app lifecycle we frequently transition from OldDot to NewDot, - * we need to artificially show the bootsplash because the app is only booted once. - */ - const showSplashScreenOnNextStart = useCallback(() => { - setIsSplashHidden(false); - setStartedTransition(false); - setFinishedTransition(false); - }, [setIsSplashHidden]); - - useEffect(() => { - if (!finishedTransition || isSplashHidden) { - return; - } - - Log.info('[HybridApp] Finished transtion', true); - BootSplash.hide().then(() => { - setIsSplashHidden(true); - Log.info('[HybridApp] Handling onboarding flow', true); - Welcome.handleHybridAppOnboarding(); - }); - }, [finishedTransition, isSplashHidden, setIsSplashHidden]); - - useEffect(() => { - if (!startedTransition) { - return; - } - - // On iOS, the transitionEnd event doesn't trigger some times. As such, we need to set a timeout. - const timeout = setTimeout(() => { - setFinishedTransition(true); - }, CONST.SCREEN_TRANSITION_END_TIMEOUT); - - const unsubscribeTransitionEnd = navigation.addListener('transitionEnd', () => { - clearTimeout(timeout); - setFinishedTransition(true); - }); - - return () => { - clearTimeout(timeout); - unsubscribeTransitionEnd(); - }; - }, [navigation, startedTransition]); - - const contextValue = useMemo( - () => ({ - navigateToExitUrl, - showSplashScreenOnNextStart, - }), - [navigateToExitUrl, showSplashScreenOnNextStart], - ); - - return {props.children}; -} - -HybridAppMiddleware.displayName = 'HybridAppMiddleware'; - -export default HybridAppMiddleware; -export type {HybridAppMiddlewareContextType}; -export {HybridAppMiddlewareContext}; diff --git a/src/components/HybridAppMiddleware/index.ios.tsx b/src/components/HybridAppMiddleware/index.ios.tsx index 5b06e5626c6e..c348cc86c974 100644 --- a/src/components/HybridAppMiddleware/index.ios.tsx +++ b/src/components/HybridAppMiddleware/index.ios.tsx @@ -3,6 +3,7 @@ import {useContext, useEffect, useState} from 'react'; import {NativeEventEmitter, NativeModules} from 'react-native'; import type {NativeModule} from 'react-native'; import {useOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; import {InitialURLContext} from '@components/InitialURLContextProvider'; import useExitTo from '@hooks/useExitTo'; import useSplashScreen from '@hooks/useSplashScreen'; @@ -14,12 +15,23 @@ import * as Welcome from '@userActions/Welcome'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {HybridAppRoute, Route} from '@src/ROUTES'; +import type {TryNewDot} from '@src/types/onyx'; type HybridAppMiddlewareProps = { authenticated: boolean; children: React.ReactNode; }; +const onboardingStatusSelector = (tryNewDot: OnyxEntry) => { + let completedHybridAppOnboarding = tryNewDot?.classicRedirect?.completedHybridAppOnboarding; + + if (typeof completedHybridAppOnboarding === 'string') { + completedHybridAppOnboarding = completedHybridAppOnboarding === 'true'; + } + + return completedHybridAppOnboarding; +}; + /* * HybridAppMiddleware is responsible for handling BootSplash visibility correctly. * It is crucial to make transitions between OldDot and NewDot look smooth. @@ -36,6 +48,20 @@ function HybridAppMiddleware({children, authenticated}: HybridAppMiddlewareProps const [isAccountLoading] = useOnyx(ONYXKEYS.ACCOUNT, {selector: (account) => account?.isLoading ?? false}); const [sessionEmail] = useOnyx(ONYXKEYS.SESSION, {selector: (session) => session?.email}); + const [completedHybridAppOnboarding] = useOnyx(ONYXKEYS.NVP_TRYNEWDOT, {selector: onboardingStatusSelector}); + + /** + * This useEffect tracks changes of `nvp_tryNewDot` value. + * We propagate it from OldDot to NewDot with native method due to limitations of old app. + */ + useEffect(() => { + if (completedHybridAppOnboarding === undefined) { + return; + } + + Log.info(`[HybridApp] Onboarding status has changed. Propagating new value to OldDot`, true, {completedHybridAppOnboarding}); + NativeModules.HybridAppModule.completeOnboarding(completedHybridAppOnboarding); + }, [completedHybridAppOnboarding]); // In iOS, the HybridApp defines the `onReturnToOldDot` event. // If we frequently transition from OldDot to NewDot during a single app lifecycle, diff --git a/src/components/HybridAppMiddleware/index.tsx b/src/components/HybridAppMiddleware/index.tsx index b8c72d9200ac..b3d346a1b65c 100644 --- a/src/components/HybridAppMiddleware/index.tsx +++ b/src/components/HybridAppMiddleware/index.tsx @@ -2,6 +2,7 @@ import type React from 'react'; import {useContext, useEffect, useState} from 'react'; import {NativeModules} from 'react-native'; import {useOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; import {InitialURLContext} from '@components/InitialURLContextProvider'; import useExitTo from '@hooks/useExitTo'; import useSplashScreen from '@hooks/useSplashScreen'; @@ -13,12 +14,23 @@ import * as Welcome from '@userActions/Welcome'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {HybridAppRoute, Route} from '@src/ROUTES'; +import type {TryNewDot} from '@src/types/onyx'; type HybridAppMiddlewareProps = { authenticated: boolean; children: React.ReactNode; }; +const onboardingStatusSelector = (tryNewDot: OnyxEntry) => { + let completedHybridAppOnboarding = tryNewDot?.classicRedirect?.completedHybridAppOnboarding; + + if (typeof completedHybridAppOnboarding === 'string') { + completedHybridAppOnboarding = completedHybridAppOnboarding === 'true'; + } + + return completedHybridAppOnboarding; +}; + /* * HybridAppMiddleware is responsible for handling BootSplash visibility correctly. * It is crucial to make transitions between OldDot and NewDot look smooth. @@ -35,6 +47,20 @@ function HybridAppMiddleware({children, authenticated}: HybridAppMiddlewareProps const [isAccountLoading] = useOnyx(ONYXKEYS.ACCOUNT, {selector: (account) => account?.isLoading ?? false}); const [sessionEmail] = useOnyx(ONYXKEYS.SESSION, {selector: (session) => session?.email}); + const [completedHybridAppOnboarding] = useOnyx(ONYXKEYS.NVP_TRYNEWDOT, {selector: onboardingStatusSelector}); + + /** + * This useEffect tracks changes of `nvp_tryNewDot` value. + * We propagate it from OldDot to NewDot with native method due to limitations of old app. + */ + useEffect(() => { + if (completedHybridAppOnboarding === undefined) { + return; + } + + Log.info(`[HybridApp] Onboarding status has changed. Propagating new value to OldDot`, true, {completedHybridAppOnboarding}); + NativeModules.HybridAppModule.completeOnboarding(completedHybridAppOnboarding); + }, [completedHybridAppOnboarding]); // Save `exitTo` when we reach /transition route. // `exitTo` should always exist during OldDot -> NewDot transitions.