From 11699ff1773563eda73e30fc4f85a31ffa5f1289 Mon Sep 17 00:00:00 2001 From: hawkbee1 Date: Thu, 14 Mar 2024 12:00:52 +0000 Subject: [PATCH] recursive build of SD lists --- .../detail/view/credentials_details_page.dart | 4 +- .../detail/widgets/claims_data.dart | 148 ----------------- .../credentials/detail/widgets/widgets.dart | 1 - .../view/selective_disclosure_pick_page.dart | 6 +- .../vc_sd_jwt_credential_pick_button.dart | 41 ++--- lib/l10n/arb/app_ca.arb | 2 +- lib/l10n/arb/app_en.arb | 2 +- lib/l10n/arb/app_es.arb | 2 +- lib/l10n/arb/app_fr.arb | 4 +- .../selective_disclosure.dart | 51 ++++++ .../widget/display_selective_disclosure.dart | 150 ++++++++++++++++++ .../widget/select_selective_disclosure.dart | 53 +++++++ pubspec.lock | 28 ++-- 13 files changed, 301 insertions(+), 191 deletions(-) delete mode 100644 lib/dashboard/home/tab_bar/credentials/detail/widgets/claims_data.dart create mode 100644 lib/selective_disclosure/selective_disclosure.dart create mode 100644 lib/selective_disclosure/widget/display_selective_disclosure.dart create mode 100644 lib/selective_disclosure/widget/select_selective_disclosure.dart 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 a5fed0007..072200dcc 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/widget/display_selective_disclosure.dart'; import 'package:altme/theme/theme.dart'; import 'package:altme/wallet/cubit/wallet_cubit.dart'; import 'package:did_kit/did_kit.dart'; @@ -307,8 +308,9 @@ class _CredentialsDetailsViewState extends State { null && widget.credentialModel.credentialSupported! .containsKey('claims')) ...[ - ClaimsData( + DisplaySelectiveDisclosure( credentialModel: widget.credentialModel, + claims: null, ), ], diff --git a/lib/dashboard/home/tab_bar/credentials/detail/widgets/claims_data.dart b/lib/dashboard/home/tab_bar/credentials/detail/widgets/claims_data.dart deleted file mode 100644 index 62bc9c572..000000000 --- a/lib/dashboard/home/tab_bar/credentials/detail/widgets/claims_data.dart +++ /dev/null @@ -1,148 +0,0 @@ -import 'dart:convert'; - -import 'package:altme/app/app.dart'; -import 'package:altme/dashboard/dashboard.dart'; -import 'package:altme/lang/cubit/lang_cubit.dart'; -import 'package:altme/theme/theme.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -class ClaimsData extends StatelessWidget { - const ClaimsData({ - super.key, - required this.credentialModel, - }); - - final CredentialModel credentialModel; - - @override - Widget build(BuildContext context) { - final credentialSupported = credentialModel.credentialSupported; - - final claims = credentialSupported!['claims']; - - if (claims is! Map) { - return Container(); - } - - final encryptedDatas = credentialModel.jwt?.split('~'); - - final credentialSubjectData = {}; - - if (encryptedDatas != null) { - encryptedDatas.removeAt(0); - - for (var element in encryptedDatas) { - try { - while (element.length % 4 != 0) { - element += '='; - } - - final decryptedData = utf8.decode(base64Decode(element)); - - if (decryptedData.isNotEmpty) { - final lisString = decryptedData - .substring(1, decryptedData.length - 1) - .replaceAll('"', '') - .split(','); - - if (lisString.length == 3) { - credentialSubjectData[lisString[1].trim()] = lisString[2].trim(); - } - } - } catch (e) { - // - } - } - } - - final languageCode = context.read().state.locale.languageCode; - - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: claims.entries.map((MapEntry map) { - String? title; - String? data; - - final key = map.key; - final value = map.value; - - if (value is! Map) return Container(); - - if (value.isEmpty) return Container(); - - if (value.containsKey('mandatory')) { - final mandatory = value['mandatory']; - if (mandatory is! bool) return Container(); - - if (!mandatory) return Container(); - } - - if (value.containsKey('display')) { - final displays = value['display']; - if (displays is! List) return Container(); - if (displays.isEmpty) return Container(); - - final displaySelectedLanguage = displays.where((element) { - if (element is Map && - element.containsKey('locale')) { - if (element['locale'].toString().contains(languageCode)) { - return true; - } - return false; - } - return false; - }).firstOrNull; - - final displayEnglish = displays.where((element) { - if (element is Map && - element.containsKey('locale')) { - if (element['locale'].toString().contains('en')) { - return true; - } - return false; - } - return false; - }).firstOrNull; - - final displayFirst = displays.where((element) { - if (element is Map && - element.containsKey('locale')) { - return true; - } - return false; - }).firstOrNull; - - final display = - displaySelectedLanguage ?? displayEnglish ?? displayFirst; - - if (display == null) return Container(); - - if (credentialSubjectData.isNotEmpty && - credentialSubjectData.containsKey(key)) { - title = display['name'].toString(); - data = credentialSubjectData[key].toString(); - } else if (credentialModel.data.containsKey(key)) { - title = display['name'].toString(); - data = credentialModel.data[key].toString(); - } - } else { - return Container(); - } - - if (title == null || data == null) return Container(); - - return Padding( - padding: const EdgeInsets.only(top: 10), - child: CredentialField( - padding: EdgeInsets.zero, - title: title, - value: data, - titleColor: Theme.of(context).colorScheme.titleColor, - valueColor: Theme.of(context).colorScheme.valueColor, - ), - ); - }).toList(), - ); - } -} diff --git a/lib/dashboard/home/tab_bar/credentials/detail/widgets/widgets.dart b/lib/dashboard/home/tab_bar/credentials/detail/widgets/widgets.dart index e8518d68b..b1f6bec12 100644 --- a/lib/dashboard/home/tab_bar/credentials/detail/widgets/widgets.dart +++ b/lib/dashboard/home/tab_bar/credentials/detail/widgets/widgets.dart @@ -1,4 +1,3 @@ -export 'claims_data.dart'; export 'credential_active_status.dart'; export 'credential_subject_data.dart'; export 'deferred_credential_data.dart'; diff --git a/lib/dashboard/home/tab_bar/credentials/present/pick/credential_manifest/view/selective_disclosure_pick_page.dart b/lib/dashboard/home/tab_bar/credentials/present/pick/credential_manifest/view/selective_disclosure_pick_page.dart index 2a97facb1..331e2cae4 100644 --- a/lib/dashboard/home/tab_bar/credentials/present/pick/credential_manifest/view/selective_disclosure_pick_page.dart +++ b/lib/dashboard/home/tab_bar/credentials/present/pick/credential_manifest/view/selective_disclosure_pick_page.dart @@ -3,6 +3,7 @@ import 'package:altme/credentials/credentials.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/widget/select_selective_disclosure.dart'; import 'package:credential_manifest/credential_manifest.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -107,15 +108,16 @@ class SelectiveDisclosurePickView extends StatelessWidget { child: credentialManifestState.filteredCredentialList.isEmpty ? const RequiredCredentialNotFound() : BasePage( - title: l10n.credentialPickTitle, + title: l10n.thisOrganisationRequestsThisInformation, titleAlignment: Alignment.topCenter, titleTrailing: const WhiteCloseButton(), padding: const EdgeInsets.symmetric( vertical: 24, horizontal: 16, ), - body: ClaimsData( + body: SelectSelectiveDisclosure( credentialModel: credentialToBePresented, + claims: null, ), navigation: SafeArea( child: Container( diff --git a/lib/dashboard/home/tab_bar/credentials/present/pick/credential_manifest/widgets/vc_sd_jwt_credential_pick_button.dart b/lib/dashboard/home/tab_bar/credentials/present/pick/credential_manifest/widgets/vc_sd_jwt_credential_pick_button.dart index 0b3213317..4170ea292 100644 --- a/lib/dashboard/home/tab_bar/credentials/present/pick/credential_manifest/widgets/vc_sd_jwt_credential_pick_button.dart +++ b/lib/dashboard/home/tab_bar/credentials/present/pick/credential_manifest/widgets/vc_sd_jwt_credential_pick_button.dart @@ -16,29 +16,30 @@ Widget vcSdJwtCredentialPickButton({ final l10n = context.l10n; final button = SafeArea( - child: Container( - padding: const EdgeInsets.all(16), - child: Tooltip( - message: l10n.credentialPickPresent, - child: MyGradientButton( - onPressed: !credentialManifestState.isButtonEnabled - ? null - : () => Navigator.of(context).pushReplacement( - SelectiveDisclosurePickPage.route( - uri: uri, - issuer: issuer, - credential: credential, - credentialToBePresented: - credentialManifestState.filteredCredentialList[ - credentialManifestState.selected.first], + child: Container( + padding: const EdgeInsets.all(16), + child: Tooltip( + message: l10n.credentialPickPresent, + child: MyGradientButton( + onPressed: !credentialManifestState.isButtonEnabled + ? null + : () => Navigator.of(context).pushReplacement( + SelectiveDisclosurePickPage.route( + uri: uri, + issuer: issuer, + credential: credential, + credentialToBePresented: + credentialManifestState.filteredCredentialList[ + credentialManifestState.selected.first], + ), ), - ), - /// next button because we will now choose the claims we will present - /// from the selected credential - text: l10n.next, + /// next button because we will now choose the claims we will present + /// from the selected credential + text: l10n.next, + ), ), ), - )); + ); return button; } diff --git a/lib/l10n/arb/app_ca.arb b/lib/l10n/arb/app_ca.arb index 005754142..8e4940453 100644 --- a/lib/l10n/arb/app_ca.arb +++ b/lib/l10n/arb/app_ca.arb @@ -887,7 +887,7 @@ "createWalletMessage": "Crea una cartera primer.", "successfullyGeneratingProof": "Prova generada correctament", "wouldYouLikeToAcceptThisCredentialsFromThisOrganisation": "Vols acceptar aquesta/es credencial/s d’aquesta organització?", - "thisOrganisationRequestsThisInformation": "L’organització demana aquesta informació", + "thisOrganisationRequestsThisInformation": "L’organització demana", "iS": "és", "isSmallerThan": "és inferior a", "isBiggerThan": "és superior a", diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index b2062168b..19ea8237b 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -886,7 +886,7 @@ "createWalletMessage": "Please create your wallet first.", "successfullyGeneratingProof": "Successfully Generated Proof", "wouldYouLikeToAcceptThisCredentialsFromThisOrganisation": "Would you like to accept this credential(s) from this organisation?", - "thisOrganisationRequestsThisInformation": "This organisation requests this information", + "thisOrganisationRequestsThisInformation": "This organisation requests", "iS": "is", "isSmallerThan": "is smaller than", "isBiggerThan": "is bigger than", diff --git a/lib/l10n/arb/app_es.arb b/lib/l10n/arb/app_es.arb index 6d53556be..8405c2e9c 100644 --- a/lib/l10n/arb/app_es.arb +++ b/lib/l10n/arb/app_es.arb @@ -887,7 +887,7 @@ "createWalletMessage": "Primero cree su cartera.", "successfullyGeneratingProof": "Prueba generada", "wouldYouLikeToAcceptThisCredentialsFromThisOrganisation": "¿Desea aceptar esta(s) credencial(es) de esta organización?", - "thisOrganisationRequestsThisInformation": "Esta organización solicita esta información", + "thisOrganisationRequestsThisInformation": "Esta organización solicita", "iS": "es", "isSmallerThan": "es menor que", "isBiggerThan": "es mayor que", diff --git a/lib/l10n/arb/app_fr.arb b/lib/l10n/arb/app_fr.arb index ced6e07a9..d9937b2c0 100644 --- a/lib/l10n/arb/app_fr.arb +++ b/lib/l10n/arb/app_fr.arb @@ -80,7 +80,7 @@ "credentialPresentRequiredCredential": "Quelqu'un demande votre", "credentialPresentConfirm": "Sélectionnez les documents", "credentialPresentCancel": "Rejeter", - "credentialPickPresent": "Présent", + "credentialPickPresent": "Présente", "credentialPickTitle": "Sélectionnez les documents", "selectYourTezosAssociatedWallet": "Sélectionnez votre portefeuille associé Tezos", "credentialPickSelect": "Sélectionnez votre identifiant", @@ -887,7 +887,7 @@ "createWalletMessage": "Veuillez d'abord créer votre portefeuille.", "successfullyGeneratingProof": "Preuve générée avec Succès", "wouldYouLikeToAcceptThisCredentialsFromThisOrganisation": "Souhaitez-vous accepter ce(s) document(s) ?", - "thisOrganisationRequestsThisInformation": "Cette organisation demande cette information", + "thisOrganisationRequestsThisInformation": "Cette organisation demande", "iS": "est", "isSmallerThan": "est inférieur à", "isBiggerThan": "est supérieur à", diff --git a/lib/selective_disclosure/selective_disclosure.dart b/lib/selective_disclosure/selective_disclosure.dart new file mode 100644 index 000000000..e68dd2111 --- /dev/null +++ b/lib/selective_disclosure/selective_disclosure.dart @@ -0,0 +1,51 @@ +import 'dart:convert'; + +import 'package:altme/dashboard/home/tab_bar/credentials/models/credential_model/credential_model.dart'; + +class SelectiveDisclosure { + SelectiveDisclosure(this.credentialModel); + final CredentialModel credentialModel; + + Map get claims { + final credentialSupported = credentialModel.credentialSupported; + + final claims = credentialSupported!['claims']; + + if (claims is! Map) { + return {}; + } + + return claims; + } + + Map get values { + final encryptedValues = credentialModel.jwt?.split('~'); + final extractedValues = {}; + if (encryptedValues != null) { + encryptedValues.removeAt(0); + + for (var element in encryptedValues) { + try { + while (element.length % 4 != 0) { + element += '='; + } + + final decryptedData = utf8.decode(base64Decode(element)); + + if (decryptedData.isNotEmpty) { + final lisString = jsonDecode(decryptedData); + if (lisString is List) { + if (lisString.length == 3) { + extractedValues[lisString[1].toString()] = lisString[2]; + } + } + } + } catch (e) { + // + } + } + } + return extractedValues; + + } +} diff --git a/lib/selective_disclosure/widget/display_selective_disclosure.dart b/lib/selective_disclosure/widget/display_selective_disclosure.dart new file mode 100644 index 000000000..b0c5b4eed --- /dev/null +++ b/lib/selective_disclosure/widget/display_selective_disclosure.dart @@ -0,0 +1,150 @@ +import 'package:altme/app/shared/widget/base/credential_field.dart'; +import 'package:altme/dashboard/home/tab_bar/credentials/models/credential_model/credential_model.dart'; +import 'package:altme/lang/cubit/lang_cubit.dart'; +import 'package:altme/selective_disclosure/selective_disclosure.dart'; +import 'package:altme/theme/theme.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:json_path/json_path.dart'; + +class DisplaySelectiveDisclosure extends StatelessWidget { + const DisplaySelectiveDisclosure({ + super.key, + required this.credentialModel, + this.claims, + }); + final CredentialModel credentialModel; + final Map? claims; + @override + Widget build(BuildContext context) { + final selectiveDisclosure = SelectiveDisclosure(credentialModel); + final currentClaims = claims ?? selectiveDisclosure.claims; + final languageCode = context.read().state.locale.languageCode; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: currentClaims.entries.map((MapEntry map) { + String? title; + String? data; + + final key = map.key; + final value = map.value; + + final bool hasChildren = + !(value as Map).containsKey('display'); + if (hasChildren && value.isNotEmpty) { + return Padding( + padding: const EdgeInsets.only(top: 10, left: 10), + child: DisplaySelectiveDisclosure( + credentialModel: credentialModel, + claims: value, + ), + ); + } else { + final display = getDisplay(key, value, languageCode); + + if (display == null) return Container(); + title = display['name'].toString(); + data = getData(selectiveDisclosure.values, credentialModel, key); + + if (data == null) return Container(); + + return displayCredentialField(title, data, context); + } + }).toList(), + ); + } + + Widget displayCredentialField( + String title, + String data, + BuildContext context, + ) { + return Padding( + padding: const EdgeInsets.only(top: 10), + child: CredentialField( + padding: EdgeInsets.zero, + title: title, + value: data, + titleColor: Theme.of(context).colorScheme.titleColor, + valueColor: Theme.of(context).colorScheme.valueColor, + ), + ); + } + + dynamic getDisplay(String key, dynamic value, String languageCode) { + if (value is! Map) return null; + + if (value.isEmpty) return null; + + if (value.containsKey('mandatory')) { + final mandatory = value['mandatory']; + if (mandatory is! bool) return null; + + // if (!mandatory) return null; + } + + if (value.containsKey('display')) { + final displays = value['display']; + if (displays is! List) return null; + if (displays.isEmpty) return null; + + final displaySelectedLanguage = displays.where((element) { + if (element is Map && element.containsKey('locale')) { + if (element['locale'].toString().contains(languageCode)) { + return true; + } + return false; + } + return false; + }).firstOrNull; + + final displayEnglish = displays.where((element) { + if (element is Map && element.containsKey('locale')) { + if (element['locale'].toString().contains('en')) { + return true; + } + return false; + } + return false; + }).firstOrNull; + + final displayFirst = displays.where((element) { + if (element is Map && element.containsKey('locale')) { + return true; + } + return false; + }).firstOrNull; + + return displaySelectedLanguage ?? displayEnglish ?? displayFirst; + } else { + return null; + } + } + + String? getData( + Map uncryptedDatas, + CredentialModel credentialModel, + String key, + ) { + 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) { + try { + final credentialModelPath = dataPath.read(credentialModel.data).first; + data = credentialModelPath.value.toString(); + } catch (e) { + data = null; + } + } + + return data; + } +} diff --git a/lib/selective_disclosure/widget/select_selective_disclosure.dart b/lib/selective_disclosure/widget/select_selective_disclosure.dart new file mode 100644 index 000000000..004f909c0 --- /dev/null +++ b/lib/selective_disclosure/widget/select_selective_disclosure.dart @@ -0,0 +1,53 @@ +import 'package:altme/app/app.dart'; +import 'package:altme/selective_disclosure/widget/display_selective_disclosure.dart'; +import 'package:altme/theme/theme.dart'; +import 'package:flutter/material.dart'; + +class SelectSelectiveDisclosure extends DisplaySelectiveDisclosure { + const SelectSelectiveDisclosure({ + super.key, + required super.credentialModel, + super.claims, + }); + + @override + Widget displayCredentialField( + String title, + String data, + BuildContext context, + ) { + return TransparentInkWell( + onTap: () {}, + child: Padding( + padding: const EdgeInsets.only(top: 10), + child: Row( + children: [ + CredentialField( + padding: EdgeInsets.zero, + title: title, + value: data, + titleColor: Theme.of(context).colorScheme.titleColor, + valueColor: Theme.of(context).colorScheme.valueColor, + ), + const Spacer(), + Padding( + padding: const EdgeInsets.all(8), + // child: Icon( + // state.contains(index) + // ? Icons.check_box + // : Icons.check_box_outline_blank, + // size: 25, + // color: Theme.of(context).colorScheme.onPrimary, + // ), + child: Icon( + Icons.check_box, + size: 25, + color: Theme.of(context).colorScheme.onPrimary, + ), + ), + ], + ), + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index c3fe0d4b8..512ef5a3c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -581,10 +581,10 @@ packages: dependency: "direct main" description: name: devicelocale - sha256: c198785f937840c207bd1765769faf4fda9a611507c0f11041a860a681f3adfa + sha256: "0812b66f9eac57bc55c6ed4c178e0779440aa4e4e7c7e32fe1db02a758501d0e" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.1" did_kit: dependency: "direct main" description: @@ -888,10 +888,10 @@ packages: dependency: transitive description: name: flutter_image_compress_common - sha256: "7cad12802628706655920089cfe9ee1d1098300e7f39a079eb160458bbc47652" + sha256: "0756440ddc647696ebc56f393be8c1055e81ef1c1f58986fb1f078af393637d8" url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.0.4" flutter_image_compress_macos: dependency: transitive description: @@ -904,18 +904,18 @@ packages: dependency: transitive description: name: flutter_image_compress_platform_interface - sha256: eb4f055138b29b04498ebcb6d569aaaee34b64d75fb74ea0d40f9790bf47ee9d + sha256: "87d0964ae72ccab1cdfbc32f825ce6b57bf87fd3576a2842bf38189d392163b8" url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.0.4" flutter_image_compress_web: dependency: transitive description: name: flutter_image_compress_web - sha256: da41cc3859f19d11c7d10be615f6a9dcf0907e7daffde7442bf4cc2486663660 + sha256: "1c8cd505be95cb2e0573cdd9d236ac0ba39c166b8df06ef1823f2a17b60e1253" url: "https://pub.dev" source: hosted - version: "0.1.3+2" + version: "0.1.4" flutter_launcher_icons: dependency: "direct dev" description: @@ -1303,10 +1303,10 @@ packages: dependency: transitive description: name: injectable - sha256: cd3c422e13270c81f64ab73c80406b2b2ed563fe59d0ff2093eb7eee63d0bbeb + sha256: bd91492afd80d0b1143b96c80283b1180393ad3989915c0e5e6928ac1266fa75 url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.3.3" intl: dependency: transitive description: @@ -1932,10 +1932,10 @@ packages: dependency: "direct main" description: name: pretty_qr_code - sha256: "47a0fde3967e01ea31985d1a11a998fab1ab900becdba592e9abb0a4034b807e" + sha256: cbdb4af29da1c1fa21dd76f809646c591320ab9e435d3b0eab867492d43607d5 url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.3.0" provider: dependency: transitive description: @@ -2649,10 +2649,10 @@ packages: dependency: transitive description: name: win32 - sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8" + sha256: "8cb58b45c47dcb42ab3651533626161d6b67a2921917d8d429791f76972b3480" url: "https://pub.dev" source: hosted - version: "5.2.0" + version: "5.3.0" win32_registry: dependency: transitive description: