mirror of
https://github.com/mosip/inji-wallet.git
synced 2026-01-07 20:53:54 -05:00
[INJIMOB-956]: SVG rendering (#2080)
* [INJIMOB-956]: Android native module integration Signed-off-by: balachandarg-tw <balachandar.g@thoughtworks.com> * [INJIMOB-956]: iOS native module integration and react native layer Signed-off-by: balachandarg-tw <balachandar.g@thoughtworks.com> * [INJIMOB-956]: React native UI design changes Signed-off-by: balachandarg-tw <balachandar.g@thoughtworks.com> * [INJIMOB-3499]: Updating android native module integration to adapt latest chagnes in libray api Signed-off-by: balachandarg-tw <balachandar.g@thoughtworks.com> * [INJIMOB-3499]: Updated Native module integration Signed-off-by: balachandarg-tw <balachandar.g@thoughtworks.com> * [INJIMOB-956]: Update tht package dependency version Signed-off-by: balachandarg-tw <balachandar.g@thoughtworks.com> * [INJIMOB-956]: Loading before SVg rendering issue fixed Signed-off-by: balachandarg-tw <balachandar.g@thoughtworks.com> * [INJIMOB-956]: show Qr code button based on fallback image id Signed-off-by: balachandarg-tw <balachandar.g@thoughtworks.com> * [INJIMOB-956]: Update Swift package dependency Signed-off-by: balachandarg-tw <balachandar.g@thoughtworks.com> * [INJIMOB-3499]: Update Swift Package dependency to develop of Renderer library Signed-off-by: balachandarg-tw <balachandar.g@thoughtworks.com> * [INJIMOB-956]: Update in received screen Signed-off-by: balachandarg-tw <balachandar.g@thoughtworks.com> * [INJIMOB-956]: Update format in renderVC call Signed-off-by: balachandarg-tw <balachandar.g@thoughtworks.com> * [INJIMOB-956]: Change ordering of the params Signed-off-by: balachandarg-tw <balachandar.g@thoughtworks.com> * [INJIMOB-956]: Changes in caching Signed-off-by: balachandarg-tw <balachandar.g@thoughtworks.com> * [INJIMOB-956]: Updating the package dependency files Signed-off-by: balachandarg-tw <balachandar.g@thoughtworks.com> --------- Signed-off-by: balachandarg-tw <balachandar.g@thoughtworks.com>
This commit is contained in:
@@ -341,7 +341,7 @@ fileignoreconfig:
|
||||
- filename: android/app/src/main/java/io/mosip/residentapp/InjiVciClientModule.java
|
||||
checksum: 17f55840bab193bc353034445ba4fce53e1ce466e95f616c15a1351f8d2f23bc
|
||||
- filename: ios/Inji.xcworkspace/xcshareddata/swiftpm/Package.resolved
|
||||
checksum: 3cedf13dd287307d7ed29958a65127919cc3e47293c1959a91388329451dbf1e
|
||||
checksum: 252427dd3d91cc71d644c0448e07683e3b7f3ff3936b31b075691c1f1cd0ba0d
|
||||
- filename: injitest/src/main/resources/Vids.json
|
||||
checksum: 8bcffed7a6dd565ae695e1b29de0655e10bd5c5420af2718defd593a687b8817
|
||||
- filename: injitest/src/main/java/inji/utils/UpdateNetworkSettings.java
|
||||
@@ -365,7 +365,7 @@ fileignoreconfig:
|
||||
- filename: android/app/src/main/AndroidManifest.xml
|
||||
checksum: 8f4bd61770b8bb0a28859ca0f3b4b095aed4e3fb5adef435cb74b9389ff13e09
|
||||
- filename: ios/Inji.xcodeproj/project.pbxproj
|
||||
checksum: 5fd66dc8d95628ae831e86caffb46bc0ecf0f42a534ebad76774ba133b8a7907
|
||||
checksum: 5bb246fa39bc7a9994b50bc5b1505d5389d0e254a4e30cfb2a57e6a1025e9087
|
||||
- filename: screens/Settings/ReceivedCardsModal.tsx
|
||||
checksum: 6dee9153a61009b0252d294154c88d5e1b241a517c76e930b391a39d7bc52392
|
||||
- filename: components/FaceScanner/FaceCompare.tsx
|
||||
@@ -406,4 +406,8 @@ fileignoreconfig:
|
||||
checksum: 2ab5f935ea3d1ec4d109d8614c2246f40e284594288566338f185611470e6928
|
||||
- filename: components/VC/common/VCUtils.tsx
|
||||
checksum: 221b56a2aeb73f39677cdb7c7528bfe17e49092ce785431d20b2cdfffb96d9cf
|
||||
- filename: shared/vcRenderer/VcRenderer.ts
|
||||
checksum: 02f7d58fb149ecd3f10dd0bdfb6e85c4b3ae41d29a40d192056ffec0367b53c6
|
||||
- filename: components/VC/Views/VCDetailView.tsx
|
||||
checksum: 28537e64ff03c0bf9580d02fda145b49181a073c3df3f792674275a62f1e5667
|
||||
version: "1.0"
|
||||
|
||||
@@ -267,6 +267,7 @@ dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
|
||||
implementation("io.mosip:injivcrenderer-aar:0.1.0-SNAPSHOT")
|
||||
implementation("io.mosip:inji-openid4vp-aar:0.5.0-SNAPSHOT"){
|
||||
changing = true
|
||||
exclude group: 'org.bouncycastle', module: 'bcpkix-jdk15on'
|
||||
|
||||
@@ -28,6 +28,7 @@ public class InjiPackage implements ReactPackage {
|
||||
modules.add(new RNDeepLinkIntentModule(reactApplicationContext));
|
||||
modules.add(new InjiOpenID4VPModule(reactApplicationContext));
|
||||
modules.add(new RNVCVerifierModule(reactApplicationContext));
|
||||
modules.add(new RNInjiVcRendererModule(reactApplicationContext));
|
||||
return modules;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package io.mosip.residentapp;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableNativeArray;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.mosip.injivcrenderer.InjiVcRenderer;
|
||||
import io.mosip.injivcrenderer.exceptions.VcRendererExceptions;
|
||||
import io.mosip.injivcrenderer.constants.CredentialFormat;
|
||||
|
||||
public class RNInjiVcRendererModule extends ReactContextBaseJavaModule {
|
||||
private static final String MODULE_NAME = "InjiVcRenderer";
|
||||
|
||||
private InjiVcRenderer injiVcRenderer;
|
||||
|
||||
public RNInjiVcRendererModule(@Nullable ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return MODULE_NAME;
|
||||
}
|
||||
@ReactMethod
|
||||
public void init(String appId) {
|
||||
injiVcRenderer = new InjiVcRenderer(appId);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void renderVC(String credentialFormat, String wellKnown, String vcJsonString, Promise promise) {
|
||||
try {
|
||||
List<Object> results = injiVcRenderer.renderVC(
|
||||
CredentialFormat.Companion.fromValue(credentialFormat),
|
||||
wellKnown,
|
||||
vcJsonString
|
||||
);
|
||||
|
||||
WritableArray resultArray = new WritableNativeArray();
|
||||
for (Object obj : results) {
|
||||
String svg = obj.toString();
|
||||
resultArray.pushString(svg);
|
||||
}
|
||||
promise.resolve(resultArray);
|
||||
} catch (Exception e) {
|
||||
rejectWithVcRendererExceptions(e, promise);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
private static void rejectWithVcRendererExceptions(Exception e, Promise promise) {
|
||||
if (e instanceof VcRendererExceptions) {
|
||||
VcRendererExceptions ex = (VcRendererExceptions) e;
|
||||
promise.reject(ex.getErrorCode(), ex.getMessage(), ex);
|
||||
} else {
|
||||
promise.reject("ERR_UNKNOWN", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,34 +77,42 @@ export const QrCodeOverlay: React.FC<QrCodeOverlayProps> = props => {
|
||||
}, []);
|
||||
const [isQrOverlayVisible, setIsQrOverlayVisible] = useState(false);
|
||||
|
||||
const toggleQrOverlay = () => setIsQrOverlayVisible(!isQrOverlayVisible);
|
||||
const overlayVisible = props.forceVisible ?? isQrOverlayVisible;
|
||||
|
||||
const toggleQrOverlay = () => {
|
||||
if (props.onClose) props.onClose();
|
||||
else setIsQrOverlayVisible(!overlayVisible);
|
||||
};
|
||||
|
||||
return (
|
||||
qrString != '' &&
|
||||
!qrError && (
|
||||
<React.Fragment>
|
||||
<View testID="qrCodeView" style={Theme.QrCodeStyles.QrView}>
|
||||
<Pressable
|
||||
{...testIDProps('qrCodePressable')}
|
||||
accessible={false}
|
||||
onPress={toggleQrOverlay}>
|
||||
<QRCode
|
||||
{...testIDProps('qrCode')}
|
||||
size={72}
|
||||
value={qrString}
|
||||
backgroundColor={Theme.Colors.QRCodeBackgroundColor}
|
||||
ecl={DEFAULT_ECL}
|
||||
onError={onQRError}
|
||||
/>
|
||||
<View
|
||||
testID="magnifierZoom"
|
||||
style={[Theme.QrCodeStyles.magnifierZoom]}>
|
||||
{SvgImage.MagnifierZoom()}
|
||||
</View>
|
||||
</Pressable>
|
||||
{props.showInlineQr !== false && (
|
||||
<Pressable
|
||||
{...testIDProps('qrCodePressable')}
|
||||
accessible={false}
|
||||
onPress={toggleQrOverlay}>
|
||||
<QRCode
|
||||
{...testIDProps('qrCode')}
|
||||
size={72}
|
||||
value={qrString}
|
||||
backgroundColor={Theme.Colors.QRCodeBackgroundColor}
|
||||
ecl={DEFAULT_ECL}
|
||||
onError={onQRError}
|
||||
/>
|
||||
<View
|
||||
testID="magnifierZoom"
|
||||
style={[Theme.QrCodeStyles.magnifierZoom]}>
|
||||
{SvgImage.MagnifierZoom()}
|
||||
</View>
|
||||
</Pressable>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<Overlay
|
||||
isVisible={isQrOverlayVisible}
|
||||
isVisible={overlayVisible}
|
||||
onBackdropPress={toggleQrOverlay}
|
||||
overlayStyle={{padding: 1, borderRadius: 21}}>
|
||||
<Column style={Theme.QrCodeStyles.expandedQrCode}>
|
||||
@@ -161,4 +169,7 @@ export const QrCodeOverlay: React.FC<QrCodeOverlayProps> = props => {
|
||||
interface QrCodeOverlayProps {
|
||||
verifiableCredential: VerifiableCredential;
|
||||
meta: VCMetadata;
|
||||
showInlineQr?: boolean;
|
||||
forceVisible?: boolean;
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {useTranslation} from 'react-i18next';
|
||||
import Icon from 'react-native-vector-icons/FontAwesome';
|
||||
import Feather from 'react-native-vector-icons/Feather';
|
||||
import { Image, ImageBackground, ImageBackgroundProps, TouchableOpacity, View } from 'react-native';
|
||||
import Feather from 'react-native-vector-icons/Feather';
|
||||
import {
|
||||
Dimensions,
|
||||
Image,
|
||||
ImageBackground,
|
||||
ImageBackgroundProps,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import {ActivityIndicator} from '../../ui/ActivityIndicator';
|
||||
|
||||
import {
|
||||
Credential,
|
||||
CredentialWrapper,
|
||||
@@ -10,26 +19,28 @@ import {
|
||||
VerifiableCredentialData,
|
||||
WalletBindingResponse,
|
||||
} from '../../../machines/VerifiableCredential/VCMetaMachine/vc';
|
||||
import { Button, Column, Row, Text } from '../../ui';
|
||||
import { Theme } from '../../ui/styleUtils';
|
||||
import { QrCodeOverlay } from '../../QrCodeOverlay';
|
||||
import { SvgImage } from '../../ui/svg';
|
||||
import { isActivationNeeded } from '../../../shared/openId4VCI/Utils';
|
||||
import {Button, Column, Row, Text} from '../../ui';
|
||||
import {Theme} from '../../ui/styleUtils';
|
||||
import {QrCodeOverlay} from '../../QrCodeOverlay';
|
||||
import {SvgImage} from '../../ui/svg';
|
||||
import {isActivationNeeded} from '../../../shared/openId4VCI/Utils';
|
||||
import {
|
||||
BOTTOM_SECTION_FIELDS_WITH_DETAILED_ADDRESS_FIELDS,
|
||||
DETAIL_VIEW_BOTTOM_SECTION_FIELDS,
|
||||
Display,
|
||||
fieldItemIterator,
|
||||
} from '../common/VCUtils';
|
||||
import { VCFormat } from '../../../shared/VCFormat';
|
||||
import {VCFormat} from '../../../shared/VCFormat';
|
||||
|
||||
import testIDProps from '../../../shared/commonUtil';
|
||||
import { ShareableInfoModal } from './ShareableInfoModal';
|
||||
import {ShareableInfoModal} from './ShareableInfoModal';
|
||||
import {SvgCss} from 'react-native-svg/css';
|
||||
import {QR_IMAGE_ID} from '../../../shared/constants';
|
||||
|
||||
const getProfileImage = (face: any) => {
|
||||
if (face) {
|
||||
return (
|
||||
<Image source={{ uri: face }} style={Theme.Styles.detailedViewImage} />
|
||||
<Image source={{uri: face}} style={Theme.Styles.detailedViewImage} />
|
||||
);
|
||||
}
|
||||
return <></>;
|
||||
@@ -38,12 +49,38 @@ const getProfileImage = (face: any) => {
|
||||
export const VCDetailView: React.FC<VCItemDetailsProps> = (
|
||||
props: VCItemDetailsProps,
|
||||
) => {
|
||||
const { t } = useTranslation('VcDetails');
|
||||
const {t} = useTranslation('VcDetails');
|
||||
const logo = props.verifiableCredentialData.issuerLogo;
|
||||
const face = props.verifiableCredentialData.face;
|
||||
const verifiableCredential = props.credential;
|
||||
const wellknownDisplayProperty = new Display(props.wellknown);
|
||||
|
||||
const [svgAspectRatio, setSvgAspectRatio] = useState<number | null>(null);
|
||||
|
||||
const {width: deviceWidth} = Dimensions.get('window');
|
||||
const targetHeight = svgAspectRatio
|
||||
? deviceWidth * svgAspectRatio
|
||||
: deviceWidth * 0.7;
|
||||
|
||||
const svgTemplate =
|
||||
Array.isArray(props.svgTemplate) && props.svgTemplate.length > 0
|
||||
? props.svgTemplate[0]
|
||||
: null;
|
||||
|
||||
useEffect(() => {
|
||||
if (svgTemplate) {
|
||||
const match = svgTemplate.match(/viewBox="0 0 (\d+) (\d+)"/);
|
||||
if (match) {
|
||||
const [, w, h] = match.map(Number);
|
||||
setSvgAspectRatio(h / w);
|
||||
} else {
|
||||
setSvgAspectRatio(null);
|
||||
}
|
||||
} else {
|
||||
setSvgAspectRatio(null);
|
||||
}
|
||||
}, [svgTemplate]);
|
||||
|
||||
const shouldShowHrLine = verifiableCredential => {
|
||||
let availableFieldNames: string[] = [];
|
||||
if (props.verifiableCredentialData.vcMetadata.format === VCFormat.ldp_vc) {
|
||||
@@ -53,7 +90,10 @@ export const VCDetailView: React.FC<VCItemDetailsProps> = (
|
||||
} else if (
|
||||
props.verifiableCredentialData.vcMetadata.format === VCFormat.mso_mdoc
|
||||
) {
|
||||
const namespaces = verifiableCredential['issuerSigned']?.['nameSpaces'] ?? verifiableCredential['nameSpaces'] ?? {};
|
||||
const namespaces =
|
||||
verifiableCredential['issuerSigned']?.['nameSpaces'] ??
|
||||
verifiableCredential['nameSpaces'] ??
|
||||
{};
|
||||
Object.keys(namespaces).forEach(namespace => {
|
||||
(namespaces[namespace] as Array<Object>).forEach(element => {
|
||||
availableFieldNames.push(
|
||||
@@ -61,11 +101,13 @@ export const VCDetailView: React.FC<VCItemDetailsProps> = (
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
else if (
|
||||
props.verifiableCredentialData.vcMetadata.format === VCFormat.vc_sd_jwt || props.verifiableCredentialData.vcMetadata.format === VCFormat.dc_sd_jwt
|
||||
} else if (
|
||||
props.verifiableCredentialData.vcMetadata.format === VCFormat.vc_sd_jwt ||
|
||||
props.verifiableCredentialData.vcMetadata.format === VCFormat.dc_sd_jwt
|
||||
) {
|
||||
availableFieldNames = Object.keys(verifiableCredential?.fullResolvedPayload);
|
||||
availableFieldNames = Object.keys(
|
||||
verifiableCredential?.fullResolvedPayload,
|
||||
);
|
||||
}
|
||||
for (const fieldName of availableFieldNames) {
|
||||
if (
|
||||
@@ -77,95 +119,146 @@ export const VCDetailView: React.FC<VCItemDetailsProps> = (
|
||||
|
||||
return false;
|
||||
};
|
||||
const [showQrOverlay, setShowQrOverlay] = useState(false);
|
||||
|
||||
const [shareModalVisible, setShareModalVisible] = useState(false);
|
||||
|
||||
if (props.loadingSvg) {
|
||||
return (
|
||||
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
|
||||
<ActivityIndicator />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Column scroll>
|
||||
<Column fill>
|
||||
<Column
|
||||
padding="10 10 3 10"
|
||||
backgroundColor={Theme.Colors.DetailedViewBackground}>
|
||||
<ImageBackground
|
||||
imageStyle={Theme.Styles.vcDetailBg}
|
||||
resizeMethod="scale"
|
||||
resizeMode="stretch"
|
||||
style={[
|
||||
Theme.Styles.openCardBgContainer,
|
||||
wellknownDisplayProperty.getBackgroundColor(),
|
||||
]}
|
||||
source={
|
||||
wellknownDisplayProperty.getBackgroundImage(
|
||||
Theme.OpenCard,
|
||||
) as ImageBackgroundProps
|
||||
}>
|
||||
<Row padding="14 14 0 14" margin="0 0 0 0">
|
||||
<Column crossAlign="center">
|
||||
{getProfileImage(face)}
|
||||
<QrCodeOverlay
|
||||
verifiableCredential={
|
||||
props.credentialWrapper as unknown as VerifiableCredential
|
||||
}
|
||||
meta={props.verifiableCredentialData.vcMetadata}
|
||||
/>
|
||||
<Column
|
||||
width={80}
|
||||
height={59}
|
||||
crossAlign="center"
|
||||
margin="12 0 0 0">
|
||||
<Image
|
||||
{...testIDProps('issuerLogo')}
|
||||
src={logo?.url}
|
||||
alt={logo?.alt_text}
|
||||
style={Theme.Styles.issuerLogo}
|
||||
resizeMethod="scale"
|
||||
resizeMode="contain"
|
||||
{svgTemplate ? (
|
||||
<Column padding="30 0 0 0">
|
||||
<Column padding="0 16 0 16">
|
||||
<SvgCss
|
||||
xml={svgTemplate}
|
||||
width="100%"
|
||||
height={targetHeight}
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
style={{backgroundColor: 'transparent'}}
|
||||
/>
|
||||
</Column>
|
||||
{svgTemplate?.includes(QR_IMAGE_ID) && (
|
||||
<Button
|
||||
testID="zoomQrCode"
|
||||
title="Tap to zoom QR Code"
|
||||
type="gradient"
|
||||
size="Large"
|
||||
onPress={() => setShowQrOverlay(true)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showQrOverlay && (
|
||||
<QrCodeOverlay
|
||||
verifiableCredential={
|
||||
props.credentialWrapper as unknown as VerifiableCredential
|
||||
}
|
||||
meta={props.verifiableCredentialData.vcMetadata}
|
||||
showInlineQr={false}
|
||||
forceVisible={true}
|
||||
onClose={() => setShowQrOverlay(false)}
|
||||
/>
|
||||
)}
|
||||
</Column>
|
||||
) : (
|
||||
<Column fill>
|
||||
<Column
|
||||
padding="10 10 3 10"
|
||||
backgroundColor={Theme.Colors.DetailedViewBackground}>
|
||||
<ImageBackground
|
||||
imageStyle={Theme.Styles.vcDetailBg}
|
||||
resizeMethod="scale"
|
||||
resizeMode="stretch"
|
||||
style={[
|
||||
Theme.Styles.openCardBgContainer,
|
||||
wellknownDisplayProperty.getBackgroundColor(),
|
||||
]}
|
||||
source={
|
||||
wellknownDisplayProperty.getBackgroundImage(
|
||||
Theme.OpenCard,
|
||||
) as ImageBackgroundProps
|
||||
}>
|
||||
<Row padding="14 14 0 14" margin="0 0 0 0">
|
||||
<Column crossAlign="center">
|
||||
{getProfileImage(face)}
|
||||
<QrCodeOverlay
|
||||
verifiableCredential={
|
||||
props.credentialWrapper as unknown as VerifiableCredential
|
||||
}
|
||||
meta={props.verifiableCredentialData.vcMetadata}
|
||||
showInlineQr={true}
|
||||
/>
|
||||
<Column
|
||||
width={80}
|
||||
height={59}
|
||||
crossAlign="center"
|
||||
margin="12 0 0 0">
|
||||
<Image
|
||||
{...testIDProps('issuerLogo')}
|
||||
src={logo?.url}
|
||||
alt={logo?.alt_text}
|
||||
style={Theme.Styles.issuerLogo}
|
||||
resizeMethod="scale"
|
||||
resizeMode="contain"
|
||||
/>
|
||||
</Column>
|
||||
</Column>
|
||||
</Column>
|
||||
<Column
|
||||
align="space-evenly"
|
||||
margin={'0 0 0 24'}
|
||||
style={{ flex: 1 }}>
|
||||
{fieldItemIterator(
|
||||
props.fields,
|
||||
props.wellknownFieldsFlag,
|
||||
verifiableCredential,
|
||||
props.wellknown,
|
||||
wellknownDisplayProperty,
|
||||
false,
|
||||
props,
|
||||
)}
|
||||
</Column>
|
||||
</Row>
|
||||
<>
|
||||
<View
|
||||
style={[
|
||||
Theme.Styles.hrLine,
|
||||
{
|
||||
borderBottomColor: wellknownDisplayProperty.getTextColor(
|
||||
Theme.Styles.hrLine.borderBottomColor,
|
||||
),
|
||||
},
|
||||
]}></View>
|
||||
<Column padding="0 14 14 14">
|
||||
{shouldShowHrLine(verifiableCredential) &&
|
||||
fieldItemIterator(
|
||||
DETAIL_VIEW_BOTTOM_SECTION_FIELDS,
|
||||
true,
|
||||
<Column
|
||||
align="space-evenly"
|
||||
margin={'0 0 0 24'}
|
||||
style={{flex: 1}}>
|
||||
{fieldItemIterator(
|
||||
props.fields,
|
||||
props.wellknownFieldsFlag,
|
||||
verifiableCredential,
|
||||
props.wellknown,
|
||||
wellknownDisplayProperty,
|
||||
true,
|
||||
false,
|
||||
props,
|
||||
)}
|
||||
</Column>
|
||||
{(props.credential.disclosedKeys != null) && (<DisclosureInfoNote />)}
|
||||
</>
|
||||
</ImageBackground>
|
||||
</Column>
|
||||
</Row>
|
||||
<>
|
||||
<View
|
||||
style={[
|
||||
Theme.Styles.hrLine,
|
||||
{
|
||||
borderBottomColor:
|
||||
wellknownDisplayProperty.getTextColor(
|
||||
Theme.Styles.hrLine.borderBottomColor,
|
||||
),
|
||||
},
|
||||
]}></View>
|
||||
<Column padding="0 14 14 14">
|
||||
{shouldShowHrLine(verifiableCredential) &&
|
||||
fieldItemIterator(
|
||||
DETAIL_VIEW_BOTTOM_SECTION_FIELDS,
|
||||
true,
|
||||
verifiableCredential,
|
||||
props.wellknown,
|
||||
wellknownDisplayProperty,
|
||||
true,
|
||||
props,
|
||||
)}
|
||||
</Column>
|
||||
{props.credential.disclosedKeys != null && (
|
||||
<DisclosureInfoNote />
|
||||
)}
|
||||
</>
|
||||
</ImageBackground>
|
||||
</Column>
|
||||
</Column>
|
||||
</Column>
|
||||
)}
|
||||
</Column>
|
||||
{props.vcHasImage &&
|
||||
{!svgTemplate &&
|
||||
props.vcHasImage &&
|
||||
!props.verifiableCredentialData?.vcMetadata.isExpired && (
|
||||
<View
|
||||
style={{
|
||||
@@ -245,9 +338,8 @@ export const VCDetailView: React.FC<VCItemDetailsProps> = (
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
{
|
||||
(props.credential.disclosedKeys != null) && (<View
|
||||
|
||||
{props.credential.disclosedKeys != null && (
|
||||
<View
|
||||
style={{
|
||||
padding: 16,
|
||||
backgroundColor: Theme.Colors.DetailedViewBackground,
|
||||
@@ -258,8 +350,8 @@ export const VCDetailView: React.FC<VCItemDetailsProps> = (
|
||||
<TouchableOpacity
|
||||
onPress={() => setShareModalVisible(true)}
|
||||
testID="viewShareableInfoLink"
|
||||
style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
|
||||
<Feather name="eye" size={20} color={"#007AFF"} />
|
||||
style={{flexDirection: 'row', alignItems: 'center', gap: 8}}>
|
||||
<Feather name="eye" size={20} color={'#007AFF'} />
|
||||
<Text
|
||||
style={{
|
||||
color: '#007AFF',
|
||||
@@ -269,34 +361,30 @@ export const VCDetailView: React.FC<VCItemDetailsProps> = (
|
||||
{t('View Shareable Information')}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>)}
|
||||
</View>
|
||||
)}
|
||||
|
||||
<ShareableInfoModal
|
||||
isVisible={shareModalVisible}
|
||||
onDismiss={() => setShareModalVisible(false)}
|
||||
disclosedPaths={Array.from(props.credential.disclosedKeys ?? {}) || []}
|
||||
/>
|
||||
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const DisclosureInfoNote = () => {
|
||||
const { t } = useTranslation('VcDetails');
|
||||
const {t} = useTranslation('VcDetails');
|
||||
return (
|
||||
<View
|
||||
style={Theme.DisclosureInfo.view}>
|
||||
<View style={Theme.DisclosureInfo.view}>
|
||||
<Row align="flex-start">
|
||||
<Icon
|
||||
name="share-square-o"
|
||||
size={18}
|
||||
color={Theme.Colors.DetailsLabel}
|
||||
style={{ marginTop: 2, marginRight: 8 }}
|
||||
style={{marginTop: 2, marginRight: 8}}
|
||||
/>
|
||||
<Text
|
||||
style={Theme.DisclosureInfo.text}>
|
||||
{t('disclosureInfoNote')}
|
||||
</Text>
|
||||
<Text style={Theme.DisclosureInfo.text}>{t('disclosureInfoNote')}</Text>
|
||||
</Row>
|
||||
</View>
|
||||
);
|
||||
@@ -313,4 +401,7 @@ export interface VCItemDetailsProps {
|
||||
onBinding?: () => void;
|
||||
activeTab?: Number;
|
||||
vcHasImage: boolean;
|
||||
svgTemplate?: string[] | null;
|
||||
svgRendererError?: string | null;
|
||||
loadingSvg?: string | null;
|
||||
}
|
||||
|
||||
@@ -36,8 +36,11 @@
|
||||
9CCCA19E2CF87A8400D5A461 /* securekeystore in Frameworks */ = {isa = PBXBuildFile; productRef = 9CCCA19D2CF87A8400D5A461 /* securekeystore */; };
|
||||
B18059E884C0ABDD17F3DC3D /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC715A2D49A985799AEE119 /* ExpoModulesProvider.swift */; };
|
||||
BB2F792D24A3F905000567C9 /* Expo.plist in Resources */ = {isa = PBXBuildFile; fileRef = BB2F792C24A3F905000567C9 /* Expo.plist */; };
|
||||
C339223B2E79A536004A01EC /* InjiVcRenderer in Frameworks */ = {isa = PBXBuildFile; productRef = C339223A2E79A536004A01EC /* InjiVcRenderer */; };
|
||||
C3F18B1A2E320C85007DBE73 /* OpenID4VP in Frameworks */ = {isa = PBXBuildFile; productRef = C3F18B192E320C85007DBE73 /* OpenID4VP */; };
|
||||
C3F18B1D2E320C9A007DBE73 /* VCIClient in Frameworks */ = {isa = PBXBuildFile; productRef = C3F18B1C2E320C9A007DBE73 /* VCIClient */; };
|
||||
C3F6A9DD2E661896006C9904 /* RNInjiVcRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3F6A9DC2E66188D006C9904 /* RNInjiVcRenderer.swift */; };
|
||||
C3F6A9DF2E661903006C9904 /* RNInjiVcRenderer.m in Sources */ = {isa = PBXBuildFile; fileRef = C3F6A9DE2E6618F6006C9904 /* RNInjiVcRenderer.m */; };
|
||||
E86208152C0335C5007C3E24 /* RNVCIClientModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = E86208142C0335C5007C3E24 /* RNVCIClientModule.swift */; };
|
||||
E86208172C0335EC007C3E24 /* RNVCIClientModule.m in Sources */ = {isa = PBXBuildFile; fileRef = E86208162C0335EC007C3E24 /* RNVCIClientModule.m */; };
|
||||
FD8D20B92BBAACDF009AD01C /* Fontisto.ttf in Resources */ = {isa = PBXBuildFile; fileRef = FD8D20A62BBAACDF009AD01C /* Fontisto.ttf */; };
|
||||
@@ -92,6 +95,8 @@
|
||||
9C7CDF422C7CC13500243A9A /* RNSecureKeystoreModule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNSecureKeystoreModule.m; sourceTree = "<group>"; };
|
||||
AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = Inji/SplashScreen.storyboard; sourceTree = "<group>"; };
|
||||
BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = "<group>"; };
|
||||
C3F6A9DC2E66188D006C9904 /* RNInjiVcRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNInjiVcRenderer.swift; sourceTree = "<group>"; };
|
||||
C3F6A9DE2E6618F6006C9904 /* RNInjiVcRenderer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNInjiVcRenderer.m; sourceTree = "<group>"; };
|
||||
D98B96A488E54CBDB286B26F /* noop-file.swift */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.swift; name = "noop-file.swift"; path = "Inji/noop-file.swift"; sourceTree = "<group>"; };
|
||||
E86208142C0335C5007C3E24 /* RNVCIClientModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNVCIClientModule.swift; sourceTree = "<group>"; };
|
||||
E86208162C0335EC007C3E24 /* RNVCIClientModule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNVCIClientModule.m; sourceTree = "<group>"; };
|
||||
@@ -124,6 +129,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
C3F18B1D2E320C9A007DBE73 /* VCIClient in Frameworks */,
|
||||
C339223B2E79A536004A01EC /* InjiVcRenderer in Frameworks */,
|
||||
9C4850432C3E5873002ECBD5 /* ios-tuvali-library in Frameworks */,
|
||||
9CAE74EE2E2E38F800C2532C /* pixelpass in Frameworks */,
|
||||
9CCCA19E2CF87A8400D5A461 /* securekeystore in Frameworks */,
|
||||
@@ -138,6 +144,8 @@
|
||||
13B07FAE1A68108700A75B9A /* Inji */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C3F6A9DE2E6618F6006C9904 /* RNInjiVcRenderer.m */,
|
||||
C3F6A9DC2E66188D006C9904 /* RNInjiVcRenderer.swift */,
|
||||
BB2F792B24A3F905000567C9 /* Supporting */,
|
||||
008F07F21AC5B25A0029DE68 /* main.jsbundle */,
|
||||
13B07FAF1A68108700A75B9A /* AppDelegate.h */,
|
||||
@@ -317,6 +325,7 @@
|
||||
9CAE74ED2E2E38F800C2532C /* pixelpass */,
|
||||
C3F18B192E320C85007DBE73 /* OpenID4VP */,
|
||||
C3F18B1C2E320C9A007DBE73 /* VCIClient */,
|
||||
C339223A2E79A536004A01EC /* InjiVcRenderer */,
|
||||
);
|
||||
productName = Inji;
|
||||
productReference = 13B07F961A680F5B00A75B9A /* Inji.app */;
|
||||
@@ -350,6 +359,7 @@
|
||||
9CAE74EC2E2E38F800C2532C /* XCRemoteSwiftPackageReference "pixelpass-ios-swift" */,
|
||||
C3F18B182E320C85007DBE73 /* XCRemoteSwiftPackageReference "inji-openid4vp-ios-swift" */,
|
||||
C3F18B1B2E320C9A007DBE73 /* XCRemoteSwiftPackageReference "inji-vci-client-ios-swift" */,
|
||||
C33922392E79A536004A01EC /* XCRemoteSwiftPackageReference "inji-vc-renderer-ios-swift" */,
|
||||
);
|
||||
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
|
||||
projectDirPath = "";
|
||||
@@ -564,6 +574,7 @@
|
||||
1EED69FD2DA914D00042EAFC /* RNDeepLinkIntentModule.m in Sources */,
|
||||
1E6875ED2CA5550F0086D870 /* RNOpenID4VPModule.swift in Sources */,
|
||||
1E55C20B2DB120C2009DF38B /* RNDeepLinkIntentModule.swift in Sources */,
|
||||
C3F6A9DD2E661896006C9904 /* RNInjiVcRenderer.swift in Sources */,
|
||||
9C48504F2C3E59B5002ECBD5 /* RNVersionModule.swift in Sources */,
|
||||
13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */,
|
||||
9C0E86BB2BEE36C300E9F9F6 /* RNPixelpassModule.m in Sources */,
|
||||
@@ -583,6 +594,7 @@
|
||||
9C48504D2C3E59B5002ECBD5 /* RNWalletModule.m in Sources */,
|
||||
B18059E884C0ABDD17F3DC3D /* ExpoModulesProvider.swift in Sources */,
|
||||
9C4850512C3E59B5002ECBD5 /* RNVersionModule.m in Sources */,
|
||||
C3F6A9DF2E661903006C9904 /* RNInjiVcRenderer.m in Sources */,
|
||||
73295844242A4AD3AA52D0BE /* noop-file.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -849,6 +861,14 @@
|
||||
version = 0.3.0;
|
||||
};
|
||||
};
|
||||
C33922392E79A536004A01EC /* XCRemoteSwiftPackageReference "inji-vc-renderer-ios-swift" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/mosip/inji-vc-renderer-ios-swift.git";
|
||||
requirement = {
|
||||
branch = develop;
|
||||
kind = branch;
|
||||
};
|
||||
};
|
||||
C3F18B182E320C85007DBE73 /* XCRemoteSwiftPackageReference "inji-openid4vp-ios-swift" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/mosip/inji-openid4vp-ios-swift.git";
|
||||
@@ -883,6 +903,11 @@
|
||||
package = 9CCCA19C2CF87A8400D5A461 /* XCRemoteSwiftPackageReference "secure-keystore-ios-swift" */;
|
||||
productName = securekeystore;
|
||||
};
|
||||
C339223A2E79A536004A01EC /* InjiVcRenderer */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = C33922392E79A536004A01EC /* XCRemoteSwiftPackageReference "inji-vc-renderer-ios-swift" */;
|
||||
productName = InjiVcRenderer;
|
||||
};
|
||||
C3F18B192E320C85007DBE73 /* OpenID4VP */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = C3F18B182E320C85007DBE73 /* XCRemoteSwiftPackageReference "inji-openid4vp-ios-swift" */;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"originHash" : "327f856a9787c0e4bf4517bc1e50ff1e45a4aee6adcc4233b1e042d022a881f0",
|
||||
"originHash" : "c97fbee22fce758b0bea2f875d3805ef3530cfb7d60c2832b0b36cf3f72da606",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "alamofire",
|
||||
@@ -73,6 +73,15 @@
|
||||
"revision" : "ca4935478360f45dd20444eac5d6bae9affc6d36"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "inji-vc-renderer-ios-swift",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/mosip/inji-vc-renderer-ios-swift.git",
|
||||
"state" : {
|
||||
"branch" : "develop",
|
||||
"revision" : "58237eae6caf198d725ecfcd397b4d6f8f2d87bd"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "inji-vci-client-ios-swift",
|
||||
"kind" : "remoteSourceControl",
|
||||
|
||||
16
ios/RNInjiVcRenderer.m
Normal file
16
ios/RNInjiVcRenderer.m
Normal file
@@ -0,0 +1,16 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "React/RCTBridgeModule.h"
|
||||
|
||||
@interface RCT_EXTERN_MODULE(InjiVcRenderer, NSObject)
|
||||
|
||||
RCT_EXTERN_METHOD(init:(NSString *)traceabilityId)
|
||||
|
||||
|
||||
RCT_EXTERN_METHOD(renderVC:(NSString *)credentialFormat
|
||||
wellKnown:(NSString *)wellKnown
|
||||
vcJsonString:(NSString *)vcJsonString
|
||||
resolver:(RCTPromiseResolveBlock)resolve
|
||||
rejecter:(RCTPromiseRejectBlock)reject)
|
||||
|
||||
|
||||
@end
|
||||
57
ios/RNInjiVcRenderer.swift
Normal file
57
ios/RNInjiVcRenderer.swift
Normal file
@@ -0,0 +1,57 @@
|
||||
import Foundation
|
||||
import React
|
||||
import InjiVcRenderer
|
||||
@objc(InjiVcRenderer)
|
||||
class RNInjiVcRenderer: NSObject, RCTBridgeModule {
|
||||
|
||||
private var vcRenderer: InjiVcRenderer?
|
||||
|
||||
@objc static func requiresMainQueueSetup() -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
static func moduleName() -> String {
|
||||
return "InjiVcRenderer"
|
||||
}
|
||||
|
||||
@objc
|
||||
func `init`(_ traceabilityId: String) {
|
||||
vcRenderer = InjiVcRenderer(traceabilityId: traceabilityId)
|
||||
}
|
||||
|
||||
|
||||
@objc(renderVC:wellKnown:vcJsonString:resolver:rejecter:)
|
||||
func renderVC(
|
||||
credentialFormat: String,
|
||||
wellKnown: String?,
|
||||
vcJsonString: String,
|
||||
resolve: @escaping RCTPromiseResolveBlock,
|
||||
reject: @escaping RCTPromiseRejectBlock
|
||||
) {
|
||||
guard let renderer = self.vcRenderer else {
|
||||
reject(nil, "InjiVcRenderer not initialized", nil)
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
let format = CredentialFormat.fromValue(credentialFormat)
|
||||
let result = try renderer.renderVC(
|
||||
credentialFormat: format,
|
||||
wellKnownJson: wellKnown,
|
||||
vcJsonString: vcJsonString
|
||||
)
|
||||
resolve(result)
|
||||
} catch {
|
||||
rejectWithVcRendererError(error, reject: reject)
|
||||
}
|
||||
}
|
||||
|
||||
func rejectWithVcRendererError(_ error: Error, reject: RCTPromiseRejectBlock) {
|
||||
if let vcRendererError = error as? VcRendererException {
|
||||
reject(vcRendererError.errorCode, vcRendererError.message, vcRendererError)
|
||||
} else {
|
||||
let nsError = NSError(domain: error.localizedDescription, code: 0)
|
||||
reject("ERR_UNKNOWN", nsError.localizedDescription, nsError)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
} from '../../components/BannerNotification';
|
||||
import {VCProcessor} from '../../components/VC/common/VCProcessor';
|
||||
import {HelpIcon} from '../../components/ui/HelpIcon';
|
||||
import VcRenderer from '../../shared/vcRenderer/VcRenderer';
|
||||
|
||||
export const ViewVcModal: React.FC<ViewVcModalProps> = props => {
|
||||
const {t} = useTranslation('ViewVcModal');
|
||||
@@ -40,6 +41,10 @@ export const ViewVcModal: React.FC<ViewVcModalProps> = props => {
|
||||
const profileImage = controller.verifiableCredentialData.face;
|
||||
const verificationStatus = controller.verificationStatus;
|
||||
const [verifiableCredential, setVerifiableCredential] = useState(null);
|
||||
const [svgTemplate, setSvgTemplate] = useState<string[] | null>(null);
|
||||
const [svgRendererError, setSvgRendererError] = useState<string[] | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
async function processVC() {
|
||||
@@ -72,6 +77,37 @@ export const ViewVcModal: React.FC<ViewVcModalProps> = props => {
|
||||
const [wellknownFieldsFlag, setWellknownFieldsFlag] = useState(false);
|
||||
const verifiableCredentialData = controller.verifiableCredentialData;
|
||||
|
||||
const [loadingSvg, setLoadingSvg] = useState<boolean>(true);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchSvg = async () => {
|
||||
try {
|
||||
setLoadingSvg(true);
|
||||
|
||||
const vcJsonString = JSON.stringify(controller.credential.credential);
|
||||
const result = await VcRenderer.getInstance().renderVC(
|
||||
controller.verifiableCredentialData.format,
|
||||
wellknown ?? null,
|
||||
vcJsonString,
|
||||
);
|
||||
|
||||
setSvgTemplate(result);
|
||||
setSvgRendererError(null);
|
||||
} catch (err: any) {
|
||||
setSvgTemplate(null);
|
||||
setSvgRendererError(err.errorCode ?? 'Unknown error');
|
||||
} finally {
|
||||
setLoadingSvg(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (controller.credential?.credential['renderMethod']) {
|
||||
requestAnimationFrame(fetchSvg);
|
||||
} else {
|
||||
setLoadingSvg(false);
|
||||
}
|
||||
}, [controller.credential?.credential, wellknown]);
|
||||
|
||||
useEffect(() => {
|
||||
getDetailedViewFields(
|
||||
verifiableCredentialData.vcMetadata.issuerHost as string,
|
||||
@@ -79,13 +115,15 @@ export const ViewVcModal: React.FC<ViewVcModalProps> = props => {
|
||||
DETAIL_VIEW_DEFAULT_FIELDS,
|
||||
verifiableCredentialData.vcMetadata.format,
|
||||
verifiableCredentialData.vcMetadata.issuerHost,
|
||||
).then(response => {
|
||||
setWellknown(response.matchingCredentialIssuerMetadata);
|
||||
setFields(response.fields);
|
||||
setWellknownFieldsFlag(response.wellknownFieldsFlag);
|
||||
}).catch(error => {
|
||||
console.error('Error fetching well-known fields:', error);
|
||||
});
|
||||
)
|
||||
.then(response => {
|
||||
setWellknown(response.matchingCredentialIssuerMetadata);
|
||||
setFields(response.fields);
|
||||
setWellknownFieldsFlag(response.wellknownFieldsFlag);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching well-known fields:', error);
|
||||
});
|
||||
}, [verifiableCredentialData?.wellKnown]);
|
||||
|
||||
const headerRight = flow => {
|
||||
@@ -164,6 +202,9 @@ export const ViewVcModal: React.FC<ViewVcModalProps> = props => {
|
||||
walletBindingResponse={controller.walletBindingResponse}
|
||||
activeTab={props.activeTab}
|
||||
vcHasImage={profileImage !== undefined}
|
||||
svgTemplate={svgTemplate}
|
||||
svgRendererError={svgRendererError}
|
||||
loadingSvg={loadingSvg}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import {SvgImage} from '../../components/ui/svg';
|
||||
import {DETAIL_VIEW_DEFAULT_FIELDS} from '../../components/VC/common/VCUtils';
|
||||
import {getDetailedViewFields} from '../../shared/openId4VCI/Utils';
|
||||
import {VCProcessor} from '../../components/VC/common/VCProcessor';
|
||||
import VcRenderer from '../../shared/vcRenderer/VcRenderer';
|
||||
|
||||
export const ReceiveVcScreen: React.FC = () => {
|
||||
const {t} = useTranslation('ReceiveVcScreen');
|
||||
@@ -26,6 +27,12 @@ export const ReceiveVcScreen: React.FC = () => {
|
||||
|
||||
const [credential, setCredential] = useState(null);
|
||||
|
||||
const [svgTemplate, setSvgTemplate] = useState<string[] | null>(null);
|
||||
const [svgRendererError, setSvgRendererError] = useState<string[] | null>(
|
||||
null,
|
||||
);
|
||||
const [loadingSvg, setLoadingSvg] = useState<boolean>(true);
|
||||
|
||||
useEffect(() => {
|
||||
async function processVC() {
|
||||
if (controller.credential) {
|
||||
@@ -40,6 +47,35 @@ export const ReceiveVcScreen: React.FC = () => {
|
||||
processVC();
|
||||
}, [controller.credential]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchSvg = async () => {
|
||||
try {
|
||||
setLoadingSvg(true);
|
||||
|
||||
const vcJsonString = JSON.stringify(controller.credential.credential);
|
||||
const result = await VcRenderer.getInstance().renderVC(
|
||||
verifiableCredentialData.vcMetadata.format,
|
||||
wellknown ?? null,
|
||||
vcJsonString,
|
||||
);
|
||||
|
||||
setSvgTemplate(result);
|
||||
setSvgRendererError(null);
|
||||
} catch (err: any) {
|
||||
setSvgTemplate(null);
|
||||
setSvgRendererError(err.errorCode ?? 'Unknown error');
|
||||
} finally {
|
||||
setLoadingSvg(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (controller.credential?.credential['renderMethod']) {
|
||||
requestAnimationFrame(fetchSvg);
|
||||
} else {
|
||||
setLoadingSvg(false);
|
||||
}
|
||||
}, [controller.credential?.credential, wellknown]);
|
||||
|
||||
useEffect(() => {
|
||||
getDetailedViewFields(
|
||||
verifiableCredentialData.vcMetadata.issuerHost,
|
||||
@@ -78,6 +114,9 @@ export const ReceiveVcScreen: React.FC = () => {
|
||||
isBindingPending={false}
|
||||
activeTab={1}
|
||||
vcHasImage={profileImage !== undefined}
|
||||
svgTemplate={svgTemplate}
|
||||
svgRendererError={svgRendererError}
|
||||
loadingSvg={loadingSvg}
|
||||
/>
|
||||
</Column>
|
||||
<Column padding="0 24" margin="32 0 0 0">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {sha256} from '@noble/hashes/sha256';
|
||||
import {VCMetadata} from './VCMetadata';
|
||||
import {NETWORK_REQUEST_FAILED} from './constants';
|
||||
import {CACHE_TTL, NETWORK_REQUEST_FAILED} from './constants';
|
||||
import {groupBy} from './javascript';
|
||||
import {Issuers} from './openId4VCI/Utils';
|
||||
import {v4 as uuid} from 'uuid';
|
||||
@@ -135,3 +135,7 @@ export const createCacheObject = (response: any) => {
|
||||
cachedTime: currentTime,
|
||||
};
|
||||
};
|
||||
|
||||
export const isCacheExpired = (timestamp: number) => {
|
||||
return Date.now() - timestamp >= CACHE_TTL;
|
||||
};
|
||||
|
||||
@@ -201,3 +201,5 @@ export const OVP_ERROR_CODE = {
|
||||
NO_MATCHING_VCS: 'invalid_transaction_data',
|
||||
DECLINED: 'access_denied',
|
||||
};
|
||||
|
||||
export const QR_IMAGE_ID = 'qrCodeImage';
|
||||
|
||||
86
shared/vcRenderer/VcRenderer.ts
Normal file
86
shared/vcRenderer/VcRenderer.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import {NativeModules} from 'react-native';
|
||||
import {MMKVLoader} from 'react-native-mmkv-storage';
|
||||
import {CACHE_TTL} from '../constants';
|
||||
import {__AppId} from '../GlobalVariables';
|
||||
import {isCacheExpired} from '../Utils';
|
||||
|
||||
const MMKV = new MMKVLoader().initialize();
|
||||
const CACHE_KEY_PREFIX = 'vc_renderer_svg_';
|
||||
|
||||
type CachedSvg = {
|
||||
data: string[];
|
||||
timestamp: number;
|
||||
};
|
||||
|
||||
class VcRenderer {
|
||||
private static instance: VcRenderer;
|
||||
private InjiVcRenderer = NativeModules.InjiVcRenderer;
|
||||
|
||||
private constructor() {
|
||||
this.InjiVcRenderer.init(__AppId.getValue());
|
||||
}
|
||||
|
||||
static getInstance(): VcRenderer {
|
||||
if (!VcRenderer.instance) {
|
||||
VcRenderer.instance = new VcRenderer();
|
||||
}
|
||||
return VcRenderer.instance;
|
||||
}
|
||||
|
||||
private createCacheKey(vcId: string) {
|
||||
return `${CACHE_KEY_PREFIX}${vcId}`;
|
||||
}
|
||||
|
||||
async renderVC(
|
||||
credentialFormat: string,
|
||||
wellKnown: string,
|
||||
vcJson: string,
|
||||
): Promise<string[]> {
|
||||
const vc = JSON.parse(vcJson);
|
||||
const vcId = vc.id ?? JSON.stringify(vc);
|
||||
const cacheKey = this.createCacheKey(vcId);
|
||||
const cachedRaw = await MMKV.getItem(cacheKey);
|
||||
if (cachedRaw && typeof cachedRaw === 'string') {
|
||||
try {
|
||||
const cached: CachedSvg = JSON.parse(cachedRaw);
|
||||
|
||||
if (!isCacheExpired(cached.timestamp)) {
|
||||
return cached.data;
|
||||
} else {
|
||||
await this.clearCache(vcId);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('::::failed to parse cached SVG, ignoring::::', e);
|
||||
await this.clearCache(vcId);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const result: string[] = await this.InjiVcRenderer.renderVC(
|
||||
credentialFormat,
|
||||
wellKnown ? JSON.stringify(wellKnown) : null,
|
||||
vcJson,
|
||||
);
|
||||
|
||||
if (result && result.length > 0) {
|
||||
const payload: CachedSvg = {
|
||||
data: result,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
await MMKV.setItem(cacheKey, JSON.stringify(payload));
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (rendererError) {
|
||||
await this.clearCache(vcId);
|
||||
throw rendererError;
|
||||
}
|
||||
}
|
||||
|
||||
async clearCache(vcId: string) {
|
||||
const cacheKey = this.createCacheKey(vcId);
|
||||
await MMKV.removeItem(cacheKey);
|
||||
}
|
||||
}
|
||||
|
||||
export default VcRenderer;
|
||||
Reference in New Issue
Block a user