Skip to content
Merged
9 changes: 8 additions & 1 deletion __mocks__/@ua/react-native-airship.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ enum EventType {
PushReceived = 'com.airship.push_received',
}

// eslint-disable-next-line no-restricted-syntax
enum PermissionStatus {
Granted = 'granted',
Denied = 'denied',
NotDetermined = 'not_determined',
}

// eslint-disable-next-line @typescript-eslint/no-namespace
namespace iOS {
/**
Expand Down Expand Up @@ -71,4 +78,4 @@ const Airship: Partial<AirshipRoot> = {

export default Airship;

export {EventType, iOS};
export {EventType, iOS, PermissionStatus};
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,22 @@ class ReactNativeHybridApp(reactContext: ReactApplicationContext) :
override fun sendAuthToken(authToken: String?) {
Log.d(NAME, "`sendAuthToken` should never be called in standalone `New Expensify` app")
}

override fun signInToOldDot(
autoGeneratedLogin: String,
autoGeneratedPassword: String,
authToken: String,
email: String,
policyID: String
) {
Log.d(NAME, "`signInToOldDot` should never be called in standalone `New Expensify` app")
}

override fun signOutFromOldDot() {
Log.d(NAME, "`signOutFromOldDot` should never be called in standalone `New Expensify` app")
}

override fun clearOldDotAfterSignOut() {
Log.d(NAME, "`clearOldDotAfterSignOut` should never be called in standalone `New Expensify` app")
}
}
12 changes: 12 additions & 0 deletions modules/hybrid-app/ios/ReactNativeHybridApp.mm
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,18 @@ - (void)sendAuthToken:(NSString *)authToken {
NSLog(@"[ReactNativeHybridApp] `sendAuthToken` should never be called in standalone `New Expensify` app");
}

- (void)signInToOldDot:(NSString *)autoGeneratedLogin autoGeneratedPassword:(NSString *)autoGeneratedPassword authToken:(NSString *)authToken email:(NSString *)email policyID:(NSString *)policyID {
NSLog(@"[ReactNativeHybridApp] `signInToOldDot` should never be called in standalone `New Expensify` app");
}

- (void)signOutFromOldDot {
NSLog(@"[ReactNativeHybridApp] `signOutFromOldDot` should never be called in standalone `New Expensify` app");
}

- (void)clearOldDotAfterSignOut {
NSLog(@"[ReactNativeHybridApp] `clearOldDotAfterSignOut` should never be called in standalone `New Expensify` app");
}

- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
Expand Down
3 changes: 3 additions & 0 deletions modules/hybrid-app/src/NativeReactNativeHybridApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ export interface Spec extends TurboModule {
completeOnboarding: (status: boolean) => void;
switchAccount: (newDotCurrentAccountEmail: string, authToken: string, policyID: string, accountID: string) => void;
sendAuthToken: (authToken: string) => void;
signInToOldDot: (autoGeneratedLogin: string, autoGeneratedPassword: string, authToken: string, email: string, policyID: string) => void;
signOutFromOldDot: () => void;
clearOldDotAfterSignOut: () => void;
}

export default TurboModuleRegistry.getEnforcing<Spec>('ReactNativeHybridApp');
9 changes: 9 additions & 0 deletions modules/hybrid-app/src/index.native.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ const HybridAppModule: HybridAppModuleType = {
sendAuthToken({authToken}) {
ReactNativeHybridApp.sendAuthToken(authToken);
},
signInToOldDot({autoGeneratedLogin, autoGeneratedPassword, authToken, email, policyID}) {
ReactNativeHybridApp.signInToOldDot(autoGeneratedLogin, autoGeneratedPassword, authToken, email, policyID);
},
signOutFromOldDot() {
ReactNativeHybridApp.signOutFromOldDot();
},
clearOldDotAfterSignOut() {
ReactNativeHybridApp.clearOldDotAfterSignOut();
},
};

export default HybridAppModule;
12 changes: 12 additions & 0 deletions modules/hybrid-app/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@ const HybridAppModule: HybridAppModuleType = {
// eslint-disable-next-line no-console
console.warn('HybridAppModule: `sendAuthToken` should never be called on web');
},
signInToOldDot() {
// eslint-disable-next-line no-console
console.warn('HybridAppModule: `signInToOldDot` should never be called on web');
},
signOutFromOldDot() {
// eslint-disable-next-line no-console
console.warn('HybridAppModule: `signOutFromOldDot` should never be called on web');
},
clearOldDotAfterSignOut() {
// eslint-disable-next-line no-console
console.warn('HybridAppModule: `clearOldDotAfterSignOut` should never be called on web');
},
};

export default HybridAppModule;
3 changes: 3 additions & 0 deletions modules/hybrid-app/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ type HybridAppModuleType = {
completeOnboarding: (args: {status: boolean}) => void;
switchAccount: (args: {newDotCurrentAccountEmail: string; authToken: string; policyID: string; accountID: string}) => void;
sendAuthToken: (args: {authToken: string}) => void;
signInToOldDot: (args: {autoGeneratedLogin: string; autoGeneratedPassword: string; authToken: string; email: string; policyID: string}) => void;
signOutFromOldDot: () => void;
clearOldDotAfterSignOut: () => void;
};

export default HybridAppModuleType;
87 changes: 47 additions & 40 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {CurrentReportIDContextProvider} from './hooks/useCurrentReportID';
import useDefaultDragAndDrop from './hooks/useDefaultDragAndDrop';
import HybridAppHandler from './HybridAppHandler';
import OnyxUpdateManager from './libs/actions/OnyxUpdateManager';
import './libs/HybridApp';
import {AttachmentModalContextProvider} from './pages/media/AttachmentModalScreen/AttachmentModalContext';
import type {Route} from './ROUTES';
import './setup/backgroundTask';
Expand Down Expand Up @@ -81,47 +82,53 @@ function App({url, hybridAppSettings}: AppProps) {
<InitialURLContextProvider url={url}>
<HybridAppHandler hybridAppSettings={hybridAppSettings} />
<GestureHandlerRootView style={fill}>
<ComposeProviders
components={[
OnyxProvider,
ThemeProvider,
ThemeStylesProvider,
ThemeIllustrationsProvider,
SafeAreaProvider,
PortalProvider,
SafeArea,
LocaleContextProvider,
HTMLEngineProvider,
PopoverContextProvider,
CurrentReportIDContextProvider,
ScrollOffsetContextProvider,
AttachmentModalContextProvider,
PickerStateProvider,
EnvironmentProvider,
CustomStatusBarAndBackgroundContextProvider,
ActiveElementRoleProvider,
ActionSheetAwareScrollViewProvider,
PlaybackContextProvider,
FullScreenContextProvider,
VolumeContextProvider,
VideoPopoverMenuContextProvider,
KeyboardProvider,
KeyboardStateProvider,
SearchRouterContextProvider,
ProductTrainingContextProvider,
InputBlurContextProvider,
FullScreenBlockingViewContextProvider,
FullScreenLoaderContextProvider,
]}
<SafeAreaProvider
initialMetrics={{
insets: {top: 0, right: 0, bottom: 0, left: 0},
frame: {x: 0, y: 0, width: 0, height: 0},
}}
>
<CustomStatusBarAndBackground />
<ErrorBoundary errorMessage="NewExpensify crash caught by error boundary">
<ColorSchemeWrapper>
<Expensify />
</ColorSchemeWrapper>
</ErrorBoundary>
<NavigationBar />
</ComposeProviders>
<ComposeProviders
components={[
OnyxProvider,
ThemeProvider,
ThemeStylesProvider,
ThemeIllustrationsProvider,
PortalProvider,
SafeArea,
LocaleContextProvider,
HTMLEngineProvider,
PopoverContextProvider,
CurrentReportIDContextProvider,
ScrollOffsetContextProvider,
AttachmentModalContextProvider,
PickerStateProvider,
EnvironmentProvider,
CustomStatusBarAndBackgroundContextProvider,
ActiveElementRoleProvider,
ActionSheetAwareScrollViewProvider,
PlaybackContextProvider,
FullScreenContextProvider,
VolumeContextProvider,
VideoPopoverMenuContextProvider,
KeyboardProvider,
KeyboardStateProvider,
SearchRouterContextProvider,
ProductTrainingContextProvider,
InputBlurContextProvider,
FullScreenBlockingViewContextProvider,
FullScreenLoaderContextProvider,
]}
>
<CustomStatusBarAndBackground />
<ErrorBoundary errorMessage="NewExpensify crash caught by error boundary">
<ColorSchemeWrapper>
<Expensify />
</ColorSchemeWrapper>
</ErrorBoundary>
<NavigationBar />
</ComposeProviders>
</SafeAreaProvider>
</GestureHandlerRootView>
</InitialURLContextProvider>
</SplashScreenStateContextProvider>
Expand Down
5 changes: 5 additions & 0 deletions src/CONFIG.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,12 @@ export default {
GOOGLE_SIGN_IN: {
// cspell:disable-next-line
WEB_CLIENT_ID: '921154746561-gpsoaqgqfuqrfsjdf8l7vohfkfj7b9up.apps.googleusercontent.com',
// cspell:disable-next-line
IOS_CLIENT_ID: '921154746561-s3uqn2oe4m85tufi6mqflbfbuajrm2i3.apps.googleusercontent.com',
// cspell:disable-next-line
HYBRID_APP_WEB_CLIENT_ID: '1008697809946-5e095eqem3o6ugtpc2rjf7v880tcp28p.apps.googleusercontent.com',
// cspell:disable-next-line
HYBRID_APP_IOS_CLIENT_ID: '1008697809946-sh04nqq0hea396s1qdqqbj6ia649odb2.apps.googleusercontent.com',
},
GCP_GEOLOCATION_API_KEY: googleGeolocationAPIKey,
FIREBASE_WEB_CONFIG: {
Expand Down
6 changes: 6 additions & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6722,6 +6722,12 @@ const CONST = {
HIDDEN: `hidden`,
},

HYBRID_APP_SIGN_IN_STATE: {
NOT_STARTED: 'notStarted',
STARTED: 'started',
FINISHED: 'finished',
},

CSV_IMPORT_COLUMNS: {
EMAIL: 'email',
NAME: 'name',
Expand Down
11 changes: 7 additions & 4 deletions src/Expensify.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ function Expensify() {
const [isSidebarLoaded] = useOnyx(ONYXKEYS.IS_SIDEBAR_LOADED, {canBeMissing: true});
const [screenShareRequest] = useOnyx(ONYXKEYS.SCREEN_SHARE_REQUEST, {canBeMissing: true});
const [lastVisitedPath] = useOnyx(ONYXKEYS.LAST_VISITED_PATH, {canBeMissing: true});
const [hybridApp] = useOnyx(ONYXKEYS.HYBRID_APP, {canBeMissing: true});

useDebugShortcut();

Expand All @@ -114,10 +115,12 @@ function Expensify() {
const isAuthenticated = useIsAuthenticated();
const autoAuthState = useMemo(() => session?.autoAuthState ?? '', [session]);

const shouldInit = isNavigationReady && hasAttemptedToOpenPublicRoom && !!preferredLocale;
const isSplashVisible = splashScreenState === CONST.BOOT_SPLASH_STATE.VISIBLE;
const isHybridAppReady = splashScreenState === CONST.BOOT_SPLASH_STATE.READY_TO_BE_HIDDEN && isAuthenticated;
const shouldHideSplash = shouldInit && (CONFIG.IS_HYBRID_APP ? isHybridAppReady : isSplashVisible);
const shouldInit = isNavigationReady && hasAttemptedToOpenPublicRoom && !!preferredLocale && (CONFIG.IS_HYBRID_APP ? !hybridApp?.loggedOutFromOldDot : true);
const shouldHideSplash =
shouldInit &&
(CONFIG.IS_HYBRID_APP
? splashScreenState === CONST.BOOT_SPLASH_STATE.READY_TO_BE_HIDDEN && (isAuthenticated || !!hybridApp?.useNewDotSignInPage)
: splashScreenState === CONST.BOOT_SPLASH_STATE.VISIBLE);

const initializeClient = () => {
if (!Visibility.isVisible()) {
Expand Down
22 changes: 17 additions & 5 deletions src/HybridAppHandler.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@
import {useContext, useState} from 'react';
import {useOnyx} from 'react-native-onyx';
import type {AppProps} from './App';
import CONFIG from './CONFIG';
import CONST from './CONST';
import {signInAfterTransitionFromOldDot} from './libs/actions/Session';
import {parseHybridAppSettings} from './libs/actions/HybridApp';
import {setupNewDotAfterTransitionFromOldDot} from './libs/actions/Session';
import ONYXKEYS from './ONYXKEYS';
import SplashScreenStateContext from './SplashScreenStateContext';
import isLoadingOnyxValue from './types/utils/isLoadingOnyxValue';

function HybridAppHandler({hybridAppSettings}: AppProps) {
const [signInHandled, setSignInHandled] = useState(false);
const {setSplashScreenState} = useContext(SplashScreenStateContext);
const {splashScreenState, setSplashScreenState} = useContext(SplashScreenStateContext);
const [tryNewDot, tryNewDotMetadata] = useOnyx(ONYXKEYS.NVP_TRY_NEW_DOT, {canBeMissing: true});

if (!CONFIG.IS_HYBRID_APP || !hybridAppSettings || signInHandled) {
const isLoading = isLoadingOnyxValue(tryNewDotMetadata);

if (!CONFIG.IS_HYBRID_APP || !hybridAppSettings || signInHandled || isLoading) {
return null;
}

signInAfterTransitionFromOldDot(hybridAppSettings).then(() => {
setSplashScreenState(CONST.BOOT_SPLASH_STATE.READY_TO_BE_HIDDEN);
const parsedHybridAppSettings = parseHybridAppSettings(hybridAppSettings);
setupNewDotAfterTransitionFromOldDot(parsedHybridAppSettings, tryNewDot).then(() => {
if (parsedHybridAppSettings.hybridApp?.loggedOutFromOldDot) {
setSplashScreenState(CONST.BOOT_SPLASH_STATE.HIDDEN);
} else if (splashScreenState === CONST.BOOT_SPLASH_STATE.VISIBLE) {
setSplashScreenState(CONST.BOOT_SPLASH_STATE.READY_TO_BE_HIDDEN);
}
setSignInHandled(true);
});

Expand Down
8 changes: 4 additions & 4 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -476,9 +476,6 @@ const ONYXKEYS = {
/** Stores recently used currencies */
RECENTLY_USED_CURRENCIES: 'nvp_recentlyUsedCurrencies',

/** States whether we transitioned from OldDot to show only certain group of screens. It should be undefined on pure NewDot. */
IS_SINGLE_NEW_DOT_ENTRY: 'isSingleNewDotEntry',

/** Company cards custom names */
NVP_EXPENSIFY_COMPANY_CARDS_CUSTOM_NAMES: 'nvp_expensify_ccCustomNames',

Expand Down Expand Up @@ -535,6 +532,9 @@ const ONYXKEYS = {
/** Set this gets redirected from global reimbursements flow */
IS_COMING_FROM_GLOBAL_REIMBURSEMENTS_FLOW: 'isComingFromGlobalReimbursementsFlow',

/** Stores HybridApp specific state required to interoperate with OldDot */
HYBRID_APP: 'hybridApp',

/** Stores information for OpenUnreportedExpensesPage API call pagination */
HAS_MORE_UNREPORTED_TRANSACTIONS_RESULTS: 'hasMoreUnreportedTransactionsResults',

Expand Down Expand Up @@ -1172,7 +1172,6 @@ type OnyxValuesMapping = {
[ONYXKEYS.APPROVAL_WORKFLOW]: OnyxTypes.ApprovalWorkflowOnyx;
[ONYXKEYS.IMPORTED_SPREADSHEET]: OnyxTypes.ImportedSpreadsheet;
[ONYXKEYS.LAST_ROUTE]: string;
[ONYXKEYS.IS_SINGLE_NEW_DOT_ENTRY]: boolean | undefined;
[ONYXKEYS.IS_USING_IMPORTED_STATE]: boolean;
[ONYXKEYS.NVP_EXPENSIFY_COMPANY_CARDS_CUSTOM_NAMES]: Record<string, string>;
[ONYXKEYS.CONCIERGE_REPORT_ID]: string;
Expand All @@ -1191,6 +1190,7 @@ type OnyxValuesMapping = {
[ONYXKEYS.SCHEDULE_CALL_DRAFT]: OnyxTypes.ScheduleCallDraft;
[ONYXKEYS.IS_FORCED_TO_CHANGE_CURRENCY]: boolean | undefined;
[ONYXKEYS.IS_COMING_FROM_GLOBAL_REIMBURSEMENTS_FLOW]: boolean | undefined;
[ONYXKEYS.HYBRID_APP]: OnyxTypes.HybridApp;
[ONYXKEYS.HAS_MORE_UNREPORTED_TRANSACTIONS_RESULTS]: boolean | undefined;
[ONYXKEYS.IS_LOADING_UNREPORTED_TRANSACTIONS]: boolean | undefined;
[ONYXKEYS.NVP_LAST_ECASH_IOS_LOGIN]: string;
Expand Down
Loading
Loading