[INJIMOB-3532] add sd jwt vp support (#2082)

* [INJIMOB-3513] add sd jwt vp support

Signed-off-by: Abhishek Paul <paul.apaul.abhishek.ap@gmail.com>

* [INJIMOB-3513] add bridge logic and sd-jwt signing for ovp

Signed-off-by: Abhishek Paul <paul.apaul.abhishek.ap@gmail.com>

* [INJIMOB-3532] add: support of OVP share in iOS

Signed-off-by: KiruthikaJeyashankar <kiruthikavjshankar@gmail.com>

* [INJIMOB-3532] add sd-jwt ovp ui

Signed-off-by: Abhishek Paul <paul.apaul.abhishek.ap@gmail.com>

* [INJIMOB-3532] refactor: optimize wallet_metadata creation logic

Signed-off-by: KiruthikaJeyashankar <kiruthikavjshankar@gmail.com>

* [INJIMOB-3532] refactor: fixed alignement issues and crash bug

Signed-off-by: Abhishek Paul <paul.apaul.abhishek.ap@gmail.com>

---------

Signed-off-by: Abhishek Paul <paul.apaul.abhishek.ap@gmail.com>
Signed-off-by: KiruthikaJeyashankar <kiruthikavjshankar@gmail.com>
Co-authored-by: KiruthikaJeyashankar <kiruthikavjshankar@gmail.com>
This commit is contained in:
abhip2565
2025-09-18 18:50:53 +05:30
committed by GitHub
parent 62b627242c
commit 0713bbb5c4
36 changed files with 1291 additions and 627 deletions

92
components/TrustModal.tsx Normal file
View File

@@ -0,0 +1,92 @@
import React from 'react';
import { Modal, View, Text, Image, ScrollView } from 'react-native';
import { Button } from './ui';
import { Theme } from './ui/styleUtils';
import { useTranslation } from 'react-i18next';
export const TrustModal = ({
isVisible,
logo,
name,
onConfirm,
onCancel,
flowType = 'issuer',
}: {
isVisible: boolean;
logo: any;
name: string;
onConfirm: () => void;
onCancel: () => void;
flowType?: 'issuer' | 'verifier';
}) => {
const { t } = useTranslation('trustScreen');
return (
<Modal transparent={true} visible={isVisible} animationType="fade">
<View style={Theme.TrustIssuerScreenStyle.modalOverlay}>
<View style={Theme.TrustIssuerScreenStyle.modalContainer}>
{(logo || name) && (
<View style={Theme.TrustIssuerScreenStyle.issuerHeader}>
{logo && (
<Image
source={{ uri: logo }}
style={Theme.TrustIssuerScreenStyle.issuerLogo}
/>
)}
{name && (
<Text style={Theme.TrustIssuerScreenStyle.issuerName}>
{name}
</Text>
)}
</View>
)}
<ScrollView
style={{ flex: 1, width: '100%' }}
contentContainerStyle={{ alignItems: 'center', paddingBottom: 10 }}
showsVerticalScrollIndicator={true}>
<Text style={Theme.TrustIssuerScreenStyle.description}>
{t(flowType == 'issuer' ? 'description' : 'verifierDescription')}
</Text>
<View style={Theme.TrustIssuerScreenStyle.infoContainer}>
{t(flowType == 'issuer' ? 'infoPoints' : 'verifierInfoPoints', { returnObjects: true }).map((point, index) => (
<View key={index} style={Theme.TrustIssuerScreenStyle.infoItem}>
<Text style={Theme.TrustIssuerScreenStyle.info}></Text>
<Text style={Theme.TrustIssuerScreenStyle.infoText}>
{point}
</Text>
</View>
))}
</View>
</ScrollView>
<View style={{ width: '100%', paddingTop: 10, paddingBottom: 5 }}>
<Button
styles={{
marginBottom: 3,
minHeight: 50,
justifyContent: 'center',
alignItems: 'center',
}}
type="gradient"
title={t(flowType == 'issuer' ? 'confirm' : 'verifierConfirm')}
onPress={onConfirm}
/>
<Button
styles={{
marginBottom: -10,
paddingBottom: 20,
minHeight: 60,
justifyContent: 'center',
alignItems: 'center',
maxWidth: '100%',
}}
type="clear"
title={t('cancel')}
onPress={onCancel}
/>
</View>
</View>
</View>
</Modal>
);
};

View File

@@ -27,6 +27,7 @@ export const VCCardView: React.FC<VCItemProps> = ({
flow,
isInitialLaunch = false,
isTopCard = false,
onDisclosuresChange,
}) => {
const controller = useVcItemController(vcMetadata);
const {t} = useTranslation();
@@ -56,17 +57,16 @@ export const VCCardView: React.FC<VCItemProps> = ({
setVc(processedData);
}
}
loadVc();
}, [isDownloading, controller.credential]);
useEffect(() => {
if (!verifiableCredentialData || !verifiableCredentialData.vcMetadata) return;
const {
issuer,
credentialConfigurationId,
vcMetadata: { format },
} = verifiableCredentialData;
if (vcMetadata.issuerHost) {
getCredentialIssuersWellKnownConfig(
vcMetadata.issuerHost,
@@ -76,13 +76,13 @@ export const VCCardView: React.FC<VCItemProps> = ({
vcMetadata.issuerHost,
)
.then(response => {
if(response && response.matchingCredentialIssuerMetadata) {
setWellknown(response.matchingCredentialIssuerMetadata);
if (response && response.matchingCredentialIssuerMetadata) {
setWellknown(response.matchingCredentialIssuerMetadata);
}
setFields(response.fields);
})
.catch(error => {
setWellknown({"fallback":"true"});
setWellknown({fallback: 'true'});
console.error(
'Error occurred while fetching wellknown for viewing VC ',
error,
@@ -94,6 +94,7 @@ export const VCCardView: React.FC<VCItemProps> = ({
if (!isVCLoaded(controller.credential) || !wellknown || !vc) {
return <VCCardSkeleton />;
}
const CardViewContent = () => (
<VCCardViewContent
vcMetadata={vcMetadata}
@@ -111,6 +112,7 @@ export const VCCardView: React.FC<VCItemProps> = ({
DISMISS={controller.DISMISS}
KEBAB_POPUP={controller.KEBAB_POPUP}
isInitialLaunch={isInitialLaunch}
onDisclosuresChange={onDisclosuresChange}
/>
);
@@ -159,4 +161,5 @@ export interface VCItemProps {
flow?: string;
isInitialLaunch?: boolean;
isTopCard?: boolean;
}
onDisclosuresChange?: (paths: string[]) => void;
}

View File

@@ -1,29 +1,184 @@
import React from 'react';
import {ImageBackground, Pressable, View, Image, ImageBackgroundProps} from 'react-native';
import {VCMetadata} from '../../../shared/VCMetadata';
import {KebabPopUp} from '../../KebabPopUp';
import {Credential} from '../../../machines/VerifiableCredential/VCMetaMachine/vc';
import {Column, Row} from '../../ui';
import {Theme} from '../../ui/styleUtils';
import {CheckBox, Icon} from 'react-native-elements';
import {SvgImage} from '../../ui/svg';
import {VcItemContainerProfileImage} from '../../VcItemContainerProfileImage';
import {isVCLoaded, getCredentialType, Display} from '../common/VCUtils';
import {VCItemFieldValue} from '../common/VCItemField';
import {WalletBinding} from '../../../screens/Home/MyVcs/WalletBinding';
import {VCVerification} from '../../VCVerification';
import {isActivationNeeded} from '../../../shared/openId4VCI/Utils';
import {VCItemContainerFlowType} from '../../../shared/Utils';
import {RemoveVcWarningOverlay} from '../../../screens/Home/MyVcs/RemoveVcWarningOverlay';
import {HistoryTab} from '../../../screens/Home/MyVcs/HistoryTab';
import {useCopilot} from 'react-native-copilot';
import {useTranslation} from 'react-i18next';
import React, { useEffect, useState } from 'react';
import { ImageBackground, Pressable, View, Image, ImageBackgroundProps } from 'react-native';
import { VCMetadata } from '../../../shared/VCMetadata';
import { KebabPopUp } from '../../KebabPopUp';
import { Credential } from '../../../machines/VerifiableCredential/VCMetaMachine/vc';
import { Column, Row, Text } from '../../ui';
import { Theme } from '../../ui/styleUtils';
import { CheckBox, Icon } from 'react-native-elements';
import { SvgImage } from '../../ui/svg';
import { VcItemContainerProfileImage } from '../../VcItemContainerProfileImage';
import { isVCLoaded, getCredentialType, Display, formatKeyLabel } from '../common/VCUtils';
import { VCItemFieldValue } from '../common/VCItemField';
import { WalletBinding } from '../../../screens/Home/MyVcs/WalletBinding';
import { VCVerification } from '../../VCVerification';
import { isActivationNeeded } from '../../../shared/openId4VCI/Utils';
import { VCItemContainerFlowType } from '../../../shared/Utils';
import { RemoveVcWarningOverlay } from '../../../screens/Home/MyVcs/RemoveVcWarningOverlay';
import { HistoryTab } from '../../../screens/Home/MyVcs/HistoryTab';
import { useCopilot } from 'react-native-copilot';
import { useTranslation } from 'react-i18next';
import testIDProps from '../../../shared/commonUtil';
export const VCCardViewContent: React.FC<VCItemContentProps> = ({
isPinned = false,
credential,
verifiableCredentialData,
wellknown,
selectable,
selected,
service,
onPress,
flow,
walletBindingResponse,
KEBAB_POPUP,
DISMISS,
isKebabPopUp,
vcMetadata,
isInitialLaunch,
onDisclosuresChange,
}) => {
const [isExpanded, setIsExpanded] = useState(false);
const [selectedFields, setSelectedFields] = useState<Record<string, boolean>>({});
useEffect(() => {
if (flow === VCItemContainerFlowType.VP_SHARE) {
setIsExpanded(selected);
}
}, [selected]);
const toggleExpand = () => {
if (flow === VCItemContainerFlowType.VP_SHARE) {
setIsExpanded(prev => !prev);
}
};
const [expandedNodes, setExpandedNodes] = useState<Record<string, boolean>>({});
const areAllSelected = (): boolean => {
return credential.disclosedKeys.every(key => selectedFields[key]);
};
const toggleSelectAll = () => {
const updated: Record<string, boolean> = {};
if (areAllSelected()) {
credential.disclosedKeys.forEach(key => {
updated[key] = false;
});
} else {
credential.disclosedKeys.forEach(key => {
updated[key] = true;
});
}
setSelectedFields(updated);
const selectedPaths = Object.keys(updated).filter(k => updated[k]);
onDisclosuresChange?.(selectedPaths);
};
const DisclosureNode: React.FC<{
name: string;
node: any;
fullPath: string;
expandedNodes: Record<string, boolean>;
setExpandedNodes: React.Dispatch<React.SetStateAction<Record<string, boolean>>>;
}> = ({ name, node, fullPath, expandedNodes, setExpandedNodes }) => {
const isExpanded = expandedNodes[fullPath] || false;
const toggleExpand = () => {
setExpandedNodes(prev => ({
...prev,
[fullPath]: !prev[fullPath],
}));
};
const isChecked = selectedFields[fullPath] || false;
return (
<Column>
<Row crossAlign="center" style={{ justifyContent: "space-between", marginBottom: -10 }}>
<Row crossAlign="center">
{node.__self && (
<CheckBox
size={22}
checked={isChecked}
checkedIcon={SvgImage.selectedCheckBox()}
uncheckedIcon={
<Icon
name="check-box-outline-blank"
color={Theme.Colors.uncheckedIcon}
size={22}
/>
}
onPress={() => handleFieldToggle(fullPath)}
/>
)}
<Text weight="semibold" color={wellknownDisplayProperty.getTextColor(Theme.Colors.plainText)} style={{ marginLeft: 8 }}>
{formatKeyLabel(name)}
</Text>
</Row>
{/* Right side: expand/collapse icon */}
{Object.keys(node.children).length > 0 && (
<Pressable onPress={toggleExpand} style={{ marginLeft: 12 }}>
<Icon
name={isExpanded ? "expand-less" : "expand-more"}
color={Theme.Colors.Icon}
/>
</Pressable>
)}
</Row>
{isExpanded &&
Object.entries(node.children).map(([childName, childNode]) => (
<Column key={childName} margin="0 0 0 15">
<DisclosureNode
name={childName}
node={childNode}
fullPath={`${fullPath}.${childName}`}
expandedNodes={expandedNodes}
setExpandedNodes={setExpandedNodes}
/>
</Column>
))}
</Column>
);
};
const handleFieldToggle = (path: string) => {
setSelectedFields(prev => {
const updated = { ...prev, [path]: !prev[path] };
// If child selected → ensure all its parents are also selected
if (updated[path]) {
const parts = path.split('.');
while (parts.length > 1) {
parts.pop();
const parent = parts.join('.');
if (credential.disclosedKeys.includes(parent)) {
updated[parent] = true;
}
}
}
else {
Object.keys(updated).forEach(p => {
if (p.startsWith(path + '.') && updated[p]) {
updated[p] = false;
}
});
}
const selectedPaths = Object.keys(updated).filter(p => updated[p]);
onDisclosuresChange?.(selectedPaths);
return updated;
});
};
export const VCCardViewContent: React.FC<VCItemContentProps> = ({isPinned = false, credential, verifiableCredentialData, wellknown, selectable, selected, service, onPress, flow, walletBindingResponse, KEBAB_POPUP, DISMISS, isKebabPopUp, vcMetadata, isInitialLaunch}) => {
const wellknownDisplayProperty = new Display(wellknown);
const vcSelectableButton =
const vcSelectableButton =
selectable &&
(flow === VCItemContainerFlowType.VP_SHARE ? (
<CheckBox
@@ -55,8 +210,8 @@ export const VCCardViewContent: React.FC<VCItemContentProps> = ({isPinned = fals
));
const issuerLogo = verifiableCredentialData.issuerLogo;
const faceImage = verifiableCredentialData.face;
const {start} = useCopilot();
const {t} = useTranslation();
const { start } = useCopilot();
const { t } = useTranslation();
return (
<ImageBackground
@@ -69,12 +224,13 @@ export const VCCardViewContent: React.FC<VCItemContentProps> = ({isPinned = fals
]}>
<View
onLayout={
isInitialLaunch
? () => start(t('copilot:cardTitle'))
: undefined
isInitialLaunch ? () => start(t('copilot:cardTitle')) : undefined
}>
<Row crossAlign="center" padding="3 0 0 3">
<VcItemContainerProfileImage isPinned={isPinned} verifiableCredentialData={verifiableCredentialData} />
<VcItemContainerProfileImage
isPinned={isPinned}
verifiableCredentialData={verifiableCredentialData}
/>
<Column fill align={'space-around'} margin="0 10 0 10">
<VCItemFieldValue
key={'credentialType'}
@@ -106,7 +262,7 @@ export const VCCardViewContent: React.FC<VCItemContentProps> = ({isPinned = fals
<>
{!verifiableCredentialData?.vcMetadata.isExpired &&
(!walletBindingResponse &&
isActivationNeeded(verifiableCredentialData?.issuer)
isActivationNeeded(verifiableCredentialData?.issuer)
? SvgImage.walletUnActivatedIcon()
: SvgImage.walletActivatedIcon())}
<Pressable
@@ -129,7 +285,66 @@ export const VCCardViewContent: React.FC<VCItemContentProps> = ({isPinned = fals
</>
)}
{vcSelectableButton}
{flow === VCItemContainerFlowType.VP_SHARE && (credential?.disclosedKeys?.length > 0) && (
<Pressable onPress={toggleExpand}>
<Icon
name={isExpanded ? 'expand-less' : 'expand-more'}
color={Theme.Colors.Icon}
/>
</Pressable>
)}
</Row>
{/* Expanded section for SD-JWT disclosed keys */}
{flow === VCItemContainerFlowType.VP_SHARE &&
isExpanded &&
credential?.disclosedKeys?.length > 0 && (
<Column padding="8 0">
<View style={{ paddingHorizontal: 6, marginTop: 8 }}>
<View
style={{...Theme.Styles.horizontalSeparator, marginBottom: 12 }}
/>
<Column>
<Text
style={Theme.Styles.disclosureTitle}>
{t('SendVPScreen:selectedFieldsTitle')}
</Text>
<Text
style={Theme.Styles.disclosureSubtitle}>
{t('SendVPScreen:selectedFieldsSubtitle')}
</Text>
</Column>
<Row style={{ marginTop: 12 }} width='100%' align='flex-end'><Pressable onPress={toggleSelectAll}>
<Text
color={Theme.Colors.Icon}
style={Theme.Styles.disclosureSelectButton}>
{areAllSelected()
? t('SendVPScreen:unselectAll')
: t('SendVPScreen:selectAll')}
</Text>
</Pressable>
</Row>
<View
style={{ ...Theme.Styles.horizontalSeparator, marginTop: 12 }}
/>
</View>
{Object.entries(buildDisclosureTree(credential.disclosedKeys)).map(
([name, node]) => (
<DisclosureNode
key={name}
name={name}
node={node}
fullPath={name}
expandedNodes={expandedNodes}
setExpandedNodes={setExpandedNodes}
/>
)
)}
</Column>
)}
<WalletBinding service={service} vcMetadata={vcMetadata} />
@@ -145,6 +360,23 @@ export const VCCardViewContent: React.FC<VCItemContentProps> = ({isPinned = fals
);
};
function buildDisclosureTree(paths: string[]) {
const root: any = {};
paths.forEach(path => {
const parts = path.split(".");
let node = root;
parts.forEach((part, idx) => {
if (!node[part]) node[part] = { __self: false, children: {} };
if (idx === parts.length - 1) {
node[part].__self = true;
}
node = node[part].children;
});
});
return root;
}
export interface VCItemContentProps {
context: any;
credential: Credential;
@@ -165,4 +397,5 @@ export interface VCItemContentProps {
isKebabPopUp: boolean;
vcMetadata: VCMetadata;
isInitialLaunch?: boolean;
onDisclosuresChange?: (disclosures: string[]) => void;
}

View File

@@ -25,9 +25,9 @@ export class VCProcessor {
return parseJSON(decodedString);
}
if(vcFormat === VCFormat.vc_sd_jwt || vcFormat === VCFormat.dc_sd_jwt) {
const { fullResolvedPayload, disclosedKeys, publicKeys } =
const { fullResolvedPayload, disclosedKeys, publicKeys,pathToDisclosures } =
reconstructSdJwtFromCompact(vcData.credential.toString());
return {fullResolvedPayload,disclosedKeys,publicKeys};
return {fullResolvedPayload,disclosedKeys,publicKeys,pathToDisclosures};
}
return getVerifiableCredential(vcData);
}
@@ -61,13 +61,16 @@ export function reconstructSdJwtFromCompact(
sdJwtCompact: string,
): {
fullResolvedPayload: Record<string, any>;
disclosedKeys: Set<string>;
publicKeys: Set<string>;
disclosedKeys: string[];
publicKeys: string[];
pathToDisclosures: Record<string, string[]>; // Mapof{claimPath -> disclosure strings}
} {
const sdJwtPublicKeys = ["iss", "sub", "aud", "exp", "nbf", "iat", "jti"];
const disclosedKeys = new Set<string>();
const publicKeys = new Set<string>();
const digestToDisclosure: Record<string, any[]> = {};
const pathToDisclosures: Record<string, string[]> = {};
const digestToDisclosureB64: Record<string, string> = {};
// Split SD-JWT into parts: [jwt, disclosure1, disclosure2, ...]
const parts = sdJwtCompact.trim().split('~');
@@ -79,17 +82,23 @@ export function reconstructSdJwtFromCompact(
// Parse disclosures
for (const disclosureB64 of disclosures) {
if(disclosureB64.length > 0) {
const decodedB64 = disclosureB64.replace(/-/g, '+').replace(/_/g, '/');
const decoded = JSON.parse(Buffer.from(decodedB64, 'base64').toString('utf-8'));
const digestInput = disclosureB64
const digest = base64url(Buffer.from(hashDigest(sdAlg,digestInput)));
digestToDisclosure[digest] = decoded;
if (disclosureB64.length > 0) {
const decodedB64 = disclosureB64.replace(/-/g, '+').replace(/_/g, '/');
const decoded = JSON.parse(Buffer.from(decodedB64, 'base64').toString('utf-8'));
const digestInput = disclosureB64;
const digest = base64url(Buffer.from(hashDigest(sdAlg, digestInput)));
digestToDisclosure[digest] = decoded;
digestToDisclosureB64[digest] = disclosureB64;
}
}
//Parse the JWT payload
function resolveDisclosures(value: any, path: string = ''): any {
//Parse the JWT payload
function resolveDisclosures(
value: any,
path: string = '',
parentDisclosures: string[] = [],
): any {
if (Array.isArray(value)) {
return value.flatMap((item, index) => {
const currentPath = `${path}[${index}]`;
@@ -105,9 +114,11 @@ export function reconstructSdJwtFromCompact(
return [];
}
disclosedKeys.add(currentPath);
return [resolveDisclosures(disclosure[1], currentPath)];
const currentDisclosures = [...parentDisclosures, digestToDisclosureB64[digest]];
pathToDisclosures[currentPath] = currentDisclosures;
return [resolveDisclosures(disclosure[1], currentPath, currentDisclosures)];
} else {
return [resolveDisclosures(item, currentPath)];
return [resolveDisclosures(item, currentPath, parentDisclosures)];
}
});
}
@@ -126,13 +137,15 @@ export function reconstructSdJwtFromCompact(
if (claimName in value) throw new Error('Overwriting existing key');
const fullPath = path ? `${path}.${claimName}` : claimName;
disclosedKeys.add(fullPath);
result[claimName] = resolveDisclosures(claimValue, fullPath);
const currentDisclosures = [...parentDisclosures, digestToDisclosureB64[digest]];
pathToDisclosures[fullPath] = currentDisclosures;
result[claimName] = resolveDisclosures(claimValue, fullPath, currentDisclosures);
}
for (const [k, v] of Object.entries(value)) {
if (k === '_sd') continue;
const fullPath = path ? `${path}.${k}` : k;
result[k] = resolveDisclosures(v, fullPath);
result[k] = resolveDisclosures(v, fullPath, parentDisclosures);
}
return result;
@@ -143,12 +156,18 @@ export function reconstructSdJwtFromCompact(
// Track public (non-selectively-disclosable) claims
for (const key of Object.keys(payload)) {
if (key !== '_sd' && key !== '_sd_alg' && sdJwtPublicKeys.includes(key)) {
if (key !== '_sd' && key !== '_sd_alg' && sdJwtPublicKeys.includes(key)) {
publicKeys.add(key);
}
}
const fullResolvedPayload = resolveDisclosures(payload);
delete fullResolvedPayload['_sd_alg'];
return { fullResolvedPayload, disclosedKeys, publicKeys };
return {
fullResolvedPayload,
disclosedKeys: Array.from(disclosedKeys),
publicKeys: Array.from(publicKeys),
pathToDisclosures,
};
}

View File

@@ -286,7 +286,7 @@ function getFullAddress(credential: CredentialSubject) {
.filter(Boolean)
.join(', ');
}
const formatKeyLabel = (key: string): string => {
export const formatKeyLabel = (key: string): string => {
return key
.replace(/\[\d+\]/g, '') // Remove [0], [1], etc.
.replace(/([a-z])([A-Z])/g, '$1 $2') // camelCase → spaced
@@ -303,7 +303,7 @@ const renderFieldRecursively = (
parentKey = '',
depth = 0,
renderedFields: Set<string>,
disclosedKeys: Set<string> = new Set(),
disclosedKeys: string[],
): JSX.Element[] => {
const fullKey = parentKey ? `${parentKey}.${key}` : key;
let shortKey =
@@ -320,12 +320,12 @@ const renderFieldRecursively = (
if (Array.isArray(value)) {
const label = formatKeyLabel(key);
const arrayFullKey = fullKey;
const isArrayDisclosed = disclosedKeys.has(arrayFullKey);
const isArrayDisclosed = disclosedKeys.includes(arrayFullKey);
return value.flatMap((item, index) => {
const itemKey = `${key}[${index}]`;
const itemFullKey = parentKey ? `${parentKey}.${itemKey}` : itemKey;
const isItemDisclosed = disclosedKeys.has(itemFullKey);
const isItemDisclosed = disclosedKeys.includes(itemFullKey);
const showDisclosureIcon = isArrayDisclosed || isItemDisclosed;
return [
@@ -436,7 +436,7 @@ const renderFieldRecursively = (
shortKey = publicKeyLabelMap[shortKey];
}
const label = formatKeyLabel(shortKey);
const isDisclosed = disclosedKeys.has(fullKey);
const isDisclosed = disclosedKeys.includes(fullKey);
return [
<Row
key={`extra-${fullKey}`}
@@ -471,7 +471,7 @@ export const fieldItemIterator = (
): JSX.Element[] => {
const fieldNameColor = display.getTextColor(Theme.Colors.DetailsLabel);
const fieldValueColor = display.getTextColor(Theme.Colors.Details);
const disclosedKeys = verifiableCredential.disclosedKeys || new Set<string>();
const disclosedKeys = verifiableCredential.disclosedKeys || [];
const renderedFields = new Set<string>();
const renderedMainFields = fields.map(field => {
@@ -501,7 +501,7 @@ export const fieldItemIterator = (
return null;
}
const isDisclosed = disclosedKeys.has(field);
const isDisclosed = disclosedKeys.includes(field);
return (
<Row
key={field}

View File

@@ -769,6 +769,24 @@ export const DefaultTheme = {
flex: 1,
justifyContent: 'space-around',
},
horizontalSeparator:{
height: 1,
backgroundColor: '#DADADA',
},
disclosureTitle:{
fontFamily: 'Inter_700Bold',
fontSize: 15,
color: Colors.Black,
},
disclosureSubtitle:{
fontSize: 13,
color: '#747474',
marginTop: 4,
},
disclosureSelectButton:{
fontSize: 14,
fontFamily: 'Inter_700Bold',
}
}),
BannerStyles: StyleSheet.create({
container: {
@@ -1173,6 +1191,21 @@ export const DefaultTheme = {
borderColor: Colors.Orange,
borderRadius: 30,
},
sharedSuccessfullyVerifierInfo:{
alignSelf: 'center',
backgroundColor: '#F5F5F5',
borderRadius: 16,
paddingVertical: 12,
paddingHorizontal: 16,
flexDirection: 'row',
justifyContent: 'center',
},
sharedSuccessfullyVerifierLogo: {
width: 40,
height: 40,
borderRadius: 8,
marginRight: 12,
}
}),
AppMetaDataStyles: StyleSheet.create({
buttonContainer: {

View File

@@ -777,6 +777,25 @@ export const PurpleTheme = {
flex: 1,
justifyContent: 'space-around',
},
horizontalSeparator:{
height: 1,
backgroundColor: '#DADADA',
marginBottom: 12,
},
disclosureTitle:{
fontFamily: 'Inter_700Bold',
fontSize: 15,
color: Colors.Black,
},
disclosureSubtitle:{
fontSize: 13,
color: '#747474',
marginTop: 4,
},
disclosureSelectButton:{
fontSize: 14,
fontFamily: 'Inter_700Bold',
}
}),
BannerStyles: StyleSheet.create({
container: {
@@ -1178,6 +1197,21 @@ export const PurpleTheme = {
borderColor: Colors.Purple,
borderRadius: 30,
},
sharedSuccessfullyVerifierInfo:{
alignSelf: 'center',
backgroundColor: '#F5F5F5',
borderRadius: 16,
paddingVertical: 12,
paddingHorizontal: 16,
flexDirection: 'row',
justifyContent: 'center',
},
sharedSuccessfullyVerifierLogo: {
width: 40,
height: 40,
borderRadius: 8,
marginRight: 12,
}
}),
AppMetaDataStyles: StyleSheet.create({
buttonContainer: {