Skip to content

Commit

Permalink
Merge pull request #56145 from software-mansion-labs/hybrid-app-andro…
Browse files Browse the repository at this point in the history
…id-fragment-v2

Hybrid app android fragment v2
  • Loading branch information
roryabraham authored Feb 3, 2025
2 parents 4cfe5b2 + 60f5a84 commit 5348eb9
Show file tree
Hide file tree
Showing 10 changed files with 57 additions and 45 deletions.
3 changes: 2 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,9 @@ import type {Route} from './ROUTES';
import './setup/backgroundTask';
import {SplashScreenStateContextProvider} from './SplashScreenStateContext';

/** Values passed to our top-level React Native component by HybridApp. Will always be undefined in "pure" NewDot builds. */
type AppProps = {
/** URL passed to our top-level React Native component by HybridApp. Will always be undefined in "pure" NewDot builds. */
/** URL containing all necessary data to run React Native app (e.g. login data) */
url?: Route;
};

Expand Down
3 changes: 3 additions & 0 deletions src/components/ScreenWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import addViewportResizeListener from '@libs/VisualViewport';
import toggleTestToolsModal from '@userActions/TestTool';
import CONST from '@src/CONST';
import CustomDevMenu from './CustomDevMenu';
import CustomStatusBarAndBackgroundContext from './CustomStatusBarAndBackground/CustomStatusBarAndBackgroundContext';
import FocusTrapForScreens from './FocusTrap/FocusTrapForScreen';
import type FocusTrapForScreenProps from './FocusTrap/FocusTrapForScreen/FocusTrapProps';
import HeaderGap from './HeaderGap';
Expand Down Expand Up @@ -152,6 +153,7 @@ function ScreenWrapper(
const {windowHeight} = useWindowDimensions(shouldUseCachedViewportHeight);
// since Modals are drawn in separate native view hierarchy we should always add paddings
const ignoreInsetsConsumption = !useContext(ModalContext).default;
const {setRootStatusBarEnabled} = useContext(CustomStatusBarAndBackgroundContext);

// We need to use isSmallScreenWidth instead of shouldUseNarrowLayout for a case where we want to show the offline indicator only on small screens
// eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth
Expand All @@ -171,6 +173,7 @@ function ScreenWrapper(

UNSTABLE_usePreventRemove(shouldReturnToOldDot, () => {
NativeModules.HybridAppModule?.closeReactNativeApp(false, false);
setRootStatusBarEnabled(false);
});

const panResponder = useRef(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {findFocusedRoute, StackActions} from '@react-navigation/native';
import {BackHandler, NativeModules} from 'react-native';
import {BackHandler} from 'react-native';
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
import getTopmostCentralPaneRoute from '@navigation/getTopmostCentralPaneRoute';
import navigationRef from '@navigation/navigationRef';
Expand All @@ -22,12 +22,6 @@ function setupCustomAndroidBackHandler() {
return false;
}

const isLastScreenOnStack = bottomTabRoutes.length === 1 && rootState?.routes?.length === 1;

if (NativeModules.HybridAppModule && isLastScreenOnStack) {
NativeModules.HybridAppModule.exitApp();
}

// Handle back press on the search page.
// We need to pop two screens, from the central pane and from the bottom tab.
if (bottomTabRoutes[bottomTabRoutes.length - 1].name === SCREENS.SEARCH.BOTTOM_TAB && focusedRoute?.name === SCREENS.SEARCH.CENTRAL_PANE) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,37 +116,35 @@ function navigateToReport({reportID, reportActionID}: ReportActionPushNotificati
const policyEmployeeAccountIDs = policyID ? getPolicyEmployeeAccountIDs(policyID) : [];
const reportBelongsToWorkspace = policyID && !isEmptyObject(report) && doesReportBelongToWorkspace(report, policyEmployeeAccountIDs, policyID);

Navigation.isNavigationReady()
.then(Navigation.waitForProtectedRoutes)
.then(() => {
// The attachment modal remains open when navigating to the report so we need to close it
Modal.close(() => {
try {
// Get rid of the transition screen, if it is on the top of the stack
if (NativeModules.HybridAppModule && Navigation.getActiveRoute().includes(ROUTES.TRANSITION_BETWEEN_APPS)) {
Navigation.goBack();
}
// If a chat is visible other than the one we are trying to navigate to, then we need to navigate back
if (Navigation.getActiveRoute().slice(1, 2) === ROUTES.REPORT && !Navigation.isActiveRoute(`r/${reportID}`)) {
Navigation.goBack();
}

Log.info('[PushNotification] onSelected() - Navigation is ready. Navigating...', false, {reportID, reportActionID});
if (!reportBelongsToWorkspace) {
Navigation.navigateWithSwitchPolicyID({route: ROUTES.HOME});
}
Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(String(reportID)));
updateLastVisitedPath(ROUTES.REPORT_WITH_ID.getRoute(String(reportID)));
} catch (error) {
let errorMessage = String(error);
if (error instanceof Error) {
errorMessage = error.message;
}

Log.alert('[PushNotification] onSelected() - failed', {reportID, reportActionID, error: errorMessage});
Navigation.waitForProtectedRoutes().then(() => {
// The attachment modal remains open when navigating to the report so we need to close it
Modal.close(() => {
try {
// Get rid of the transition screen, if it is on the top of the stack
if (NativeModules.HybridAppModule && Navigation.getActiveRoute().includes(ROUTES.TRANSITION_BETWEEN_APPS)) {
Navigation.goBack();
}
});
// If a chat is visible other than the one we are trying to navigate to, then we need to navigate back
if (Navigation.getActiveRoute().slice(1, 2) === ROUTES.REPORT && !Navigation.isActiveRoute(`r/${reportID}`)) {
Navigation.goBack();
}

Log.info('[PushNotification] onSelected() - Navigation is ready. Navigating...', false, {reportID, reportActionID});
if (!reportBelongsToWorkspace) {
Navigation.navigateWithSwitchPolicyID({route: ROUTES.HOME});
}
Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(String(reportID)));
updateLastVisitedPath(ROUTES.REPORT_WITH_ID.getRoute(String(reportID)));
} catch (error) {
let errorMessage = String(error);
if (error instanceof Error) {
errorMessage = error.message;
}

Log.alert('[PushNotification] onSelected() - failed', {reportID, reportActionID, error: errorMessage});
}
});
});

return Promise.resolve();
}
Expand Down
8 changes: 7 additions & 1 deletion src/libs/actions/Travel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,12 @@ function provisionDomain(domain: string) {
Navigation.navigate(ROUTES.TRAVEL_TCS.getRoute(domain));
}

function bookATrip(translate: LocaleContextProps['translate'], setCtaErrorMessage: Dispatch<SetStateAction<string>>, ctaErrorMessage = ''): void {
function bookATrip(
translate: LocaleContextProps['translate'],
setCtaErrorMessage: Dispatch<SetStateAction<string>>,
setRootStatusBarEnabled: (isEnabled: boolean) => void,
ctaErrorMessage = '',
): void {
if (!activePolicyID) {
return;
}
Expand All @@ -138,6 +143,7 @@ function bookATrip(translate: LocaleContextProps['translate'], setCtaErrorMessag

Log.info('[HybridApp] Returning to OldDot after opening TravelDot');
NativeModules.HybridAppModule.closeReactNativeApp(false, false);
setRootStatusBarEnabled(false);
})
?.catch(() => {
setCtaErrorMessage(translate('travel.errorMessage'));
Expand Down
5 changes: 4 additions & 1 deletion src/pages/OnboardingEmployees/BaseOnboardingEmployees.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React, {useMemo, useState} from 'react';
import React, {useContext, useMemo, useState} from 'react';
import {NativeModules} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import Button from '@components/Button';
import CustomStatusBarAndBackgroundContext from '@components/CustomStatusBarAndBackground/CustomStatusBarAndBackgroundContext';
import FormHelpMessage from '@components/FormHelpMessage';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import ScreenWrapper from '@components/ScreenWrapper';
Expand Down Expand Up @@ -34,6 +35,7 @@ function BaseOnboardingEmployees({shouldUseNativeStyles, route}: BaseOnboardingE
const [onboardingPolicyID] = useOnyx(ONYXKEYS.ONBOARDING_POLICY_ID);
const [onboardingAdminsChatReportID] = useOnyx(ONYXKEYS.ONBOARDING_ADMINS_CHAT_REPORT_ID);
const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY);
const {setRootStatusBarEnabled} = useContext(CustomStatusBarAndBackgroundContext);

const paidGroupPolicy = Object.values(allPolicies ?? {}).find(isPaidGroupPolicy);

Expand Down Expand Up @@ -106,6 +108,7 @@ function BaseOnboardingEmployees({shouldUseNativeStyles, route}: BaseOnboardingE
}

NativeModules.HybridAppModule.closeReactNativeApp(false, true);
setRootStatusBarEnabled(false);
}}
pressOnEnter
/>
Expand Down
7 changes: 5 additions & 2 deletions src/pages/Search/EmptySearchView.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React, {useMemo, useState} from 'react';
import React, {useContext, useMemo, useState} from 'react';
import {Linking, View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import type {OnyxCollection} from 'react-native-onyx';
import ConfirmModal from '@components/ConfirmModal';
import CustomStatusBarAndBackgroundContext from '@components/CustomStatusBarAndBackground/CustomStatusBarAndBackgroundContext';
import DotIndicatorMessage from '@components/DotIndicatorMessage';
import EmptyStateComponent from '@components/EmptyStateComponent';
import type {FeatureListItem} from '@components/FeatureList';
Expand Down Expand Up @@ -60,6 +61,7 @@ function EmptySearchView({type, hasResults}: EmptySearchViewProps) {
const shouldRedirectToExpensifyClassic = useMemo(() => {
return areAllGroupPoliciesExpenseChatDisabled((allPolicies as OnyxCollection<Policy>) ?? {});
}, [allPolicies]);
const {setRootStatusBarEnabled} = useContext(CustomStatusBarAndBackgroundContext);

const [ctaErrorMessage, setCtaErrorMessage] = useState('');

Expand Down Expand Up @@ -133,7 +135,7 @@ function EmptySearchView({type, hasResults}: EmptySearchViewProps) {
buttons: [
{
buttonText: translate('search.searchResults.emptyTripResults.buttonText'),
buttonAction: () => bookATrip(translate, setCtaErrorMessage, ctaErrorMessage),
buttonAction: () => bookATrip(translate, setCtaErrorMessage, setRootStatusBarEnabled, ctaErrorMessage),
success: true,
},
],
Expand Down Expand Up @@ -238,6 +240,7 @@ function EmptySearchView({type, hasResults}: EmptySearchViewProps) {
styles.emptyStateFolderWebStyles,
subtitleComponent,
hasSeenTour,
setRootStatusBarEnabled,
ctaErrorMessage,
navatticURL,
shouldRedirectToExpensifyClassic,
Expand Down
6 changes: 4 additions & 2 deletions src/pages/Travel/ManageTrips.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, {useState} from 'react';
import React, {useContext, useState} from 'react';
import {Linking, View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import CustomStatusBarAndBackgroundContext from '@components/CustomStatusBarAndBackground/CustomStatusBarAndBackgroundContext';
import type {FeatureListItem} from '@components/FeatureList';
import FeatureList from '@components/FeatureList';
import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
Expand Down Expand Up @@ -34,6 +35,7 @@ function ManageTrips() {
const {translate} = useLocalize();
const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID);
const policy = usePolicy(activePolicyID);
const {setRootStatusBarEnabled} = useContext(CustomStatusBarAndBackgroundContext);

const [ctaErrorMessage, setCtaErrorMessage] = useState('');

Expand All @@ -55,7 +57,7 @@ function ManageTrips() {
ctaText={translate('travel.bookTravel')}
ctaAccessibilityLabel={translate('travel.bookTravel')}
onCtaPress={() => {
bookATrip(translate, setCtaErrorMessage, ctaErrorMessage);
bookATrip(translate, setCtaErrorMessage, setRootStatusBarEnabled, ctaErrorMessage);
}}
ctaErrorMessage={ctaErrorMessage}
illustration={LottieAnimations.TripsEmptyState}
Expand Down
5 changes: 4 additions & 1 deletion src/pages/settings/InitialSettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {ValueOf} from 'type-fest';
import AccountSwitcher from '@components/AccountSwitcher';
import AccountSwitcherSkeletonView from '@components/AccountSwitcherSkeletonView';
import ConfirmModal from '@components/ConfirmModal';
import CustomStatusBarAndBackgroundContext from '@components/CustomStatusBarAndBackground/CustomStatusBarAndBackgroundContext';
import DelegateNoAccessModal from '@components/DelegateNoAccessModal';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
Expand Down Expand Up @@ -100,6 +101,7 @@ function InitialSettingsPage({currentUserPersonalDetails}: InitialSettingsPagePr
const emojiCode = currentUserPersonalDetails?.status?.emojiCode ?? '';
const [allConnectionSyncProgresses] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS}`);
const {setInitialURL} = useContext(InitialURLContext);
const {setRootStatusBarEnabled} = useContext(CustomStatusBarAndBackgroundContext);

const [privateSubscription] = useOnyx(ONYXKEYS.NVP_PRIVATE_SUBSCRIPTION);
const subscriptionPlan = useSubscriptionPlan();
Expand Down Expand Up @@ -243,6 +245,7 @@ function InitialSettingsPage({currentUserPersonalDetails}: InitialSettingsPagePr
action: () => {
NativeModules.HybridAppModule.closeReactNativeApp(false, true);
setInitialURL(undefined);
setRootStatusBarEnabled(false);
},
}
: {
Expand Down Expand Up @@ -285,7 +288,7 @@ function InitialSettingsPage({currentUserPersonalDetails}: InitialSettingsPagePr
},
],
};
}, [styles.pt4, signOut, setInitialURL, shouldOpenBookACall, isActingAsDelegate]);
}, [styles.pt4, setInitialURL, setRootStatusBarEnabled, isActingAsDelegate, shouldOpenBookACall, signOut]);

/**
* Retuns JSX.Element with menu items
Expand Down
1 change: 0 additions & 1 deletion src/types/modules/react-native.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ type HybridAppModule = {
closeReactNativeApp: (shouldSignOut: boolean, shouldSetNVP: boolean) => void;
completeOnboarding: (status: boolean) => void;
switchAccount: (newDotCurrentAccountEmail: string, authToken: string, policyID: string, accountID: string) => void;
exitApp: () => void;
};

type RNTextInputResetModule = {
Expand Down

0 comments on commit 5348eb9

Please sign in to comment.