diff --git a/android/app/build.gradle b/android/app/build.gradle index 94b6b511..60b68715 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -258,7 +258,7 @@ dependencies { implementation 'com.facebook.soloader:soloader:0.10.1+' implementation("io.mosip:pixelpass:0.2.0") implementation("io.mosip:secure-keystore:0.2.0") - implementation("io.mosip:tuvali:0.5.0") + implementation("io.mosip:tuvali:0.5.1-SNAPSHOT") implementation("io.mosip:inji-vci-client:0.1.0") def isGifEnabled = (findProperty('expo.gif.enabled') ?: "") == "true"; diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index a5a8d7ff..fc062777 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,46 +1,86 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/java/io/mosip/residentapp/InjiPackage.java b/android/app/src/main/java/io/mosip/residentapp/InjiPackage.java index 24addbcc..d0ec9bad 100644 --- a/android/app/src/main/java/io/mosip/residentapp/InjiPackage.java +++ b/android/app/src/main/java/io/mosip/residentapp/InjiPackage.java @@ -25,6 +25,7 @@ public class InjiPackage implements ReactPackage { modules.add(new RNVersionModule()); modules.add(new RNWalletModule(new RNEventEmitter(reactApplicationContext), new Wallet(reactApplicationContext), reactApplicationContext)); modules.add(new RNVerifierModule(new RNEventEmitter(reactApplicationContext), new Verifier(reactApplicationContext), reactApplicationContext)); + modules.add(new RNQrLoginIntentModule(reactApplicationContext)); return modules; } diff --git a/android/app/src/main/java/io/mosip/residentapp/IntentData.java b/android/app/src/main/java/io/mosip/residentapp/IntentData.java new file mode 100644 index 00000000..b0684c50 --- /dev/null +++ b/android/app/src/main/java/io/mosip/residentapp/IntentData.java @@ -0,0 +1,19 @@ +package io.mosip.residentapp; + +public class IntentData { + private String qrData = ""; + private static IntentData intentData; + public static IntentData getInstance() { + if(intentData == null) + intentData = new IntentData(); + return intentData; + } + public String getQrData() { + return qrData; + } + + public void setQrData(String qrData) { + this.qrData = qrData; + } + +} \ No newline at end of file diff --git a/android/app/src/main/java/io/mosip/residentapp/MainActivity.java b/android/app/src/main/java/io/mosip/residentapp/MainActivity.java index edd4eb8a..b69e716d 100644 --- a/android/app/src/main/java/io/mosip/residentapp/MainActivity.java +++ b/android/app/src/main/java/io/mosip/residentapp/MainActivity.java @@ -3,20 +3,19 @@ import expo.modules.ReactActivityDelegateWrapper; import android.Manifest; import android.content.Context; +import android.content.Intent; import android.content.pm.PackageManager; +import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.util.Log; -import androidx.annotation.CallSuper; -import androidx.annotation.NonNull; + import androidx.annotation.RequiresApi; import com.facebook.react.ReactActivity; import com.facebook.react.ReactActivityDelegate; -import com.facebook.react.ReactRootView; -import com.facebook.react.ReactActivityDelegate; import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint; import com.facebook.react.defaults.DefaultReactActivityDelegate; -import expo.modules.ReactActivityDelegateWrapper; + +import java.util.Objects; /** * IMPORTANT NOTE: The Android permission flow here works @@ -43,6 +42,22 @@ public class MainActivity extends ReactActivity { // This is required for expo-splash-screen. setTheme(R.style.AppTheme); super.onCreate(null); + Intent intent = getIntent(); + readAndSetQRLoginIntentData(intent); + } + + @Override + public void onNewIntent(Intent intent) { + super.onNewIntent(intent); + readAndSetQRLoginIntentData(intent); + } + + private void readAndSetQRLoginIntentData(Intent intent){ + Uri data = intent.getData(); + if(data != null && Objects.equals(data.getScheme(), "io.mosip.residentapp.inji")){ + IntentData intentData = IntentData.getInstance(); + intentData.setQrData(String.valueOf(data)); + } } /** diff --git a/android/app/src/main/java/io/mosip/residentapp/RNQrLoginIntentModule.java b/android/app/src/main/java/io/mosip/residentapp/RNQrLoginIntentModule.java new file mode 100644 index 00000000..f471f55f --- /dev/null +++ b/android/app/src/main/java/io/mosip/residentapp/RNQrLoginIntentModule.java @@ -0,0 +1,38 @@ +package io.mosip.residentapp; + +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; + +public class RNQrLoginIntentModule extends ReactContextBaseJavaModule { + + + @Override + public String getName() { + return "QrLoginIntent"; + } + + RNQrLoginIntentModule(ReactApplicationContext context) { + super(context); + } + + @ReactMethod + public void isQrLoginByDeepLink(Promise promise) { + try { + + IntentData intentData = IntentData.getInstance(); + promise.resolve(intentData.getQrData()); + + } catch (Exception e) { + promise.reject("E_UNKNOWN", e.getMessage()); + } + } + + @ReactMethod + public void resetQRLoginDeepLinkData(){ + IntentData intentData = IntentData.getInstance(); + intentData.setQrData(""); + } + +} \ No newline at end of file diff --git a/machines/app.ts b/machines/app.ts index 7eea184f..53dfece8 100644 --- a/machines/app.ts +++ b/machines/app.ts @@ -32,6 +32,9 @@ import { createVcMetaMachine, vcMetaMachine, } from './VerifiableCredential/VCMetaMachine/VCMetaMachine'; +import {NativeModules} from 'react-native'; + +const QrLoginIntent = NativeModules.QrLoginIntent; const model = createModel( { @@ -40,6 +43,7 @@ const model = createModel( isReadError: false, isDecryptError: false, isKeyInvalidateError: false, + linkCode: '', }, { events: { @@ -56,6 +60,7 @@ const model = createModel( APP_INFO_RECEIVED: (info: AppInfo) => ({info}), STORE_RESPONSE: (response: unknown) => ({response}), RESET_KEY_INVALIDATE_ERROR_DISMISS: () => ({}), + RESET_LINKCODE: () => ({}), }, }, ); @@ -78,6 +83,9 @@ export const appMachine = model.createMachine( DECRYPT_ERROR: { actions: ['setIsDecryptError'], }, + RESET_LINKCODE: { + actions: ['resetLinkCode'], + }, DECRYPT_ERROR_DISMISS: { actions: ['unsetIsDecryptError'], }, @@ -166,6 +174,17 @@ export const appMachine = model.createMachine( checking: {}, active: { entry: ['forwardToServices'], + invoke: [ + { + src: 'isQrLoginByDeepLink', + onDone: { + actions: ['setLinkCode'], + }, + }, + { + src: 'resetQRLoginDeepLinkData', + }, + ], }, inactive: { entry: ['forwardToServices'], @@ -198,7 +217,17 @@ export const appMachine = model.createMachine( }, { actions: { - forwardToServices: pure((context, event) => + setLinkCode: assign({ + linkCode: (_, event) => { + if (event.data != '') + return new URL(event.data).searchParams.get('linkCode')!!; + return ''; + }, + }), + resetLinkCode: assign({ + linkCode: '', + }), + forwardToSerices: pure((context, event) => Object.values(context.serviceRefs).map(serviceRef => send({...event, type: `APP_${event.type}`}, {to: serviceRef}), ), @@ -345,6 +374,15 @@ export const appMachine = model.createMachine( }, services: { + isQrLoginByDeepLink: () => async () => { + const data = await QrLoginIntent.isQrLoginByDeepLink(); + //console.log('DeepLink: ', data); + return data; + }, + resetQRLoginDeepLinkData: () => async () => { + return await QrLoginIntent.resetQRLoginDeepLinkData(); + }, + getAppInfo: () => async callback => { const appInfo = { deviceId: getDeviceId(), @@ -447,3 +485,7 @@ export function selectIsDecryptError(state: State) { export function selectIsKeyInvalidateError(state: State) { return state.context.isKeyInvalidateError; } + +export function selectIsLinkCode(state: State) { + return state.context.linkCode; +} diff --git a/machines/app.typegen.ts b/machines/app.typegen.ts index 933e4337..66ac40f0 100644 --- a/machines/app.typegen.ts +++ b/machines/app.typegen.ts @@ -1,55 +1,89 @@ +// This file was automatically generated. Edits will be overwritten - // This file was automatically generated. Edits will be overwritten - - export interface Typegen0 { - '@@xstate/typegen': true; - internalEvents: { - "xstate.init": { type: "xstate.init" }; - }; - invokeSrcNameMap: { - "checkFocusState": "done.invoke.app.ready.focus:invocation[0]"; -"checkNetworkState": "done.invoke.app.ready.network:invocation[0]"; -"getAppInfo": "done.invoke.app.init.info:invocation[0]"; - }; - missingImplementations: { - actions: never; - delays: never; - guards: never; - services: never; - }; - eventsCausingActions: { - "forwardToServices": "ACTIVE" | "INACTIVE" | "OFFLINE" | "ONLINE"; -"loadCredentialRegistryHostFromStorage": "READY"; -"loadCredentialRegistryInConstants": "STORE_RESPONSE"; -"loadEsignetHostFromConstants": "STORE_RESPONSE"; -"loadEsignetHostFromStorage": "READY"; -"logServiceEvents": "READY"; -"logStoreEvents": "KEY_INVALIDATE_ERROR" | "RESET_KEY_INVALIDATE_ERROR_DISMISS" | "xstate.init"; -"requestDeviceInfo": "REQUEST_DEVICE_INFO"; -"resetKeyInvalidateError": "READY" | "RESET_KEY_INVALIDATE_ERROR_DISMISS"; -"setAppInfo": "APP_INFO_RECEIVED"; -"setIsDecryptError": "DECRYPT_ERROR"; -"setIsReadError": "ERROR"; -"spawnServiceActors": "READY"; -"spawnStoreActor": "KEY_INVALIDATE_ERROR" | "RESET_KEY_INVALIDATE_ERROR_DISMISS" | "xstate.init"; -"unsetIsDecryptError": "DECRYPT_ERROR_DISMISS" | "READY"; -"unsetIsReadError": "READY"; -"updateKeyInvalidateError": "ERROR" | "KEY_INVALIDATE_ERROR"; - }; - eventsCausingDelays: { - - }; - eventsCausingGuards: { - - }; - eventsCausingServices: { - "checkFocusState": "APP_INFO_RECEIVED"; -"checkNetworkState": "APP_INFO_RECEIVED"; -"getAppInfo": "STORE_RESPONSE"; - }; - matchesStates: "init" | "init.credentialRegistry" | "init.info" | "init.services" | "init.store" | "ready" | "ready.focus" | "ready.focus.active" | "ready.focus.checking" | "ready.focus.inactive" | "ready.network" | "ready.network.checking" | "ready.network.offline" | "ready.network.online" | "waiting" | { "init"?: "credentialRegistry" | "info" | "services" | "store"; -"ready"?: "focus" | "network" | { "focus"?: "active" | "checking" | "inactive"; -"network"?: "checking" | "offline" | "online"; }; }; - tags: never; - } - \ No newline at end of file +export interface Typegen0 { + '@@xstate/typegen': true; + internalEvents: { + 'done.invoke.app.ready.focus.active:invocation[0]': { + type: 'done.invoke.app.ready.focus.active:invocation[0]'; + data: unknown; + __tip: 'See the XState TS docs to learn how to strongly type this.'; + }; + 'xstate.init': {type: 'xstate.init'}; + }; + invokeSrcNameMap: { + checkFocusState: 'done.invoke.app.ready.focus:invocation[0]'; + checkNetworkState: 'done.invoke.app.ready.network:invocation[0]'; + getAppInfo: 'done.invoke.app.init.info:invocation[0]'; + isQrLoginByDeepLink: 'done.invoke.app.ready.focus.active:invocation[0]'; + resetQRLoginDeepLinkData: 'done.invoke.app.ready.focus.active:invocation[1]'; + }; + missingImplementations: { + actions: 'forwardToServices'; + delays: never; + guards: never; + services: never; + }; + eventsCausingActions: { + forwardToServices: 'ACTIVE' | 'INACTIVE' | 'OFFLINE' | 'ONLINE'; + loadCredentialRegistryHostFromStorage: 'READY'; + loadCredentialRegistryInConstants: 'STORE_RESPONSE'; + loadEsignetHostFromConstants: 'STORE_RESPONSE'; + loadEsignetHostFromStorage: 'READY'; + logServiceEvents: 'READY'; + logStoreEvents: + | 'KEY_INVALIDATE_ERROR' + | 'RESET_KEY_INVALIDATE_ERROR_DISMISS' + | 'xstate.init'; + requestDeviceInfo: 'REQUEST_DEVICE_INFO'; + resetKeyInvalidateError: 'READY' | 'RESET_KEY_INVALIDATE_ERROR_DISMISS'; + resetLinkCode: 'RESET_LINKCODE'; + setAppInfo: 'APP_INFO_RECEIVED'; + setIsDecryptError: 'DECRYPT_ERROR'; + setIsReadError: 'ERROR'; + setLinkCode: 'done.invoke.app.ready.focus.active:invocation[0]'; + spawnServiceActors: 'READY'; + spawnStoreActor: + | 'KEY_INVALIDATE_ERROR' + | 'RESET_KEY_INVALIDATE_ERROR_DISMISS' + | 'xstate.init'; + unsetIsDecryptError: 'DECRYPT_ERROR_DISMISS' | 'READY'; + unsetIsReadError: 'READY'; + updateKeyInvalidateError: 'ERROR' | 'KEY_INVALIDATE_ERROR'; + }; + eventsCausingDelays: {}; + eventsCausingGuards: {}; + eventsCausingServices: { + checkFocusState: 'APP_INFO_RECEIVED'; + checkNetworkState: 'APP_INFO_RECEIVED'; + getAppInfo: 'STORE_RESPONSE'; + isQrLoginByDeepLink: 'ACTIVE'; + resetQRLoginDeepLinkData: 'ACTIVE'; + }; + matchesStates: + | 'init' + | 'init.credentialRegistry' + | 'init.info' + | 'init.services' + | 'init.store' + | 'ready' + | 'ready.focus' + | 'ready.focus.active' + | 'ready.focus.checking' + | 'ready.focus.inactive' + | 'ready.network' + | 'ready.network.checking' + | 'ready.network.offline' + | 'ready.network.online' + | 'waiting' + | { + init?: 'credentialRegistry' | 'info' | 'services' | 'store'; + ready?: + | 'focus' + | 'network' + | { + focus?: 'active' | 'checking' | 'inactive'; + network?: 'checking' | 'offline' | 'online'; + }; + }; + tags: never; +} diff --git a/machines/bleShare/scan/scanActions.ts b/machines/bleShare/scan/scanActions.ts index b6e83c5b..dce95987 100644 --- a/machines/bleShare/scan/scanActions.ts +++ b/machines/bleShare/scan/scanActions.ts @@ -46,12 +46,14 @@ export const ScanActions = (model: any, QR_LOGIN_REF_ID: any) => { }, }), + resetLinkCode: model.assign({ + linkcode: '', + }), updateShowFaceAuthConsent: model.assign({ showFaceAuthConsent: (_, event) => { return event.response || event.response === null; }, }), - setShowFaceAuthConsent: model.assign({ showFaceAuthConsent: (_, event) => { return !event.isDoNotAskAgainChecked; @@ -230,6 +232,10 @@ export const ScanActions = (model: any, QR_LOGIN_REF_ID: any) => { linkCode: (_, event) => new URL(event.params).searchParams.get('linkCode'), }), + + setLinkCodeFromDeepLink: assign({ + linkCode: (_, event) => event.linkCode, + }), setQuickShareData: assign({ quickShareData: (_, event) => JSON.parse( diff --git a/machines/bleShare/scan/scanMachine.ts b/machines/bleShare/scan/scanMachine.ts index e8d391d6..7fac41d2 100644 --- a/machines/bleShare/scan/scanMachine.ts +++ b/machines/bleShare/scan/scanMachine.ts @@ -82,6 +82,20 @@ export const scanMachine = cond: 'isMinimumStorageRequiredForAuditEntryReached', target: 'restrictSharingVc', }, + { + target: 'qrLoginViaDeepLink', + }, + ], + }, + }, + qrLoginViaDeepLink: { + on: { + QRLOGIN_VIA_DEEP_LINK: [ + { + actions: ['setChildRef', 'setLinkCodeFromDeepLink'], + cond: (_, event) => event.linkCode != '', + target: '#scan.showQrLogin', + }, { target: 'startPermissionCheck', }, @@ -362,7 +376,10 @@ export const scanMachine = }, }, on: { - DISMISS: '#scan.checkFaceAuthConsent', + DISMISS: { + target: '#scan.checkFaceAuthConsent', + actions: ['resetLinkCode'], + }, }, initial: 'idle', states: { @@ -385,6 +402,7 @@ export const scanMachine = getStartEventData(TelemetryConstants.FlowType.qrLogin), ), ], + exit: ['resetLinkCode'], }, connecting: { invoke: { diff --git a/machines/bleShare/scan/scanMachine.typegen.ts b/machines/bleShare/scan/scanMachine.typegen.ts index eac4e373..1b39b24b 100644 --- a/machines/bleShare/scan/scanMachine.typegen.ts +++ b/machines/bleShare/scan/scanMachine.typegen.ts @@ -18,6 +18,7 @@ export interface Typegen0 { type: 'xstate.after(DESTROY_TIMEOUT)#scan.clearingConnection'; }; 'xstate.init': {type: 'xstate.init'}; + 'xstate.stop': {type: 'xstate.stop'}; }; invokeSrcNameMap: { checkBluetoothPermission: 'done.invoke.scan.checkBluetoothPermission.checking:invocation[0]'; @@ -55,6 +56,7 @@ export interface Typegen0 { | 'removeLoggers' | 'resetFaceCaptureBannerStatus' | 'resetFlowType' + | 'resetLinkCode' | 'resetSelectedVc' | 'resetShowQuickShareSuccessBanner' | 'sendBLEConnectionErrorEvent' @@ -67,6 +69,7 @@ export interface Typegen0 { | 'setChildRef' | 'setFlowType' | 'setLinkCode' + | 'setLinkCodeFromDeepLink' | 'setQuickShareData' | 'setReadyForBluetoothStateCheck' | 'setReceiverInfo' @@ -132,7 +135,10 @@ export interface Typegen0 { | 'SCREEN_BLUR' | 'STORE_RESPONSE' | 'xstate.init'; - resetFaceCaptureBannerStatus: 'ACCEPT_REQUEST' | 'CLOSE_BANNER'; + resetFaceCaptureBannerStatus: + | 'ACCEPT_REQUEST' + | 'CLOSE_BANNER' + | 'STORE_RESPONSE'; resetFlowType: | 'DISCONNECT' | 'DISMISS' @@ -141,6 +147,15 @@ export interface Typegen0 { | 'RESET' | 'SCREEN_BLUR' | 'xstate.init'; + resetLinkCode: + | 'BLE_ERROR' + | 'DISMISS' + | 'DISMISS_QUICK_SHARE_BANNER' + | 'RESET' + | 'SCREEN_BLUR' + | 'SCREEN_FOCUS' + | 'SELECT_VC' + | 'xstate.stop'; resetSelectedVc: | 'DISCONNECT' | 'DISMISS' @@ -151,15 +166,16 @@ export interface Typegen0 { | 'xstate.init'; resetShowQuickShareSuccessBanner: 'DISMISS' | 'DISMISS_QUICK_SHARE_BANNER'; sendBLEConnectionErrorEvent: 'BLE_ERROR'; - sendScanData: 'SCAN'; + sendScanData: 'QRLOGIN_VIA_DEEP_LINK' | 'SCAN'; sendVCShareFlowCancelEndEvent: 'CANCEL'; sendVCShareFlowTimeoutEndEvent: 'CANCEL' | 'RETRY'; sendVcShareSuccessEvent: 'VC_ACCEPTED'; sendVcSharingStartEvent: 'SCAN'; setBleError: 'BLE_ERROR'; - setChildRef: 'STORE_RESPONSE'; + setChildRef: 'QRLOGIN_VIA_DEEP_LINK' | 'STORE_RESPONSE'; setFlowType: 'SELECT_VC'; setLinkCode: 'SCAN'; + setLinkCodeFromDeepLink: 'QRLOGIN_VIA_DEEP_LINK'; setQuickShareData: 'SCAN'; setReadyForBluetoothStateCheck: 'BLUETOOTH_PERMISSION_ENABLED'; setReceiverInfo: 'CONNECTED'; @@ -194,7 +210,7 @@ export interface Typegen0 { uptoAndroid11: '' | 'START_PERMISSION_CHECK'; }; eventsCausingServices: { - QrLogin: 'SCAN'; + QrLogin: 'QRLOGIN_VIA_DEEP_LINK' | 'SCAN'; checkBluetoothPermission: | '' | 'BLUETOOTH_STATE_DISABLED' @@ -251,6 +267,7 @@ export interface Typegen0 { | 'loadVCS.idle' | 'loadVCS.navigatingToHome' | 'nearByDevicesPermissionDenied' + | 'qrLoginViaDeepLink' | 'recheckBluetoothState' | 'recheckBluetoothState.checking' | 'recheckBluetoothState.enabled' diff --git a/machines/bleShare/scan/scanModel.ts b/machines/bleShare/scan/scanModel.ts index 952c0bd9..0b3e27b1 100644 --- a/machines/bleShare/scan/scanModel.ts +++ b/machines/bleShare/scan/scanModel.ts @@ -55,6 +55,7 @@ const ScanEvents = { }), ALLOWED: () => ({}), DENIED: () => ({}), + QRLOGIN_VIA_DEEP_LINK: (linkCode: string) => ({linkCode}), }; export const ScanModel = createModel( diff --git a/machines/bleShare/scan/scanSelectors.ts b/machines/bleShare/scan/scanSelectors.ts index 64c77a00..60fd4a31 100644 --- a/machines/bleShare/scan/scanSelectors.ts +++ b/machines/bleShare/scan/scanSelectors.ts @@ -125,3 +125,7 @@ export function selectIsMinimumStorageRequiredForAuditEntryLimitReached( export function selectIsFaceVerificationConsent(state: State) { return state.matches('reviewing.faceVerificationConsent'); } + +export function selectIsQrLoginViaDeepLink(state: State) { + return state.matches('qrLoginViaDeepLink'); +} diff --git a/routes/index.ts b/routes/index.ts index c4d105a8..66352212 100644 --- a/routes/index.ts +++ b/routes/index.ts @@ -12,8 +12,8 @@ import {NotificationsScreen} from '../screens/NotificationsScreen'; import {SetupLanguageScreen} from '../screens/SetupLanguageScreen'; import {IntroSlidersScreen} from '../screens/Home/IntroSlidersScreen'; import {RequestLayout} from '../screens/Request/RequestLayout'; -import {RequestStackParamList} from '../screens/Request/RequestLayoutController'; import {SplashScreen} from '../screens/SplashScreen'; +import {RequestStackParamList} from './routesConstants'; export const baseRoutes: Screen[] = [ { diff --git a/screens/MainLayout.tsx b/screens/MainLayout.tsx index 8364d7cf..1317e975 100644 --- a/screens/MainLayout.tsx +++ b/screens/MainLayout.tsx @@ -1,4 +1,4 @@ -import React, {useContext} from 'react'; +import React, {useContext, useEffect} from 'react'; import { BottomTabNavigationOptions, createBottomTabNavigator, @@ -17,9 +17,18 @@ import {CopilotProvider} from 'react-native-copilot'; import {View} from 'react-native'; import {CopilotTooltip} from '../components/CopilotTooltip'; import {Copilot} from '../components/ui/Copilot'; +import {useSelector} from '@xstate/react'; +import {selectIsLinkCode} from '../machines/app'; +import {NavigationProp, useNavigation} from '@react-navigation/native'; +import {BOTTOM_TAB_ROUTES, ScanStackParamList} from '../routes/routesConstants'; +import {MainBottomTabParamList} from '../routes/routeTypes'; const {Navigator, Screen} = createBottomTabNavigator(); +type ScanLayoutNavigation = NavigationProp< + ScanStackParamList & MainBottomTabParamList +>; + export const MainLayout: React.FC = () => { const {t} = useTranslation('MainLayout'); @@ -31,6 +40,15 @@ export const MainLayout: React.FC = () => { tabBarActiveTintColor: Theme.Colors.IconBg, ...Theme.BottomTabBarStyle, }; + const navigation = useNavigation(); + + const linkCode = useSelector(appService, selectIsLinkCode); + + useEffect(() => { + if (linkCode != '') { + navigation.navigate(BOTTOM_TAB_ROUTES.share); + } + }, [linkCode]); return (