Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ND changes for Hybrid App switch to fragments on Android #55010

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
fc8cb33
Prevent app from crash when putting app in background from ND
jnowakow Jan 9, 2025
7b7e501
Merge branch 'main' into hybrid-app-android-fragment
war-in Jan 10, 2025
0748146
Merge branch 'main' into hybrid-app-android-fragment
jnowakow Jan 10, 2025
cef80e4
remove pendingIntent functionality & clean exitApp method
war-in Jan 10, 2025
e4cea5c
Merge branch 'hybrid-app-android-fragment' of github.com:software-man…
jnowakow Jan 13, 2025
7f16473
Do not duplicate isNavigationReady call
jnowakow Jan 14, 2025
c202a77
Merge branch 'main' into hybrid-app-android-fragment
war-in Jan 15, 2025
4ea095f
Merge branch 'main' into hybrid-app-android-fragment
war-in Jan 16, 2025
bb171ee
Merge branch 'main' into hybrid-app-android-fragment
war-in Jan 20, 2025
e92ae29
fix crashes (util they're fixed on main)
war-in Jan 20, 2025
f9ecc5a
Merge branch 'main' into hybrid-app-android-fragment
war-in Jan 21, 2025
c739b5a
fix status bar color when returning to OD
war-in Jan 21, 2025
e361946
Merge branch 'main' into hybrid-app-android-fragment
war-in Jan 22, 2025
4a19dcd
don't show bootsplash when app in background
war-in Jan 22, 2025
ced4cb3
rename new prop
war-in Jan 22, 2025
118761d
Merge branch 'main' into hybrid-app-android-fragment
war-in Jan 23, 2025
640a650
revert bootsplash changes
war-in Jan 23, 2025
efbce53
Merge branch 'main' into hybrid-app-android-fragment
war-in Jan 27, 2025
447038a
fix status bar on travel page
war-in Jan 27, 2025
f2525bf
fix lint
war-in Jan 27, 2025
44f8275
Merge branch 'main' into hybrid-app-android-fragment
war-in Jan 28, 2025
abdb000
update bookATrip function after merge
war-in Jan 28, 2025
fdabcc9
Merge branch 'main' into hybrid-app-android-fragment
war-in Jan 28, 2025
0df0295
Merge branch 'refs/heads/main' into hybrid-app-android-fragment
war-in Jan 29, 2025
233446a
Merge branch 'refs/heads/main' into hybrid-app-android-fragment
war-in Jan 30, 2025
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
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();
}

Comment on lines -25 to -30
Copy link
Contributor

@staszekscp staszekscp Jan 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why has this code been deleted?

EDIT: I think I figured that out, but I'd like to make sure anyway 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When ND was on home screen and back button was clicked app crashed instead of going to background

// 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
25 changes: 14 additions & 11 deletions 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 All @@ -12,11 +13,11 @@ import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';
import {completeOnboarding} from '@libs/actions/Report';
import Navigation from '@libs/Navigation/Navigation';
import * as PolicyUtils from '@libs/PolicyUtils';
import * as Policy from '@userActions/Policy/Policy';
import * as Report from '@userActions/Report';
import * as Welcome from '@userActions/Welcome';
import {isPaidGroupPolicy} from '@libs/PolicyUtils';
import {createWorkspace, generatePolicyID} from '@userActions/Policy/Policy';
import {setOnboardingAdminsChatReportID, setOnboardingCompanySize, setOnboardingPolicyID} from '@userActions/Welcome';
import CONST from '@src/CONST';
import type {OnboardingCompanySize} from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
Expand All @@ -34,8 +35,9 @@ 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(PolicyUtils.isPaidGroupPolicy);
const paidGroupPolicy = Object.values(allPolicies ?? {}).find(isPaidGroupPolicy);

const {onboardingIsMediumOrLargerScreenWidth} = useResponsiveLayout();
const [selectedCompanySize, setSelectedCompanySize] = useState<OnboardingCompanySize | null | undefined>(onboardingCompanySize);
Expand Down Expand Up @@ -69,19 +71,19 @@ function BaseOnboardingEmployees({shouldUseNativeStyles, route}: BaseOnboardingE
setError(translate('onboarding.errorSelection'));
return;
}
Welcome.setOnboardingCompanySize(selectedCompanySize);
setOnboardingCompanySize(selectedCompanySize);

const shouldCreateWorkspace = !onboardingPolicyID && !paidGroupPolicy;

// We need `adminsChatReportID` for `Report.completeOnboarding`, but at the same time, we don't want to call `Policy.createWorkspace` more than once.
// If we have already created a workspace, we want to reuse the `onboardingAdminsChatReportID` and `onboardingPolicyID`.
const {adminsChatReportID, policyID} = shouldCreateWorkspace
? Policy.createWorkspace(undefined, true, '', Policy.generatePolicyID(), CONST.ONBOARDING_CHOICES.MANAGE_TEAM)
? createWorkspace(undefined, true, '', generatePolicyID(), CONST.ONBOARDING_CHOICES.MANAGE_TEAM)
: {adminsChatReportID: onboardingAdminsChatReportID, policyID: onboardingPolicyID};

if (shouldCreateWorkspace) {
Welcome.setOnboardingAdminsChatReportID(adminsChatReportID);
Welcome.setOnboardingPolicyID(policyID);
setOnboardingAdminsChatReportID(adminsChatReportID);
setOnboardingPolicyID(policyID);
}

// For MICRO companies (1-10 employees), we want to remain on NewDot.
Expand All @@ -93,7 +95,7 @@ function BaseOnboardingEmployees({shouldUseNativeStyles, route}: BaseOnboardingE
// For other company sizes we want to complete onboarding here.
// At this point `onboardingPurposeSelected` should always exist as we set it in `BaseOnboardingPurpose`.
if (onboardingPurposeSelected) {
Report.completeOnboarding(
completeOnboarding(
onboardingPurposeSelected,
CONST.ONBOARDING_MESSAGES[onboardingPurposeSelected],
undefined,
Expand All @@ -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