diff --git a/locales/en/cie/cieNotSupported.md b/locales/en/cie/cieNotSupported.md new file mode 100644 index 00000000000..55876216e22 --- /dev/null +++ b/locales/en/cie/cieNotSupported.md @@ -0,0 +1 @@ +To login you need a device with NFC technology and the operating system with the minimum requirements (**iOS** devices are not yet compatible but will become compatible with the next updates). \ No newline at end of file diff --git a/locales/en/cie/cieNotSupported_android.md b/locales/en/cie/cieNotSupported_android.md new file mode 100644 index 00000000000..0f8ac65f48d --- /dev/null +++ b/locales/en/cie/cieNotSupported_android.md @@ -0,0 +1 @@ +Specifically, the cases in which you **cannot** access with an Electronic Identity Card (CIE) on your Android device are: \ No newline at end of file diff --git a/locales/en/index.yml b/locales/en/index.yml index 5b2f329829b..22b355e6e61 100644 --- a/locales/en/index.yml +++ b/locales/en/index.yml @@ -266,6 +266,12 @@ spid: line2: Insert your email address to enable it. authentication: landing: + cie_unsupported: + title: Login with CIE unavailable + body: !include cie/cieNotSupported.md + android_desc: !include cie/cieNotSupported_android.md + os_version_unsupported: your operating system does not have a version compatible with CIE authentication + nfc_incompatible: your device does not have an NFC antenna compatible with CIE authentication contentTitleCie: Don't have SPID or CIE? cie_information_request: !include cie.md spid_or_cie: To use IO you must have one of these digital tools, which certify your identity. diff --git a/locales/it/cie/cieNotSupported.md b/locales/it/cie/cieNotSupported.md new file mode 100644 index 00000000000..0585c880724 --- /dev/null +++ b/locales/it/cie/cieNotSupported.md @@ -0,0 +1 @@ +Per effettuare il login è necessario avere un dispositivo con tecnologia NFC e il sistema operativo con i requisiti minimi necessari (i dispositivi **iOS** non sono ancora compatibili ma a breve lo diventeranno con i prossimi aggiornamenti). \ No newline at end of file diff --git a/locales/it/cie/cieNotSupported_android.md b/locales/it/cie/cieNotSupported_android.md new file mode 100644 index 00000000000..f93e8181ec3 --- /dev/null +++ b/locales/it/cie/cieNotSupported_android.md @@ -0,0 +1 @@ +Nello specifico, i casi in cui **non** è possibile accedere con Carta di Identità Elettronica (CIE) su dispositivo Android sono: \ No newline at end of file diff --git a/locales/it/index.yml b/locales/it/index.yml index 34333a5c82c..c0074b671d2 100644 --- a/locales/it/index.yml +++ b/locales/it/index.yml @@ -271,6 +271,12 @@ spid: line2: Inserisci la tua email per attivarla. authentication: landing: + cie_unsupported: + title: Accesso con CIE non disponibile + body: !include cie/cieNotSupported.md + android_desc: !include cie/cieNotSupported_android.md + os_version_unsupported: il tuo sistema operativo non ha una versione compatibile con l'autenticazione tramite CIE + nfc_incompatible: il tuo dispositivo non ha una antenna NFC compatibile con l'autenticazione tramite CIE contentTitleCie: Non hai SPID o la CIE? cie_information_request: !include cie.md spid_or_cie: Per utilizzare IO devi dotarti di uno di questi strumenti digitali, che certificano in maniera certa la tua identità. diff --git a/ts/components/cie/CieNotSupported.tsx b/ts/components/cie/CieNotSupported.tsx new file mode 100644 index 00000000000..3fe92f0ffc8 --- /dev/null +++ b/ts/components/cie/CieNotSupported.tsx @@ -0,0 +1,73 @@ +import { Body, List, ListItem, Text, View } from "native-base"; +import * as React from "react"; +import { Platform } from "react-native"; +import I18n from "../../i18n"; +import customVariables from "../../theme/variables"; +import IconFont from "../ui/IconFont"; +import Markdown from "../ui/Markdown"; + +type Props = { + hasCieApiLevelSupport: boolean; + hasCieNFCFeature: boolean; +}; + +const ICON_SIZE = 16; + +const CieNotSupported: React.FunctionComponent = props => { + return ( + + + {I18n.t("authentication.landing.cie_unsupported.body")} + + {Platform.OS === "android" && ( + + + + {I18n.t("authentication.landing.cie_unsupported.android_desc")} + + + + + + + + {I18n.t( + "authentication.landing.cie_unsupported.os_version_unsupported" + )} + + + + + + + + {I18n.t( + "authentication.landing.cie_unsupported.nfc_incompatible" + )} + + + + + + )} + + ); +}; + +export default CieNotSupported; diff --git a/ts/sagas/cie.ts b/ts/sagas/cie.ts index 5eb8fc94481..610e9a91f1b 100644 --- a/ts/sagas/cie.ts +++ b/ts/sagas/cie.ts @@ -2,7 +2,12 @@ import cieManager from "@pagopa/react-native-cie"; import { Millisecond } from "italia-ts-commons/lib/units"; import { SagaIterator } from "redux-saga"; import { call, put, takeLatest } from "redux-saga/effects"; -import { cieIsSupported, nfcIsEnabled } from "../store/actions/cie"; +import { + cieIsSupported, + hasApiLevelSupport, + hasNFCFeature, + nfcIsEnabled +} from "../store/actions/cie"; import { SagaCallReturnType } from "../types/utils"; import { startTimer } from "../utils/timer"; @@ -11,6 +16,10 @@ export function* watchCieAuthenticationSaga(): SagaIterator { yield takeLatest(nfcIsEnabled.request, checkNfcEnablementSaga); // check if the device is compliant with CIE authentication yield call(checkCieAvailabilitySaga, cieManager.isCIEAuthenticationSupported); + // check if the device has the API Level compliant with CIE authentication + yield call(checkHasApiLevelSupportSaga, cieManager.hasApiLevelSupport); + // check if the device has the NFC Feature to support CIE authentication + yield call(checkHasNfcFeatureSaga, cieManager.hasNFCFeature); } // stop cie manager to listen nfc tags @@ -22,6 +31,40 @@ export function* stopCieManager(): SagaIterator { } } +/** + * check if the device has the API Level to support CIE authentication + * see https://github.com/pagopa/io-cie-android-sdk/blob/29cc1165bbd3d90d61239369f22ec78b2e4c8f6c/index.js#L155 + */ +export function* checkHasApiLevelSupportSaga( + hasApiLevelSupported: typeof cieManager["hasApiLevelSupport"] +): SagaIterator { + try { + const response: SagaCallReturnType< + typeof hasApiLevelSupported + > = yield call(hasApiLevelSupported); + yield put(hasApiLevelSupport.success(response)); + } catch (e) { + yield put(hasApiLevelSupport.failure(new Error(e))); + } +} + +/** + * check if the device has the NFC Feature to support CIE authentication + * see https://github.com/pagopa/io-cie-android-sdk/blob/29cc1165bbd3d90d61239369f22ec78b2e4c8f6c/index.js#L169 + */ +export function* checkHasNfcFeatureSaga( + hasNfcFeatureSupported: typeof cieManager["hasNFCFeature"] +): SagaIterator { + try { + const response: SagaCallReturnType< + typeof hasNfcFeatureSupported + > = yield call(hasNfcFeatureSupported); + yield put(hasNFCFeature.success(response)); + } catch (e) { + yield put(hasNFCFeature.failure(new Error(e))); + } +} + /** * check if the device is compatible with CIE authentication * see https://github.com/pagopa/io-cie-android-sdk/blob/29cc1165bbd3d90d61239369f22ec78b2e4c8f6c/index.js#L125 diff --git a/ts/screens/authentication/LandingScreen.tsx b/ts/screens/authentication/LandingScreen.tsx index d6839d1eb4c..dc9d68cad2b 100644 --- a/ts/screens/authentication/LandingScreen.tsx +++ b/ts/screens/authentication/LandingScreen.tsx @@ -9,13 +9,17 @@ import { StyleSheet } from "react-native"; import { NavigationInjectedProps } from "react-navigation"; import { connect } from "react-redux"; import ButtonDefaultOpacity from "../../components/ButtonDefaultOpacity"; +import CieNotSupported from "../../components/cie/CieNotSupported"; +import { ContextualHelp } from "../../components/ContextualHelp"; import { DevScreenButton } from "../../components/DevScreenButton"; +import { withLightModalContext } from "../../components/helpers/withLightModalContext"; import { HorizontalScroll } from "../../components/HorizontalScroll"; import { LandingCardComponent } from "../../components/LandingCardComponent"; import BaseScreenComponent, { ContextualHelpPropsMarkdown } from "../../components/screens/BaseScreenComponent"; import IconFont from "../../components/ui/IconFont"; +import { LightModalContextInterface } from "../../components/ui/LightModal"; import I18n from "../../i18n"; import { IdentityProvider } from "../../models/IdentityProvider"; import ROUTES from "../../navigation/routes"; @@ -25,7 +29,11 @@ import { } from "../../store/actions/authentication"; import { Dispatch } from "../../store/actions/types"; import { isSessionExpiredSelector } from "../../store/reducers/authentication"; -import { isCieSupportedSelector } from "../../store/reducers/cie"; +import { + hasApiLevelSupportSelector, + hasNFCFeatureSelector, + isCieSupportedSelector +} from "../../store/reducers/cie"; import { GlobalState } from "../../store/reducers/types"; import variables from "../../theme/variables"; import { ComponentProps } from "../../types/react"; @@ -33,6 +41,7 @@ import { isDevEnv } from "../../utils/environment"; import { showToast } from "../../utils/showToast"; type Props = NavigationInjectedProps & + LightModalContextInterface & ReturnType & ReturnType; @@ -89,6 +98,9 @@ const styles = StyleSheet.create({ }, flex: { flex: 1 + }, + noCie: { + opacity: 0.35 } }); @@ -120,6 +132,21 @@ class LandingScreen extends React.PureComponent { } } + private openUnsupportedCIEModal = () => { + this.props.showAnimatedModal( + ( + + )} + /> + ); + }; + private navigateToMarkdown = () => this.props.navigation.navigate(ROUTES.MARKDOWN); @@ -127,8 +154,12 @@ class LandingScreen extends React.PureComponent { this.props.navigation.navigate(ROUTES.AUTHENTICATION_IDP_SELECTION); private navigateToCiePinScreen = () => { - this.props.dispatchIdpCieSelected(); - this.props.navigation.navigate(ROUTES.CIE_PIN_SCREEN); + if (this.props.isCieSupported) { + this.props.dispatchIdpCieSelected(); + this.props.navigation.navigate(ROUTES.CIE_PIN_SCREEN); + } else { + this.openUnsupportedCIEModal(); + } }; private navigateToSpidCieInformationRequest = () => @@ -162,28 +193,57 @@ class LandingScreen extends React.PureComponent { - {this.props.isCieSupported && ( - - - {I18n.t("authentication.landing.loginCie")} - - )} + + + + {this.props.isCieSupported + ? I18n.t("authentication.landing.loginCie") + : I18n.t("authentication.landing.loginSpid")} + + - - {I18n.t("authentication.landing.loginSpid")} + + + {this.props.isCieSupported + ? I18n.t("authentication.landing.loginSpid") + : I18n.t("authentication.landing.loginCie")} + { const mapStateToProps = (state: GlobalState) => { const isCIEAuthenticationSupported = isCieSupportedSelector(state); + const hasApiLevelSupport = hasApiLevelSupportSelector(state); + const hasNFCFeature = hasNFCFeatureSelector(state); return { isSessionExpired: isSessionExpiredSelector(state), - isCieSupported: pot.getOrElse(isCIEAuthenticationSupported, false) + isCieSupported: pot.getOrElse(isCIEAuthenticationSupported, false), + hasCieApiLevelSupport: pot.getOrElse(hasApiLevelSupport, false), + hasCieNFCFeature: pot.getOrElse(hasNFCFeature, false) }; }; @@ -220,4 +284,4 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ export default connect( mapStateToProps, mapDispatchToProps -)(LandingScreen); +)(withLightModalContext(LandingScreen)); diff --git a/ts/store/actions/cie.ts b/ts/store/actions/cie.ts index caf20e293f4..53e7d6cf145 100644 --- a/ts/store/actions/cie.ts +++ b/ts/store/actions/cie.ts @@ -4,6 +4,18 @@ import { ActionType, createAsyncAction } from "typesafe-actions"; +export const hasApiLevelSupport = createAsyncAction( + "CIE_HAS_API_LEVEL_REQUEST", + "CIE_HAS_API_LEVEL_SUCCESS", + "CIE_HAS_API_LEVEL_FAILURE" +)(); + +export const hasNFCFeature = createAsyncAction( + "CIE_HAS_NFC_FEATURE_REQUEST", + "CIE_HAS_NFC_FEATURE_SUCCESS", + "CIE_HAS_NFC_FEATURE_FAILURE" +)(); + export const cieIsSupported = createAsyncAction( "CIE_IS_SUPPORTED_REQUEST", "CIE_IS_SUPPORTED_SUCCESS", @@ -23,6 +35,8 @@ export const updateReadingState = createAsyncAction( )(); export type CieAuthenticationActions = + | ActionType + | ActionType | ActionType | ActionType | ActionType; diff --git a/ts/store/reducers/cie.ts b/ts/store/reducers/cie.ts index 4e5b83ae181..ce4b7315569 100644 --- a/ts/store/reducers/cie.ts +++ b/ts/store/reducers/cie.ts @@ -5,6 +5,8 @@ import * as pot from "italia-ts-commons/lib/pot"; import { getType } from "typesafe-actions"; import { cieIsSupported, + hasApiLevelSupport, + hasNFCFeature, nfcIsEnabled, updateReadingState } from "../actions/cie"; @@ -12,12 +14,16 @@ import { Action } from "../actions/types"; import { GlobalState } from "./types"; export type CieState = { + hasApiLevelSupport: pot.Pot; + hasNFCFeature: pot.Pot; isCieSupported: pot.Pot; isNfcEnabled: pot.Pot; readingEvent: pot.Pot; }; const INITIAL_STATE: CieState = { + hasApiLevelSupport: pot.none, + hasNFCFeature: pot.none, isCieSupported: pot.none, isNfcEnabled: pot.none, readingEvent: pot.none @@ -38,6 +44,29 @@ export default function cieReducer( ...state, isCieSupported: pot.toError(state.isCieSupported, action.payload) }; + case getType(hasApiLevelSupport.success): + return { + ...state, + hasApiLevelSupport: pot.some(action.payload) + }; + case getType(hasApiLevelSupport.failure): + return { + ...state, + hasApiLevelSupport: pot.toError( + state.hasApiLevelSupport, + action.payload + ) + }; + case getType(hasNFCFeature.success): + return { + ...state, + hasNFCFeature: pot.some(action.payload) + }; + case getType(hasNFCFeature.failure): + return { + ...state, + hasNFCFeature: pot.toError(state.hasNFCFeature, action.payload) + }; case getType(nfcIsEnabled.success): return { ...state, @@ -66,6 +95,12 @@ export default function cieReducer( } // Selectors +export const hasNFCFeatureSelector = (state: GlobalState) => + state.cie.hasNFCFeature; + +export const hasApiLevelSupportSelector = (state: GlobalState) => + state.cie.hasApiLevelSupport; + export const isCieSupportedSelector = (state: GlobalState) => state.cie.isCieSupported;