diff --git a/packages/legacy/core/App/components/misc/CredentialCard.tsx b/packages/legacy/core/App/components/misc/CredentialCard.tsx index 58fec9b2c0..90d46dac9d 100644 --- a/packages/legacy/core/App/components/misc/CredentialCard.tsx +++ b/packages/legacy/core/App/components/misc/CredentialCard.tsx @@ -1,6 +1,6 @@ import { CredentialExchangeRecord, W3cCredentialRecord } from '@credo-ts/core' -import { Attribute, BrandingOverlayType, Predicate } from '@hyperledger/aries-oca/build/legacy' -import React from 'react' +import { Attribute, BrandingOverlayType, CredentialOverlay, Predicate } from '@hyperledger/aries-oca/build/legacy' +import React, { useEffect, useState } from 'react' import { ViewStyle } from 'react-native' import { TOKENS, useServices } from '../../container-api' @@ -9,8 +9,11 @@ import { GenericFn } from '../../types/fn' import CredentialCard10 from './CredentialCard10' import CredentialCard11, { CredentialErrors } from './CredentialCard11' -import OpenIDCredentialCard from '../../modules/openid/components/OpenIDCredentialCard' import { GenericCredentialExchangeRecord } from '../../types/credentials' +import { BrandingOverlay } from '@hyperledger/aries-oca' +import { getCredentialForDisplay } from '../../modules/openid/display' +import { buildOverlayFromW3cCredential } from '../../utils/oca' +import { useTranslation } from 'react-i18next' interface CredentialCardProps { credential?: GenericCredentialExchangeRecord @@ -42,6 +45,27 @@ const CredentialCard: React.FC = ({ // add ability to reference credential by ID, allows us to get past react hook restrictions const [bundleResolver] = useServices([TOKENS.UTIL_OCA_RESOLVER]) const { ColorPallet } = useTheme() + const [overlay, setOverlay] = useState>({}) + const { i18n } = useTranslation() + + useEffect(() => { + const resolveOverlay = async (w3cCred: W3cCredentialRecord) => { + const credentialDisplay = getCredentialForDisplay(w3cCred) + + const resolvedOverlay = await buildOverlayFromW3cCredential({ + credentialDisplay, + language: i18n.language, + resolver: bundleResolver, + }) + + setOverlay(resolvedOverlay) + } + + if (credential instanceof W3cCredentialRecord) { + resolveOverlay(credential) + } + }, [credential, bundleResolver, i18n.language]) + const getCredOverlayType = (type: BrandingOverlayType) => { if (proof) { return ( @@ -90,7 +114,15 @@ const CredentialCard: React.FC = ({ } if (credential instanceof W3cCredentialRecord) { - return + return ( + + ) } else { return getCredOverlayType(bundleResolver.getBrandingOverlayType()) } diff --git a/packages/legacy/core/App/components/misc/CredentialCard11.tsx b/packages/legacy/core/App/components/misc/CredentialCard11.tsx index a913467122..a1d75fb973 100644 --- a/packages/legacy/core/App/components/misc/CredentialCard11.tsx +++ b/packages/legacy/core/App/components/misc/CredentialCard11.tsx @@ -49,6 +49,7 @@ interface CredentialCard11Props { credentialErrors: CredentialErrors[] hasAltCredentials?: boolean handleAltCredChange?: () => void + brandingOverlay?: CredentialOverlay } /* @@ -93,6 +94,7 @@ const CredentialCard11: React.FC = ({ hasAltCredentials, credentialErrors = [], handleAltCredChange, + brandingOverlay, }) => { const { width } = useWindowDimensions() const borderRadius = 10 @@ -285,6 +287,11 @@ const CredentialCard11: React.FC = ({ }, [credential, cardData, parseAttribute, flaggedAttributes]) useEffect(() => { + if (brandingOverlay) { + setOverlay(brandingOverlay as unknown as CredentialOverlay) + return + } + const params = { identifiers: credential ? getCredentialIdentifiers(credential) : { schemaId, credentialDefinitionId: credDefId }, attributes: proof ? [] : credential?.credentialAttributes, @@ -335,6 +342,7 @@ const CredentialCard11: React.FC = ({ proof, credHelpActionOverrides, navigation, + brandingOverlay, ]) const CredentialCardLogo: React.FC = () => { diff --git a/packages/legacy/core/App/components/views/CredentialCardLogo.tsx b/packages/legacy/core/App/components/views/CredentialCardLogo.tsx new file mode 100644 index 0000000000..b0ef31bc68 --- /dev/null +++ b/packages/legacy/core/App/components/views/CredentialCardLogo.tsx @@ -0,0 +1,58 @@ +import { Image, StyleSheet, Text, View } from 'react-native' +import { BrandingOverlay } from '@hyperledger/aries-oca' +import { CredentialOverlay } from '@hyperledger/aries-oca/build/legacy' +import { useTheme } from '../../contexts/theme' +import { toImageSource } from '../../utils/credential' + +type Props = { + overlay: CredentialOverlay +} + +const logoHeight = 80 +const paddingHorizontal = 24 + +const CredentialCardLogo: React.FC = ({ overlay }: Props) => { + const { TextTheme } = useTheme() + + const styles = StyleSheet.create({ + logoContainer: { + top: -0.5 * logoHeight, + left: paddingHorizontal, + marginBottom: -1 * logoHeight, + width: logoHeight, + height: logoHeight, + backgroundColor: '#ffffff', + borderRadius: 8, + justifyContent: 'center', + alignItems: 'center', + shadowColor: '#000', + shadowOffset: { + width: 1, + height: 1, + }, + shadowOpacity: 0.3, + }, + }) + + return ( + + {overlay.brandingOverlay?.logo ? ( + + ) : ( + + {(overlay.metaOverlay?.name ?? overlay.metaOverlay?.issuer ?? 'C')?.charAt(0).toUpperCase()} + + )} + + ) +} + +export default CredentialCardLogo diff --git a/packages/legacy/core/App/components/views/CredentialDetailPrimaryHeader.tsx b/packages/legacy/core/App/components/views/CredentialDetailPrimaryHeader.tsx new file mode 100644 index 0000000000..43c7a4a22c --- /dev/null +++ b/packages/legacy/core/App/components/views/CredentialDetailPrimaryHeader.tsx @@ -0,0 +1,72 @@ +import { StyleSheet, Text, useWindowDimensions, View } from 'react-native' +import { BrandingOverlay } from '@hyperledger/aries-oca' +import { CredentialOverlay } from '@hyperledger/aries-oca/build/legacy' +import CardWatermark from '../../components/misc/CardWatermark' +import { useTheme } from '../../contexts/theme' +import { credentialTextColor } from '../../utils/credential' +import { testIdWithKey } from '../../utils/testable' + +type CredentialDetailPrimaryHeaderProps = { + overlay: CredentialOverlay +} + +const paddingHorizontal = 24 +const paddingVertical = 16 +const logoHeight = 80 + +const CredentialDetailPrimaryHeader: React.FC = ({ overlay }: CredentialDetailPrimaryHeaderProps) => { + const { TextTheme, ColorPallet } = useTheme() + const { width, height } = useWindowDimensions() + const styles = StyleSheet.create({ + primaryHeaderContainer: { + paddingHorizontal, + paddingVertical, + }, + textContainer: { + color: credentialTextColor(ColorPallet, overlay.brandingOverlay?.primaryBackgroundColor), + }, + }) + + return ( + + + {overlay.metaOverlay?.watermark && ( + + )} + + {overlay.metaOverlay?.issuer} + + + {overlay.metaOverlay?.name} + + + + ) +} + +export default CredentialDetailPrimaryHeader diff --git a/packages/legacy/core/App/components/views/CredentialDetailSecondaryHeader.tsx b/packages/legacy/core/App/components/views/CredentialDetailSecondaryHeader.tsx new file mode 100644 index 0000000000..843f20839d --- /dev/null +++ b/packages/legacy/core/App/components/views/CredentialDetailSecondaryHeader.tsx @@ -0,0 +1,43 @@ +import React from 'react' +import { BrandingOverlay } from '@hyperledger/aries-oca' +import { CredentialOverlay } from '@hyperledger/aries-oca/build/legacy' +import { ImageBackground, StyleSheet, View } from 'react-native' +import { toImageSource } from '../../utils/credential' +import { testIdWithKey } from '../../utils/testable' + +type CredentialDetailSecondaryHeaderProps = { + overlay: CredentialOverlay +} + +const logoHeight = 80 + +const CredentialDetailSecondaryHeader: React.FC = ({ overlay }: CredentialDetailSecondaryHeaderProps) => { + const styles = StyleSheet.create({ + secondaryHeaderContainer: { + height: 1.5 * logoHeight, + backgroundColor: + (overlay.brandingOverlay?.backgroundImage + ? 'rgba(0, 0, 0, 0)' + : overlay.brandingOverlay?.secondaryBackgroundColor) ?? 'rgba(0, 0, 0, 0.24)', + }, + }) + + return ( + <> + {overlay.brandingOverlay?.backgroundImage ? ( + + + + ) : ( + + )} + + ) +} + +export default CredentialDetailSecondaryHeader diff --git a/packages/legacy/core/App/modules/openid/context/OpenIDCredentialRecordProvider.tsx b/packages/legacy/core/App/modules/openid/context/OpenIDCredentialRecordProvider.tsx index 996dcdb23f..15dd97465c 100644 --- a/packages/legacy/core/App/modules/openid/context/OpenIDCredentialRecordProvider.tsx +++ b/packages/legacy/core/App/modules/openid/context/OpenIDCredentialRecordProvider.tsx @@ -15,6 +15,7 @@ type OpenIDCredentialRecord = W3cCredentialRecord | SdJwtVcRecord | undefined export type OpenIDCredentialContext = { openIdState: OpenIDCredentialRecordState + getCredentialById: (id: string) => Promise storeCredential: (cred: W3cCredentialRecord | SdJwtVcRecord) => Promise removeCredential: (cred: W3cCredentialRecord | SdJwtVcRecord) => Promise } @@ -87,6 +88,11 @@ export const OpenIDCredentialRecordProvider: React.FC { + checkAgent() + return await agent?.w3cCredentials.getCredentialRecordById(id) + } + async function storeCredential(cred: W3cCredentialRecord | SdJwtVcRecord): Promise { checkAgent() if (cred instanceof W3cCredentialRecord) { @@ -146,6 +152,7 @@ export const OpenIDCredentialRecordProvider: React.FC {children} diff --git a/packages/legacy/core/App/modules/openid/screens/OpenIDCredentialDetails.tsx b/packages/legacy/core/App/modules/openid/screens/OpenIDCredentialDetails.tsx new file mode 100644 index 0000000000..8af159e72f --- /dev/null +++ b/packages/legacy/core/App/modules/openid/screens/OpenIDCredentialDetails.tsx @@ -0,0 +1,182 @@ +import React, { useEffect, useState } from 'react' +import { StackScreenProps } from '@react-navigation/stack' +import { CredentialStackParams, Screens } from '../../../types/navigators' +import { getCredentialForDisplay } from '../display' +import { SafeAreaView } from 'react-native-safe-area-context' +import CommonRemoveModal from '../../../components/modals/CommonRemoveModal' +import { ModalUsage } from '../../../types/remove' +import { DeviceEventEmitter, StyleSheet, Text, View } from 'react-native' +import { TextTheme } from '../../../theme' +import { useTranslation } from 'react-i18next' +import { testIdWithKey } from '../../../utils/testable' +import { useTheme } from '../../../contexts/theme' +import { BifoldError } from '../../../types/error' +import { EventTypes } from '../../../constants' +import { useAgent } from '@credo-ts/react-hooks' +import RecordRemove from '../../../components/record/RecordRemove' +import { useOpenIDCredentials } from '../context/OpenIDCredentialRecordProvider' +import { CredentialOverlay } from '@hyperledger/aries-oca/build/legacy' +import { W3cCredentialDisplay } from '../types' +import { TOKENS, useServices } from '../../../container-api' +import { BrandingOverlay } from '@hyperledger/aries-oca' +import Record from '../../../components/record/Record' +import { W3cCredentialRecord } from '@credo-ts/core' +import { buildOverlayFromW3cCredential } from '../../../utils/oca' +import CredentialDetailSecondaryHeader from '../../../components/views/CredentialDetailSecondaryHeader' +import CredentialCardLogo from '../../../components/views/CredentialCardLogo' +import CredentialDetailPrimaryHeader from '../../../components/views/CredentialDetailPrimaryHeader' + +export enum OpenIDCredScreenMode { + offer, + details, +} + +type OpenIDCredentialDetailsProps = StackScreenProps + +const paddingHorizontal = 24 +const paddingVertical = 16 + +const OpenIDCredentialDetails: React.FC = ({ navigation, route }) => { + const { credentialId } = route.params + + const [credential, setCredential] = useState(undefined) + const [credentialDisplay, setCredentialDisplay] = useState() + const { t, i18n } = useTranslation() + const { ColorPallet } = useTheme() + const { agent } = useAgent() + const { removeCredential } = useOpenIDCredentials() + const [bundleResolver] = useServices([TOKENS.UTIL_OCA_RESOLVER]) + + const [isRemoveModalDisplayed, setIsRemoveModalDisplayed] = useState(false) + + const [overlay, setOverlay] = useState>({ + bundle: undefined, + presentationFields: [], + metaOverlay: undefined, + brandingOverlay: undefined, + }) + + const styles = StyleSheet.create({ + container: { + backgroundColor: overlay.brandingOverlay?.primaryBackgroundColor, + display: 'flex', + }, + }) + + useEffect(() => { + if (!agent) return + + const fetchCredential = async () => { + try { + const credentialExchangeRecord = await agent.w3cCredentials.getCredentialRecordById(credentialId) + setCredential(credentialExchangeRecord) + } catch (error) { + // credential not found for id, display an error + DeviceEventEmitter.emit( + EventTypes.ERROR_ADDED, + new BifoldError(t('Error.Title1033'), t('Error.Message1033'), t('CredentialDetails.CredentialNotFound'), 1033) + ) + } + } + fetchCredential() + }, [credentialId, agent, t]) + + useEffect(() => { + if (!credential) return + + try { + const credDisplay = getCredentialForDisplay(credential) + setCredentialDisplay(credDisplay) + } catch (error) { + DeviceEventEmitter.emit( + EventTypes.ERROR_ADDED, + new BifoldError(t('Error.Title1033'), t('Error.Message1033'), t('CredentialDetails.CredentialNotFound'), 1033) + ) + } + }, [credential, t]) + + useEffect(() => { + if (!credentialDisplay || !bundleResolver || !i18n || !credentialDisplay.display) { + return + } + + const resolveOverlay = async () => { + const resolvedOverlay = await buildOverlayFromW3cCredential({ + credentialDisplay, + language: i18n.language, + resolver: bundleResolver, + }) + + setOverlay(resolvedOverlay) + } + + resolveOverlay() + }, [credentialDisplay, bundleResolver, i18n]) + + const toggleDeclineModalVisible = () => setIsRemoveModalDisplayed(!isRemoveModalDisplayed) + + const handleDeclineTouched = async () => { + handleRemove() + } + + const handleRemove = async () => { + if (!credential) return + try { + await removeCredential(credential) + navigation.pop() + } catch (err) { + const error = new BifoldError(t('Error.Title1025'), t('Error.Message1025'), (err as Error)?.message ?? err, 1025) + DeviceEventEmitter.emit(EventTypes.ERROR_ADDED, error) + } + } + + const header = () => { + if (!credentialDisplay) return null + + return ( + + + + + + ) + } + + const footer = () => { + if (!credentialDisplay) return null + return ( + + + + {t('CredentialDetails.IssuedBy') + ' '} + + {credentialDisplay.display.issuer.name || t('ContactDetails.AContact')} + + + + + + ) + } + + return ( + + + + + ) +} + +export default OpenIDCredentialDetails diff --git a/packages/legacy/core/App/modules/openid/screens/OpenIDCredentialOffer.tsx b/packages/legacy/core/App/modules/openid/screens/OpenIDCredentialOffer.tsx index c434972de2..6beabed20e 100644 --- a/packages/legacy/core/App/modules/openid/screens/OpenIDCredentialOffer.tsx +++ b/packages/legacy/core/App/modules/openid/screens/OpenIDCredentialOffer.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useEffect } from 'react' import { StackScreenProps } from '@react-navigation/stack' import { DeliveryStackParams, Screens, TabStacks } from '../../../types/navigators' import { getCredentialForDisplay } from '../display' @@ -6,46 +6,81 @@ import { Edge, SafeAreaView } from 'react-native-safe-area-context' import CommonRemoveModal from '../../../components/modals/CommonRemoveModal' import { ModalUsage } from '../../../types/remove' import { useState } from 'react' -import { DeviceEventEmitter, FlatList, StyleSheet, Text, View } from 'react-native' +import { DeviceEventEmitter, StyleSheet, Text, View } from 'react-native' import { TextTheme } from '../../../theme' import { useTranslation } from 'react-i18next' import Button, { ButtonType } from '../../../components/buttons/Button' import { testIdWithKey } from '../../../utils/testable' -import RecordHeader from '../../../components/record/RecordHeader' -import RecordFooter from '../../../components/record/RecordFooter' import { useTheme } from '../../../contexts/theme' -import OpenIDCredentialCard from '../components/OpenIDCredentialCard' -import { buildFieldsFromOpenIDTemplate } from '../utils/utils' -import RecordField from '../../../components/record/RecordField' import { BifoldError } from '../../../types/error' import { EventTypes } from '../../../constants' import { useAgent } from '@credo-ts/react-hooks' import CredentialOfferAccept from '../../../screens/CredentialOfferAccept' -import RecordRemove from '../../../components/record/RecordRemove' import { useOpenIDCredentials } from '../context/OpenIDCredentialRecordProvider' +import { CredentialOverlay, OCABundleResolveAllParams } from '@hyperledger/aries-oca/build/legacy' +import { TOKENS, useServices } from '../../../container-api' +import { BrandingOverlay } from '@hyperledger/aries-oca' +import Record from '../../../components/record/Record' +import { CredentialCard } from '../../../components/misc' +import { buildFieldsFromW3cCredsCredential } from '../../../utils/oca' -export enum OpenIDCredScreenMode { - offer, - details, -} - -type OpenIDCredentialDetailsProps = StackScreenProps +type OpenIDCredentialDetailsProps = StackScreenProps -const OpenIDCredentialDetails: React.FC = ({ navigation, route }) => { +const OpenIDCredentialOffer: React.FC = ({ navigation, route }) => { // FIXME: change params to accept credential id to avoid 'non-serializable' warnings - const { credential, screenMode } = route.params + const { credential } = route.params const credentialDisplay = getCredentialForDisplay(credential) - const { display, attributes } = credentialDisplay - const fields = buildFieldsFromOpenIDTemplate(attributes) - const { t } = useTranslation() + const { display } = credentialDisplay + const { t, i18n } = useTranslation() const { ColorPallet } = useTheme() const { agent } = useAgent() - const { storeCredential, removeCredential } = useOpenIDCredentials() + const { storeCredential } = useOpenIDCredentials() const [isRemoveModalDisplayed, setIsRemoveModalDisplayed] = useState(false) const [buttonsVisible, setButtonsVisible] = useState(true) const [acceptModalVisible, setAcceptModalVisible] = useState(false) + const [bundleResolver] = useServices([TOKENS.UTIL_OCA_RESOLVER]) + + const [overlay, setOverlay] = useState>({ + bundle: undefined, + presentationFields: [], + metaOverlay: undefined, + brandingOverlay: undefined, + }) + + useEffect(() => { + if (!credentialDisplay || !bundleResolver || !i18n || !credentialDisplay.display) { + return + } + + const resolveOverlay = async () => { + const params: OCABundleResolveAllParams = { + identifiers: { + schemaId: '', + credentialDefinitionId: credentialDisplay.id, + }, + meta: { + alias: credentialDisplay.display.issuer.name, + credConnectionId: undefined, + credName: credentialDisplay.display.name, + }, + attributes: buildFieldsFromW3cCredsCredential(credentialDisplay), + language: i18n.language, + } + + const bundle = await bundleResolver.resolveAllBundles(params) + + setOverlay({ + ...(bundle as CredentialOverlay), + presentationFields: bundle.presentationFields, + }) + } + + resolveOverlay() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [bundleResolver, i18n.language]) + const styles = StyleSheet.create({ headerTextContainer: { paddingHorizontal: 25, @@ -64,16 +99,14 @@ const OpenIDCredentialDetails: React.FC = ({ navig const handleDeclineTouched = async () => { toggleDeclineModalVisible() - if (screenMode === OpenIDCredScreenMode.offer) - navigation.getParent()?.navigate(TabStacks.HomeStack, { screen: Screens.Home }) - else handleRemove() + navigation.getParent()?.navigate(TabStacks.HomeStack, { screen: Screens.Home }) } const handleAcceptTouched = async () => { + if (!agent) { + return + } try { - if (!agent) { - return - } await storeCredential(credential) setAcceptModalVisible(true) } catch (err: unknown) { @@ -83,16 +116,6 @@ const OpenIDCredentialDetails: React.FC = ({ navig } } - const handleRemove = async () => { - try { - await removeCredential(credential) - navigation.pop() - } catch (err) { - const error = new BifoldError(t('Error.Title1025'), t('Error.Message1025'), (err as Error)?.message ?? err, 1025) - DeviceEventEmitter.emit(EventTypes.ERROR_ADDED, error) - } - } - const footerButton = ( title: string, buttonPress: () => void, @@ -117,18 +140,15 @@ const OpenIDCredentialDetails: React.FC = ({ navig const header = () => { return ( <> - {screenMode === OpenIDCredScreenMode.offer && ( - - - {display.issuer.name || t('ContactDetails.AContact')}{' '} - {t('CredentialOffer.IsOfferingYouACredential')} - - - )} - + + + {display.issuer.name || t('ContactDetails.AContact')}{' '} + {t('CredentialOffer.IsOfferingYouACredential')} + + {credential && ( - + )} @@ -141,84 +161,41 @@ const OpenIDCredentialDetails: React.FC = ({ navig const paddingBottom = 26 return ( - {screenMode === OpenIDCredScreenMode.offer ? ( - - {footerButton( - t('Global.Accept'), - handleAcceptTouched, - ButtonType.Primary, - testIdWithKey('AcceptCredentialOffer'), - t('Global.Accept') - )} - {footerButton( - t('Global.Decline'), - toggleDeclineModalVisible, - ButtonType.Secondary, - testIdWithKey('DeclineCredentialOffer'), - t('Global.Decline') - )} - - ) : ( - <> - - - {t('CredentialDetails.IssuedBy') + ' '} - {display.issuer.name || t('ContactDetails.AContact')} - - - - - )} + + {footerButton( + t('Global.Accept'), + handleAcceptTouched, + ButtonType.Primary, + testIdWithKey('AcceptCredentialOffer'), + t('Global.Accept') + )} + {footerButton( + t('Global.Decline'), + toggleDeclineModalVisible, + ButtonType.Secondary, + testIdWithKey('DeclineCredentialOffer'), + t('Global.Decline') + )} + ) } - const body = () => { - return ( - name || index.toString()} - renderItem={({ item: attr, index }) => ( - - )} - ListHeaderComponent={{header()}} - ListFooterComponent={footer ? {footer()} : null} - /> - ) - } - - const screenEdges: Edge[] = - screenMode === OpenIDCredScreenMode.offer ? ['bottom', 'left', 'right'] : ['left', 'right'] + const screenEdges: Edge[] = ['bottom', 'left', 'right'] return ( - {body()} - {screenMode === OpenIDCredScreenMode.offer && ( - - )} + + = ({ navig ) } -export default OpenIDCredentialDetails +export default OpenIDCredentialOffer diff --git a/packages/legacy/core/App/navigators/CredentialStack.tsx b/packages/legacy/core/App/navigators/CredentialStack.tsx index ea38cc759f..81adbff81d 100644 --- a/packages/legacy/core/App/navigators/CredentialStack.tsx +++ b/packages/legacy/core/App/navigators/CredentialStack.tsx @@ -10,13 +10,16 @@ import { CredentialStackParams, Screens } from '../types/navigators' import { useDefaultStackOptions } from './defaultStackOptions' import { TOKENS, useServices } from '../container-api' -import OpenIDCredentialDetails from '../modules/openid/screens/OpenIDCredentialOffer' +import OpenIDCredentialDetails from '../modules/openid/screens/OpenIDCredentialDetails' const CredentialStack: React.FC = () => { const Stack = createStackNavigator() const theme = useTheme() const { t } = useTranslation() - const [CredentialListHeaderRight, ScreenOptionsDictionary] = useServices([TOKENS.COMPONENT_CRED_LIST_HEADER_RIGHT, TOKENS.OBJECT_SCREEN_CONFIG]) + const [CredentialListHeaderRight, ScreenOptionsDictionary] = useServices([ + TOKENS.COMPONENT_CRED_LIST_HEADER_RIGHT, + TOKENS.OBJECT_SCREEN_CONFIG, + ]) const defaultStackOptions = useDefaultStackOptions(theme) return ( @@ -28,7 +31,7 @@ const CredentialStack: React.FC = () => { title: t('Screens.Credentials'), headerRight: () => , headerLeft: () => , - ...ScreenOptionsDictionary[Screens.Credentials] + ...ScreenOptionsDictionary[Screens.Credentials], })} /> { component={CredentialDetails} options={{ title: t('Screens.CredentialDetails'), - ...ScreenOptionsDictionary[Screens.CredentialDetails] + ...ScreenOptionsDictionary[Screens.CredentialDetails], }} /> { component={OpenIDCredentialDetails} options={{ title: t('Screens.CredentialDetails'), - ...ScreenOptionsDictionary[Screens.OpenIDCredentialDetails] + ...ScreenOptionsDictionary[Screens.OpenIDCredentialDetails], }} /> diff --git a/packages/legacy/core/App/navigators/DeliveryStack.tsx b/packages/legacy/core/App/navigators/DeliveryStack.tsx index 0261d291b0..55601f5154 100644 --- a/packages/legacy/core/App/navigators/DeliveryStack.tsx +++ b/packages/legacy/core/App/navigators/DeliveryStack.tsx @@ -10,9 +10,9 @@ import ProofRequest from '../screens/ProofRequest' import { DeliveryStackParams, Screens } from '../types/navigators' import { useDefaultStackOptions } from './defaultStackOptions' -import OpenIDCredentialDetails from '../modules/openid/screens/OpenIDCredentialOffer' import OpenIDProofPresentation from '../modules/openid/screens/OpenIDProofPresentation' import { TOKENS, useServices } from '../container-api' +import OpenIDCredentialOffer from '../modules/openid/screens/OpenIDCredentialOffer' const DeliveryStack: React.FC = () => { const Stack = createStackNavigator() @@ -31,19 +31,16 @@ const DeliveryStack: React.FC = () => { presentation: 'modal', headerLeft: () => null, headerRight: () => , - ...ScreenOptionsDictionary[Screens.Connection] + ...ScreenOptionsDictionary[Screens.Connection], }} > - + { component={CredentialOffer} options={{ title: t('Screens.CredentialOffer'), - ...ScreenOptionsDictionary[Screens.CredentialOffer] + ...ScreenOptionsDictionary[Screens.CredentialOffer], }} /> { component={OpenIDProofPresentation} options={{ title: t('Screens.ProofRequest'), - ...ScreenOptionsDictionary[Screens.OpenIDProofPresentation] + ...ScreenOptionsDictionary[Screens.OpenIDProofPresentation], }} /> diff --git a/packages/legacy/core/App/screens/Connection.tsx b/packages/legacy/core/App/screens/Connection.tsx index deb1082d16..5a41d379ed 100644 --- a/packages/legacy/core/App/screens/Connection.tsx +++ b/packages/legacy/core/App/screens/Connection.tsx @@ -22,7 +22,6 @@ import { AttestationEventTypes } from '../types/attestation' import { BifoldError } from '../types/error' import { EventTypes } from '../constants' import { testIdWithKey } from '../utils/testable' -import { OpenIDCredScreenMode } from '../modules/openid/screens/OpenIDCredentialOffer' import Toast from 'react-native-toast-message' import { ToastType } from '../components/toast/BaseToast' import { OpenId4VPRequestRecord } from 'modules/openid/types' @@ -304,9 +303,8 @@ const Connection: React.FC = ({ navigation, route }) => { ) { logger?.info(`Connection: Handling OpenID4VCi Credential, navigate to CredentialOffer`) dispatch({ inProgress: false }) - navigation.replace(Screens.OpenIDCredentialDetails, { + navigation.replace(Screens.OpenIDCredentialOffer, { credential: state.notificationRecord, - screenMode: OpenIDCredScreenMode.offer, }) return } diff --git a/packages/legacy/core/App/screens/CredentialDetails.tsx b/packages/legacy/core/App/screens/CredentialDetails.tsx index 54d6c88e89..be89333b72 100644 --- a/packages/legacy/core/App/screens/CredentialDetails.tsx +++ b/packages/legacy/core/App/screens/CredentialDetails.tsx @@ -6,11 +6,10 @@ import { BrandingOverlay } from '@hyperledger/aries-oca' import { Attribute, BrandingOverlayType, CredentialOverlay } from '@hyperledger/aries-oca/build/legacy' import React, { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' -import { DeviceEventEmitter, Image, ImageBackground, StyleSheet, Text, View, useWindowDimensions } from 'react-native' +import { DeviceEventEmitter, StyleSheet, Text, View } from 'react-native' import { SafeAreaView } from 'react-native-safe-area-context' import Toast from 'react-native-toast-message' -import CardWatermark from '../components/misc/CardWatermark' import CredentialCard from '../components/misc/CredentialCard' import InfoBox, { InfoBoxType } from '../components/misc/InfoBox' import CommonRemoveModal from '../components/modals/CommonRemoveModal' @@ -24,23 +23,20 @@ import { BifoldError } from '../types/error' import { CredentialMetadata, credentialCustomMetadata } from '../types/metadata' import { CredentialStackParams, Screens } from '../types/navigators' import { ModalUsage } from '../types/remove' -import { - credentialTextColor, - getCredentialIdentifiers, - isValidAnonCredsCredential, - toImageSource, -} from '../utils/credential' +import { getCredentialIdentifiers, isValidAnonCredsCredential } from '../utils/credential' import { formatTime, useCredentialConnectionLabel } from '../utils/helpers' import { buildFieldsFromAnonCredsCredential } from '../utils/oca' import { testIdWithKey } from '../utils/testable' import { HistoryCardType, HistoryRecord } from '../modules/history/types' import { parseCredDefFromId } from '../utils/cred-def' +import CredentialCardLogo from '../components/views/CredentialCardLogo' +import CredentialDetailPrimaryHeader from '../components/views/CredentialDetailPrimaryHeader' +import CredentialDetailSecondaryHeader from '../components/views/CredentialDetailSecondaryHeader' type CredentialDetailsProps = StackScreenProps const paddingHorizontal = 24 const paddingVertical = 16 -const logoHeight = 80 const CredentialDetails: React.FC = ({ navigation, route }) => { if (!route?.params) { @@ -92,45 +88,12 @@ const CredentialDetails: React.FC = ({ navigation, route brandingOverlay: undefined, }) const credentialConnectionLabel = useCredentialConnectionLabel(credential) - const { width, height } = useWindowDimensions() const styles = StyleSheet.create({ container: { backgroundColor: overlay.brandingOverlay?.primaryBackgroundColor, display: 'flex', }, - secondaryHeaderContainer: { - height: 1.5 * logoHeight, - backgroundColor: - (overlay.brandingOverlay?.backgroundImage - ? 'rgba(0, 0, 0, 0)' - : overlay.brandingOverlay?.secondaryBackgroundColor) ?? 'rgba(0, 0, 0, 0.24)', - }, - primaryHeaderContainer: { - paddingHorizontal, - paddingVertical, - }, - statusContainer: {}, - logoContainer: { - top: -0.5 * logoHeight, - left: paddingHorizontal, - marginBottom: -1 * logoHeight, - width: logoHeight, - height: logoHeight, - backgroundColor: '#ffffff', - borderRadius: 8, - justifyContent: 'center', - alignItems: 'center', - shadowColor: '#000', - shadowOffset: { - width: 1, - height: 1, - }, - shadowOpacity: 0.3, - }, - textContainer: { - color: credentialTextColor(ColorPallet, overlay.brandingOverlay?.primaryBackgroundColor), - }, }) useEffect(() => { @@ -258,90 +221,6 @@ const CredentialDetails: React.FC = ({ navigation, route } }, [credential, agent]) - const CredentialCardLogo: React.FC = () => { - return ( - - {overlay.brandingOverlay?.logo ? ( - - ) : ( - - {(overlay.metaOverlay?.name ?? overlay.metaOverlay?.issuer ?? 'C')?.charAt(0).toUpperCase()} - - )} - - ) - } - - const CredentialDetailPrimaryHeader: React.FC = () => { - return ( - - - {overlay.metaOverlay?.watermark && ( - - )} - - {overlay.metaOverlay?.issuer} - - - {overlay.metaOverlay?.name} - - - - ) - } - - const CredentialDetailSecondaryHeader: React.FC = () => { - return ( - <> - {overlay.brandingOverlay?.backgroundImage ? ( - - - - ) : ( - - )} - - ) - } - const CredentialRevocationMessage: React.FC<{ credential: CredentialExchangeRecord }> = ({ credential }) => { return ( = ({ navigation, route ) : ( - - - + + + {isRevoked && !isRevokedMessageHidden ? ( diff --git a/packages/legacy/core/App/screens/ListCredentials.tsx b/packages/legacy/core/App/screens/ListCredentials.tsx index 84a1570971..bb8b067a67 100644 --- a/packages/legacy/core/App/screens/ListCredentials.tsx +++ b/packages/legacy/core/App/screens/ListCredentials.tsx @@ -18,7 +18,6 @@ import { TOKENS, useServices } from '../container-api' import { EmptyListProps } from '../components/misc/EmptyList' import { CredentialListFooterProps } from '../types/credential-list-footer' import { useOpenIDCredentials } from '../modules/openid/context/OpenIDCredentialRecordProvider' -import { OpenIDCredScreenMode } from '../modules/openid/screens/OpenIDCredentialOffer' import { GenericCredentialExchangeRecord } from '../types/credentials' import { CredentialErrors } from '../components/misc/CredentialCard11' @@ -87,10 +86,7 @@ const ListCredentials: React.FC = () => { } onPress={() => { if (cred instanceof W3cCredentialRecord) { - navigation.navigate(Screens.OpenIDCredentialDetails, { - credential: cred, - screenMode: OpenIDCredScreenMode.details, - }) + navigation.navigate(Screens.OpenIDCredentialDetails, { credentialId: cred.id }) } else { navigation.navigate(Screens.CredentialDetails, { credentialId: cred.id }) } diff --git a/packages/legacy/core/App/types/navigators.ts b/packages/legacy/core/App/types/navigators.ts index e8874f4d75..d6cb4e9039 100644 --- a/packages/legacy/core/App/types/navigators.ts +++ b/packages/legacy/core/App/types/navigators.ts @@ -1,7 +1,6 @@ import { SdJwtVcRecord, W3cCredentialRecord } from '@credo-ts/core' import { NavigatorScreenParams } from '@react-navigation/native' import { StackNavigationOptions } from '@react-navigation/stack' -import { OpenIDCredScreenMode } from '../modules/openid/screens/OpenIDCredentialOffer' import { OpenId4VPRequestRecord } from '../modules/openid/types' import { LayoutProps } from '../layout/ScreenLayout' @@ -20,6 +19,7 @@ export enum Screens { CredentialDetails = 'Credential Details', CredentialOffer = 'Credential Offer', OpenIDCredentialDetails = 'Open ID Credential details', + OpenIDCredentialOffer = 'Open ID Credential offer', OpenIDProofPresentation = 'Open ID Proof Presentation', ProofRequest = 'Proof Request', ProofRequestDetails = 'Proof Request Details', @@ -139,10 +139,7 @@ export type ProofRequestsStackParams = { export type CredentialStackParams = { [Screens.Credentials]: undefined [Screens.CredentialDetails]: { credentialId: string } - [Screens.OpenIDCredentialDetails]: { - credential: SdJwtVcRecord | W3cCredentialRecord - screenMode: OpenIDCredScreenMode - } + [Screens.OpenIDCredentialDetails]: { credentialId: string } } export type HomeStackParams = { @@ -176,7 +173,6 @@ export type NotificationStackParams = { [Screens.CredentialDetails]: { credentialId: string } [Screens.OpenIDCredentialDetails]: { credential: SdJwtVcRecord | W3cCredentialRecord - screenMode: OpenIDCredScreenMode } [Screens.CredentialOffer]: { credentialId: string } [Screens.ProofRequest]: { proofId: string } @@ -199,9 +195,8 @@ export type DeliveryStackParams = { [Screens.OnTheWay]: { credentialId: string } [Screens.Declined]: { credentialId: string } [Screens.Chat]: { connectionId: string } - [Screens.OpenIDCredentialDetails]: { + [Screens.OpenIDCredentialOffer]: { credential: SdJwtVcRecord | W3cCredentialRecord - screenMode: OpenIDCredScreenMode } [Screens.OpenIDProofPresentation]: { credential: OpenId4VPRequestRecord } } diff --git a/packages/legacy/core/App/utils/oca.ts b/packages/legacy/core/App/utils/oca.ts index cbc334f393..0aa94b9469 100644 --- a/packages/legacy/core/App/utils/oca.ts +++ b/packages/legacy/core/App/utils/oca.ts @@ -1,11 +1,33 @@ import { CredentialExchangeRecord } from '@credo-ts/core' import { AnonCredsProofRequestTemplatePayloadData, CredentialSharedProofData } from '@hyperledger/aries-bifold-verifier' -import { Attribute, Field, Predicate } from '@hyperledger/aries-oca/build/legacy' +import { + Attribute, + CredentialOverlay, + Field, + OCABundleResolveAllParams, + OCABundleResolverType, + Predicate, +} from '@hyperledger/aries-oca/build/legacy' +import { W3cCredentialDisplay } from '../modules/openid/types' +import { BrandingOverlay } from '@hyperledger/aries-oca' export const buildFieldsFromAnonCredsCredential = (credential: CredentialExchangeRecord): Array => { return credential?.credentialAttributes?.map((attr) => new Attribute(attr)) || [] } +export const buildFieldsFromW3cCredsCredential = (value: W3cCredentialDisplay): Array => { + return ( + Object.entries(value.attributes).map( + ([key, value]) => + new Attribute({ + name: key, + value: value as string | number | null, + mimeType: typeof value === 'number' ? 'text/number' : 'text/plain', + }) + ) || [] + ) +} + export const buildFieldsFromAnonCredsProofRequestTemplate = ( data: AnonCredsProofRequestTemplatePayloadData ): Array => { @@ -68,3 +90,34 @@ export const buildFieldsFromSharedAnonCredsProof = (data: CredentialSharedProofD } return fields } + +export const buildOverlayFromW3cCredential = async ({ + credentialDisplay, + language, + resolver, +}: { + credentialDisplay: W3cCredentialDisplay + language: string + resolver: OCABundleResolverType +}): Promise> => { + const params: OCABundleResolveAllParams = { + identifiers: { + schemaId: '', + credentialDefinitionId: credentialDisplay.id, + }, + meta: { + alias: credentialDisplay.display.issuer.name, + credConnectionId: undefined, + credName: credentialDisplay.display.name, + }, + attributes: buildFieldsFromW3cCredsCredential(credentialDisplay), + language, + } + + const bundle = await resolver.resolveAllBundles(params) + + return { + ...(bundle as CredentialOverlay), + presentationFields: buildFieldsFromW3cCredsCredential(credentialDisplay), + } +} diff --git a/packages/oca/src/legacy/resolver/oca.ts b/packages/oca/src/legacy/resolver/oca.ts index 50da14768f..ddf2e52f12 100644 --- a/packages/oca/src/legacy/resolver/oca.ts +++ b/packages/oca/src/legacy/resolver/oca.ts @@ -102,6 +102,7 @@ export interface Meta { alias?: string credName?: string credConnectionId?: string + logo?: string } export class OCABundle implements OCABundleType { @@ -347,7 +348,9 @@ export class DefaultOCABundleResolver implements OCABundleResolverType { const overlayBundle = bundle ?? defaultBundle const metaOverlay = overlayBundle?.metaOverlay const brandingOverlay = overlayBundle?.brandingOverlay - + if (brandingOverlay && 'logo' in brandingOverlay) { + (brandingOverlay as BrandingOverlay).logo = params.meta?.logo; + } return { bundle: overlayBundle, presentationFields: fields, metaOverlay, brandingOverlay } } }