diff --git a/lib/app/shared/enum/type/credential_subject_type/credential_subject_type.dart b/lib/app/shared/enum/type/credential_subject_type/credential_subject_type.dart index 27debc610..65f963d7a 100644 --- a/lib/app/shared/enum/type/credential_subject_type/credential_subject_type.dart +++ b/lib/app/shared/enum/type/credential_subject_type/credential_subject_type.dart @@ -63,4 +63,6 @@ enum CredentialSubjectType { civicPassCredential, employeeCredential, legalPersonalCredential, + identityCredential, + eudiPid, } diff --git a/lib/app/shared/enum/type/credential_subject_type/credential_subject_type_extension.dart b/lib/app/shared/enum/type/credential_subject_type/credential_subject_type_extension.dart index 7fbd628e6..98165ae0e 100644 --- a/lib/app/shared/enum/type/credential_subject_type/credential_subject_type_extension.dart +++ b/lib/app/shared/enum/type/credential_subject_type/credential_subject_type_extension.dart @@ -85,6 +85,8 @@ extension CredentialSubjectTypeExtension on CredentialSubjectType { case CredentialSubjectType.civicPassCredential: case CredentialSubjectType.employeeCredential: case CredentialSubjectType.legalPersonalCredential: + case CredentialSubjectType.identityCredential: + case CredentialSubjectType.eudiPid: return Colors.white; } } @@ -203,6 +205,10 @@ extension CredentialSubjectTypeExtension on CredentialSubjectType { return 'ProofOfTwitterStats'; case CredentialSubjectType.civicPassCredential: return 'CivicPassCredential'; + case CredentialSubjectType.identityCredential: + return 'IdentityCredential'; + case CredentialSubjectType.eudiPid: + return 'EudiPid'; case CredentialSubjectType.defaultCredential: return ''; } @@ -240,8 +246,6 @@ extension CredentialSubjectTypeExtension on CredentialSubjectType { return BinanceAssociatedAddressModel.fromJson(json); case CredentialSubjectType.certificateOfEmployment: return CertificateOfEmploymentModel.fromJson(json); - case CredentialSubjectType.defaultCredential: - return DefaultCredentialSubjectModel.fromJson(json); case CredentialSubjectType.emailPass: return EmailPassModel.fromJson(json); case CredentialSubjectType.identityPass: @@ -324,6 +328,12 @@ extension CredentialSubjectTypeExtension on CredentialSubjectType { return EmployeeCredentialModel.fromJson(json); case CredentialSubjectType.legalPersonalCredential: return LegalPersonCredentialModel.fromJson(json); + case CredentialSubjectType.defaultCredential: + return DefaultCredentialSubjectModel.fromJson(json); + case CredentialSubjectType.identityCredential: + return IdentityCredentialSubjectModel.fromJson(json); + case CredentialSubjectType.eudiPid: + return EudipidSubjectModel.fromJson(json); } } @@ -555,6 +565,10 @@ extension CredentialSubjectTypeExtension on CredentialSubjectType { return 'Employee Credential'; case CredentialSubjectType.legalPersonalCredential: return 'Legal Person Credential'; + case CredentialSubjectType.identityCredential: + return 'Identity Credential'; + case CredentialSubjectType.eudiPid: + return 'EudiPid'; case CredentialSubjectType.defaultCredential: return ''; } @@ -620,6 +634,8 @@ extension CredentialSubjectTypeExtension on CredentialSubjectType { case CredentialSubjectType.civicPassCredential: case CredentialSubjectType.employeeCredential: case CredentialSubjectType.legalPersonalCredential: + case CredentialSubjectType.identityCredential: + case CredentialSubjectType.eudiPid: return false; } } @@ -652,6 +668,10 @@ extension CredentialSubjectTypeExtension on CredentialSubjectType { VCFormatType.vcSdJWT, ]; + case CredentialSubjectType.identityCredential: + case CredentialSubjectType.eudiPid: + return [VCFormatType.vcSdJWT]; + case CredentialSubjectType.over18: case CredentialSubjectType.phonePass: case CredentialSubjectType.livenessCard: @@ -996,6 +1016,8 @@ extension CredentialSubjectTypeExtension on CredentialSubjectType { case CredentialSubjectType.civicPassCredential: case CredentialSubjectType.legalPersonalCredential: case CredentialSubjectType.walletCredential: + case CredentialSubjectType.identityCredential: + case CredentialSubjectType.eudiPid: break; } @@ -1130,6 +1152,8 @@ extension CredentialSubjectTypeExtension on CredentialSubjectType { case CredentialSubjectType.civicPassCredential: case CredentialSubjectType.employeeCredential: case CredentialSubjectType.legalPersonalCredential: + case CredentialSubjectType.identityCredential: + case CredentialSubjectType.eudiPid: return 0; } } diff --git a/lib/app/shared/helper_functions/helper_functions.dart b/lib/app/shared/helper_functions/helper_functions.dart index f775fc72b..540b4435d 100644 --- a/lib/app/shared/helper_functions/helper_functions.dart +++ b/lib/app/shared/helper_functions/helper_functions.dart @@ -4,9 +4,9 @@ import 'dart:io'; import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/oidc4vc/oidc4vc.dart'; -import 'package:altme/selective_disclosure/selective_disclosure.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:convert/convert.dart'; +import 'package:credential_manifest/credential_manifest.dart'; import 'package:crypto/crypto.dart'; import 'package:dartez/dartez.dart'; @@ -1594,77 +1594,6 @@ Future<(String?, String?, String?, String?)> getClientDetails({ return (display, credentialSupported); } -String? getClaimsData({ - required Map uncryptedDatas, - required CredentialModel credentialModel, - required String key, - required bool selectFromSelectiveDisclosure, -}) { - String? data; - - final JsonPath dataPath = JsonPath( - // ignore: prefer_interpolation_to_compose_strings - r'$..' + key, - ); - - try { - final uncryptedDataPath = dataPath.read(uncryptedDatas).first; - data = uncryptedDataPath.value.toString(); - } catch (e) { - if (!selectFromSelectiveDisclosure) { - try { - final credentialModelPath = dataPath.read(credentialModel.data).first; - data = credentialModelPath.value.toString(); - } catch (e) { - data = null; - } - } - } - - return data; -} - -String? getPicture({ - required CredentialModel credentialModel, -}) { - if (credentialModel.format.toString() != VCFormatType.vcSdJWT.value) { - return null; - } - - final credentialSupported = credentialModel.credentialSupported; - if (credentialSupported == null) return null; - - final claims = credentialSupported['claims']; - if (claims is! Map) return null; - - final picture = claims['picture']; - if (picture == null) return null; - if (picture is! Map) return null; - - if (picture.containsKey('mandatory')) { - final mandatory = picture['mandatory']; - if (mandatory is! bool) return null; - } - - final valueType = picture['value_type']; - if (valueType == null) return null; - - if (valueType == 'image/jpeg') { - final selectiveDisclosure = SelectiveDisclosure(credentialModel); - - final data = getClaimsData( - uncryptedDatas: selectiveDisclosure.values, - credentialModel: credentialModel, - key: 'picture', - selectFromSelectiveDisclosure: false, - ); - - return data; - } else { - return null; - } -} - List getStringCredentialsForToken({ required List credentialsToBePresented, required ProfileCubit profileCubit, @@ -1688,3 +1617,77 @@ String hash(String text) { final digest = sha256.convert(bytes); return base64Url.encode(digest.bytes).replaceAll('=', ''); } + +//(presentLdpVc, presentJwtVc, presentJwtVcJson, presentVcSdJwt) +(bool, bool, bool, bool) getPresentVCDetails({ + required VCFormatType vcFormatType, + required PresentationDefinition presentationDefinition, + required Map? clientMetaData, +}) { + bool presentLdpVc = false; + bool presentJwtVc = false; + bool presentJwtVcJson = false; + bool presentVcSdJwt = false; + + if (presentationDefinition.format != null) { + /// ldp_vc + presentLdpVc = presentationDefinition.format?.ldpVc != null; + + /// jwt_vc + presentJwtVc = presentationDefinition.format?.jwtVc != null; + + /// jwt_vc_json + presentJwtVcJson = presentationDefinition.format?.jwtVcJson != null; + + /// vc+sd-jwt + presentVcSdJwt = presentationDefinition.format?.vcSdJwt != null; + } else { + if (clientMetaData == null) { + /// credential manifest case + if (vcFormatType == VCFormatType.ldpVc) { + presentLdpVc = true; + } else if (vcFormatType == VCFormatType.jwtVc) { + presentJwtVc = true; + } else if (vcFormatType == VCFormatType.jwtVcJson) { + presentJwtVcJson = true; + } else if (vcFormatType == VCFormatType.vcSdJWT) { + presentVcSdJwt = true; + } + } else { + final vpFormats = clientMetaData['vp_formats'] as Map; + + /// ldp_vc + presentLdpVc = vpFormats.containsKey('ldp_vc'); + + /// jwt_vc + presentJwtVc = vpFormats.containsKey('jwt_vc'); + + /// jwt_vc_json + presentJwtVcJson = vpFormats.containsKey('jwt_vc_json'); + + /// vc+sd-jwt + presentVcSdJwt = vpFormats.containsKey('vc+sd-jwt'); + } + + if (!presentLdpVc && vcFormatType == VCFormatType.ldpVc) { + presentLdpVc = true; + } else if (!presentJwtVc && (vcFormatType == VCFormatType.jwtVc)) { + presentJwtVc = true; + } else if (!presentJwtVcJson && (vcFormatType == VCFormatType.jwtVcJson)) { + presentJwtVcJson = true; + } else if (!presentJwtVc && vcFormatType == VCFormatType.vcSdJWT) { + presentVcSdJwt = true; + } + } + + if (!presentLdpVc && !presentJwtVc && !presentJwtVcJson && !presentVcSdJwt) { + throw ResponseMessage( + data: { + 'error': 'invalid_request', + 'error_description': 'VC format is missing', + }, + ); + } + + return (presentLdpVc, presentJwtVc, presentJwtVcJson, presentVcSdJwt); +} diff --git a/lib/app/shared/widget/base/credential_field.dart b/lib/app/shared/widget/base/credential_field.dart index a197a5297..80b2909a9 100644 --- a/lib/app/shared/widget/base/credential_field.dart +++ b/lib/app/shared/widget/base/credential_field.dart @@ -5,6 +5,7 @@ class CredentialField extends StatelessWidget { const CredentialField({ super.key, required this.value, + required this.showVertically, this.title, this.titleColor, this.valueColor, @@ -16,6 +17,7 @@ class CredentialField extends StatelessWidget { final Color? titleColor; final Color? valueColor; final EdgeInsetsGeometry padding; + final bool showVertically; @override Widget build(BuildContext context) { @@ -27,6 +29,7 @@ class CredentialField extends StatelessWidget { titleColor: titleColor, valueColor: valueColor, padding: padding, + showVertically: showVertically, ), ); } @@ -40,6 +43,7 @@ class DisplayCredentialField extends StatelessWidget { this.titleColor, this.valueColor, required this.padding, + required this.showVertically, }); final String? title; @@ -47,33 +51,31 @@ class DisplayCredentialField extends StatelessWidget { final Color? titleColor; final Color? valueColor; final EdgeInsetsGeometry padding; + final bool showVertically; @override Widget build(BuildContext context) { + final textTheme = Theme.of(context).textTheme; + return Padding( padding: padding, child: SelectableText.rich( textAlign: TextAlign.left, TextSpan( children: [ - if (title != null) + if (title != null) ...[ TextSpan( - text: '$title: ', - style: titleColor == null - ? Theme.of(context).textTheme.credentialFieldTitle - : Theme.of(context) - .textTheme - .credentialFieldTitle - .copyWith(color: titleColor), + text: showVertically ? title : '$title: ', + style: textTheme.credentialFieldTitle.copyWith( + color: titleColor, + ), ), + if (showVertically) const TextSpan(text: ' \n'), + ], TextSpan( text: value, - style: valueColor == null - ? Theme.of(context).textTheme.credentialFieldDescription - : Theme.of(context) - .textTheme - .credentialFieldDescription - .copyWith(color: valueColor), + style: textTheme.credentialFieldDescription + .copyWith(color: valueColor), ), ], ), diff --git a/lib/chat_room/view/chat_room_view.dart b/lib/chat_room/view/chat_room_view.dart index 4aaf6b431..7215c4d9e 100644 --- a/lib/chat_room/view/chat_room_view.dart +++ b/lib/chat_room/view/chat_room_view.dart @@ -35,8 +35,19 @@ class _ChatRoomViewState extends State { (_) async { liveChatCubit = context.read(); - if (context.read().state.model.walletType == - WalletType.enterprise) { + final displayChatSupport = context + .read() + .state + .model + .profileSetting + .helpCenterOptions + .displayChatSupport; + + final isEnterprise = + context.read().state.model.walletType == + WalletType.enterprise; + + if (displayChatSupport || isEnterprise) { // for enterprise we are initialisation at start await context.read().init(); } diff --git a/lib/dashboard/drawer/ssi/manage_did/view/did_menu.dart b/lib/dashboard/drawer/ssi/manage_did/view/did_menu.dart index 1df2d12d5..4e32543da 100644 --- a/lib/dashboard/drawer/ssi/manage_did/view/did_menu.dart +++ b/lib/dashboard/drawer/ssi/manage_did/view/did_menu.dart @@ -54,6 +54,10 @@ class DidView extends StatelessWidget { itemBuilder: (context, index) { final didKeyType = DidKeyType.values[index]; + if (didKeyType == DidKeyType.jwtClientAttestation) { + return Container(); + } + final title = didKeyType.getTitle(l10n); return DrawerItem( title: title, diff --git a/lib/dashboard/home/tab_bar/credentials/detail/view/credentials_details_page.dart b/lib/dashboard/home/tab_bar/credentials/detail/view/credentials_details_page.dart index 594ee5bfb..0bcbb3bc5 100644 --- a/lib/dashboard/home/tab_bar/credentials/detail/view/credentials_details_page.dart +++ b/lib/dashboard/home/tab_bar/credentials/detail/view/credentials_details_page.dart @@ -7,6 +7,7 @@ import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/dashboard/home/tab_bar/credentials/models/activity/activity.dart'; import 'package:altme/l10n/l10n.dart'; import 'package:altme/polygon_id/polygon_id.dart'; +import 'package:altme/selective_disclosure/selective_disclosure.dart'; import 'package:altme/selective_disclosure/widget/display_selective_disclosure.dart'; import 'package:altme/theme/theme.dart'; import 'package:altme/wallet/cubit/wallet_cubit.dart'; @@ -152,9 +153,17 @@ class _CredentialsDetailsViewState extends State { String? credentialImage; if (containClaims) { - credentialImage = getPicture(credentialModel: widget.credentialModel); + credentialImage = SelectiveDisclosure(widget.credentialModel).getPicture; } + final credentialSubjectType = widget.credentialModel.credentialPreview + .credentialSubjectModel.credentialSubjectType; + + final showVerticalDescription = + credentialSubjectType == CredentialSubjectType.eudiPid || + credentialSubjectType == CredentialSubjectType.identityCredential || + credentialSubjectType == CredentialSubjectType.verifiableIdCard; + return BlocConsumer( listener: (context, state) { if (state.status == AppStatus.loading) { @@ -200,23 +209,7 @@ class _CredentialsDetailsViewState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ if (credentialImage != null) - AspectRatio( - aspectRatio: Sizes.credentialAspectRatio, - child: ClipRRect( - borderRadius: BorderRadius.circular( - Sizes.credentialBorderRadius, - ), - child: CachedImageFromNetwork( - credentialImage, - fit: BoxFit.contain, - width: double.infinity, - bgColor: Colors.transparent, - height: double.infinity, - errorMessage: '', - showLoading: false, - ), - ), - ) + PictureDisplay(credentialImage: credentialImage) else CredentialDisplay( credentialModel: widget.credentialModel, @@ -330,6 +323,7 @@ class _CredentialsDetailsViewState extends State { /// credentialSubjectData CredentialSubjectData( credentialModel: widget.credentialModel, + showVertically: showVerticalDescription, ), /// selective disclouse data - _sd @@ -338,6 +332,7 @@ class _CredentialsDetailsViewState extends State { DisplaySelectiveDisclosure( credentialModel: widget.credentialModel, claims: null, + showVertically: showVerticalDescription, ), ], @@ -365,6 +360,7 @@ class _CredentialsDetailsViewState extends State { isDeveloperMode) ...[ DeveloperDetails( credentialModel: widget.credentialModel, + showVertically: showVerticalDescription, ), ], @@ -372,6 +368,7 @@ class _CredentialsDetailsViewState extends State { if (widget.credentialModel.pendingInfo != null) ...[ DeferredCredentialData( credentialModel: widget.credentialModel, + showVertically: showVerticalDescription, ), ], ], @@ -384,6 +381,7 @@ class _CredentialsDetailsViewState extends State { itemBuilder: (context, index) { return ActivityWidget( activity: reversedList[index], + showVertically: showVerticalDescription, ); }, ), diff --git a/lib/dashboard/home/tab_bar/credentials/detail/widgets/credential_subject_data.dart b/lib/dashboard/home/tab_bar/credentials/detail/widgets/credential_subject_data.dart index 88ea01dcb..eecab729e 100644 --- a/lib/dashboard/home/tab_bar/credentials/detail/widgets/credential_subject_data.dart +++ b/lib/dashboard/home/tab_bar/credentials/detail/widgets/credential_subject_data.dart @@ -10,9 +10,11 @@ class CredentialSubjectData extends StatelessWidget { const CredentialSubjectData({ super.key, required this.credentialModel, + required this.showVertically, }); final CredentialModel credentialModel; + final bool showVertically; @override Widget build(BuildContext context) { @@ -97,6 +99,7 @@ class CredentialSubjectData extends StatelessWidget { value: data, titleColor: Theme.of(context).colorScheme.titleColor, valueColor: Theme.of(context).colorScheme.valueColor, + showVertically: showVertically, ), ); }).toList(), diff --git a/lib/dashboard/home/tab_bar/credentials/detail/widgets/deferred_credential_data.dart b/lib/dashboard/home/tab_bar/credentials/detail/widgets/deferred_credential_data.dart index f3423aff8..38778ef15 100644 --- a/lib/dashboard/home/tab_bar/credentials/detail/widgets/deferred_credential_data.dart +++ b/lib/dashboard/home/tab_bar/credentials/detail/widgets/deferred_credential_data.dart @@ -8,9 +8,11 @@ class DeferredCredentialData extends StatelessWidget { const DeferredCredentialData({ super.key, required this.credentialModel, + required this.showVertically, }); final CredentialModel credentialModel; + final bool showVertically; @override Widget build(BuildContext context) { @@ -26,6 +28,7 @@ class DeferredCredentialData extends StatelessWidget { value: credentialModel.pendingInfo!.issuer ?? '', titleColor: Theme.of(context).colorScheme.titleColor, valueColor: Theme.of(context).colorScheme.valueColor, + showVertically: showVertically, ), const SizedBox(height: 10), CredentialField( @@ -36,6 +39,7 @@ class DeferredCredentialData extends StatelessWidget { ), titleColor: Theme.of(context).colorScheme.titleColor, valueColor: Theme.of(context).colorScheme.valueColor, + showVertically: showVertically, ), ], ); diff --git a/lib/dashboard/home/tab_bar/credentials/detail/widgets/developer_details.dart b/lib/dashboard/home/tab_bar/credentials/detail/widgets/developer_details.dart index e0a78bfdf..320ee2a73 100644 --- a/lib/dashboard/home/tab_bar/credentials/detail/widgets/developer_details.dart +++ b/lib/dashboard/home/tab_bar/credentials/detail/widgets/developer_details.dart @@ -8,9 +8,11 @@ class DeveloperDetails extends StatelessWidget { const DeveloperDetails({ super.key, required this.credentialModel, + required this.showVertically, }); final CredentialModel credentialModel; + final bool showVertically; @override Widget build(BuildContext context) { @@ -31,6 +33,7 @@ class DeveloperDetails extends StatelessWidget { value: credentialModel.getFormat, titleColor: Theme.of(context).colorScheme.titleColor, valueColor: Theme.of(context).colorScheme.valueColor, + showVertically: showVertically, ), const SizedBox(height: 10), CredentialField( @@ -39,6 +42,7 @@ class DeveloperDetails extends StatelessWidget { value: issuerDid, titleColor: Theme.of(context).colorScheme.titleColor, valueColor: Theme.of(context).colorScheme.valueColor, + showVertically: showVertically, ), if (credentialModel.credentialPreview.credentialSubjectModel is! WalletCredentialModel && @@ -50,6 +54,7 @@ class DeveloperDetails extends StatelessWidget { value: subjectDid, titleColor: Theme.of(context).colorScheme.titleColor, valueColor: Theme.of(context).colorScheme.valueColor, + showVertically: showVertically, ), ], const SizedBox(height: 10), @@ -59,6 +64,7 @@ class DeveloperDetails extends StatelessWidget { value: type, titleColor: Theme.of(context).colorScheme.titleColor, valueColor: Theme.of(context).colorScheme.valueColor, + showVertically: showVertically, ), ], ); diff --git a/lib/dashboard/home/tab_bar/credentials/models/eudipid/eudipid_subject_model.dart b/lib/dashboard/home/tab_bar/credentials/models/eudipid/eudipid_subject_model.dart new file mode 100644 index 000000000..6831da48f --- /dev/null +++ b/lib/dashboard/home/tab_bar/credentials/models/eudipid/eudipid_subject_model.dart @@ -0,0 +1,23 @@ +import 'package:altme/app/app.dart'; +import 'package:altme/dashboard/home/tab_bar/credentials/models/credential_subject/credential_subject_model.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'eudipid_subject_model.g.dart'; + +@JsonSerializable(explicitToJson: true) +class EudipidSubjectModel extends CredentialSubjectModel { + EudipidSubjectModel({ + super.id, + super.type, + super.issuedBy, + }) : super( + credentialSubjectType: CredentialSubjectType.eudiPid, + credentialCategory: CredentialCategory.identityCards, + ); + + factory EudipidSubjectModel.fromJson(Map json) => + _$EudipidSubjectModelFromJson(json); + + @override + Map toJson() => _$EudipidSubjectModelToJson(this); +} diff --git a/lib/dashboard/home/tab_bar/credentials/models/identity_credential/identity_credential_subject_model.dart b/lib/dashboard/home/tab_bar/credentials/models/identity_credential/identity_credential_subject_model.dart new file mode 100644 index 000000000..189328594 --- /dev/null +++ b/lib/dashboard/home/tab_bar/credentials/models/identity_credential/identity_credential_subject_model.dart @@ -0,0 +1,23 @@ +import 'package:altme/app/app.dart'; +import 'package:altme/dashboard/home/tab_bar/credentials/models/credential_subject/credential_subject_model.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'identity_credential_subject_model.g.dart'; + +@JsonSerializable(explicitToJson: true) +class IdentityCredentialSubjectModel extends CredentialSubjectModel { + IdentityCredentialSubjectModel({ + super.id, + super.type, + super.issuedBy, + }) : super( + credentialSubjectType: CredentialSubjectType.identityCredential, + credentialCategory: CredentialCategory.identityCards, + ); + + factory IdentityCredentialSubjectModel.fromJson(Map json) => + _$IdentityCredentialSubjectModelFromJson(json); + + @override + Map toJson() => _$IdentityCredentialSubjectModelToJson(this); +} diff --git a/lib/dashboard/home/tab_bar/credentials/models/model.dart b/lib/dashboard/home/tab_bar/credentials/models/model.dart index a2ddca221..c9142e079 100644 --- a/lib/dashboard/home/tab_bar/credentials/models/model.dart +++ b/lib/dashboard/home/tab_bar/credentials/models/model.dart @@ -28,10 +28,12 @@ export 'ethereum_associated_address/ethereum_associated_address_model.dart'; export 'ethereum_poo_address/ethereum_poo_address_model.dart'; export 'eu_diploma_card/eu_diploma_card_model.dart'; export 'eu_verifiable_id/eu_verifiable_id_model.dart'; +export 'eudipid/eudipid_subject_model.dart'; export 'fantom_associated_address/fantom_associated_address_credential.dart'; export 'fantom_associated_address/fantom_associated_address_model.dart'; export 'fantom_poo_address/fantom_poo_address_model.dart'; export 'gender/gender_model.dart'; +export 'identity_credential/identity_credential_subject_model.dart'; export 'identity_pass/identity_pass_model.dart'; export 'kyc_age_credential/kyc_age_credential_model.dart'; export 'kyc_country_of_residence/kyc_country_of_residence_model.dart'; diff --git a/lib/dashboard/home/tab_bar/credentials/present/pick/credential_manifest/cubit/credential_manifest_pick_cubit.dart b/lib/dashboard/home/tab_bar/credentials/present/pick/credential_manifest/cubit/credential_manifest_pick_cubit.dart index 594231902..678c6d097 100644 --- a/lib/dashboard/home/tab_bar/credentials/present/pick/credential_manifest/cubit/credential_manifest_pick_cubit.dart +++ b/lib/dashboard/home/tab_bar/credentials/present/pick/credential_manifest/cubit/credential_manifest_pick_cubit.dart @@ -5,6 +5,7 @@ import 'package:credential_manifest/credential_manifest.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:json_annotation/json_annotation.dart'; +import 'package:oidc4vc/oidc4vc.dart'; part 'credential_manifest_pick_state.dart'; @@ -16,11 +17,13 @@ class CredentialManifestPickCubit extends Cubit { required List credentialList, required CredentialModel credential, required int inputDescriptorIndex, + required VCFormatType vcFormatType, }) : super(const CredentialManifestPickState(filteredCredentialList: [])) { filterList( credentialList: credentialList, credential: credential, inputDescriptorIndex: inputDescriptorIndex, + vcFormatType: vcFormatType, ); } @@ -28,6 +31,7 @@ class CredentialManifestPickCubit extends Cubit { required List credentialList, required CredentialModel credential, required int inputDescriptorIndex, + required VCFormatType vcFormatType, }) { var presentationDefinition = credential.credentialManifest!.presentationDefinition!; @@ -40,6 +44,8 @@ class CredentialManifestPickCubit extends Cubit { presentationDefinition: presentationDefinition, credentialList: List.from(credentialList), inputDescriptorIndex: inputDescriptorIndex, + clientMetaData: null, + vcFormatType: vcFormatType, ); emit( @@ -53,6 +59,7 @@ class CredentialManifestPickCubit extends Cubit { void toggle({ required int index, required InputDescriptor inputDescriptor, + required bool isVcSdJWT, }) { final bool isSelected = state.selected.contains(index); @@ -63,11 +70,16 @@ class CredentialManifestPickCubit extends Cubit { selected = List.from(state.selected) ..removeWhere((element) => element == index); } else { - /// selecting the credential - selected = [ - ...state.selected, - ...[index], - ]; + if (isVcSdJWT) { + /// selecting one credential + selected = [index]; + } else { + /// selecting multiple credential + selected = [ + ...state.selected, + ...[index], + ]; + } } bool isButtonEnabled = selected.isNotEmpty; diff --git a/lib/dashboard/home/tab_bar/credentials/present/pick/credential_manifest/helpers/filter_credential_list_by_format.dart b/lib/dashboard/home/tab_bar/credentials/present/pick/credential_manifest/helpers/filter_credential_list_by_format.dart index f2c55ffa3..0183f8046 100644 --- a/lib/dashboard/home/tab_bar/credentials/present/pick/credential_manifest/helpers/filter_credential_list_by_format.dart +++ b/lib/dashboard/home/tab_bar/credentials/present/pick/credential_manifest/helpers/filter_credential_list_by_format.dart @@ -1,36 +1,57 @@ +import 'package:altme/app/app.dart'; import 'package:altme/dashboard/home/tab_bar/credentials/models/credential_model/credential_model.dart'; import 'package:credential_manifest/credential_manifest.dart'; +import 'package:oidc4vc/oidc4vc.dart'; -List filterCredenialListByFormat( - List credentialList, - Format? format, - List filterList, -) { +List filterCredenialListByFormat({ + required VCFormatType vcFormatType, + required Map? clientMetaData, + required List credentialList, + required PresentationDefinition presentationDefinition, + required List filterList, +}) { + final credentials = List.from(credentialList); if (filterList.isNotEmpty) { - final isJwtVpInJwtVCRequired = format?.jwtVp != null; - - final presentLdpVc = format?.ldpVc != null; - final presentJwtVc = format?.jwtVc != null; + final isJwtVpInJwtVCRequired = presentationDefinition.format?.jwtVp != null; if (isJwtVpInJwtVCRequired) { - credentialList.removeWhere( + credentials.removeWhere( (CredentialModel credentialModel) => credentialModel.jwt == null, ); } - /// remove ldp_vc - if (presentJwtVc) { - credentialList.removeWhere( - (CredentialModel credentialModel) => credentialModel.jwt == null, - ); - } + final (presentLdpVc, presentJwtVc, presentJwtVcJson, presentVcSdJwt) = + getPresentVCDetails( + clientMetaData: clientMetaData, + presentationDefinition: presentationDefinition, + vcFormatType: vcFormatType, + ); - /// remove jwt_vc - if (presentLdpVc) { - credentialList.removeWhere( - (CredentialModel credentialModel) => credentialModel.jwt != null, - ); - } + credentials.removeWhere( + (CredentialModel credentialModel) { + /// remove ldpVc + if (presentLdpVc) { + return credentialModel.format != VCFormatType.ldpVc.value; + } + + /// remove jwtVc + if (presentJwtVc) { + return credentialModel.format != VCFormatType.jwtVc.value; + } + + /// remove JwtVcJson + if (presentJwtVcJson) { + return credentialModel.format != VCFormatType.jwtVcJson.value; + } + + /// remove vcSdJwt + if (presentVcSdJwt) { + return credentialModel.format != VCFormatType.vcSdJWT.value; + } + + return false; + }, + ); } - return credentialList; + return credentials; } diff --git a/lib/dashboard/home/tab_bar/credentials/present/pick/credential_manifest/helpers/get_credentials_from_presentation_definition.dart b/lib/dashboard/home/tab_bar/credentials/present/pick/credential_manifest/helpers/get_credentials_from_presentation_definition.dart index 61a2f1882..3c17af70b 100644 --- a/lib/dashboard/home/tab_bar/credentials/present/pick/credential_manifest/helpers/get_credentials_from_presentation_definition.dart +++ b/lib/dashboard/home/tab_bar/credentials/present/pick/credential_manifest/helpers/get_credentials_from_presentation_definition.dart @@ -1,9 +1,12 @@ import 'package:altme/dashboard/home/tab_bar/credentials/credential.dart'; import 'package:altme/dashboard/home/tab_bar/credentials/present/pick/credential_manifest/helpers/filter_credential_list_by_format.dart'; import 'package:credential_manifest/credential_manifest.dart'; +import 'package:oidc4vc/oidc4vc.dart'; List getCredentialsFromPresentationDefinition({ + required VCFormatType vcFormatType, required PresentationDefinition presentationDefinition, + required Map? clientMetaData, required List credentialList, required int inputDescriptorIndex, }) { @@ -12,9 +15,11 @@ List getCredentialsFromPresentationDefinition({ []; final credentialListFilteredByFormat = filterCredenialListByFormat( - List.from(credentialList), - presentationDefinition.format, - filterList, + credentialList: List.from(credentialList), + presentationDefinition: presentationDefinition, + filterList: filterList, + clientMetaData: clientMetaData, + vcFormatType: vcFormatType, ); /// If we have some instructions we filter the wallet's diff --git a/lib/dashboard/home/tab_bar/credentials/present/pick/credential_manifest/view/credential_manifest_credential_offer_pick_page.dart b/lib/dashboard/home/tab_bar/credentials/present/pick/credential_manifest/view/credential_manifest_credential_offer_pick_page.dart index 0a547ede4..6675a2da9 100644 --- a/lib/dashboard/home/tab_bar/credentials/present/pick/credential_manifest/view/credential_manifest_credential_offer_pick_page.dart +++ b/lib/dashboard/home/tab_bar/credentials/present/pick/credential_manifest/view/credential_manifest_credential_offer_pick_page.dart @@ -52,6 +52,14 @@ class CredentialManifestOfferPickPage extends StatelessWidget { credential: credential, credentialList: context.read().state.credentials, inputDescriptorIndex: inputDescriptorIndex, + vcFormatType: context + .read() + .state + .model + .profileSetting + .selfSovereignIdentityOptions + .customOidc4vcProfile + .vcFormatType, ); }, child: CredentialManifestOfferPickView( @@ -176,6 +184,7 @@ class CredentialManifestOfferPickView extends StatelessWidget { presentationDefinition .inputDescriptors[ inputDescriptorIndex], + isVcSdJWT: isVcSdJWT, ); }, ); diff --git a/lib/dashboard/home/tab_bar/credentials/present/pick/selective_disclosure/cubit/selective_disclosure_pick_cubit.dart b/lib/dashboard/home/tab_bar/credentials/present/pick/selective_disclosure/cubit/selective_disclosure_pick_cubit.dart index 07a4670c5..f7b3f6b88 100644 --- a/lib/dashboard/home/tab_bar/credentials/present/pick/selective_disclosure/cubit/selective_disclosure_pick_cubit.dart +++ b/lib/dashboard/home/tab_bar/credentials/present/pick/selective_disclosure/cubit/selective_disclosure_pick_cubit.dart @@ -27,4 +27,23 @@ class SelectiveDisclosureCubit extends Cubit { } emit(state.copyWith(selected: selected)); } + + void saveIndexOfSDJWT(int index) { + final bool isSelected = state.selectedSDIndexInJWT.contains(index); + + late List selected; + + if (isSelected) { + /// deSelecting the credential + selected = List.from(state.selectedSDIndexInJWT) + ..removeWhere((element) => element == index); + } else { + /// selecting the credential + selected = [ + ...state.selectedSDIndexInJWT, + ...[index], + ]; + } + emit(state.copyWith(selectedSDIndexInJWT: selected)); + } } diff --git a/lib/dashboard/home/tab_bar/credentials/present/pick/selective_disclosure/cubit/selective_disclosure_pick_state.dart b/lib/dashboard/home/tab_bar/credentials/present/pick/selective_disclosure/cubit/selective_disclosure_pick_state.dart index 66273d6f8..db15c0470 100644 --- a/lib/dashboard/home/tab_bar/credentials/present/pick/selective_disclosure/cubit/selective_disclosure_pick_state.dart +++ b/lib/dashboard/home/tab_bar/credentials/present/pick/selective_disclosure/cubit/selective_disclosure_pick_state.dart @@ -5,6 +5,7 @@ class SelectiveDisclosureState extends Equatable { const SelectiveDisclosureState({ this.message, this.selected = const [], + this.selectedSDIndexInJWT = const [], }); factory SelectiveDisclosureState.fromJson(Map json) => @@ -12,13 +13,16 @@ class SelectiveDisclosureState extends Equatable { final StateMessage? message; final List selected; + final List selectedSDIndexInJWT; SelectiveDisclosureState copyWith({ List? selected, + List? selectedSDIndexInJWT, StateMessage? message, }) { return SelectiveDisclosureState( selected: selected ?? this.selected, + selectedSDIndexInJWT: selectedSDIndexInJWT ?? this.selectedSDIndexInJWT, message: message, ); } @@ -28,6 +32,7 @@ class SelectiveDisclosureState extends Equatable { @override List get props => [ selected, + selectedSDIndexInJWT, message, ]; } diff --git a/lib/dashboard/home/tab_bar/credentials/present/pick/selective_disclosure/view/selective_disclosure_pick_page.dart b/lib/dashboard/home/tab_bar/credentials/present/pick/selective_disclosure/view/selective_disclosure_pick_page.dart index 128795ffc..43cef0fb5 100644 --- a/lib/dashboard/home/tab_bar/credentials/present/pick/selective_disclosure/view/selective_disclosure_pick_page.dart +++ b/lib/dashboard/home/tab_bar/credentials/present/pick/selective_disclosure/view/selective_disclosure_pick_page.dart @@ -4,6 +4,7 @@ import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/l10n/l10n.dart'; import 'package:altme/scan/cubit/scan_cubit.dart'; +import 'package:altme/selective_disclosure/selective_disclosure.dart'; import 'package:altme/selective_disclosure/widget/display_selective_disclosure.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -88,6 +89,12 @@ class SelectiveDisclosurePickView extends StatelessWidget { }, child: BlocBuilder( builder: (context, state) { + final profileSetting = + context.read().state.model.profileSetting; + + final credentialImage = + SelectiveDisclosure(credentialToBePresented).getPicture; + return BasePage( title: l10n.thisOrganisationRequestsThisInformation, titleAlignment: Alignment.topCenter, @@ -96,13 +103,30 @@ class SelectiveDisclosurePickView extends StatelessWidget { vertical: 24, horizontal: 16, ), - body: DisplaySelectiveDisclosure( - credentialModel: credentialToBePresented, - claims: null, - selectedIndex: state.selected, - onPressed: (index) { - context.read().toggle(index); - }, + body: Column( + children: [ + if (credentialImage != null) + PictureDisplay(credentialImage: credentialImage) + else + CredentialDisplay( + credentialModel: credentialToBePresented, + credDisplayType: CredDisplayType.List, + profileSetting: profileSetting, + ), + const SizedBox(height: 20), + DisplaySelectiveDisclosure( + credentialModel: credentialToBePresented, + claims: null, + selectedIndex: state.selected, + onPressed: (claimIndex, sdIndexInJWT) { + context.read().toggle(claimIndex); + context + .read() + .saveIndexOfSDJWT(sdIndexInJWT); + }, + showVertically: true, + ), + ], ), navigation: SafeArea( child: Container( @@ -114,7 +138,7 @@ class SelectiveDisclosurePickView extends StatelessWidget { ? null : () => present( context: context, - selectedIndex: state.selected, + selectedSDIndexInJWT: state.selectedSDIndexInJWT, uri: uri, ), text: l10n.credentialPickPresent, @@ -130,7 +154,7 @@ class SelectiveDisclosurePickView extends StatelessWidget { Future present({ required BuildContext context, - required List selectedIndex, + required List selectedSDIndexInJWT, required Uri uri, }) async { final bool userPINCodeForAuthentication = context @@ -157,14 +181,17 @@ class SelectiveDisclosurePickView extends StatelessWidget { } } - final encryptedValues = credentialToBePresented.jwt?.split('~'); + final encryptedValues = credentialToBePresented.jwt + ?.split('~') + .where((element) => element.isNotEmpty) + .toList(); if (encryptedValues != null) { var newJwt = '${encryptedValues[0]}~'; encryptedValues.removeAt(0); - for (final index in selectedIndex) { + for (final index in selectedSDIndexInJWT) { newJwt = '$newJwt${encryptedValues[index]}~'; } diff --git a/lib/dashboard/home/tab_bar/credentials/widgets/activity_widget.dart b/lib/dashboard/home/tab_bar/credentials/widgets/activity_widget.dart index ca5383f2f..10e5c3126 100644 --- a/lib/dashboard/home/tab_bar/credentials/widgets/activity_widget.dart +++ b/lib/dashboard/home/tab_bar/credentials/widgets/activity_widget.dart @@ -8,9 +8,11 @@ class ActivityWidget extends StatelessWidget { const ActivityWidget({ super.key, required this.activity, + required this.showVertically, }); final Activity activity; + final bool showVertically; @override Widget build(BuildContext context) { @@ -28,6 +30,7 @@ class ActivityWidget extends StatelessWidget { titleColor: titleColor, valueColor: valueColor, padding: EdgeInsets.zero, + showVertically: showVertically, ), const SizedBox(height: 5), CredentialField( @@ -36,6 +39,7 @@ class ActivityWidget extends StatelessWidget { titleColor: titleColor, valueColor: valueColor, padding: EdgeInsets.zero, + showVertically: showVertically, ), const SizedBox(height: 10), Text( @@ -66,6 +70,7 @@ class ActivityWidget extends StatelessWidget { titleColor: titleColor, valueColor: valueColor, padding: EdgeInsets.zero, + showVertically: showVertically, ), const SizedBox(height: 5), ], diff --git a/lib/dashboard/home/tab_bar/credentials/widgets/credential_display.dart b/lib/dashboard/home/tab_bar/credentials/widgets/credential_display.dart index 86e788f63..53c071afd 100644 --- a/lib/dashboard/home/tab_bar/credentials/widgets/credential_display.dart +++ b/lib/dashboard/home/tab_bar/credentials/widgets/credential_display.dart @@ -95,6 +95,23 @@ class CredentialDisplay extends StatelessWidget { } } + case CredentialSubjectType.identityCredential: + case CredentialSubjectType.eudiPid: + switch (credDisplayType) { + case CredDisplayType.List: + return DefaultCredentialWidget( + credentialModel: credentialModel, + showBgDecoration: false, + displyalDescription: displyalDescription, + ); + case CredDisplayType.Detail: + return DefaultCredentialWidget( + credentialModel: credentialModel, + showBgDecoration: false, + descriptionMaxLine: 5, + ); + } + case CredentialSubjectType.emailPass: return EmailPassWidget(credentialModel: credentialModel); diff --git a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/identity_pass_widget.dart b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/identity_pass_widget.dart index f9e326931..86fc0c03b 100644 --- a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/identity_pass_widget.dart +++ b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/identity_pass_widget.dart @@ -25,6 +25,7 @@ class IdentityPassWidget extends StatelessWidget { CredentialField( title: l10n.expires, value: identityPassModel.expires!, + showVertically: false, ) else const SizedBox.shrink(), @@ -32,6 +33,7 @@ class IdentityPassWidget extends StatelessWidget { CredentialField( title: l10n.jobTitle, value: identityPassModel.recipient!.jobTitle, + showVertically: false, ) else const SizedBox.shrink(), @@ -39,6 +41,7 @@ class IdentityPassWidget extends StatelessWidget { CredentialField( title: l10n.firstName, value: identityPassModel.recipient!.familyName, + showVertically: false, ) else const SizedBox.shrink(), @@ -46,6 +49,7 @@ class IdentityPassWidget extends StatelessWidget { CredentialField( title: l10n.lastName, value: identityPassModel.recipient!.givenName, + showVertically: false, ) else const SizedBox.shrink(), @@ -60,6 +64,7 @@ class IdentityPassWidget extends StatelessWidget { CredentialField( title: l10n.address, value: identityPassModel.recipient!.address, + showVertically: false, ) else const SizedBox.shrink(), @@ -69,6 +74,7 @@ class IdentityPassWidget extends StatelessWidget { value: UiDate.formatStringDate( identityPassModel.recipient!.birthDate, ), + showVertically: false, ) else const SizedBox.shrink(), @@ -76,6 +82,7 @@ class IdentityPassWidget extends StatelessWidget { CredentialField( title: l10n.personalMail, value: identityPassModel.recipient!.email, + showVertically: false, ) else const SizedBox.shrink(), @@ -83,6 +90,7 @@ class IdentityPassWidget extends StatelessWidget { CredentialField( title: l10n.gender, value: identityPassModel.recipient!.gender, + showVertically: false, ) else const SizedBox.shrink(), @@ -90,6 +98,7 @@ class IdentityPassWidget extends StatelessWidget { CredentialField( title: l10n.personalPhone, value: identityPassModel.recipient!.telephone, + showVertically: false, ) else const SizedBox.shrink(), diff --git a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/professional_experience_assessment_widget.dart b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/professional_experience_assessment_widget.dart index 8cc252da4..b517b5274 100644 --- a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/professional_experience_assessment_widget.dart +++ b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/professional_experience_assessment_widget.dart @@ -28,14 +28,17 @@ class ProfessionalExperienceAssessmentWidget extends StatelessWidget { CredentialField( title: l10n.lastName, value: professionalExperienceAssessmentModel.givenName!, + showVertically: false, ), CredentialField( title: l10n.firstName, value: professionalExperienceAssessmentModel.familyName!, + showVertically: false, ), CredentialField( title: l10n.jobTitle, value: professionalExperienceAssessmentModel.title!, + showVertically: false, ), Padding( padding: const EdgeInsets.all(8), @@ -66,6 +69,7 @@ class ProfessionalExperienceAssessmentWidget extends StatelessWidget { ), CredentialField( value: professionalExperienceAssessmentModel.description!, + showVertically: false, ), SkillsListDisplay( skillWidgetList: professionalExperienceAssessmentModel.skills!, diff --git a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/professional_skill_assessment_widget.dart b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/professional_skill_assessment_widget.dart index f87c847fa..c618b3756 100644 --- a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/professional_skill_assessment_widget.dart +++ b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/professional_skill_assessment_widget.dart @@ -25,10 +25,12 @@ class ProfessionalSkillAssessmentWidget extends StatelessWidget { CredentialField( title: l10n.lastName, value: professionalSkillAssessmentModel.givenName!, + showVertically: false, ), CredentialField( title: l10n.firstName, value: professionalSkillAssessmentModel.familyName!, + showVertically: false, ), SkillsListDisplay( skillWidgetList: professionalSkillAssessmentModel.skills!, diff --git a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/resident_card_widget.dart b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/resident_card_widget.dart index 336fe444b..4195ed1b2 100644 --- a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/resident_card_widget.dart +++ b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/resident_card_widget.dart @@ -30,10 +30,12 @@ class ResidentCardWidget extends StatelessWidget { CredentialField( title: l10n.lastName, value: residentCardModel.familyName!, + showVertically: false, ), CredentialField( title: l10n.firstName, value: residentCardModel.givenName!, + showVertically: false, ), Padding( padding: const EdgeInsets.all(8), @@ -53,26 +55,32 @@ class ResidentCardWidget extends StatelessWidget { CredentialField( title: l10n.birthdate, value: UiDate.formatStringDate(residentCardModel.birthDate!), + showVertically: false, ), CredentialField( title: l10n.birthplace, value: residentCardModel.birthPlace!, + showVertically: false, ), CredentialField( title: l10n.address, value: residentCardModel.address!, + showVertically: false, ), CredentialField( title: l10n.maritalStatus, value: residentCardModel.maritalStatus!, + showVertically: false, ), CredentialField( title: l10n.identifier, value: residentCardModel.identifier!, + showVertically: false, ), CredentialField( title: l10n.nationality, value: residentCardModel.nationality!, + showVertically: false, ), ], ), diff --git a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/self_issued_widget.dart b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/self_issued_widget.dart index 7a51e4e09..d4a2fbd48 100644 --- a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/self_issued_widget.dart +++ b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/self_issued_widget.dart @@ -26,6 +26,7 @@ class SelfIssuedWidget extends StatelessWidget { CredentialField( title: l10n.firstName, value: selfIssuedModel.givenName!, + showVertically: false, ) else const SizedBox.shrink(), @@ -33,6 +34,7 @@ class SelfIssuedWidget extends StatelessWidget { CredentialField( title: l10n.lastName, value: selfIssuedModel.familyName!, + showVertically: false, ) else const SizedBox.shrink(), @@ -40,6 +42,7 @@ class SelfIssuedWidget extends StatelessWidget { CredentialField( title: l10n.personalPhone, value: selfIssuedModel.telephone!, + showVertically: false, ) else const SizedBox.shrink(), @@ -47,6 +50,7 @@ class SelfIssuedWidget extends StatelessWidget { CredentialField( title: l10n.address, value: selfIssuedModel.address!, + showVertically: false, ) else const SizedBox.shrink(), @@ -54,6 +58,7 @@ class SelfIssuedWidget extends StatelessWidget { CredentialField( title: l10n.personalMail, value: selfIssuedModel.email!, + showVertically: false, ) else const SizedBox.shrink(), @@ -61,6 +66,7 @@ class SelfIssuedWidget extends StatelessWidget { CredentialField( title: l10n.companyName, value: selfIssuedModel.workFor!, + showVertically: false, ) else const SizedBox.shrink(), @@ -68,6 +74,7 @@ class SelfIssuedWidget extends StatelessWidget { CredentialField( title: l10n.companyWebsite, value: selfIssuedModel.companyWebsite!, + showVertically: false, ) else const SizedBox.shrink(), @@ -75,6 +82,7 @@ class SelfIssuedWidget extends StatelessWidget { CredentialField( title: l10n.jobTitle, value: selfIssuedModel.jobTitle!, + showVertically: false, ) else const SizedBox.shrink(), diff --git a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/wallet_credential_widget.dart b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/wallet_credential_widget.dart index 2d90f9cd9..4e3a49b66 100644 --- a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/wallet_credential_widget.dart +++ b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/wallet_credential_widget.dart @@ -57,6 +57,7 @@ class WalletCredentialetailsWidget extends StatelessWidget { value: walletCredential.publicKey ?? '', titleColor: titleColor, valueColor: valueColor, + showVertically: false, ), const SizedBox(height: 10), ] else ...[ @@ -68,6 +69,7 @@ class WalletCredentialetailsWidget extends StatelessWidget { value: walletCredential.walletInstanceKey ?? '', titleColor: titleColor, valueColor: valueColor, + showVertically: false, ), const SizedBox(height: 10), CredentialField( @@ -78,6 +80,7 @@ class WalletCredentialetailsWidget extends StatelessWidget { ), titleColor: titleColor, valueColor: valueColor, + showVertically: false, ), const SizedBox(height: 10), CredentialField( @@ -88,6 +91,7 @@ class WalletCredentialetailsWidget extends StatelessWidget { ), titleColor: titleColor, valueColor: valueColor, + showVertically: false, ), ], ); diff --git a/lib/dashboard/home/tab_bar/credentials/widgets/display_signature.dart b/lib/dashboard/home/tab_bar/credentials/widgets/display_signature.dart index 4db035a5d..b41dd97b7 100644 --- a/lib/dashboard/home/tab_bar/credentials/widgets/display_signature.dart +++ b/lib/dashboard/home/tab_bar/credentials/widgets/display_signature.dart @@ -18,7 +18,11 @@ class DisplaySignatures extends StatelessWidget { Widget build(BuildContext context) { return Column( children: [ - CredentialField(title: localizations.signedBy, value: signature.name), + CredentialField( + title: localizations.signedBy, + value: signature.name, + showVertically: false, + ), if (signature.image != '') Padding( padding: const EdgeInsets.all(8), diff --git a/lib/dashboard/home/tab_bar/credentials/widgets/labeled_display_mapping_widget.dart b/lib/dashboard/home/tab_bar/credentials/widgets/labeled_display_mapping_widget.dart index 47170588f..654bb82c2 100644 --- a/lib/dashboard/home/tab_bar/credentials/widgets/labeled_display_mapping_widget.dart +++ b/lib/dashboard/home/tab_bar/credentials/widgets/labeled_display_mapping_widget.dart @@ -26,6 +26,7 @@ class LabeledDisplayMappingWidget extends StatelessWidget { title: object.label, titleColor: titleColor, valueColor: valueColor, + showVertically: false, ); } @@ -62,6 +63,7 @@ class LabeledDisplayMappingWidget extends StatelessWidget { value: fallback, titleColor: titleColor, valueColor: valueColor, + showVertically: false, ); } } diff --git a/lib/dashboard/home/tab_bar/credentials/widgets/list_item.dart b/lib/dashboard/home/tab_bar/credentials/widgets/list_item.dart index 5364df7ee..9c870ae3c 100644 --- a/lib/dashboard/home/tab_bar/credentials/widgets/list_item.dart +++ b/lib/dashboard/home/tab_bar/credentials/widgets/list_item.dart @@ -86,7 +86,6 @@ class CredentialsDisplayItem extends StatelessWidget { Widget build(BuildContext context) { final profileSetting = context.read().state.model.profileSetting; - return _BaseItem( enabled: true, onTap: onTap, @@ -116,10 +115,8 @@ class DisplaySelectionElement extends StatelessWidget { @override Widget build(BuildContext context) { - //final credential = Credential.fromJsonOrDummy(credentialModel.data); final profileSetting = context.read().state.model.profileSetting; - return CredentialSelectionPadding( child: Column( children: [ @@ -144,25 +141,3 @@ class DisplaySelectionElement extends StatelessWidget { ); } } - -class CredentialIcon extends StatelessWidget { - const CredentialIcon({ - super.key, - required this.iconData, - this.color, - this.size = 24, - }); - - final IconData iconData; - final Color? color; - final double size; - - @override - Widget build(BuildContext context) { - return Icon( - iconData, - size: size, - color: color ?? Theme.of(context).colorScheme.primaryContainer, - ); - } -} diff --git a/lib/dashboard/home/tab_bar/credentials/widgets/picture_display.dart b/lib/dashboard/home/tab_bar/credentials/widgets/picture_display.dart new file mode 100644 index 000000000..bf64e87d8 --- /dev/null +++ b/lib/dashboard/home/tab_bar/credentials/widgets/picture_display.dart @@ -0,0 +1,32 @@ +import 'package:altme/app/app.dart'; +import 'package:flutter/material.dart'; + +class PictureDisplay extends StatelessWidget { + const PictureDisplay({ + super.key, + required this.credentialImage, + }); + + final String credentialImage; + + @override + Widget build(BuildContext context) { + return AspectRatio( + aspectRatio: Sizes.credentialAspectRatio, + child: ClipRRect( + borderRadius: BorderRadius.circular( + Sizes.credentialBorderRadius, + ), + child: CachedImageFromNetwork( + credentialImage, + fit: BoxFit.contain, + width: double.infinity, + bgColor: Colors.transparent, + height: double.infinity, + errorMessage: '', + showLoading: false, + ), + ), + ); + } +} diff --git a/lib/dashboard/home/tab_bar/credentials/widgets/widgets.dart b/lib/dashboard/home/tab_bar/credentials/widgets/widgets.dart index 8f18765da..ef1239775 100644 --- a/lib/dashboard/home/tab_bar/credentials/widgets/widgets.dart +++ b/lib/dashboard/home/tab_bar/credentials/widgets/widgets.dart @@ -24,6 +24,7 @@ export 'display_wiget.dart'; export 'dummy_credential_image.dart'; export 'labeled_display_mapping_widget.dart'; export 'list_item.dart'; +export 'picture_display.dart'; export 'skills_list_display.dart'; export 'star_rating.dart'; export 'white_close_button.dart'; diff --git a/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart b/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart index e28131b5b..ee365f939 100644 --- a/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart +++ b/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart @@ -279,7 +279,12 @@ class QRCodeScanCubit extends Cubit { ); final PresentationDefinition? presentationDefinition = credentialManifest.presentationDefinition; - final isPresentable = await isVCPresentable(presentationDefinition); + final isPresentable = await isVCPresentable( + presentationDefinition: presentationDefinition, + clientMetaData: null, + vcFormatType: profileCubit.state.model.profileSetting + .selfSovereignIdentityOptions.customOidc4vcProfile.vcFormatType, + ); if (!isPresentable) { emit( @@ -812,9 +817,11 @@ class QRCodeScanCubit extends Cubit { } } - Future isVCPresentable( - PresentationDefinition? presentationDefinition, - ) async { + Future isVCPresentable({ + required VCFormatType vcFormatType, + required PresentationDefinition? presentationDefinition, + required Map? clientMetaData, + }) async { if (presentationDefinition != null && presentationDefinition.inputDescriptors.isNotEmpty) { final newPresentationDefinition = @@ -828,6 +835,8 @@ class QRCodeScanCubit extends Cubit { presentationDefinition: newPresentationDefinition, credentialList: List.from(credentialList), inputDescriptorIndex: index, + clientMetaData: clientMetaData, + vcFormatType: vcFormatType, ); if (filteredCredentialList.isEmpty) { return false; @@ -915,8 +924,10 @@ class QRCodeScanCubit extends Cubit { ); } + Map? clientMetaData; + if (presentationDefinition.format == null) { - final Map? clientMetaData = await getClientMetada( + clientMetaData = await getClientMetada( client: client, uri: state.uri!, ); @@ -957,7 +968,12 @@ class QRCodeScanCubit extends Cubit { presentationDefinition, ); - final isPresentable = await isVCPresentable(presentationDefinition); + final isPresentable = await isVCPresentable( + presentationDefinition: presentationDefinition, + clientMetaData: clientMetaData, + vcFormatType: profileCubit.state.model.profileSetting + .selfSovereignIdentityOptions.customOidc4vcProfile.vcFormatType, + ); if (!isPresentable) { emit( @@ -1350,7 +1366,6 @@ class QRCodeScanCubit extends Cubit { credential: selectedCredentials[i], isLastCall: i + 1 == selectedCredentials.length, issuer: issuer, - profileCubit: profileCubit, jwtDecode: jwtDecode, blockchainType: walletCubit.state.currentAccount!.blockchainType, deferredCredentialEndpoint: deferredCredentialEndpoint, diff --git a/lib/oidc4vc/add_credential_data.dart b/lib/oidc4vc/add_credential_data.dart index 3b6dffa61..ef270fdce 100644 --- a/lib/oidc4vc/add_credential_data.dart +++ b/lib/oidc4vc/add_credential_data.dart @@ -14,7 +14,6 @@ Future addCredentialData({ required String format, required OpenIdConfiguration? openIdConfiguration, required SecureStorageProvider secureStorageProvider, - required ProfileCubit profileCubit, required CredentialsCubit credentialsCubit, required String scannedResponse, required dynamic credential, @@ -56,7 +55,7 @@ Future addCredentialData({ ), data: const {}, jwt: null, - format: 'jwt_vc', + format: format, image: '', shareLink: '', pendingInfo: PendingInfo( diff --git a/lib/scan/cubit/scan_cubit.dart b/lib/scan/cubit/scan_cubit.dart index 55741187a..c675e076b 100644 --- a/lib/scan/cubit/scan_cubit.dart +++ b/lib/scan/cubit/scan_cubit.dart @@ -818,74 +818,17 @@ class ScanCubit extends Cubit { final nonce = uri.queryParameters['nonce'] ?? ''; final clientId = uri.queryParameters['client_id'] ?? ''; - bool presentLdpVc = false; - bool presentJwtVc = false; - bool presentJwtVcJson = false; - bool presentVcSdJwt = false; - - if (presentationDefinition.format != null) { - /// ldp_vc - presentLdpVc = presentationDefinition.format?.ldpVc != null; - - /// jwt_vc - presentJwtVc = presentationDefinition.format?.jwtVc != null; - - /// jwt_vc_json - presentJwtVcJson = presentationDefinition.format?.jwtVcJson != null; - - /// vc+sd-jwt - presentVcSdJwt = presentationDefinition.format?.vcSdJwt != null; - } else { - if (clientMetaData == null) { - throw ResponseMessage( - data: { - 'error': 'invalid_request', - 'error_description': 'Client metaData is invalid', - }, - ); - } - - final vpFormats = clientMetaData['vp_formats'] as Map; - - /// ldp_vc - presentLdpVc = vpFormats.containsKey('ldp_vc'); - - /// jwt_vc - presentJwtVc = vpFormats.containsKey('jwt_vc'); - - /// jwt_vc_json - presentJwtVcJson = vpFormats.containsKey('jwt_vc_json'); - - /// vc+sd-jwt - presentVcSdJwt = vpFormats.containsKey('vc+sd-jwt'); - } - final customOidc4vcProfile = profileSetting.selfSovereignIdentityOptions.customOidc4vcProfile; final vcFormatType = customOidc4vcProfile.vcFormatType; - if (!presentLdpVc && vcFormatType == VCFormatType.ldpVc) { - presentLdpVc = true; - } else if (!presentJwtVc && (vcFormatType == VCFormatType.jwtVc)) { - presentJwtVc = true; - } else if (!presentJwtVcJson && (vcFormatType == VCFormatType.jwtVcJson)) { - presentJwtVcJson = true; - } else if (!presentJwtVc && vcFormatType == VCFormatType.vcSdJWT) { - presentVcSdJwt = true; - } - - if (!presentLdpVc && - !presentJwtVc && - !presentJwtVcJson && - !presentVcSdJwt) { - throw ResponseMessage( - data: { - 'error': 'invalid_request', - 'error_description': 'VC format is missing', - }, - ); - } + final (presentLdpVc, presentJwtVc, presentJwtVcJson, presentVcSdJwt) = + getPresentVCDetails( + clientMetaData: clientMetaData, + presentationDefinition: presentationDefinition, + vcFormatType: vcFormatType, + ); if (presentLdpVc) { final options = jsonEncode({ diff --git a/lib/selective_disclosure/selective_disclosure.dart b/lib/selective_disclosure/selective_disclosure.dart index e68dd2111..d615f384a 100644 --- a/lib/selective_disclosure/selective_disclosure.dart +++ b/lib/selective_disclosure/selective_disclosure.dart @@ -1,6 +1,8 @@ import 'dart:convert'; import 'package:altme/dashboard/home/tab_bar/credentials/models/credential_model/credential_model.dart'; +import 'package:json_path/json_path.dart'; +import 'package:oidc4vc/oidc4vc.dart'; class SelectiveDisclosure { SelectiveDisclosure(this.credentialModel); @@ -9,17 +11,48 @@ class SelectiveDisclosure { Map get claims { final credentialSupported = credentialModel.credentialSupported; - final claims = credentialSupported!['claims']; + var claims = credentialSupported?['claims']; - if (claims is! Map) { + if (claims == null || claims is! Map) { return {}; } + final order = credentialSupported?['order']; + + if (order != null && order is List) { + final orderList = order.map((e) => e.toString()).toList(); + + final orderedClaims = {}; + final remainingClaims = {}; + + // Order elements based on the order list + for (final key in orderList) { + if (claims.containsKey(key)) { + orderedClaims[key] = claims[key]; + } else {} + } + + // Add remaining elements to the end of the ordered map + claims.forEach((key, value) { + if (!orderedClaims.containsKey(key)) { + remainingClaims[key] = value; + } + }); + + orderedClaims.addAll(remainingClaims); + + claims = orderedClaims; + } + return claims; } - Map get values { - final encryptedValues = credentialModel.jwt?.split('~'); + Map get extractedValuesFromJwt { + final encryptedValues = credentialModel.jwt + ?.split('~') + .where((element) => element.isNotEmpty) + .toList(); + final extractedValues = {}; if (encryptedValues != null) { encryptedValues.removeAt(0); @@ -46,6 +79,65 @@ class SelectiveDisclosure { } } return extractedValues; + } + + String? get getPicture { + if (credentialModel.format.toString() != VCFormatType.vcSdJWT.value) { + return null; + } + + final credentialSupported = credentialModel.credentialSupported; + if (credentialSupported == null) return null; + + final claims = credentialSupported['claims']; + if (claims is! Map) return null; + + final picture = claims['picture']; + if (picture == null) return null; + if (picture is! Map) return null; + + if (picture.containsKey('mandatory')) { + final mandatory = picture['mandatory']; + if (mandatory is! bool) return null; + } + + final valueType = picture['value_type']; + if (valueType == null) return null; + + if (valueType == 'image/jpeg') { + final (data, _) = getClaimsData(key: 'picture'); + return data; + } else { + return null; + } + } + + /// claimsdata, isfromDisclosureOfJWT + (String?, bool) getClaimsData({ + required String key, + }) { + String? data; + bool isfromDisclosureOfJWT = false; + + final JsonPath dataPath = JsonPath( + // ignore: prefer_interpolation_to_compose_strings + r'$..' + key, + ); + + try { + final uncryptedDataPath = dataPath.read(extractedValuesFromJwt).first; + data = uncryptedDataPath.value.toString(); + isfromDisclosureOfJWT = true; + } catch (e) { + try { + final credentialModelPath = dataPath.read(credentialModel.data).first; + data = credentialModelPath.value.toString(); + isfromDisclosureOfJWT = false; + } catch (e) { + data = null; + } + } + return (data, isfromDisclosureOfJWT); } } diff --git a/lib/selective_disclosure/widget/display_selective_disclosure.dart b/lib/selective_disclosure/widget/display_selective_disclosure.dart index 9e5d77871..c66e0ce2f 100644 --- a/lib/selective_disclosure/widget/display_selective_disclosure.dart +++ b/lib/selective_disclosure/widget/display_selective_disclosure.dart @@ -10,13 +10,16 @@ class DisplaySelectiveDisclosure extends StatelessWidget { const DisplaySelectiveDisclosure({ super.key, required this.credentialModel, + required this.showVertically, this.claims, this.onPressed, this.selectedIndex, }); + final CredentialModel credentialModel; + final bool showVertically; final Map? claims; - final void Function(int)? onPressed; + final void Function(int, int)? onPressed; final List? selectedIndex; @override @@ -33,23 +36,28 @@ class DisplaySelectiveDisclosure extends StatelessWidget { final key = map.key; final value = map.value; - final index = currentClaims.entries + final claimIndex = currentClaims.entries + .toList() + .indexWhere((entry) => entry.key == key); + + final sdIndexInJWT = selectiveDisclosure.extractedValuesFromJwt.entries .toList() - .indexWhere((entry) => entry.key == map.key); + .indexWhere((entry) => entry.key == key); final bool hasChildren = !(value as Map).containsKey('display'); if (hasChildren && value.isNotEmpty) { return TransparentInkWell( - onTap: () => onPressed?.call(index), + onTap: () => onPressed?.call(claimIndex, sdIndexInJWT), child: Row( - crossAxisAlignment: CrossAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.only(left: 10, top: 10), child: DisplaySelectiveDisclosure( credentialModel: credentialModel, claims: value, + showVertically: showVertically, ), ), if (selectedIndex != null) ...[ @@ -57,7 +65,7 @@ class DisplaySelectiveDisclosure extends StatelessWidget { Padding( padding: const EdgeInsets.only(top: 15, right: 10), child: Icon( - selectedIndex!.contains(index) + selectedIndex!.contains(claimIndex) ? Icons.check_box : Icons.check_box_outline_blank, size: 25, @@ -74,18 +82,19 @@ class DisplaySelectiveDisclosure extends StatelessWidget { if (display == null) return Container(); title = display['name'].toString(); - data = getClaimsData( - uncryptedDatas: selectiveDisclosure.values, - credentialModel: credentialModel, + final (claimsData, isfromDisclosureOfJWT) = + SelectiveDisclosure(credentialModel).getClaimsData( key: key, - selectFromSelectiveDisclosure: selectedIndex != null, ); + data = claimsData; + if (data == null) return Container(); return TransparentInkWell( - onTap: () => onPressed?.call(index), + onTap: () => onPressed?.call(claimIndex, sdIndexInJWT), child: Row( + crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.only(top: 10), @@ -95,14 +104,15 @@ class DisplaySelectiveDisclosure extends StatelessWidget { value: data, titleColor: Theme.of(context).colorScheme.titleColor, valueColor: Theme.of(context).colorScheme.valueColor, + showVertically: showVertically, ), ), - if (selectedIndex != null) ...[ + if (selectedIndex != null && isfromDisclosureOfJWT) ...[ const Spacer(), Padding( padding: const EdgeInsets.only(top: 15, right: 10), child: Icon( - selectedIndex!.contains(index) + selectedIndex!.contains(claimIndex) ? Icons.check_box : Icons.check_box_outline_blank, size: 25, diff --git a/lib/theme/app_theme/app_theme.dart b/lib/theme/app_theme/app_theme.dart index 095b7c05c..0085936a7 100644 --- a/lib/theme/app_theme/app_theme.dart +++ b/lib/theme/app_theme/app_theme.dart @@ -552,7 +552,7 @@ extension CustomTextTheme on TextTheme { TextStyle get credentialFieldTitle => GoogleFonts.poppins( color: const Color(0xff212121), fontSize: 14, - fontWeight: FontWeight.w800, + fontWeight: FontWeight.w400, ); TextStyle get credentialFieldDescription => GoogleFonts.poppins( diff --git a/pubspec.lock b/pubspec.lock index ec32453ff..c2626a9fe 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -29,10 +29,10 @@ packages: dependency: transitive description: name: android_intent_plus - sha256: e1c62bb41c90e15083b7fb84dc327fe90396cc9c1445b55ff1082144fabfb4d9 + sha256: e92d14009f3f6ebafca6a601958aaebb793559fb03a1961fe3c5596db95af2cb url: "https://pub.dev" source: hosted - version: "4.0.3" + version: "5.0.1" ansicolor: dependency: transitive description: @@ -302,10 +302,10 @@ packages: dependency: transitive description: name: camera_android - sha256: "351429510121d179b9aac5a2e8cb525c3cd6c39f4d709c5f72dfb21726e52371" + sha256: "15a6543878a41c141807ffab496f66b7fef6da0f23372f5513fc6349e60f437e" url: "https://pub.dev" source: hosted - version: "0.10.8+16" + version: "0.10.8+17" camera_avfoundation: dependency: transitive description: @@ -771,10 +771,10 @@ packages: dependency: "direct main" description: name: file_picker - sha256: caa6bc229eab3e32eb2f37b53a5f9d22a6981474afd210c512a7546c1e1a04f6 + sha256: "1bbf65dd997458a08b531042ec3794112a6c39c07c37ff22113d2e7e4f81d4e4" url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "6.2.1" file_saver: dependency: "direct main" description: @@ -981,10 +981,10 @@ packages: dependency: "direct main" description: name: flutter_markdown - sha256: cb44f7831b23a6bdd0f501718b0d2e8045cbc625a15f668af37ddb80314821db + sha256: "87e11b9df25a42e2db315b8b7a51fae8e66f57a4b2f50ec4b822d0fa155e6b52" url: "https://pub.dev" source: hosted - version: "0.6.21" + version: "0.6.22" flutter_native_timezone: dependency: "direct main" description: @@ -1589,10 +1589,10 @@ packages: dependency: "direct main" description: name: model_viewer_plus - sha256: daeee3c498ece539125933e233252aebdc0b6d842538efb7061f7eaf3b97939e + sha256: "633efbd081efe9af8b149c5553fa1e3e83a298ccc151955b0fe9d56a666943be" url: "https://pub.dev" source: hosted - version: "1.7.0" + version: "1.7.2" nested: dependency: transitive description: @@ -2099,10 +2099,10 @@ packages: dependency: transitive description: name: share_plus_platform_interface - sha256: df08bc3a07d01f5ea47b45d03ffcba1fa9cd5370fb44b3f38c70e42cced0f956 + sha256: "251eb156a8b5fa9ce033747d73535bf53911071f8d3b6f4f0b578505ce0d4496" url: "https://pub.dev" source: hosted - version: "3.3.1" + version: "3.4.0" shared_preferences: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 0342da83d..20eea910a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: altme description: AltMe Flutter App -version: 2.4.0+417 +version: 2.4.1+419 environment: sdk: ">=3.1.0 <4.0.0" diff --git a/test/app/shared/widget/base/credential_field_test.dart b/test/app/shared/widget/base/credential_field_test.dart index 258162ca0..99dbec8c2 100644 --- a/test/app/shared/widget/base/credential_field_test.dart +++ b/test/app/shared/widget/base/credential_field_test.dart @@ -9,6 +9,7 @@ void main() { key: GlobalKey(), value: 'value', title: 'title', + showVertically: true, ), ); } @@ -19,6 +20,7 @@ void main() { key: GlobalKey(), value: '', title: 'title', + showVertically: true, ), ); }