diff --git a/package.json b/package.json
index fd4d0b1467a..8c95c272193 100644
--- a/package.json
+++ b/package.json
@@ -27,8 +27,8 @@
"idpay_api": "https://raw.githubusercontent.com/pagopa/cstar-infrastructure/v6.9.1/src/domains/idpay-app/api/idpay_appio_full/openapi.appio.full.yml",
"lollipop_api": "https://raw.githubusercontent.com/pagopa/io-backend/v13.25.1-RELEASE/api_lollipop_first_consumer.yaml",
"fast_login_api": "https://raw.githubusercontent.com/pagopa/io-backend/v13.25.1-RELEASE/openapi/generated/api_fast_login.yaml",
- "pagopa_api_walletv3": "https://raw.githubusercontent.com/pagopa/pagopa-infra/4cd111e94432ff62580adc391de78a5462a7128e/src/domains/wallet-app/api/payment-wallet/v1/_openapi.json.tpl",
- "pagopa_api_ecommerce": "https://raw.githubusercontent.com/pagopa/pagopa-infra/65878f9913fcc0eaff499ba8a1a20427a412c010/src/domains/ecommerce-app/api/ecommerce-io/v1/_openapi.json.tpl",
+ "pagopa_api_walletv3": "https://raw.githubusercontent.com/pagopa/pagopa-infra/740e7dcc5ea2ea19639316fea6797bbd504dd0ae/src/domains/wallet-app/api/payment-wallet/v1/_openapi.json.tpl",
+ "pagopa_api_ecommerce": "https://raw.githubusercontent.com/pagopa/pagopa-infra/5190135ac34791cf66c1986735d4134bcaf4096f/src/domains/ecommerce-app/api/ecommerce-io/v1/_openapi.json.tpl",
"private": true,
"scripts": {
"start": "react-native start",
diff --git a/ts/features/bonus/cgn/components/detail/CgnUnsubscribe.tsx b/ts/features/bonus/cgn/components/detail/CgnUnsubscribe.tsx
index ec0eebcd348..9ef0c1fd4cd 100644
--- a/ts/features/bonus/cgn/components/detail/CgnUnsubscribe.tsx
+++ b/ts/features/bonus/cgn/components/detail/CgnUnsubscribe.tsx
@@ -11,6 +11,7 @@ import { isError, isReady } from "../../../../../common/model/RemoteValue";
import { navigateBack } from "../../../../../store/actions/navigation";
import { cgnDetails } from "../../store/actions/details";
import { IOToast } from "../../../../../components/Toast";
+import { skipToastShowingDueToE2ECrash } from "./ToastPatch";
const CgnUnsubscribe = () => {
const dispatch = useIODispatch();
@@ -38,7 +39,11 @@ const CgnUnsubscribe = () => {
if (isReady(unsubscriptionStatus)) {
navigateBack();
dispatch(cgnDetails.request());
- IOToast.success(I18n.t("bonus.cgn.activation.deactivate.toast"));
+ // This is needed to prevent a crash while running E2E tests. Showing
+ // the toast causes random crashes upon calling device.reloadReactNative
+ if (!skipToastShowingDueToE2ECrash) {
+ IOToast.success(I18n.t("bonus.cgn.activation.deactivate.toast"));
+ }
}
if (isError(unsubscriptionStatus) && !isFirstRender.current) {
IOToast.error(I18n.t("global.genericError"));
diff --git a/ts/features/bonus/cgn/components/detail/ToastPatch.e2e.ts b/ts/features/bonus/cgn/components/detail/ToastPatch.e2e.ts
new file mode 100644
index 00000000000..cd3658899d7
--- /dev/null
+++ b/ts/features/bonus/cgn/components/detail/ToastPatch.e2e.ts
@@ -0,0 +1 @@
+export const skipToastShowingDueToE2ECrash = true;
diff --git a/ts/features/bonus/cgn/components/detail/ToastPatch.ts b/ts/features/bonus/cgn/components/detail/ToastPatch.ts
new file mode 100644
index 00000000000..b6d723d80f9
--- /dev/null
+++ b/ts/features/bonus/cgn/components/detail/ToastPatch.ts
@@ -0,0 +1 @@
+export const skipToastShowingDueToE2ECrash = false;
diff --git a/ts/features/wallet/onboarding/__e2e__/creditCardOnboarding.e2e.ts b/ts/features/wallet/onboarding/__e2e__/creditCardOnboarding.e2e.ts
index 27ec8188c1f..642a158fd29 100644
--- a/ts/features/wallet/onboarding/__e2e__/creditCardOnboarding.e2e.ts
+++ b/ts/features/wallet/onboarding/__e2e__/creditCardOnboarding.e2e.ts
@@ -17,16 +17,12 @@ describe("Credit Card onboarding", () => {
// Footer, Wallet icon
await element(by.text(I18n.t("global.navigator.wallet"))).tap();
- await waitFor(
- element(by.text(I18n.t("wallet.newPaymentMethod.add").toUpperCase()))
- )
+ await waitFor(element(by.id("walletAddNewPaymentMethodTestId")))
.toBeVisible()
.withTimeout(e2eWaitRenderTimeout);
// Button "+ Add"
- await element(
- by.text(I18n.t("wallet.newPaymentMethod.add").toUpperCase())
- ).tap();
+ await element(by.id("walletAddNewPaymentMethodTestId")).tap();
await waitFor(element(by.text(I18n.t("wallet.paymentMethod"))))
.toBeVisible()
diff --git a/ts/features/walletV3/common/components/WalletDetailsPaymentMethodFeatures.tsx b/ts/features/walletV3/common/components/WalletDetailsPaymentMethodFeatures.tsx
index 959c7e90a41..b3799b6f200 100644
--- a/ts/features/walletV3/common/components/WalletDetailsPaymentMethodFeatures.tsx
+++ b/ts/features/walletV3/common/components/WalletDetailsPaymentMethodFeatures.tsx
@@ -19,7 +19,7 @@ type Props = { paymentMethod: WalletInfo };
* - global settings (payment capability, favourite, etc.)
*/
const WalletDetailsPaymentMethodFeatures = ({ paymentMethod }: Props) => {
- const isMethodExpired = isPaymentMethodExpired(paymentMethod);
+ const isMethodExpired = isPaymentMethodExpired(paymentMethod.details);
const isIdpayEnabled = useIOSelector(isIdPayEnabledSelector);
if (isMethodExpired) {
diff --git a/ts/features/walletV3/common/utils/index.ts b/ts/features/walletV3/common/utils/index.ts
index 67866723415..812c3a3b7b6 100644
--- a/ts/features/walletV3/common/utils/index.ts
+++ b/ts/features/walletV3/common/utils/index.ts
@@ -1,25 +1,20 @@
-import _ from "lodash";
-import * as O from "fp-ts/lib/Option";
import {
IOLogoPaymentType,
IOPaymentLogos,
ListItemTransactionStatusWithBadge
} from "@pagopa/io-app-design-system";
-import I18n from "i18n-js";
+import * as O from "fp-ts/lib/Option";
import { pipe } from "fp-ts/lib/function";
-
-import { isExpiredDate } from "../../../../utils/dates";
+import I18n from "i18n-js";
+import _ from "lodash";
+import { Bundle } from "../../../../../definitions/pagopa/ecommerce/Bundle";
import { ServiceNameEnum } from "../../../../../definitions/pagopa/walletv3/ServiceName";
-import { PaymentSupportStatus } from "../../../../types/paymentMethodCapabilities";
-import {
- TypeEnum,
- WalletInfoDetails,
- WalletInfoDetails1
-} from "../../../../../definitions/pagopa/walletv3/WalletInfoDetails";
import { ServiceStatusEnum } from "../../../../../definitions/pagopa/walletv3/ServiceStatus";
import { WalletInfo } from "../../../../../definitions/pagopa/walletv3/WalletInfo";
+import { PaymentSupportStatus } from "../../../../types/paymentMethodCapabilities";
+import { isExpiredDate } from "../../../../utils/dates";
import { findFirstCaseInsensitive } from "../../../../utils/object";
-import { Bundle } from "../../../../../definitions/pagopa/ecommerce/Bundle";
+import { UIWalletInfoDetails } from "../../details/types/UIWalletInfoDetails";
import { WalletPaymentPspSortType } from "../../payment/types";
/**
@@ -50,16 +45,15 @@ export const getBadgeTextByTransactionStatus = (
* left if expiring date can't be evaluated
* @param paymentMethod
*/
-export const isPaymentMethodExpired = (paymentMethod: WalletInfo): boolean => {
- switch (paymentMethod.details?.type) {
- case TypeEnum.PAYPAL:
- return false;
- case TypeEnum.CARDS:
- const cardDetails = paymentMethod.details as WalletInfoDetails1;
- return isExpiredDate(cardDetails.expiryDate);
- }
- return false;
-};
+export const isPaymentMethodExpired = (
+ details?: UIWalletInfoDetails
+): boolean =>
+ pipe(
+ details?.expiryDate,
+ O.fromNullable,
+ O.map(isExpiredDate),
+ O.getOrElse(() => false)
+ );
/**
* true if the given paymentMethod supports the given walletFunction
@@ -111,25 +105,25 @@ export const isPaymentSupported = (
};
export const getPaymentLogo = (
- selectedMethod: WalletInfoDetails
+ details: UIWalletInfoDetails
): IOLogoPaymentType | undefined => {
- switch (selectedMethod.type) {
- case TypeEnum.CARDS:
- const cardsType = selectedMethod as WalletInfoDetails1;
- const { brand } = cardsType;
- return pipe(
- brand,
- findFirstCaseInsensitive(IOPaymentLogos),
- O.fold(
- () => undefined,
- ([logoName, _]) => logoName
- )
- ) as IOLogoPaymentType;
- case TypeEnum.PAYPAL:
- return "payPal";
- default:
- return undefined;
+ if (details.maskedEmail !== undefined) {
+ return "payPal";
+ } else if (details.maskedNumber !== undefined) {
+ return "bancomatPay";
+ } else if (details.maskedPan !== undefined) {
+ return pipe(
+ details.brand,
+ O.fromNullable,
+ O.chain(findFirstCaseInsensitive(IOPaymentLogos)),
+ O.fold(
+ () => undefined,
+ ([logoName, _]) => logoName
+ )
+ ) as IOLogoPaymentType;
}
+
+ return undefined;
};
export const WALLET_PAYMENT_TERMS_AND_CONDITIONS_URL =
diff --git a/ts/features/walletV3/details/screens/WalletDetailsScreen.tsx b/ts/features/walletV3/details/screens/WalletDetailsScreen.tsx
index ea637521529..64c923cf7b8 100644
--- a/ts/features/walletV3/details/screens/WalletDetailsScreen.tsx
+++ b/ts/features/walletV3/details/screens/WalletDetailsScreen.tsx
@@ -20,12 +20,7 @@ import {
walletDetailsInstrumentSelector
} from "../store";
import { walletDetailsGetInstrument } from "../store/actions";
-import {
- TypeEnum,
- WalletInfoDetails,
- WalletInfoDetails1,
- WalletInfoDetails2
-} from "../../../../../definitions/pagopa/walletv3/WalletInfoDetails";
+import { UIWalletInfoDetails } from "../types/UIWalletInfoDetails";
export type WalletDetailsScreenNavigationParams = Readonly<{
walletId: string;
@@ -36,44 +31,38 @@ export type WalletDetailsScreenRouteProps = RouteProp<
"WALLET_DETAILS_SCREEN"
>;
-const generateCardComponent = (walletDetails: WalletInfoDetails) => {
- switch (walletDetails.type) {
- case TypeEnum.PAYPAL:
- const paypalDetails = walletDetails as WalletInfoDetails2;
- return (
-
- );
- case TypeEnum.CARDS:
- default:
- const cardDetails = walletDetails as WalletInfoDetails1;
- return (
-
- );
+const generateCardComponent = (details: UIWalletInfoDetails) => {
+ if (details.maskedEmail !== undefined) {
+ return (
+
+ );
}
+
+ return (
+
+ );
};
-const generateCardHeaderTitle = (walletDetails?: WalletInfoDetails) => {
- switch (walletDetails?.type) {
- case TypeEnum.CARDS:
- const cardDetails = walletDetails as WalletInfoDetails1;
- const capitalizedCardCircuit = capitalize(
- cardDetails.brand.toLowerCase() ?? ""
- );
- return `${capitalizedCardCircuit} ••${cardDetails.maskedPan}`;
- default:
- return "";
+const generateCardHeaderTitle = (details?: UIWalletInfoDetails) => {
+ if (details?.maskedPan !== undefined) {
+ const capitalizedCardCircuit = capitalize(
+ details.brand?.toLowerCase() ?? ""
+ );
+ return `${capitalizedCardCircuit} ••${details.maskedPan}`;
}
+
+ return "";
};
/**
diff --git a/ts/features/walletV3/details/types/UIWalletInfoDetails.ts b/ts/features/walletV3/details/types/UIWalletInfoDetails.ts
new file mode 100644
index 00000000000..56f40ccbd47
--- /dev/null
+++ b/ts/features/walletV3/details/types/UIWalletInfoDetails.ts
@@ -0,0 +1,40 @@
+import * as t from "io-ts";
+import {
+ WalletInfoDetails1,
+ WalletInfoDetails2,
+ WalletInfoDetails3
+} from "../../../../../definitions/pagopa/walletv3/WalletInfoDetails";
+
+/**
+ * Transforms all required props from WalletInfoDetails1 to partial
+ */
+export const UIWalletInfoDetails1 = t.partial({
+ ...WalletInfoDetails1.types[0].props,
+ ...WalletInfoDetails1.types[0].props
+});
+
+/**
+ * Transforms all required props from WalletInfoDetails2 to partial
+ */
+export const UIWalletInfoDetails2 = t.partial({
+ ...WalletInfoDetails2.types[0].props,
+ ...WalletInfoDetails2.types[1].props
+});
+
+/**
+ * Transforms all required props from WalletInfoDetails3 to partial
+ */
+export const UIWalletInfoDetails3 = t.partial({
+ ...WalletInfoDetails3.types[0].props,
+ ...WalletInfoDetails3.types[1].props
+});
+
+/**
+ * This type is used to bypass the `type` props of {@see WalletInfoDetails}
+ */
+export const UIWalletInfoDetails = t.intersection(
+ [UIWalletInfoDetails1, UIWalletInfoDetails2, UIWalletInfoDetails3],
+ "UIWalletInfoDetails"
+);
+
+export type UIWalletInfoDetails = t.TypeOf;
diff --git a/ts/features/walletV3/payment/components/WalletPaymentConfirmContent.tsx b/ts/features/walletV3/payment/components/WalletPaymentConfirmContent.tsx
index d7c8a22c6d2..456914c71e8 100644
--- a/ts/features/walletV3/payment/components/WalletPaymentConfirmContent.tsx
+++ b/ts/features/walletV3/payment/components/WalletPaymentConfirmContent.tsx
@@ -1,4 +1,3 @@
-import React from "react";
import {
Body,
GradientScrollView,
@@ -7,35 +6,29 @@ import {
ModuleCheckout,
VSpacer
} from "@pagopa/io-app-design-system";
-import { useNavigation } from "@react-navigation/native";
import { openAuthenticationSession } from "@pagopa/io-react-native-login-utils";
+import { useNavigation } from "@react-navigation/native";
+import React from "react";
+import { Bundle } from "../../../../../definitions/pagopa/ecommerce/Bundle";
+import { PaymentRequestsGetResponse } from "../../../../../definitions/pagopa/ecommerce/PaymentRequestsGetResponse";
+import I18n from "../../../../i18n";
import {
AppParamsList,
IOStackNavigationProp
} from "../../../../navigation/params/AppParamsList";
-import { WalletPaymentRoutes } from "../navigation/routes";
+import { format } from "../../../../utils/dates";
+import { formatNumberCentsToAmount } from "../../../../utils/stringBuilder";
+import { capitalize } from "../../../../utils/strings";
import {
WALLET_PAYMENT_TERMS_AND_CONDITIONS_URL,
getPaymentLogo
} from "../../common/utils";
-import { format } from "../../../../utils/dates";
-import { capitalize } from "../../../../utils/strings";
-import { PaymentRequestsGetResponse } from "../../../../../definitions/pagopa/ecommerce/PaymentRequestsGetResponse";
-import { Bundle } from "../../../../../definitions/pagopa/ecommerce/Bundle";
-import {
- TypeEnum,
- WalletInfoDetails,
- WalletInfoDetails1,
- WalletInfoDetails2,
- WalletInfoDetails3
-} from "../../../../../definitions/pagopa/walletv3/WalletInfoDetails";
-import I18n from "../../../../i18n";
-
-import { formatNumberCentsToAmount } from "../../../../utils/stringBuilder";
+import { UIWalletInfoDetails } from "../../details/types/UIWalletInfoDetails";
+import { WalletPaymentRoutes } from "../navigation/routes";
import { WalletPaymentTotalAmount } from "./WalletPaymentTotalAmount";
export type WalletPaymentConfirmContentProps = {
- paymentMethodDetails: WalletInfoDetails;
+ paymentMethodDetails: UIWalletInfoDetails;
selectedPsp: Bundle;
paymentDetails: PaymentRequestsGetResponse;
isLoading?: boolean;
@@ -127,33 +120,26 @@ export const WalletPaymentConfirmContent = ({
);
};
-const getPaymentSubtitle = (cardDetails: WalletInfoDetails) => {
- switch (cardDetails.type) {
- case TypeEnum.CARDS:
- const cardsDetail = cardDetails as WalletInfoDetails1;
- return `${format(cardsDetail.expiryDate, "MM/YY")}`;
- case TypeEnum.PAYPAL:
- return I18n.t("wallet.onboarding.paypal.name");
- case TypeEnum.BANCOMATPAY:
- const bancomatpayDetail = cardDetails as WalletInfoDetails3;
- return `${bancomatpayDetail.bankName}`;
- default:
- return "";
+const getPaymentSubtitle = (details: UIWalletInfoDetails): string => {
+ if (details.maskedPan !== undefined) {
+ return `${format(details.expiryDate, "MM/YY")}`;
+ } else if (details.maskedEmail !== undefined) {
+ return I18n.t("wallet.onboarding.paypal.name");
+ } else if (details.maskedNumber !== undefined) {
+ return `${details.bankName}`;
}
+
+ return "";
};
-const getPaymentTitle = (cardDetails: WalletInfoDetails) => {
- switch (cardDetails.type) {
- case TypeEnum.CARDS:
- const cardsDetail = cardDetails as WalletInfoDetails1;
- return `${capitalize(cardsDetail.brand)} ••${cardsDetail.maskedPan}`;
- case TypeEnum.PAYPAL:
- const paypalDetail = cardDetails as WalletInfoDetails2;
- return `${paypalDetail.maskedEmail}`;
- case TypeEnum.BANCOMATPAY:
- const bancomatpayDetail = cardDetails as WalletInfoDetails3;
- return `${bancomatpayDetail.maskedNumber}`;
- default:
- return "";
+const getPaymentTitle = (details: UIWalletInfoDetails): string => {
+ if (details.maskedPan !== undefined) {
+ return `${capitalize(details.brand || "")} ••${details.maskedPan}`;
+ } else if (details.maskedEmail !== undefined) {
+ return `${details.maskedEmail}`;
+ } else if (details.maskedNumber !== undefined) {
+ return `${details.maskedNumber}`;
}
+
+ return "";
};
diff --git a/ts/features/walletV3/payment/saga/networking/handleWalletPaymentAuthorization.ts b/ts/features/walletV3/payment/saga/networking/handleWalletPaymentAuthorization.ts
index d634e782503..2a1a9614566 100644
--- a/ts/features/walletV3/payment/saga/networking/handleWalletPaymentAuthorization.ts
+++ b/ts/features/walletV3/payment/saga/networking/handleWalletPaymentAuthorization.ts
@@ -2,16 +2,17 @@ import * as E from "fp-ts/lib/Either";
import { pipe } from "fp-ts/lib/function";
import { call, put } from "typed-redux-saga/macro";
import { ActionType } from "typesafe-actions";
+import {
+ LanguageEnum,
+ RequestAuthorizationRequest
+} from "../../../../../../definitions/pagopa/ecommerce/RequestAuthorizationRequest";
+import { WalletDetailTypeEnum } from "../../../../../../definitions/pagopa/ecommerce/WalletDetailType";
import { SagaCallReturnType } from "../../../../../types/utils";
import { getGenericError, getNetworkError } from "../../../../../utils/errors";
import { readablePrivacyReport } from "../../../../../utils/reporters";
import { withRefreshApiCall } from "../../../../fastLogin/saga/utils";
import { PaymentClient } from "../../api/client";
import { walletPaymentAuthorization } from "../../store/actions/networking";
-import {
- LanguageEnum,
- RequestAuthorizationRequest
-} from "../../../../../../definitions/pagopa/ecommerce/RequestAuthorizationRequest";
export function* handleWalletPaymentAuthorization(
requestTransactionAuthorization: PaymentClient["requestTransactionAuthorization"],
@@ -23,7 +24,10 @@ export function* handleWalletPaymentAuthorization(
isAllCCP: true,
language: LanguageEnum.IT,
pspId: action.payload.pspId,
- walletId: action.payload.walletId
+ details: {
+ detailType: WalletDetailTypeEnum.wallet,
+ walletId: action.payload.walletId
+ }
};
const requestTransactionAuthorizationRequest =
requestTransactionAuthorization({
diff --git a/ts/features/walletV3/payment/screens/WalletPaymentPickMethodScreen.tsx b/ts/features/walletV3/payment/screens/WalletPaymentPickMethodScreen.tsx
index 23c53a5caa0..bed3a5835dc 100644
--- a/ts/features/walletV3/payment/screens/WalletPaymentPickMethodScreen.tsx
+++ b/ts/features/walletV3/payment/screens/WalletPaymentPickMethodScreen.tsx
@@ -20,7 +20,6 @@ import React, { useEffect, useMemo } from "react";
import { View } from "react-native";
import { PaymentMethodResponse } from "../../../../../definitions/pagopa/walletv3/PaymentMethodResponse";
import { WalletInfo } from "../../../../../definitions/pagopa/walletv3/WalletInfo";
-import { WalletInfoDetails1 } from "../../../../../definitions/pagopa/walletv3/WalletInfoDetails";
import { useHeaderSecondLevel } from "../../../../hooks/useHeaderSecondLevel";
import I18n from "../../../../i18n";
import {
@@ -43,6 +42,7 @@ import {
} from "../store/selectors";
import { WalletPaymentMissingMethodsError } from "../components/WalletPaymentMissingMethodsError";
import { useWalletPaymentGoBackHandler } from "../hooks/useWalletPaymentGoBackHandler";
+import { UIWalletInfoDetails } from "../../details/types/UIWalletInfoDetails";
type SavedMethodState = {
kind: "saved";
@@ -265,29 +265,29 @@ const mapGenericToRadioItem = (
const mapSavedToRadioItem = (
method: WalletInfo
): RadioItem | undefined => {
- switch (method.details?.type) {
- case "CARDS":
- const cardDetails = method.details as WalletInfoDetails1;
- return {
- id: method.walletId,
- value: `${capitalize(cardDetails.brand)} ••${cardDetails.maskedPan}`,
- startImage: getIconWithFallback(cardDetails.brand)
- };
- case "PAYPAL":
- return {
- id: method.walletId,
- value: "PayPal",
- startImage: getIconWithFallback("paypal")
- };
- case "BANCOMATPAY":
- return {
- id: method.walletId,
- value: "BANCOMAT Pay",
- startImage: getIconWithFallback("bancomatpay")
- };
- default:
- return undefined;
+ const details = method.details as UIWalletInfoDetails;
+
+ if (details.maskedPan !== undefined) {
+ return {
+ id: method.walletId,
+ value: `${capitalize(details.brand)} ••${details.maskedPan}`,
+ startImage: getIconWithFallback(details.brand)
+ };
+ } else if (details.maskedEmail !== undefined) {
+ return {
+ id: method.walletId,
+ value: "PayPal",
+ startImage: getIconWithFallback("paypal")
+ };
+ } else if (details.maskedNumber !== undefined) {
+ return {
+ id: method.walletId,
+ value: "BANCOMAT Pay",
+ startImage: getIconWithFallback("bancomatpay")
+ };
}
+
+ return undefined;
};
const isMethodDisabledForAmount = (
diff --git a/ts/navigation/components/HeaderFirstLevelHandler.tsx b/ts/navigation/components/HeaderFirstLevelHandler.tsx
index 136922219fd..4e6186f6aaf 100644
--- a/ts/navigation/components/HeaderFirstLevelHandler.tsx
+++ b/ts/navigation/components/HeaderFirstLevelHandler.tsx
@@ -166,10 +166,12 @@ export const HeaderFirstLevelHandler = () => {
type: "twoActions",
firstAction: helpAction,
backgroundColor: "dark",
+ testID: "wallet-home-header-title",
secondAction: {
icon: "add",
accessibilityLabel: I18n.t("wallet.accessibility.addElement"),
- onPress: presentWalletHomeHeaderBottomsheet
+ onPress: presentWalletHomeHeaderBottomsheet,
+ testID: "walletAddNewPaymentMethodTestId"
}
};
break;
diff --git a/ts/utils/permission.ts b/ts/utils/permission.ts
index 3837a443b45..098e301df12 100644
--- a/ts/utils/permission.ts
+++ b/ts/utils/permission.ts
@@ -11,6 +11,11 @@ export const requestIOPermission = async (
permission: RNPermissions.Permission,
rationale?: RNPermissions.Rationale
): Promise => {
+ // Be aware that some permissions may return "unavailable" event if the library
+ // documents them as supported. One notorious case is the iOS PHOTO_LIBRARY_ADD_ONLY
+ // permission. If such permission is automatically handled by the system upon request
+ // (such as PHOTO_LIBRARY_ADD_ONLY is), then you should not use this function to
+ // check nor to request such permission
const checkResult = await RNPermissions.check(permission);
if (checkResult === "granted") {
return true;
@@ -83,8 +88,10 @@ export const requestSaveToGalleryPermission = async (
RNPermissions.PERMISSIONS.ANDROID.WRITE_EXTERNAL_STORAGE,
rationale
),
- ios: requestIOPermission(
- RNPermissions.PERMISSIONS.IOS.PHOTO_LIBRARY_ADD_ONLY
- ),
+ // on iOS the permission is handled by adding NSPhotoLibraryAddUsageDescription and NSPhotoLibraryUsageDescription
+ // into the Info.plist file and the permission request is automatically handled by the system when using the
+ // Cameraroll.save method. Asking for the PHOTO_LIBRARY_ADD_ONLY permission results in an "unavailable" response
+ // from the react-native-permissions library (even if the library documentation declares that it is supported) so
+ // it cannot be used to determine the permission status.
default: Promise.resolve(true)
});