Skip to content

Commit

Permalink
chore(IT Wallet): [SIW-780] Update ItwPrProximityQrCodeScreen with …
Browse files Browse the repository at this point in the history
…proximity saga (#5361)

### This PR depends on #5362 

## Short description
Include a summary of the changes.

## List of changes proposed in this pull request
- Update ItwPrProximityQrCodeScreen
- Add mocked mDL response

## How to test
Start the app. After wallet activation try to request a credential like
the mDL. Go to the credential detail and tap on "Show QR Code". Using a
verifier app Scan te QR Code to start proximity flow. The credential
should be correctly verified.
  • Loading branch information
hevelius authored Dec 22, 2023
1 parent 4858220 commit 5f55e58
Show file tree
Hide file tree
Showing 10 changed files with 258 additions and 53 deletions.
25 changes: 6 additions & 19 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,26 +44,13 @@
<!-- Required for local notifications -->
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />

<!-- Request legacy Bluetooth permissions on older devices. -->
<uses-permission android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />

<!-- Needed only if your app looks for Bluetooth devices.
If your app doesn't use Bluetooth scan results to derive physical
location information, you can
<a href="#assert-never-for-location">strongly assert that your app
doesn't derive physical location</a>. -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />

<!-- Needed only if your app makes the device discoverable to Bluetooth
devices. -->
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />

<!-- Needed only if your app communicates with already-paired Bluetooth
devices. -->
<uses-permission android:name="android.permission.BLUETOOTH" tools:remove="android:maxSdkVersion" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" tools:remove="android:maxSdkVersion" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" android:maxSdkVersion="28"/>
<uses-permission-sdk-23 android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30"/>
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />

<application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:usesCleartextTraffic="true" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="false" android:requestLegacyExternalStorage="true" android:theme="@style/AppTheme">

Expand Down
4 changes: 2 additions & 2 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ PODS:
- React-Core
- pagopa-io-react-native-login-utils (0.2.0):
- React-Core
- pagopa-io-react-native-proximity (0.1.7):
- pagopa-io-react-native-proximity (0.1.8):
- React-Core
- PromisesObjC (2.0.0)
- Protobuf (3.19.1)
Expand Down Expand Up @@ -962,7 +962,7 @@ SPEC CHECKSUMS:
pagopa-io-react-native-crypto: 644fece16966f2e1ea1f872344ee5a3c6c8761a1
pagopa-io-react-native-jwt: 0b096d1b173e438a963cb81f251a33988b46f5fa
pagopa-io-react-native-login-utils: 929e584c817572ce7a3e41e0c6f96c7990bd314d
pagopa-io-react-native-proximity: 8cca3b015edc42b945257eccae67f7df5fcc1f44
pagopa-io-react-native-proximity: 885de67439e48d95dedbf36628c252e2d05556a6
PromisesObjC: 68159ce6952d93e17b2dfe273b8c40907db5ba58
Protobuf: 3724efa50cb2846d7ccebc8691c574e85fd74471
RCT-Folly: b9d9fe1fc70114b751c076104e52f3b1b5e5a95a
Expand Down
11 changes: 9 additions & 2 deletions locales/en/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3437,8 +3437,15 @@ features:
success:
title: "Done!"
subtitle: "Go back to the {{organizationName}} website to continue."
qrCodeScreen:
loading: "Wait a few seconds for the verification request..."
qrCodeScreen:
title: "Personal QR code"
proximity:
start: "Proximity check started"
stop: "Proximity check completed"
peripheralConnected: "Verification device connected"
sessionEstablished: "A session has been established with the verification device"
documentRequestReceived: "Credential checking in progress"
documentPresentationCompleted: "Credential check completed"
missingFeatureScreen:
headerTitle: "Screen not available"
title: "This screen will be available soon."
Expand Down
9 changes: 8 additions & 1 deletion locales/it/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3439,7 +3439,14 @@ features:
title: "Fatto!"
subtitle: "Torna sul sito di {{organizationName}} per continuare."
qrCodeScreen:
loading: "Attendi qualche secondo la richiesta di verifica..."
title: "Codice QR personale"
proximity:
start: "Verifica in prossimità avviata"
stop: "Verifica in prossimità terminata"
peripheralConnected: "Dispositivo di verifica connesso"
sessionEstablished: "E' stata stabilita una sessione con il dispositivo di verifica"
documentRequestReceived: "Controllo della credenziale in corso"
documentPresentationCompleted: "Controllo della credenziale completato"
missingFeatureScreen:
headerTitle: "Schermata non disponibile"
title: "Questa schermata sarà presto disponibile"
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@
"@pagopa/io-react-native-crypto": "^0.2.1",
"@pagopa/io-react-native-jwt": "1.1.0",
"@pagopa/io-react-native-login-utils": "^0.2.0",
"@pagopa/io-react-native-proximity": "^0.1.7",
"@pagopa/io-react-native-proximity": "^0.1.8",
"@pagopa/io-react-native-wallet": "^0.11.1",
"@pagopa/react-native-cie": "1.2.0",
"@pagopa/react-native-nodelibs": "^0.1.0",
Expand Down
14 changes: 13 additions & 1 deletion ts/features/it-wallet/saga/itwProximitySaga.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,20 @@ export function* watchItwProximitySaga(): SagaIterator {
}

// start proximity manager
function* handleStartProximityManagerSaga(): SagaIterator {
function* handleStartProximityManagerSaga(
action: ReturnType<typeof startProximityManager.request>
): SagaIterator {
try {
yield* call(ProximityManager.start);
yield* call(ProximityManager.setListeners, {
onEvent: action.payload.onEvent,
onSuccess: action.payload.onSuccess,
onError: action.payload.onError
});
yield* call(
ProximityManager.setOnDocumentRequestHandler,
action.payload.onDocumentsRequestReceived
);
yield* put(startProximityManager.success(true));
yield* put(generateQrCode.request());
} catch {
Expand Down Expand Up @@ -71,6 +82,7 @@ function* handleStopProximityManagerSaga(): SagaIterator {
// TODO: remove all listners before stopping the proximity manager
// we need a new method in the proximity manager [SIW-775]
yield* call(ProximityManager.stop);
yield* call(ProximityManager.removeListeners);
yield* put(stopProximityManager.success(true));
} catch {
yield* put(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,46 @@
import * as React from "react";
import { Image, SafeAreaView, View } from "react-native";
import {
Image,
PermissionsAndroid,
Platform,
SafeAreaView,
View
} from "react-native";
import * as pot from "@pagopa/ts-commons/lib/pot";
import RNQRGenerator from "rn-qr-generator";
import {
IOStyles,
IconButton,
LabelSmall,
LoadingSpinner,
VSpacer
} from "@pagopa/io-app-design-system";
import { useNavigation } from "@react-navigation/native";
import {
DocumentRequest,
EventData,
ProximityManager
} from "@pagopa/io-react-native-proximity";
import { useOnFirstRender } from "../../../../utils/hooks/useOnFirstRender";
import { IOStackNavigationProp } from "../../../../navigation/params/AppParamsList";
import { ItwParamsList } from "../../navigation/ItwParamsList";
import I18n from "../../../../i18n";
import BaseScreenComponent from "../../../../components/screens/BaseScreenComponent";
import ItwKoView from "../../components/ItwKoView";
import { getItwGenericMappedError } from "../../utils/itwErrorsUtils";

// A mocked QR code to be used in the proximity flow
// TODO: remove this mocked QR code after the proximity flow is implemented [SIW-688]
const mockedQrCode =
"mdoc:owBjMS4wAYIB2BhYS6QBAiABIVggUCnUgO0nCmTWOkqZLpQJh1uO2Q0YCTbYtUowBJU6ltEiWCBPkYpJZpEY4emfmR_2eFS5XQN68wihmgEoiMVEf8M3_gKBgwIBowD0AfULUJr9sL_rAkZCk114baNK4rY";
import { mockedmDLResponse } from "../../utils/mocks";
import ItwContinueView from "../../components/ItwContinueView";
import ROUTES from "../../../../navigation/routes";
import { useIODispatch, useIOSelector } from "../../../../store/hooks";
import {
ProximityManagerStatusEnum,
startProximityManager,
stopProximityManager
} from "../../store/actions/itwProximityActions";
import {
proximityStatusSelector,
qrcodeSelector
} from "../../store/reducers/itwProximityReducer";

/**
* A screen that shows a QR code to be scanned by the other device
Expand All @@ -28,24 +49,147 @@ const mockedQrCode =
const ItwPrProximityQrCodeScreen = () => {
const [qrCodeUri, setQrCodeUri] = React.useState("");
const [isLoading, setIsLoading] = React.useState(false);
const [eventMessage, setEventMessage] = React.useState("");
const [isError, setIsError] = React.useState(false);
const [isProximityCompleted, setIsProximityCompleted] = React.useState(false);
const navigation = useNavigation<IOStackNavigationProp<ItwParamsList>>();
const dispatch = useIODispatch();
const proximityStatus = useIOSelector(proximityStatusSelector);
const qrCode = useIOSelector(qrcodeSelector);

useOnFirstRender(() => {
RNQRGenerator.generate({
value: mockedQrCode,
height: 300,
width: 300,
correctionLevel: "H"
})
.then(({ uri }) => {
setQrCodeUri(uri);
setIsLoading(true);
handleAndroidPermissions().catch(_ => setIsError(true));
dispatch(
startProximityManager.request({
onSuccess,
onError,
onEvent,
onDocumentsRequestReceived
})
.catch(_ => {
setIsError(true);
});
);
});

const generateQrCode = React.useCallback(() => {
if (pot.isSome(qrCode)) {
RNQRGenerator.generate({
value: qrCode.value,
height: 300,
width: 300,
correctionLevel: "H"
})
.then(async response => {
setQrCodeUri(response.uri);
setIsLoading(true);
await ProximityManager.startScan();
})
.catch(_ => {
setIsError(true);
});
} else {
setIsError(true);
}
}, [qrCode]);

React.useEffect(() => {
if (proximityStatus === ProximityManagerStatusEnum.STARTED) {
setIsLoading(false);
generateQrCode();
}
}, [generateQrCode, proximityStatus]);

/**
* This is a temporary function to map the event to a message
* to be displayed to the user. The messages will be replaced
* by a locale when and if will be available by design.
* @todo replace the messages with a locale when and if will be available by design or
* remove this function if no messages required.
* @param event
* @returns
*/
const mapEventToMessage = (event: EventData) => {
switch (event.type) {
case "ON_BLE_START":
return I18n.t(
"features.itWallet.presentation.qrCodeScreen.proximity.start"
);
case "ON_BLE_STOP":
return I18n.t(
"features.itWallet.presentation.qrCodeScreen.proximity.stop"
);
case "ON_PERIPHERAL_CONNECTED":
return I18n.t(
"features.itWallet.presentation.qrCodeScreen.proximity.peripheralConnected"
);
case "ON_SESSION_ESTABLISHMENT":
return I18n.t(
"features.itWallet.presentation.qrCodeScreen.proximity.sessionEstablished"
);
case "ON_DOCUMENT_REQUESTS_RECEIVED":
return I18n.t(
"features.itWallet.presentation.qrCodeScreen.proximity.documentRequestReceived"
);
case "ON_DOCUMENT_PRESENTATION_COMPLETED":
setIsProximityCompleted(true);
return I18n.t(
"features.itWallet.presentation.qrCodeScreen.proximity.documentPresentationCompleted"
);
break;
default:
return "";
}
};

const onEvent = (event: EventData) => {
setEventMessage(mapEventToMessage(event));
};

const onSuccess = (_: EventData) => {
// TODO: handle success event
};

const onError = (_: EventData) => {
setIsError(true);
};

const onDocumentsRequestReceived = (_: Array<DocumentRequest>) => {
// TODO: maybe should be navigate to a data sharing
// screen to get user consent
ProximityManager.dataPresentation(mockedmDLResponse).catch(_ => {
setIsError(true);
});
};

const handleAndroidPermissions = async () => {
if (
Platform.OS === "android" &&
Platform.Version >= 31 &&
PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN &&
PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT
) {
await PermissionsAndroid.requestMultiple([
PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT
]);
} else if (
Platform.OS === "android" &&
Platform.Version >= 23 &&
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION
) {
await PermissionsAndroid.check(
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION
).then(async checkResult => {
if (
!checkResult &&
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION
) {
await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION
);
}
});
}
};

/**
* This component is used to display a loading spinner and a text.
* It is used during proximity flow. After proximity integration [SIW-688] this
Expand All @@ -58,7 +202,7 @@ const ItwPrProximityQrCodeScreen = () => {
<LoadingSpinner />
<VSpacer size={8} />
<LabelSmall color={"black"} weight="Regular">
{I18n.t("features.itWallet.presentation.qrCodeScreen.loading")}
{eventMessage}
</LabelSmall>
</View>
);
Expand All @@ -72,11 +216,42 @@ const ItwPrProximityQrCodeScreen = () => {
};

if (isError) {
dispatch(stopProximityManager.request());
return <ErrorView />;
}

if (isProximityCompleted) {
dispatch(stopProximityManager.request());
return (
<ItwContinueView
title={I18n.t(
"features.itWallet.presentation.qrCodeScreen.proximity.documentPresentationCompleted"
)}
pictogram={"success"}
action={{
label: I18n.t("global.buttons.confirm"),
accessibilityLabel: I18n.t("global.buttons.confirm"),
onPress: () =>
navigation.navigate(ROUTES.MAIN, { screen: ROUTES.ITWALLET_HOME })
}}
/>
);
}

const customGoBack: React.ReactElement = (
<IconButton
icon={Platform.OS === "ios" ? "backiOS" : "backAndroid"}
color={"neutral"}
onPress={() => {
dispatch(stopProximityManager.request());
navigation.goBack();
}}
accessibilityLabel={I18n.t("global.buttons.back")}
/>
);

return (
<BaseScreenComponent goBack={true}>
<BaseScreenComponent goBack={true} customGoBack={customGoBack}>
<SafeAreaView style={(IOStyles.flex, IOStyles.alignCenter)}>
<View
style={{
Expand All @@ -87,7 +262,7 @@ const ItwPrProximityQrCodeScreen = () => {
{qrCodeUri !== "" && (
<>
<LabelSmall color={"black"}>
{"Il tuo codice QR personale"}
{I18n.t("features.itWallet.presentation.qrCodeScreen.title")}
</LabelSmall>
<VSpacer size={16} />
<Image
Expand Down
Loading

0 comments on commit 5f55e58

Please sign in to comment.