[INJIMOB-1911]: add logic for QR login via deeplink (#1601)

* [INJIMOB-1911]: add logic for QR login via deeplink

Signed-off-by: Alka Prasad <prasadalka1998@gmail.com>

* [INJIMOB-1911]: bump up tuvali version

Signed-off-by: Alka Prasad <prasadalka1998@gmail.com>

* [INJIMOB-1911]: bump up kotlin patch version

Signed-off-by: Alka Prasad <prasadalka1998@gmail.com>

* [INJIMOB-1911]: rename the singleton variable name

Signed-off-by: Alka Prasad <prasadalka1998@gmail.com>

* [INJIMOB-1911]: extract common code in a function

Signed-off-by: Alka Prasad <prasadalka1998@gmail.com>

* [INJIMOB-1911]: refactor some logic and remove redundant code

Signed-off-by: Alka Prasad <prasadalka1998@gmail.com>

---------

Signed-off-by: Alka Prasad <prasadalka1998@gmail.com>
This commit is contained in:
Alka Prasad
2024-09-10 10:09:06 +05:30
committed by GitHub
parent c29e46bf75
commit a78b2d2ef3
16 changed files with 379 additions and 115 deletions

View File

@@ -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";

View File

@@ -1,46 +1,86 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="io.mosip.residentapp" xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" android:maxSdkVersion="28"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.USE_BIOMETRIC"/>
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<queries>
<intent>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="https"/>
</intent>
</queries>
<application tools:replace="usesCleartextTraffic" android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="false" android:theme="@style/AppTheme" android:usesCleartextTraffic="false">
<meta-data android:name="expo.modules.updates.ENABLED" android:value="true"/>
<meta-data android:name="expo.modules.updates.EXPO_SDK_VERSION" android:value="48.0.0"/>
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ALWAYS"/>
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0"/>
<meta-data android:name="expo.modules.updates.EXPO_UPDATE_URL" android:value="https://exp.host/@anonymous/inji"/>
<activity android:name=".MainActivity" android:exported="true" android:label="@string/app_name" android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|locale|layoutDirection" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="io.mosip.residentapp"/>
</intent-filter>
</activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity"/>
</application>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="io.mosip.residentapp">
<uses-permission
android:name="android.permission.ACCESS_COARSE_LOCATION"
android:maxSdkVersion="28" />
<uses-permission
android:name="android.permission.ACCESS_FINE_LOCATION"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission
android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
</intent>
</queries>
<application
android:name=".MainApplication"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/AppTheme"
android:usesCleartextTraffic="false"
tools:replace="usesCleartextTraffic">
<meta-data
android:name="expo.modules.updates.ENABLED"
android:value="true" />
<meta-data
android:name="expo.modules.updates.EXPO_SDK_VERSION"
android:value="48.0.0" />
<meta-data
android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH"
android:value="ALWAYS" />
<meta-data
android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS"
android:value="0" />
<meta-data
android:name="expo.modules.updates.EXPO_UPDATE_URL"
android:value="https://exp.host/@anonymous/inji" />
<activity
android:name=".MainActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|locale|layoutDirection"
android:exported="true"
android:label="@string/app_name"
android:launchMode="singleTask"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="io.mosip.residentapp.inji" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
</application>
</manifest>

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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));
}
}
/**

View File

@@ -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("");
}
}

View File

@@ -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;
}

View File

@@ -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;
}
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;
}

View File

@@ -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(

View File

@@ -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: {

View File

@@ -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'

View File

@@ -55,6 +55,7 @@ const ScanEvents = {
}),
ALLOWED: () => ({}),
DENIED: () => ({}),
QRLOGIN_VIA_DEEP_LINK: (linkCode: string) => ({linkCode}),
};
export const ScanModel = createModel(

View File

@@ -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');
}

View File

@@ -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[] = [
{

View File

@@ -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<ScanLayoutNavigation>();
const linkCode = useSelector(appService, selectIsLinkCode);
useEffect(() => {
if (linkCode != '') {
navigation.navigate(BOTTOM_TAB_ROUTES.share);
}
}, [linkCode]);
return (
<CopilotProvider

View File

@@ -24,6 +24,7 @@ import {
selectIsFaceIdentityVerified,
selectCredential,
selectVerifiableCredentialData,
selectIsQrLoginViaDeepLink,
} from '../../machines/bleShare/scan/scanSelectors';
import {
selectBleError,
@@ -43,6 +44,7 @@ import {BOTTOM_TAB_ROUTES, SCAN_ROUTES} from '../../routes/routesConstants';
import {ScanStackParamList} from '../../routes/routesConstants';
import {VCShareFlowType} from '../../shared/Utils';
import {Theme} from '../../components/ui/styleUtils';
import {APP_EVENTS, selectIsLinkCode} from '../../machines/app';
type ScanLayoutNavigation = NavigationProp<
ScanStackParamList & MainBottomTabParamList
@@ -75,6 +77,10 @@ export function useScanLayout() {
scanService,
selectVerifiableCredentialData,
);
const isQrLoginViaDeepLink = useSelector(
scanService,
selectIsQrLoginViaDeepLink,
);
const locationError = {message: '', button: ''};
@@ -121,6 +127,7 @@ export function useScanLayout() {
scanService,
selectIsExchangingDeviceInfoTimeout,
);
const linkCode = useSelector(appService, selectIsLinkCode);
const isAccepted = useSelector(scanService, selectIsAccepted);
const isRejected = useSelector(scanService, selectIsRejected);
const isSent = useSelector(scanService, selectIsSent);
@@ -262,6 +269,9 @@ export function useScanLayout() {
if (isDone) {
changeTabBarVisible('flex');
navigation.navigate(BOTTOM_TAB_ROUTES.home);
} else if (isQrLoginViaDeepLink) {
scanService.send(ScanEvents.QRLOGIN_VIA_DEEP_LINK(linkCode));
appService.send(APP_EVENTS.RESET_LINKCODE());
} else if (
isReviewing &&
flowType === VCShareFlowType.SIMPLE_SHARE &&
@@ -284,6 +294,7 @@ export function useScanLayout() {
isBleError,
flowType,
isAccepted,
isQrLoginViaDeepLink,
]);
return {