From b71fba9092dd897549ccd4be8128db9c81a3b39e Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Mon, 13 Feb 2023 10:20:49 +0530 Subject: [PATCH 001/190] linter update --- lib/app/shared/m_web3_client/m_web3_client.dart | 2 +- lib/app/view/app.dart | 1 - .../confirm_connection/cubit/confirm_connection_cubit.dart | 1 - lib/dashboard/dashboard/widgets/what_is_new_dialog.dart | 6 +++--- lib/dashboard/drawer/drawer/view/reset_wallet_menu.dart | 3 +-- .../credentials/detail/view/credentials_details_page.dart | 2 +- .../credential_manifest_credential_offer_pick_page.dart | 7 +++++-- .../cubit/confirm_token_transaction_cubit.dart | 2 +- .../qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart | 2 +- lib/ebsi/initiate_ebsi_credential_issuance.dart | 6 +----- lib/splash/view/splash_page.dart | 4 +++- packages/ebsi/lib/src/verifier_token_parameters.dart | 2 +- packages/ebsi/test/src/ebsi_test.dart | 6 +++--- packages/ebsi/test/src/verifier_token_parameters_test.dart | 2 +- 14 files changed, 22 insertions(+), 24 deletions(-) diff --git a/lib/app/shared/m_web3_client/m_web3_client.dart b/lib/app/shared/m_web3_client/m_web3_client.dart index 6eda15461..260289145 100644 --- a/lib/app/shared/m_web3_client/m_web3_client.dart +++ b/lib/app/shared/m_web3_client/m_web3_client.dart @@ -148,7 +148,7 @@ class MWeb3Client { // .take(1) // .listen((event) { // final decoded = - // transferEvent.decodeResults(event.topics ?? [], event.data ?? ''); + // transferEvent.decodeResults(event.topics ?? [], event.data ?? ''); // ignore: lines_longer_than_80_chars // final from = decoded[0] as EthereumAddress; // final to = decoded[1] as EthereumAddress; diff --git a/lib/app/view/app.dart b/lib/app/view/app.dart index 2db23b587..a7c5b99e7 100644 --- a/lib/app/view/app.dart +++ b/lib/app/view/app.dart @@ -32,7 +32,6 @@ import 'package:key_generator/key_generator.dart'; import 'package:matrix/matrix.dart'; import 'package:secure_storage/secure_storage.dart' as secure_storage; import 'package:secure_storage/secure_storage.dart'; -import 'package:uuid/uuid.dart'; class App extends StatelessWidget { const App({super.key, this.flavorMode = FlavorMode.production}); diff --git a/lib/dashboard/connection/confirm_connection/cubit/confirm_connection_cubit.dart b/lib/dashboard/connection/confirm_connection/cubit/confirm_connection_cubit.dart index 896c0f581..55e4a826f 100644 --- a/lib/dashboard/connection/confirm_connection/cubit/confirm_connection_cubit.dart +++ b/lib/dashboard/connection/confirm_connection/cubit/confirm_connection_cubit.dart @@ -7,7 +7,6 @@ import 'package:beacon_flutter/beacon_flutter.dart'; import 'package:bloc/bloc.dart'; import 'package:dartez/dartez.dart'; import 'package:equatable/equatable.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:wallet_connect/wallet_connect.dart'; diff --git a/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart b/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart index c641dd842..1d573e6a8 100644 --- a/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart +++ b/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart @@ -54,10 +54,10 @@ class WhatIsNewDialog extends StatelessWidget { ), const SizedBox(height: Sizes.spaceXSmall), const NewFeature( - 'Integration of Matrix.org to give users access to a decentralized chat in Altme', + 'Integration of Matrix.org to give users access to a decentralized chat in Altme', // ignore: lines_longer_than_80_chars ), const NewFeature( - 'Compliance with EBSI and support of new official ID documents (diplomas...)', + 'Compliance with EBSI and support of new official ID documents (diplomas...)', // ignore: lines_longer_than_80_chars ), Text( '1.8.13', @@ -223,7 +223,7 @@ class WhatIsNewDialog extends StatelessWidget { 'Beacon integration to connect to Tezos dApps', ), const NewFeature( - 'Get multiple identity credentials after identity verification (OpenID for VC Issuance)', + 'Get multiple identity credentials after identity verification (OpenID for VC Issuance)', // ignore: lines_longer_than_80_chars ), const NewFeature( 'Choose card categories to display', diff --git a/lib/dashboard/drawer/drawer/view/reset_wallet_menu.dart b/lib/dashboard/drawer/drawer/view/reset_wallet_menu.dart index 9f740e9c4..8411315fa 100644 --- a/lib/dashboard/drawer/drawer/view/reset_wallet_menu.dart +++ b/lib/dashboard/drawer/drawer/view/reset_wallet_menu.dart @@ -1,6 +1,5 @@ import 'package:altme/app/app.dart'; -import 'package:altme/dashboard/drawer/drawer/drawer.dart'; -import 'package:altme/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart'; +import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/l10n/l10n.dart'; import 'package:altme/pin_code/pin_code.dart'; import 'package:altme/theme/theme.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 5e0b53602..69697c71e 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 @@ -145,7 +145,7 @@ class _CredentialsDetailsViewState extends State { // titleTrailing: IconButton( // onPressed: () { // Navigator.of(context) - // .push(CredentialQrPage.route(widget.credentialModel)); + // .push(CredentialQrPage.route(widget.credentialModel)); // ignore: lines_longer_than_80_chars // }, // icon: Icon( // Icons.qr_code, 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 20b4e7697..4c542666d 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 @@ -113,7 +113,9 @@ class CredentialManifestOfferPickView extends StatelessWidget { titleAlignment: Alignment.topCenter, titleTrailing: const WhiteCloseButton(), padding: const EdgeInsets.symmetric( - vertical: 24, horizontal: 16), + vertical: 24, + horizontal: 16, + ), body: Column( children: [ Text( @@ -168,7 +170,8 @@ class CredentialManifestOfferPickView extends StatelessWidget { presentationDefinition .inputDescriptors[ inputDescriptorIndex]; - //no sure if I'm correct to take first field + // TODO(all): no sure if I'm correct to + // take first field //to check optional final isOptional = inputDescriptor .constraints diff --git a/lib/dashboard/home/tab_bar/tokens/confirm_token_transaction/cubit/confirm_token_transaction_cubit.dart b/lib/dashboard/home/tab_bar/tokens/confirm_token_transaction/cubit/confirm_token_transaction_cubit.dart index 04c9f658d..e9bbfb20b 100644 --- a/lib/dashboard/home/tab_bar/tokens/confirm_token_transaction/cubit/confirm_token_transaction_cubit.dart +++ b/lib/dashboard/home/tab_bar/tokens/confirm_token_transaction/cubit/confirm_token_transaction_cubit.dart @@ -448,7 +448,7 @@ class ConfirmTokenTransactionCubit extends Cubit { emit(state.loading()); //final rpcUrl = manageNetworkCubit.state.network.rpcNodeUrl; - final rpcUrl = await web3RpcMainnetInfuraURL(); + //final rpcUrl = await web3RpcMainnetInfuraURL(); final amount = tokenAmount * double.parse( 1.toStringAsFixed(int.parse(token.decimals)).replaceAll('.', ''), 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 332fd425b..94c3253e1 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 @@ -208,7 +208,7 @@ class QRCodeScanCubit extends Cubit { var claims = r'{"id_token": {"email": "None"}, "vp_token": {"presentation_definition": {"id": "0db80a1b-a3ac-11ed-bbb4-0a1628958560", "input_descriptors": [{"id": "0db80b58-a3ac-11ed-9a0b-0a1628958560", "name": "Input descriptor 1", "purpose": " ", "constraints": {"fields": [{"path": ["$..credentialSchema.id"], "filter": {"type": "string", "pattern": "https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"}}]}}], "format": {"jwt_vp": {"alg": ["ES256K", "ES256", "PS256", "RS256"]}}}}}'; - // TODO(hawkbee) change when correction is done on verifier + // TODO(hawkbee): change when correction is done on verifier claims = claims.replaceAll("'email': None", "'email': 'None'"); claims = claims.replaceAll("'", '"'); final jsonPath = JsonPath(r'$..input_descriptors'); diff --git a/lib/ebsi/initiate_ebsi_credential_issuance.dart b/lib/ebsi/initiate_ebsi_credential_issuance.dart index 3248e08cd..eec0c5bc3 100644 --- a/lib/ebsi/initiate_ebsi_credential_issuance.dart +++ b/lib/ebsi/initiate_ebsi_credential_issuance.dart @@ -1,7 +1,4 @@ -import 'dart:convert'; - import 'package:altme/app/shared/constants/parameters.dart'; -import 'package:altme/app/shared/constants/secure_storage_keys.dart'; import 'package:altme/app/shared/dio_client/dio_client.dart'; import 'package:altme/app/shared/helper_functions/helper_functions.dart'; import 'package:altme/app/shared/launch_url/launch_url.dart'; @@ -9,8 +6,7 @@ import 'package:altme/ebsi/add_ebsi_credential.dart'; import 'package:altme/wallet/wallet.dart'; import 'package:dio/dio.dart'; import 'package:ebsi/ebsi.dart'; -// ignore: implementation_imports -import 'package:secure_storage/src/secure_storage.dart'; +import 'package:secure_storage/secure_storage.dart'; Future initiateEbsiCredentialIssuance( String scannedResponse, diff --git a/lib/splash/view/splash_page.dart b/lib/splash/view/splash_page.dart index 5ac654336..b2b78524f 100644 --- a/lib/splash/view/splash_page.dart +++ b/lib/splash/view/splash_page.dart @@ -122,7 +122,9 @@ class _SplashViewState extends State { } }); if (isBeaconRequest && beaconData != '') { - context.read().peerFromDeepLink(beaconData); + unawaited( + context.read().peerFromDeepLink(beaconData), + ); } } }, diff --git a/packages/ebsi/lib/src/verifier_token_parameters.dart b/packages/ebsi/lib/src/verifier_token_parameters.dart index 19f33ef25..79794a6a2 100644 --- a/packages/ebsi/lib/src/verifier_token_parameters.dart +++ b/packages/ebsi/lib/src/verifier_token_parameters.dart @@ -58,7 +58,7 @@ class VerifierTokenParameters extends TokenParameters { // @override // String get kid { // const kid = - // '''did:ebsi:zo9FR1YfAKFP3Q6dvrhxcXxnfeDiJDP97kmnqhyAUSACj#Cgcg1y9xj9uWFw56PMc29XBd9EReixzvnftBz8JwQFiB'''; + // '''did:ebsi:zo9FR1YfAKFP3Q6dvrhxcXxnfeDiJDP97kmnqhyAUSACj#Cgcg1y9xj9uWFw56PMc29XBd9EReixzvnftBz8JwQFiB''';// ignore: lines_longer_than_80_chars // return kid; // } diff --git a/packages/ebsi/test/src/ebsi_test.dart b/packages/ebsi/test/src/ebsi_test.dart index 1dc170268..4b1ad74d9 100644 --- a/packages/ebsi/test/src/ebsi_test.dart +++ b/packages/ebsi/test/src/ebsi_test.dart @@ -207,13 +207,13 @@ void main() { const tokenUrl = 'https://talao.co/sandbox/ebsi/issuer/vgvghylozl/token'; const tokenResponse = - '{"access_token":"7a07dd19-a879-11ed-ad95-0a1628958560","c_nonce":"7a07de0f-a879-11ed-822b-0a1628958560","token_type":"Bearer","expires_in":1000}'; + '{"access_token":"7a07dd19-a879-11ed-ad95-0a1628958560","c_nonce":"7a07de0f-a879-11ed-822b-0a1628958560","token_type":"Bearer","expires_in":1000}'; // ignore: lines_longer_than_80_chars const credentialRequestUrl = 'https://talao.co/sandbox/ebsi/issuer/vgvghylozl/credential'; const credentialRequestResponse = - '{"format":"jwt_vc","credential":"eyJhbGciOiJFUzI1NksiLCJraWQiOiJkaWQ6ZWJzaTp6aFN3NXJQWGtjSGp2cXV3blZjVHp6QiM1MTVhOWM0MzZjMGYyYWQzYWI2NWQ2Y2VmYzVjMWYwNmMwNWI4YWRmY2Y1NGVlMDZkYzgwNTQzMjA0NzBmZmFmIiwidHlwIjoiSldUIn0.eyJleHAiOjE2NzU5NDcwNTguODkyNzc4LCJpYXQiOjE2NzU5NDYwNTguODkyNzcxLCJpc3MiOiJkaWQ6ZWJzaTp6aFN3NXJQWGtjSGp2cXV3blZjVHp6QiIsImp0aSI6InVybjp1dWlkOjZiMWQ4NDExLTllZDUtNDU2Ni05YzdmLTRjMjQxNjVmZjIzNiIsIm5iZiI6MTY3NTk0NjA1OC44OTI3NzYsIm5vbmNlIjoiMTFmNWY2MDAtYTg3Ni0xMWVkLTkwZjItMGExNjI4OTU4NTYwIiwic3ViIjoiZGlkOmVic2k6em94UkdWWlFuZFRmUWs1NEI3dEtkd3dOZGhhaTVnbTlGOE5hdjhlY2VOQUJhIiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwiY3JlZGVudGlhbFNjaGVtYSI6eyJpZCI6Imh0dHBzOi8vYXBpLnByZXByb2QuZWJzaS5ldS90cnVzdGVkLXNjaGVtYXMtcmVnaXN0cnkvdjEvc2NoZW1hcy8weGJmNzhmYzA4YTdhOWYyOGY1NDc5ZjU4ZGVhMjY5ZDM2NTdmNTRmMTNjYTM3ZDM4MGNkNGU5MjIzN2ZiNjkxZGQiLCJ0eXBlIjoiSnNvblNjaGVtYVZhbGlkYXRvcjIwMTgifSwiY3JlZGVudGlhbFN0YXR1cyI6eyJpZCI6Imh0dHBzOi8vZXNzaWYuZXVyb3BhLmV1L3N0YXR1cy9lZHVjYXRpb24jaGlnaGVyRWR1Y2F0aW9uIzM5MmFjN2Y2LTM5OWEtNDM3Yi1hMjY4LTQ2OTFlYWQ4ZjE3NiIsInR5cGUiOiJDcmVkZW50aWFsU3RhdHVzTGlzdDIwMjAifSwiY3JlZGVudGlhbFN1YmplY3QiOnsiYXdhcmRpbmdPcHBvcnR1bml0eSI6eyJhd2FyZGluZ0JvZHkiOnsiZWlkYXNMZWdhbElkZW50aWZpZXIiOiJVbmtub3duIiwiaG9tZXBhZ2UiOiJodHRwczovL2xlYXN0b24uYmNkaXBsb21hLmNvbS8iLCJpZCI6ImRpZDplYnNpOnpkUnZ2S2JYaFZWQnNYaGF0anVpQmhzIiwicHJlZmVycmVkTmFtZSI6IkxlYXN0b24gVW5pdmVyc2l0eSIsInJlZ2lzdHJhdGlvbiI6IjA1OTcwNjVKIn0sImVuZGVkQXRUaW1lIjoiMjAyMC0wNi0yNlQwMDowMDowMFoiLCJpZCI6Imh0dHBzOi8vbGVhc3Rvbi5iY2RpcGxvbWEuY29tL2xhdy1lY29ub21pY3MtbWFuYWdlbWVudCNBd2FyZGluZ09wcG9ydHVuaXR5IiwiaWRlbnRpZmllciI6Imh0dHBzOi8vY2VydGlmaWNhdGUtZGVtby5iY2RpcGxvbWEuY29tL2NoZWNrLzg3RUQyRjIyNzBFNkM0MTQ1NkU5NEI4NkI5RDkxMTVCNEUzNUJDQ0FEMjAwQTQ5Qjg0NjU5MkMxNEY3OUM4NkJWMUZuYmxsdGEwTlpUbkprUjNsRFdsUm1URGxTUlVKRVZGWklTbU5tWXpKaFVVNXNaVUo1WjJGSlNIcFdibVpaIiwibG9jYXRpb24iOiJGUkFOQ0UiLCJzdGFydGVkQXRUaW1lIjoiMjAxOS0wOS0wMlQwMDowMDowMFoifSwiZGF0ZU9mQmlydGgiOiIxOTkzLTA0LTA4IiwiZmFtaWx5TmFtZSI6IkRPRSIsImdpdmVuTmFtZXMiOiJKYW5lIiwiZ3JhZGluZ1NjaGVtZSI6eyJpZCI6Imh0dHBzOi8vbGVhc3Rvbi5iY2RpcGxvbWEuY29tL2xhdy1lY29ub21pY3MtbWFuYWdlbWVudCNHcmFkaW5nU2NoZW1lIiwidGl0bGUiOiIyIHllYXIgZnVsbC10aW1lIHByb2dyYW1tZSAvIDQgc2VtZXN0ZXJzIn0sImlkIjoiZGlkOmVic2k6em94UkdWWlFuZFRmUWs1NEI3dEtkd3dOZGhhaTVnbTlGOE5hdjhlY2VOQUJhIiwiaWRlbnRpZmllciI6IjA5MDQwMDgwODRIIiwibGVhcm5pbmdBY2hpZXZlbWVudCI6eyJhZGRpdGlvbmFsTm90ZSI6WyJESVNUUklCVVRJT04gTUFOQUdFTUVOVCJdLCJkZXNjcmlwdGlvbiI6IlRoZSBNYXN0ZXIgaW4gSW5mb3JtYXRpb24gYW5kIENvbXB1dGVyIFNjaWVuY2VzIChNSUNTKSBhdCB0aGUgVW5pdmVyc2l0eSBvZiBMdXhlbWJvdXJnIGVuYWJsZXMgc3R1ZGVudHMgdG8gYWNxdWlyZSBkZWVwZXIga25vd2xlZGdlIGluIGNvbXB1dGVyIHNjaWVuY2UgYnkgdW5kZXJzdGFuZGluZyBpdHMgYWJzdHJhY3QgYW5kIGludGVyZGlzY2lwbGluYXJ5IGZvdW5kYXRpb25zLCBmb2N1c2luZyBvbiBwcm9ibGVtIHNvbHZpbmcgYW5kIGRldmVsb3BpbmcgbGlmZWxvbmcgbGVhcm5pbmcgc2tpbGxzLiIsImlkIjoiaHR0cHM6Ly9sZWFzdG9uLmJjZGlwbG9tYS5jb20vbGF3LWVjb25vbWljcy1tYW5hZ2VtZW50I0xlYXJuaW5nQWNoaWV2bWVudCIsInRpdGxlIjoiTWFzdGVyIGluIEluZm9ybWF0aW9uIGFuZCBDb21wdXRlciBTY2llbmNlcyJ9LCJsZWFybmluZ1NwZWNpZmljYXRpb24iOnsiZWN0c0NyZWRpdFBvaW50cyI6MTIwLCJlcWZMZXZlbCI6NywiaWQiOiJodHRwczovL2xlYXN0b24uYmNkaXBsb21hLmNvbS9sYXctZWNvbm9taWNzLW1hbmFnZW1lbnQjTGVhcm5pbmdTcGVjaWZpY2F0aW9uIiwiaXNjZWRmQ29kZSI6WyI3Il0sIm5xZkxldmVsIjpbIjciXX19LCJldmlkZW5jZSI6eyJkb2N1bWVudFByZXNlbmNlIjpbIlBoeXNpY2FsIl0sImV2aWRlbmNlRG9jdW1lbnQiOlsiUGFzc3BvcnQiXSwiaWQiOiJodHRwczovL2Vzc2lmLmV1cm9wYS5ldS90c3ItdmEvZXZpZGVuY2UvZjJhZWVjOTctZmMwZC00MmJmLThjYTctMDU0ODE5MmQ1Njc4Iiwic3ViamVjdFByZXNlbmNlIjoiUGh5c2ljYWwiLCJ0eXBlIjpbIkRvY3VtZW50VmVyaWZpY2F0aW9uIl0sInZlcmlmaWVyIjoiZGlkOmVic2k6Mjk2MmZiNzg0ZGY2MWJhYTI2N2M4MTMyNDk3NTM5ZjhjNjc0YjM3YzEyNDRhN2EifSwiaWQiOiJ1cm46dXVpZDo2YjFkODQxMS05ZWQ1LTQ1NjYtOWM3Zi00YzI0MTY1ZmYyMzYiLCJpc3N1YW5jZURhdGUiOiIyMDIzLTAyLTA5VDEyOjM0OjE4WiIsImlzc3VlZCI6IjIwMjMtMDItMDlUMTI6MzQ6MThaIiwiaXNzdWVyIjoiZGlkOmVic2k6emhTdzVyUFhrY0hqdnF1d25WY1R6ekIiLCJwcm9vZiI6eyJjcmVhdGVkIjoiMjAyMi0wNC0yN1QxMjoyNTowN1oiLCJjcmVhdG9yIjoiZGlkOmVic2k6emRSdnZLYlhoVlZCc1hoYXRqdWlCaHMiLCJkb21haW4iOiJodHRwczovL2FwaS5wcmVwcm9kLmVic2kuZXUiLCJqd3MiOiJleUppTmpRaU9tWmhiSE5sTENKamNtbDBJanBiSW1JMk5DSmRMQ0poYkdjaU9pSkZVekkxTmtzaWZRLi5tSUJuTThYRFFxU1lLUU5YX0x2YUpobXNieUNyNU9aNWNVMlprLVJlcUxwcjRkb0ZzZ21vb2JrTzUxMjh0WnktOEtpbVZqSmtHdzB3TDF1QlduTUxXUSIsIm5vbmNlIjoiM2VhNjhkYWUtZDA3YS00ZGFhLTkzMmItZmJiNThmNWMyMGM0IiwidHlwZSI6IkVjZHNhU2VjcDI1NmsxU2lnbmF0dXJlMjAxOSJ9LCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVmVyaWZpYWJsZUF0dGVzdGF0aW9uIiwiVmVyaWZpYWJsZURpcGxvbWEiXSwidmFsaWRGcm9tIjoiMjAyMy0wMi0wOVQxMjozNDoxOFoifX0.uQK9sK-VtqmKjLJIw_v5Ff5xAMDAosZCtl1LFYxZhUolReD6a7O-NI1f5lcswBCZPLfJ-HJyb5iShehHObzFDA","c_nonce":"128952c8-a876-11ed-bbc4-0a1628958560","c_nonce_expires_in":1000}'; + '{"format":"jwt_vc","credential":"eyJhbGciOiJFUzI1NksiLCJraWQiOiJkaWQ6ZWJzaTp6aFN3NXJQWGtjSGp2cXV3blZjVHp6QiM1MTVhOWM0MzZjMGYyYWQzYWI2NWQ2Y2VmYzVjMWYwNmMwNWI4YWRmY2Y1NGVlMDZkYzgwNTQzMjA0NzBmZmFmIiwidHlwIjoiSldUIn0.eyJleHAiOjE2NzU5NDcwNTguODkyNzc4LCJpYXQiOjE2NzU5NDYwNTguODkyNzcxLCJpc3MiOiJkaWQ6ZWJzaTp6aFN3NXJQWGtjSGp2cXV3blZjVHp6QiIsImp0aSI6InVybjp1dWlkOjZiMWQ4NDExLTllZDUtNDU2Ni05YzdmLTRjMjQxNjVmZjIzNiIsIm5iZiI6MTY3NTk0NjA1OC44OTI3NzYsIm5vbmNlIjoiMTFmNWY2MDAtYTg3Ni0xMWVkLTkwZjItMGExNjI4OTU4NTYwIiwic3ViIjoiZGlkOmVic2k6em94UkdWWlFuZFRmUWs1NEI3dEtkd3dOZGhhaTVnbTlGOE5hdjhlY2VOQUJhIiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwiY3JlZGVudGlhbFNjaGVtYSI6eyJpZCI6Imh0dHBzOi8vYXBpLnByZXByb2QuZWJzaS5ldS90cnVzdGVkLXNjaGVtYXMtcmVnaXN0cnkvdjEvc2NoZW1hcy8weGJmNzhmYzA4YTdhOWYyOGY1NDc5ZjU4ZGVhMjY5ZDM2NTdmNTRmMTNjYTM3ZDM4MGNkNGU5MjIzN2ZiNjkxZGQiLCJ0eXBlIjoiSnNvblNjaGVtYVZhbGlkYXRvcjIwMTgifSwiY3JlZGVudGlhbFN0YXR1cyI6eyJpZCI6Imh0dHBzOi8vZXNzaWYuZXVyb3BhLmV1L3N0YXR1cy9lZHVjYXRpb24jaGlnaGVyRWR1Y2F0aW9uIzM5MmFjN2Y2LTM5OWEtNDM3Yi1hMjY4LTQ2OTFlYWQ4ZjE3NiIsInR5cGUiOiJDcmVkZW50aWFsU3RhdHVzTGlzdDIwMjAifSwiY3JlZGVudGlhbFN1YmplY3QiOnsiYXdhcmRpbmdPcHBvcnR1bml0eSI6eyJhd2FyZGluZ0JvZHkiOnsiZWlkYXNMZWdhbElkZW50aWZpZXIiOiJVbmtub3duIiwiaG9tZXBhZ2UiOiJodHRwczovL2xlYXN0b24uYmNkaXBsb21hLmNvbS8iLCJpZCI6ImRpZDplYnNpOnpkUnZ2S2JYaFZWQnNYaGF0anVpQmhzIiwicHJlZmVycmVkTmFtZSI6IkxlYXN0b24gVW5pdmVyc2l0eSIsInJlZ2lzdHJhdGlvbiI6IjA1OTcwNjVKIn0sImVuZGVkQXRUaW1lIjoiMjAyMC0wNi0yNlQwMDowMDowMFoiLCJpZCI6Imh0dHBzOi8vbGVhc3Rvbi5iY2RpcGxvbWEuY29tL2xhdy1lY29ub21pY3MtbWFuYWdlbWVudCNBd2FyZGluZ09wcG9ydHVuaXR5IiwiaWRlbnRpZmllciI6Imh0dHBzOi8vY2VydGlmaWNhdGUtZGVtby5iY2RpcGxvbWEuY29tL2NoZWNrLzg3RUQyRjIyNzBFNkM0MTQ1NkU5NEI4NkI5RDkxMTVCNEUzNUJDQ0FEMjAwQTQ5Qjg0NjU5MkMxNEY3OUM4NkJWMUZuYmxsdGEwTlpUbkprUjNsRFdsUm1URGxTUlVKRVZGWklTbU5tWXpKaFVVNXNaVUo1WjJGSlNIcFdibVpaIiwibG9jYXRpb24iOiJGUkFOQ0UiLCJzdGFydGVkQXRUaW1lIjoiMjAxOS0wOS0wMlQwMDowMDowMFoifSwiZGF0ZU9mQmlydGgiOiIxOTkzLTA0LTA4IiwiZmFtaWx5TmFtZSI6IkRPRSIsImdpdmVuTmFtZXMiOiJKYW5lIiwiZ3JhZGluZ1NjaGVtZSI6eyJpZCI6Imh0dHBzOi8vbGVhc3Rvbi5iY2RpcGxvbWEuY29tL2xhdy1lY29ub21pY3MtbWFuYWdlbWVudCNHcmFkaW5nU2NoZW1lIiwidGl0bGUiOiIyIHllYXIgZnVsbC10aW1lIHByb2dyYW1tZSAvIDQgc2VtZXN0ZXJzIn0sImlkIjoiZGlkOmVic2k6em94UkdWWlFuZFRmUWs1NEI3dEtkd3dOZGhhaTVnbTlGOE5hdjhlY2VOQUJhIiwiaWRlbnRpZmllciI6IjA5MDQwMDgwODRIIiwibGVhcm5pbmdBY2hpZXZlbWVudCI6eyJhZGRpdGlvbmFsTm90ZSI6WyJESVNUUklCVVRJT04gTUFOQUdFTUVOVCJdLCJkZXNjcmlwdGlvbiI6IlRoZSBNYXN0ZXIgaW4gSW5mb3JtYXRpb24gYW5kIENvbXB1dGVyIFNjaWVuY2VzIChNSUNTKSBhdCB0aGUgVW5pdmVyc2l0eSBvZiBMdXhlbWJvdXJnIGVuYWJsZXMgc3R1ZGVudHMgdG8gYWNxdWlyZSBkZWVwZXIga25vd2xlZGdlIGluIGNvbXB1dGVyIHNjaWVuY2UgYnkgdW5kZXJzdGFuZGluZyBpdHMgYWJzdHJhY3QgYW5kIGludGVyZGlzY2lwbGluYXJ5IGZvdW5kYXRpb25zLCBmb2N1c2luZyBvbiBwcm9ibGVtIHNvbHZpbmcgYW5kIGRldmVsb3BpbmcgbGlmZWxvbmcgbGVhcm5pbmcgc2tpbGxzLiIsImlkIjoiaHR0cHM6Ly9sZWFzdG9uLmJjZGlwbG9tYS5jb20vbGF3LWVjb25vbWljcy1tYW5hZ2VtZW50I0xlYXJuaW5nQWNoaWV2bWVudCIsInRpdGxlIjoiTWFzdGVyIGluIEluZm9ybWF0aW9uIGFuZCBDb21wdXRlciBTY2llbmNlcyJ9LCJsZWFybmluZ1NwZWNpZmljYXRpb24iOnsiZWN0c0NyZWRpdFBvaW50cyI6MTIwLCJlcWZMZXZlbCI6NywiaWQiOiJodHRwczovL2xlYXN0b24uYmNkaXBsb21hLmNvbS9sYXctZWNvbm9taWNzLW1hbmFnZW1lbnQjTGVhcm5pbmdTcGVjaWZpY2F0aW9uIiwiaXNjZWRmQ29kZSI6WyI3Il0sIm5xZkxldmVsIjpbIjciXX19LCJldmlkZW5jZSI6eyJkb2N1bWVudFByZXNlbmNlIjpbIlBoeXNpY2FsIl0sImV2aWRlbmNlRG9jdW1lbnQiOlsiUGFzc3BvcnQiXSwiaWQiOiJodHRwczovL2Vzc2lmLmV1cm9wYS5ldS90c3ItdmEvZXZpZGVuY2UvZjJhZWVjOTctZmMwZC00MmJmLThjYTctMDU0ODE5MmQ1Njc4Iiwic3ViamVjdFByZXNlbmNlIjoiUGh5c2ljYWwiLCJ0eXBlIjpbIkRvY3VtZW50VmVyaWZpY2F0aW9uIl0sInZlcmlmaWVyIjoiZGlkOmVic2k6Mjk2MmZiNzg0ZGY2MWJhYTI2N2M4MTMyNDk3NTM5ZjhjNjc0YjM3YzEyNDRhN2EifSwiaWQiOiJ1cm46dXVpZDo2YjFkODQxMS05ZWQ1LTQ1NjYtOWM3Zi00YzI0MTY1ZmYyMzYiLCJpc3N1YW5jZURhdGUiOiIyMDIzLTAyLTA5VDEyOjM0OjE4WiIsImlzc3VlZCI6IjIwMjMtMDItMDlUMTI6MzQ6MThaIiwiaXNzdWVyIjoiZGlkOmVic2k6emhTdzVyUFhrY0hqdnF1d25WY1R6ekIiLCJwcm9vZiI6eyJjcmVhdGVkIjoiMjAyMi0wNC0yN1QxMjoyNTowN1oiLCJjcmVhdG9yIjoiZGlkOmVic2k6emRSdnZLYlhoVlZCc1hoYXRqdWlCaHMiLCJkb21haW4iOiJodHRwczovL2FwaS5wcmVwcm9kLmVic2kuZXUiLCJqd3MiOiJleUppTmpRaU9tWmhiSE5sTENKamNtbDBJanBiSW1JMk5DSmRMQ0poYkdjaU9pSkZVekkxTmtzaWZRLi5tSUJuTThYRFFxU1lLUU5YX0x2YUpobXNieUNyNU9aNWNVMlprLVJlcUxwcjRkb0ZzZ21vb2JrTzUxMjh0WnktOEtpbVZqSmtHdzB3TDF1QlduTUxXUSIsIm5vbmNlIjoiM2VhNjhkYWUtZDA3YS00ZGFhLTkzMmItZmJiNThmNWMyMGM0IiwidHlwZSI6IkVjZHNhU2VjcDI1NmsxU2lnbmF0dXJlMjAxOSJ9LCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVmVyaWZpYWJsZUF0dGVzdGF0aW9uIiwiVmVyaWZpYWJsZURpcGxvbWEiXSwidmFsaWRGcm9tIjoiMjAyMy0wMi0wOVQxMjozNDoxOFoifX0.uQK9sK-VtqmKjLJIw_v5Ff5xAMDAosZCtl1LFYxZhUolReD6a7O-NI1f5lcswBCZPLfJ-HJyb5iShehHObzFDA","c_nonce":"128952c8-a876-11ed-bbc4-0a1628958560","c_nonce_expires_in":1000}'; // ignore: lines_longer_than_80_chars dioAdapter ..onGet( issuer, @@ -247,7 +247,7 @@ void main() { test('get token data with credentialRequestUri', () async { const expectedTokenData = - '{"code":"cb803d46-9c88-11ed-bdb3-0a1628958560","grant_type":"authorization_code"}'; + '{"code":"cb803d46-9c88-11ed-bdb3-0a1628958560","grant_type":"authorization_code"}'; // ignore: lines_longer_than_80_chars final tokenData = ebsi.buildTokenData( credentialRequest, ); diff --git a/packages/ebsi/test/src/verifier_token_parameters_test.dart b/packages/ebsi/test/src/verifier_token_parameters_test.dart index 9d9a3f3e3..330bb5661 100644 --- a/packages/ebsi/test/src/verifier_token_parameters_test.dart +++ b/packages/ebsi/test/src/verifier_token_parameters_test.dart @@ -64,7 +64,7 @@ void main() { // }); // test('didKey', () { - // const didKey = 'did:ebsi:zo4FR1YfAKFP3Q6dvqhxcXxnfeDiJDP97kmnqhyAUSACj'; + // const didKey = 'did:ebsi:zo4FR1YfAKFP3Q6dvqhxcXxnfeDiJDP97kmnqhyAUSACj'; // ignore: lines_longer_than_80_chars // expect(verifierTokenParameters.didKey, didKey); // }); From 7d272b1994837d250e4187ff2f1dd14bad86866b Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Mon, 13 Feb 2023 12:05:24 +0530 Subject: [PATCH 002/190] packages upgrade and linter update --- analysis_options.yaml | 3 +- lib/app/shared/widget/image_card_text.dart | 4 +- .../drawer/live_chat/view/live_chat_page.dart | 2 +- .../lib/credential_manifest.dart | 1 + packages/credential_manifest/pubspec.yaml | 4 +- .../lib/cryptocurrency_keys.dart | 1 + packages/cryptocurrency_keys/pubspec.yaml | 2 +- packages/did_kit/lib/did_kit.dart | 1 + packages/did_kit/pubspec.yaml | 2 +- packages/ebsi/lib/src/ebsi.dart | 2 +- packages/ebsi/pubspec.yaml | 6 +- packages/jwt_decode/lib/jwt_decode.dart | 1 + packages/jwt_decode/pubspec.yaml | 2 +- packages/key_generator/lib/key_generator.dart | 1 + packages/key_generator/pubspec.yaml | 4 +- .../secure_storage/lib/secure_storage.dart | 1 + packages/secure_storage/pubspec.yaml | 4 +- pubspec.lock | 125 +++++++++--------- pubspec.yaml | 76 +++++------ 19 files changed, 126 insertions(+), 116 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index be7297dee..df35d6a43 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -14,4 +14,5 @@ linter: prefer_constructors_over_static_methods: false avoid_dynamic_calls: false use_build_context_synchronously: false - use_string_buffers: false \ No newline at end of file + use_string_buffers: false + always_put_required_named_parameters_first: false \ No newline at end of file diff --git a/lib/app/shared/widget/image_card_text.dart b/lib/app/shared/widget/image_card_text.dart index bbac938c6..33fe7b766 100644 --- a/lib/app/shared/widget/image_card_text.dart +++ b/lib/app/shared/widget/image_card_text.dart @@ -1,8 +1,8 @@ -/// This widget is used to adapt text size on image card when user change -/// phone orientation import 'package:altme/theme/theme.dart'; import 'package:flutter/material.dart'; +/// This widget is used to adapt text size on image card when user change +/// phone orientation class ImageCardText extends StatelessWidget { const ImageCardText({ super.key, diff --git a/lib/dashboard/drawer/live_chat/view/live_chat_page.dart b/lib/dashboard/drawer/live_chat/view/live_chat_page.dart index 369a4ca8c..90967526f 100644 --- a/lib/dashboard/drawer/live_chat/view/live_chat_page.dart +++ b/lib/dashboard/drawer/live_chat/view/live_chat_page.dart @@ -4,7 +4,7 @@ import 'package:altme/l10n/l10n.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_chat_types/flutter_chat_types.dart'; -import 'package:flutter_chat_ui/flutter_chat_ui.dart' hide Message, FileMessage; +import 'package:flutter_chat_ui/flutter_chat_ui.dart' hide FileMessage, Message; class LiveChatPage extends StatelessWidget { const LiveChatPage({ diff --git a/packages/credential_manifest/lib/credential_manifest.dart b/packages/credential_manifest/lib/credential_manifest.dart index e40e4ca01..a55675fe7 100644 --- a/packages/credential_manifest/lib/credential_manifest.dart +++ b/packages/credential_manifest/lib/credential_manifest.dart @@ -1,3 +1,4 @@ +/// credential_manifest library credential_manifest; export 'src/credential_manifest.dart'; diff --git a/packages/credential_manifest/pubspec.yaml b/packages/credential_manifest/pubspec.yaml index d7562cc4b..008f8d6e9 100644 --- a/packages/credential_manifest/pubspec.yaml +++ b/packages/credential_manifest/pubspec.yaml @@ -17,5 +17,5 @@ dev_dependencies: build_runner: ^2.3.3 flutter_test: sdk: flutter - json_serializable: ^6.6.0 - very_good_analysis: ^3.1.0 \ No newline at end of file + json_serializable: ^6.6.1 + very_good_analysis: ^4.0.0+1 \ No newline at end of file diff --git a/packages/cryptocurrency_keys/lib/cryptocurrency_keys.dart b/packages/cryptocurrency_keys/lib/cryptocurrency_keys.dart index fa5bec3dc..cffe7aa12 100644 --- a/packages/cryptocurrency_keys/lib/cryptocurrency_keys.dart +++ b/packages/cryptocurrency_keys/lib/cryptocurrency_keys.dart @@ -1,3 +1,4 @@ +/// cryptocurrency_keys library cryptocurrency_keys; export 'src/cryptocurrency_keys.dart'; diff --git a/packages/cryptocurrency_keys/pubspec.yaml b/packages/cryptocurrency_keys/pubspec.yaml index a465f0212..24daaa6d9 100644 --- a/packages/cryptocurrency_keys/pubspec.yaml +++ b/packages/cryptocurrency_keys/pubspec.yaml @@ -20,4 +20,4 @@ dev_dependencies: build_runner: ^2.3.3 flutter_test: sdk: flutter - very_good_analysis: ^3.1.0 \ No newline at end of file + very_good_analysis: ^4.0.0+1 \ No newline at end of file diff --git a/packages/did_kit/lib/did_kit.dart b/packages/did_kit/lib/did_kit.dart index 3f8059423..cf0ea4aef 100644 --- a/packages/did_kit/lib/did_kit.dart +++ b/packages/did_kit/lib/did_kit.dart @@ -1,3 +1,4 @@ +/// did_kit library did_kit; export 'src/did_kit_provider.dart'; diff --git a/packages/did_kit/pubspec.yaml b/packages/did_kit/pubspec.yaml index 3ecd480c5..6245cf1ee 100644 --- a/packages/did_kit/pubspec.yaml +++ b/packages/did_kit/pubspec.yaml @@ -16,4 +16,4 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - very_good_analysis: ^3.1.0 \ No newline at end of file + very_good_analysis: ^4.0.0+1 \ No newline at end of file diff --git a/packages/ebsi/lib/src/ebsi.dart b/packages/ebsi/lib/src/ebsi.dart index a5e2d39ea..f0a7c994e 100644 --- a/packages/ebsi/lib/src/ebsi.dart +++ b/packages/ebsi/lib/src/ebsi.dart @@ -399,7 +399,7 @@ class Ebsi { 'vp_token': vpToken }; try { - final presentationResponse = await client.post( + await client.post( uri.queryParameters['redirect_uri']!, options: Options(headers: responseHeaders), data: responseData, diff --git a/packages/ebsi/pubspec.yaml b/packages/ebsi/pubspec.yaml index b55baedcc..64271886e 100644 --- a/packages/ebsi/pubspec.yaml +++ b/packages/ebsi/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: bip39: ^1.0.6 crypto: ^3.0.2 dart_bip32_bip44: ^0.2.0 - dart_jsonwebtoken: ^2.6.4 + dart_jsonwebtoken: ^2.7.1 dart_web3: ^0.0.3 dio: ^4.0.6 ed25519_hd_key: ^2.2.0 @@ -22,7 +22,7 @@ dependencies: http_mock_adapter: ^0.3.3 jose: ^0.3.2 json_path: ^0.4.2 - pinenacl: ^0.3.4 + pinenacl: ^0.3.3 # tezart from git depends on pinenacl ^0.3.3 secp256k1: ^0.3.0 tezart: git: @@ -33,4 +33,4 @@ dev_dependencies: flutter_test: sdk: flutter mocktail: ^0.3.0 - very_good_analysis: ^3.1.0 + very_good_analysis: ^4.0.0+1 diff --git a/packages/jwt_decode/lib/jwt_decode.dart b/packages/jwt_decode/lib/jwt_decode.dart index dd8458264..76a1a7b33 100644 --- a/packages/jwt_decode/lib/jwt_decode.dart +++ b/packages/jwt_decode/lib/jwt_decode.dart @@ -1,3 +1,4 @@ +/// jwt_decode library jwt_decode; export 'src/jwt_decode.dart'; diff --git a/packages/jwt_decode/pubspec.yaml b/packages/jwt_decode/pubspec.yaml index bfe58ae18..7c3887542 100644 --- a/packages/jwt_decode/pubspec.yaml +++ b/packages/jwt_decode/pubspec.yaml @@ -12,4 +12,4 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - very_good_analysis: ^3.1.0 \ No newline at end of file + very_good_analysis: ^4.0.0+1 \ No newline at end of file diff --git a/packages/key_generator/lib/key_generator.dart b/packages/key_generator/lib/key_generator.dart index c54f709e8..21636271a 100644 --- a/packages/key_generator/lib/key_generator.dart +++ b/packages/key_generator/lib/key_generator.dart @@ -1,3 +1,4 @@ +/// key_generator library key_generator; export 'src/enum.dart'; diff --git a/packages/key_generator/pubspec.yaml b/packages/key_generator/pubspec.yaml index 31dc91ad9..efd19a709 100644 --- a/packages/key_generator/pubspec.yaml +++ b/packages/key_generator/pubspec.yaml @@ -20,7 +20,7 @@ dependencies: url: https://github.com/autonomy-system/tezart.git ref: bd4b8db6e3a352590a6e556d31df8aef60db3465 dev_dependencies: - coverage: ^1.1.0 + coverage: ^1.6.3 mocktail: ^0.3.0 test: ^1.19.2 - very_good_analysis: ^3.1.0 + very_good_analysis: ^4.0.0+1 diff --git a/packages/secure_storage/lib/secure_storage.dart b/packages/secure_storage/lib/secure_storage.dart index aceef4b1e..89c19c85b 100644 --- a/packages/secure_storage/lib/secure_storage.dart +++ b/packages/secure_storage/lib/secure_storage.dart @@ -1,3 +1,4 @@ +/// secure_storage library secure_storage; export 'src/secure_storage.dart'; diff --git a/packages/secure_storage/pubspec.yaml b/packages/secure_storage/pubspec.yaml index 9eb6a0f3a..15c571e5a 100644 --- a/packages/secure_storage/pubspec.yaml +++ b/packages/secure_storage/pubspec.yaml @@ -9,11 +9,11 @@ environment: dependencies: flutter: sdk: flutter - flutter_secure_storage: ^7.0.1 + flutter_secure_storage: ^8.0.0 dev_dependencies: flutter_test: sdk: flutter mocktail: ^0.3.0 test: ^1.22.0 - very_good_analysis: ^3.1.0 \ No newline at end of file + very_good_analysis: ^4.0.0+1 \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 099a7c76b..194c1b85d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -126,18 +126,18 @@ packages: dependency: "direct main" description: name: bloc - sha256: bd4f8027bfa60d96c8046dec5ce74c463b2c918dce1b0d36593575995344534a + sha256: "658a5ae59edcf1e58aac98b000a71c762ad8f46f1394c34a52050cafb3e11a80" url: "https://pub.dev" source: hosted - version: "8.1.0" + version: "8.1.1" bloc_test: dependency: "direct dev" description: name: bloc_test - sha256: "622b97678bf8c06a94f4c26a89ee9ebf7319bf775383dee2233e86e1f94ee28d" + sha256: ffbb60c17ee3d8e3784cb78071088e353199057233665541e8ac6cd438dca8ad url: "https://pub.dev" source: hosted - version: "9.1.0" + version: "9.1.1" blurhash_dart: dependency: transitive description: @@ -254,10 +254,10 @@ packages: dependency: "direct main" description: name: camera - sha256: "045e7739f9362f3c01d5c7187ac7e2ba9e2bc9f2ae6470ecdcc5e34d060ed81c" + sha256: e7ac55af24a890d20276821eb3c95857074d03b7de6f9892b99a205ee30bd179 url: "https://pub.dev" source: hosted - version: "0.10.2+1" + version: "0.10.3" camera_android: dependency: transitive description: @@ -358,18 +358,18 @@ packages: dependency: "direct main" description: name: connectivity_plus - sha256: "745ebcccb1ef73768386154428a55250bc8d44059c19fd27aecda2a6dc013a22" + sha256: "8875e8ed511a49f030e313656154e4bbbcef18d68dfd32eb853fac10bce48e96" url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.3" connectivity_plus_platform_interface: dependency: transitive description: name: connectivity_plus_platform_interface - sha256: b8795b9238bf83b64375f63492034cb3d8e222af4d9ce59dda085edf038fa06f + sha256: cf1d1c28f4416f8c654d7dc3cd638ec586076255d407cef3ddbdaf178272a71a url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.2.4" convert: dependency: "direct main" description: @@ -489,15 +489,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.8" - desk360flutter: - dependency: "direct main" - description: - path: "." - ref: "1.0.0" - resolved-ref: e1cf387c7069fe97a9257b6d381a90afa0d5259b - url: "https://github.com/Teknasyon-Teknoloji/desk360-flutter-sdk.git" - source: git - version: "0.1.0" device_frame: dependency: transitive description: @@ -510,10 +501,10 @@ packages: dependency: "direct main" description: name: device_info_plus - sha256: "7ff671ed0a6356fa8f2e1ae7d3558d3fb7b6a41e24455e4f8df75b811fb8e4ab" + sha256: "1d6e5a61674ba3a68fb048a7c7b4ff4bebfed8d7379dbe8f2b718231be9a7c95" url: "https://pub.dev" source: hosted - version: "8.0.0" + version: "8.1.0" device_info_plus_platform_interface: dependency: transitive description: @@ -639,14 +630,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.5" - event_bus: - dependency: transitive - description: - name: event_bus - sha256: "7f06f2c22173693f20b672c2f74f5d146a715a9e6b6b8928898146e199550ddd" - url: "https://pub.dev" - source: hosted - version: "1.1.0" fake_async: dependency: transitive description: @@ -728,10 +711,10 @@ packages: dependency: "direct main" description: name: flutter_bloc - sha256: "890c51c8007f0182360e523518a0c732efb89876cb4669307af7efada5b55557" + sha256: "434951eea948dbe87f737b674281465f610b8259c16c097b8163ce138749a775" url: "https://pub.dev" source: hosted - version: "8.1.1" + version: "8.1.2" flutter_blurhash: dependency: transitive description: @@ -837,10 +820,10 @@ packages: dependency: "direct main" description: name: flutter_markdown - sha256: "818cf6c28377ba2c91ed283c96fd712e9c175dd2d2488eb7fc93b6afb9ad2e08" + sha256: "7b25c10de1fea883f3c4f9b8389506b54053cd00807beab69fd65c8653a2711f" url: "https://pub.dev" source: hosted - version: "0.6.13+1" + version: "0.6.14" flutter_native_timezone: dependency: "direct main" description: @@ -869,26 +852,26 @@ packages: dependency: transitive description: name: flutter_secure_storage - sha256: f2afec1f1762c040a349ea2a588e32f442da5d0db3494a52a929a97c9e550bc5 + sha256: "98352186ee7ad3639ccc77ad7924b773ff6883076ab952437d20f18a61f0a7c5" url: "https://pub.dev" source: hosted - version: "7.0.1" + version: "8.0.0" flutter_secure_storage_linux: dependency: transitive description: name: flutter_secure_storage_linux - sha256: "736436adaf91552433823f51ce22e098c2f0551db06b6596f58597a25b8ea797" + sha256: "0912ae29a572230ad52d8a4697e5518d7f0f429052fd51df7e5a7952c7efe2a3" url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.1.3" flutter_secure_storage_macos: dependency: transitive description: name: flutter_secure_storage_macos - sha256: ff0768a6700ea1d9620e03518e2e25eac86a8bd07ca3556e9617bfa5ace4bd00 + sha256: "083add01847fc1c80a07a08e1ed6927e9acd9618a35e330239d4422cd2a58c50" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.0" flutter_secure_storage_platform_interface: dependency: transitive description: @@ -909,10 +892,10 @@ packages: dependency: transitive description: name: flutter_secure_storage_windows - sha256: ca89c8059cf439985aa83c59619b3674c7ef6cc2e86943d169a7369d6a69cab5 + sha256: fc2910ec9b28d60598216c29ea763b3a96c401f0ce1d13cdf69ccb0e5c93c3ee url: "https://pub.dev" source: hosted - version: "1.1.3" + version: "2.0.0" flutter_sodium: dependency: transitive description: @@ -925,10 +908,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: "6ff9fa12892ae074092de2fa6a9938fb21dbabfdaa2ff57dc697ff912fc8d4b2" + sha256: f999d84ad2efda1c4c3956e7968b713b3a24b06f0a0e4798e844e16bbb9bb70b url: "https://pub.dev" source: hosted - version: "1.1.6" + version: "2.0.0+1" flutter_test: dependency: "direct dev" description: flutter @@ -967,10 +950,10 @@ packages: dependency: "direct main" description: name: google_fonts - sha256: "8f099045e2f2a30e4d4d0a35f40c6bc941a8f2ca0e10ad9d214ee9edd3f37483" + sha256: "927573f2e8a8d65c17931e21918ad0ab0666b1b636537de7c4932bdb487b190f" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "4.0.3" graphs: dependency: transitive description: @@ -1151,10 +1134,10 @@ packages: dependency: "direct dev" description: name: json_serializable - sha256: "040088d9eb2337f3a51ddabe35261e2fea1c351e3dd29d755ed1290b6b700a75" + sha256: dadc08bd61f72559f938dd08ec20dbfec6c709bba83515085ea943d2078d187a url: "https://pub.dev" source: hosted - version: "6.6.0" + version: "6.6.1" jwt_decode: dependency: "direct main" description: @@ -1301,10 +1284,10 @@ packages: dependency: "direct main" description: name: mobile_scanner - sha256: d0824e90ec6b202287df5daa6a727d73edd191a20cef6692a50686ff26d66704 + sha256: "4045e8441e21f1fb8998a76fbffd054510dd3a3b1dee55c7c9a2083eee687345" url: "https://pub.dev" source: hosted - version: "3.0.0-beta.4" + version: "3.0.0" mockingjay: dependency: "direct dev" description: @@ -1413,10 +1396,10 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: f619162573096d428ccde2e33f92e05b5a179cd6f0e3120c1005f181bee8ed16 + sha256: "8df5ab0a481d7dc20c0e63809e90a588e496d276ba53358afc4c4443d0a00697" url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.3" package_info_plus_platform_interface: dependency: transitive description: @@ -1772,10 +1755,10 @@ packages: dependency: "direct main" description: name: share_plus - sha256: e387077716f80609bb979cd199331033326033ecd1c8f200a90c5f57b1c9f55e + sha256: "8c6892037b1824e2d7e8f59d54b3105932899008642e6372e5079c6939b4b625" url: "https://pub.dev" source: hosted - version: "6.3.0" + version: "6.3.1" share_plus_platform_interface: dependency: transitive description: @@ -2098,10 +2081,10 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: "698fa0b4392effdc73e9e184403b627362eb5fbf904483ac9defbb1c2191d809" + sha256: e8f2efc804810c0f2f5b485f49e7942179f56eabcfe81dce3387fec4bb55876b url: "https://pub.dev" source: hosted - version: "6.1.8" + version: "6.1.9" url_launcher_android: dependency: transitive description: @@ -2166,6 +2149,30 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.7" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: "09562ef5f47aa84f6567495adb6b9cb2a3192b82c352623b8bd00b300d62603b" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: "886e57742644ebed024dc3ade29712e37eea1b03d294fb314c0a3386243fe5a6" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: "5d9010c4a292766c55395b2288532579a85673f8148460d1e233d98ffe10d24e" + url: "https://pub.dev" + source: hosted + version: "1.0.1" vector_math: dependency: transitive description: @@ -2178,10 +2185,10 @@ packages: dependency: "direct dev" description: name: very_good_analysis - sha256: "4815adc7ded57657038d2bb2a7f332c50e3c8152f7d3c6acf8f6b7c0cc81e5e2" + sha256: ebc48c51db35beeeec8c414e32f7bd78e612bd7f5992ccb0d46e19edaeb40b08 url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "4.0.0+1" visibility_detector: dependency: transitive description: @@ -2336,5 +2343,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.19.0 <3.0.0" - flutter: ">=3.3.7" + dart: ">=2.19.0 <3.7.0" + flutter: ">=3.7.0-0" diff --git a/pubspec.yaml b/pubspec.yaml index da993a547..beaa00682 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,10 +15,10 @@ dependencies: # beacon_flutter: # path: ../beacon bip39: ^1.0.6 - bloc: ^8.0.3 + bloc: ^8.1.0 cached_network_image: ^3.2.1 - camera: ^0.10.0 - connectivity_plus: ^3.0.2 + camera: ^0.10.3 + connectivity_plus: ^3.0.3 convert: ^3.1.1 credential_manifest: path: packages/credential_manifest @@ -28,44 +28,40 @@ dependencies: dartez: git: url: https://github.com/TalebRafiepour/Dartez.git - ref: main - desk360flutter: - git: - url: https://github.com/Teknasyon-Teknoloji/desk360-flutter-sdk.git - ref: 1.0.0 - device_info_plus: ^8.0.0 + ref: main + device_info_plus: ^8.1.0 device_preview: ^1.1.0 devicelocale: ^0.5.5 did_kit: path: packages/did_kit dio: ^4.0.6 - dotted_border: ^2.0.0+2 + dotted_border: ^2.0.0+3 ebsi: path: packages/ebsi ed25519_hd_key: ^2.2.0 - equatable: ^2.0.3 + equatable: ^2.0.5 file_picker: ^5.2.5 file_saver: ^0.1.1 flutter: sdk: flutter flutter_appauth: ^4.2.1 - flutter_bloc: ^8.0.1 + flutter_bloc: ^8.1.2 flutter_chat_types: ^3.6.0 flutter_chat_ui: ^1.6.6 flutter_dotenv: ^5.0.2 - flutter_html: ^3.0.0-alpha.5 + flutter_html: ^3.0.0-alpha.6 flutter_local_notifications: ^13.0.0 flutter_localizations: sdk: flutter - flutter_markdown: ^0.6.13 + flutter_markdown: ^0.6.14 flutter_native_timezone: ^2.0.0 - flutter_svg: ^1.0.0 - google_fonts: ^3.0.1 + flutter_svg: ^2.0.0+1 + google_fonts: ^4.0.3 #google_mlkit_face_detection: ^0.5.0 http: ^0.13.5 - image: ^3.1.3 + image: ^3.0.2 image_picker: ^0.8.6+1 - intl: ^0.17.0 + intl: ^0.17.0 #flutter_localizations from sdk which depends on intl 0.17.0 jose: ^0.3.3 json_annotation: ^4.8.0 json_path: ^0.4.2 @@ -73,27 +69,27 @@ dependencies: path: packages/jwt_decode key_generator: path: packages/key_generator - local_auth: ^2.1.0 + local_auth: ^2.1.3 logger: ^1.1.0 - matrix: ^0.15.13 + matrix: ^0.15.13 # new version available mime: ^1.0.4 - mobile_scanner: ^3.0.0-beta.1 - network_image_mock: ^2.1.0 - no_screenshot: ^0.0.1+5 + mobile_scanner: ^3.0.0 + network_image_mock: ^2.1.1 + no_screenshot: ^0.0.1+6 open_filex: ^4.3.2 - package_info_plus: ^3.0.2 - passbase_flutter: ^2.13.3 - path: ^1.8.2 - path_provider: ^2.0.8 + package_info_plus: ^3.0.3 + passbase_flutter: ^2.13.3 # new version available + path: ^1.8.2 # flutter_test from sdk depends on path 1.8.2 + path_provider: ^2.0.12 permission_handler: ^10.2.0 - pointycastle: ^3.5.2 + pointycastle: ^3.6.2 pretty_qr_code: ^2.0.2 qr_flutter: ^4.0.0 screenshot: ^1.3.0 secure_application: ^3.8.0 secure_storage: path: packages/secure_storage - share_plus: ^6.3.0 + share_plus: ^6.3.1 shimmer: ^2.0.0 switcher: ^1.0.0 tezart: @@ -102,8 +98,8 @@ dependencies: ref: bd4b8db6e3a352590a6e556d31df8aef60db3465 timezone: ^0.9.1 uni_links: ^0.5.1 - url_launcher: ^6.0.17 - uuid: ^3.0.5 + url_launcher: ^6.1.9 + uuid: ^3.0.7 #wallet_connect: ^1.0.4 wallet_connect: git: @@ -111,9 +107,9 @@ dependencies: ref: fba9b209ee2b61b62ddd643f07f6a7f64426d7b2 web3dart: ^2.6.1 webview_flutter: ^4.0.2 - webview_flutter_android: ^3.2.1 - webview_flutter_wkwebview: ^3.0.2 - workmanager: ^0.5.0 + webview_flutter_android: ^3.2.4 + webview_flutter_wkwebview: ^3.0.5 + workmanager: ^0.5.1 dependency_overrides: # Because flutter_sodium 0.2.0 depends on ffi ^1.0.0 and no versions of flutter_sodium match >0.2.0 <0.3.0, flutter_sodium ^0.2.0 requires ffi ^1.0.0 @@ -123,17 +119,17 @@ dependency_overrides: stream_channel: ^2.1.0 dev_dependencies: - bloc_test: ^9.0.3 + bloc_test: ^9.1.1 build_runner: ^2.3.3 - flutter_launcher_icons: "^0.11.0" + flutter_launcher_icons: ^0.11.0 #flutter_launcher_icons 0.11.0 depends on image ^3.0.2 flutter_test: sdk: flutter - http_mock_adapter: ^0.3.2 - json_serializable: ^6.6.0 + http_mock_adapter: ^0.3.3 + json_serializable: ^6.6.1 mockingjay: ^0.3.0 - mockito: ^5.1.0 + mockito: ^5.3.2 mocktail: ^0.3.0 - very_good_analysis: ^3.1.0 + very_good_analysis: ^4.0.0+1 flutter: uses-material-design: true From b6246815bcc22bdd574001ff300e51c416407b64 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Mon, 13 Feb 2023 14:02:46 +0530 Subject: [PATCH 003/190] copy paste private key for did and cryptos #1326 --- .../drawer/drawer/widgets/drawer_item.dart | 8 +++-- .../view/account_private_key_page.dart | 33 +++++++++---------- .../view/did/did_private_key_page.dart | 27 ++++++++++++--- .../did_ebsi/did_ebsi_private_key_page.dart | 27 ++++++++++++--- 4 files changed, 66 insertions(+), 29 deletions(-) diff --git a/lib/dashboard/drawer/drawer/widgets/drawer_item.dart b/lib/dashboard/drawer/drawer/widgets/drawer_item.dart index 0e770e900..a7f53b39c 100644 --- a/lib/dashboard/drawer/drawer/widgets/drawer_item.dart +++ b/lib/dashboard/drawer/drawer/widgets/drawer_item.dart @@ -32,9 +32,11 @@ class DrawerItem extends StatelessWidget { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - title, - style: Theme.of(context).textTheme.drawerItem, + Expanded( + child: MyText( + title, + style: Theme.of(context).textTheme.drawerItem, + ), ), if (trailing != null) trailing! diff --git a/lib/dashboard/drawer/manage_accounts/view/account_private_key_page.dart b/lib/dashboard/drawer/manage_accounts/view/account_private_key_page.dart index 848c144b8..566988ab8 100644 --- a/lib/dashboard/drawer/manage_accounts/view/account_private_key_page.dart +++ b/lib/dashboard/drawer/manage_accounts/view/account_private_key_page.dart @@ -1,6 +1,7 @@ import 'package:altme/app/app.dart'; import 'package:altme/l10n/l10n.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; class AccountPrivateKeyPage extends StatefulWidget { const AccountPrivateKeyPage({ @@ -80,6 +81,20 @@ class _AccountPrivateKeyPageState extends State decoration: TextDecoration.underline, ), ), + const SizedBox(height: Sizes.spaceXLarge), + CopyButton( + onTap: () async { + await Clipboard.setData( + ClipboardData(text: widget.privateKey), + ); + AlertMessage.showStateMessage( + context: context, + stateMessage: StateMessage.success( + stringMessage: l10n.copiedToClipboard, + ), + ); + }, + ), Expanded( child: Center( child: AnimatedBuilder( @@ -94,24 +109,6 @@ class _AccountPrivateKeyPageState extends State ), ), ), - // const SizedBox( - // height: Sizes.spaceXLarge, - // ), - // CopyButton( - // onTap: () async { - // await Clipboard.setData( - // ClipboardData( - // text: privateKey, - // ), - // ); - // AlertMessage.showStateMessage( - // context: context, - // stateMessage: StateMessage.success( - // stringMessage: l10n.copiedToClipboard, - // ), - // ); - // }, - // ), ], ), ), diff --git a/lib/dashboard/drawer/manage_did/view/did/did_private_key_page.dart b/lib/dashboard/drawer/manage_did/view/did/did_private_key_page.dart index 543237ddf..c15d6f523 100644 --- a/lib/dashboard/drawer/manage_did/view/did/did_private_key_page.dart +++ b/lib/dashboard/drawer/manage_did/view/did/did_private_key_page.dart @@ -3,6 +3,7 @@ import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/l10n/l10n.dart'; import 'package:altme/theme/theme.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:secure_storage/secure_storage.dart'; @@ -81,10 +82,28 @@ class _DIDPrivateKeyPageState extends State ), BlocBuilder( builder: (context, state) { - return Text( - state, - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.titleMedium, + return Column( + children: [ + Text( + state, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.titleMedium, + ), + const SizedBox(height: Sizes.spaceXLarge), + CopyButton( + onTap: () async { + await Clipboard.setData( + ClipboardData(text: state), + ); + AlertMessage.showStateMessage( + context: context, + stateMessage: StateMessage.success( + stringMessage: l10n.copiedToClipboard, + ), + ); + }, + ), + ], ); }, ), diff --git a/lib/dashboard/drawer/manage_did/view/did_ebsi/did_ebsi_private_key_page.dart b/lib/dashboard/drawer/manage_did/view/did_ebsi/did_ebsi_private_key_page.dart index 6cbd14a7e..c9e45422f 100644 --- a/lib/dashboard/drawer/manage_did/view/did_ebsi/did_ebsi_private_key_page.dart +++ b/lib/dashboard/drawer/manage_did/view/did_ebsi/did_ebsi_private_key_page.dart @@ -4,6 +4,7 @@ import 'package:altme/theme/theme.dart'; import 'package:dio/dio.dart'; import 'package:ebsi/ebsi.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:secure_storage/secure_storage.dart'; class DidEbsiPrivateKeyPage extends StatefulWidget { @@ -85,10 +86,28 @@ class _DidEbsiPrivateKeyPageState extends State builder: (context, snapshot) { switch (snapshot.connectionState) { case ConnectionState.done: - return Text( - snapshot.data!, - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.titleMedium, + return Column( + children: [ + Text( + snapshot.data!, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.titleMedium, + ), + const SizedBox(height: Sizes.spaceXLarge), + CopyButton( + onTap: () async { + await Clipboard.setData( + ClipboardData(text: snapshot.data), + ); + AlertMessage.showStateMessage( + context: context, + stateMessage: StateMessage.success( + stringMessage: l10n.copiedToClipboard, + ), + ); + }, + ), + ], ); case ConnectionState.waiting: case ConnectionState.none: From f1c69425b62fac659d5638c4aa18e00adca69a17 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Mon, 13 Feb 2023 12:59:25 +0330 Subject: [PATCH 004/190] text typo in help center menu #1196 --- lib/dashboard/drawer/drawer/view/help_center_menu.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dashboard/drawer/drawer/view/help_center_menu.dart b/lib/dashboard/drawer/drawer/view/help_center_menu.dart index aefb8c8f6..a81aa2fb6 100644 --- a/lib/dashboard/drawer/drawer/view/help_center_menu.dart +++ b/lib/dashboard/drawer/drawer/view/help_center_menu.dart @@ -71,7 +71,7 @@ class HelpCenterView extends StatelessWidget { Text( AltMeStrings.appContactWebsiteName, textAlign: TextAlign.left, - style: Theme.of(context).textTheme.titleSmall, + style: Theme.of(context).textTheme.drawerItem, ), const SizedBox(width: 16), Icon( From 11beee518839ec1cab3fbd3bf5baa1300260a008 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Mon, 13 Feb 2023 13:47:26 +0330 Subject: [PATCH 005/190] fix text style in new features popup --- lib/dashboard/dashboard/widgets/new_feature.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/dashboard/dashboard/widgets/new_feature.dart b/lib/dashboard/dashboard/widgets/new_feature.dart index f7d62da50..afe57ed99 100644 --- a/lib/dashboard/dashboard/widgets/new_feature.dart +++ b/lib/dashboard/dashboard/widgets/new_feature.dart @@ -28,7 +28,6 @@ class NewFeature extends StatelessWidget { child: Text( feature, style: Theme.of(context).textTheme.defaultDialogBody, - textAlign: TextAlign.justify, ), ), ], From 9028c65d92e1c1dc05cd987c266986e69a2e2c15 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Mon, 13 Feb 2023 20:06:38 +0530 Subject: [PATCH 006/190] add eu diploma card UI #1349 --- assets/image/eu_diploma_card.png | Bin 0 -> 82403 bytes lib/app/shared/constants/image_strings.dart | 2 + .../credential_subject_type.dart | 1 + .../credential_subject_type_extension.dart | 6 ++ .../detail/view/credentials_details_page.dart | 10 +++ .../list/cubit/credential_list_cubit.dart | 3 + .../eu_diploma_card_model.dart | 27 ++++++ .../home_credential/home_credential.dart | 1 + .../tab_bar/credentials/models/model.dart | 1 + .../widgets/credential_display.dart | 3 + .../credential_widget/credential_widget.dart | 1 + .../eu_diploma_card_widget.dart | 83 ++++++++++++++++++ lib/ebsi/add_ebsi_credential.dart | 5 ++ pubspec.lock | 2 +- 14 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 assets/image/eu_diploma_card.png create mode 100644 lib/dashboard/home/tab_bar/credentials/models/eu_diploma_card/eu_diploma_card_model.dart create mode 100644 lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/eu_diploma_card_widget.dart diff --git a/assets/image/eu_diploma_card.png b/assets/image/eu_diploma_card.png new file mode 100644 index 0000000000000000000000000000000000000000..5adb1de9f424495eabf693e05500dbfb1445d072 GIT binary patch literal 82403 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

Kb$AmF7fZOobC8OeE9gJj=>k~!>%0D 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 bH4s4GNo*azSPy|`sE^_&HQ5@OFQNYjHLFAf literal 0 HcmV?d00001 diff --git a/lib/app/shared/constants/image_strings.dart b/lib/app/shared/constants/image_strings.dart index 9960e7f8a..20ae488e8 100644 --- a/lib/app/shared/constants/image_strings.dart +++ b/lib/app/shared/constants/image_strings.dart @@ -67,7 +67,9 @@ class ImageStrings { static const String talaoCommunityCard = '$imagePath/talao_community_card.png'; static const String verifiableIdCard = '$imagePath/verifiable_id_card.png'; + static const String linkedInCard = '$imagePath/linkedin_card.png'; + static const String euDiplomaCard = '$imagePath/eu_diploma_card.png'; static const String myAccountCard = '$imagePath/my_account_card.png'; static const String pooAccountCard = '$imagePath/poo_account_card.png'; 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 912ae113d..0735444fc 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 @@ -56,4 +56,5 @@ enum CredentialSubjectType { aragoOver18, pcdsAgentCertificate, twitterCard, + euDiplomaCard, } 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 c1a3bd36f..a0136cd4a 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 @@ -76,6 +76,7 @@ extension CredentialSubjectTypeExtension on CredentialSubjectType { case CredentialSubjectType.binancePooAddress: case CredentialSubjectType.tezosPooAddress: case CredentialSubjectType.pcdsAgentCertificate: + case CredentialSubjectType.euDiplomaCard: return Colors.white; } } @@ -143,6 +144,7 @@ extension CredentialSubjectTypeExtension on CredentialSubjectType { case CredentialSubjectType.binancePooAddress: case CredentialSubjectType.tezosPooAddress: case CredentialSubjectType.pcdsAgentCertificate: + case CredentialSubjectType.euDiplomaCard: return Icons.perm_identity; } } @@ -262,6 +264,8 @@ extension CredentialSubjectTypeExtension on CredentialSubjectType { return 'AragoOver18'; case CredentialSubjectType.pcdsAgentCertificate: return 'PCDSAgentCertificate'; + case CredentialSubjectType.euDiplomaCard: + return 'https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd'; case CredentialSubjectType.defaultCredential: return ''; } @@ -373,6 +377,8 @@ extension CredentialSubjectTypeExtension on CredentialSubjectType { return BinancePooAddressModel.fromJson(json); case CredentialSubjectType.tezosPooAddress: return TezosPooAddressModel.fromJson(json); + case CredentialSubjectType.euDiplomaCard: + return EUDiplomaCardModel.fromJson(json); } } 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 69697c71e..5f9041e91 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 @@ -131,6 +131,10 @@ class _CredentialsDetailsViewState extends State { .credentialSubjectModel.credentialSubjectType == CredentialSubjectType.linkedInCard; + final bool isEUDiplomaCard = widget.credentialModel.credentialPreview + .credentialSubjectModel.credentialSubjectType == + CredentialSubjectType.euDiplomaCard; + final bool disAllowDelete = widget.credentialModel.credentialPreview .credentialSubjectModel.credentialSubjectType == CredentialSubjectType.walletCredential || @@ -181,6 +185,12 @@ class _CredentialsDetailsViewState extends State { ), ); } else { + if (isEUDiplomaCard) { + /// removing type that was added in add_ebsi_credential.dart // ignore: lines_longer_than_80_chars + widget.credentialModel.data['credentialSubject'] + .remove('type'); + } + final box = context.findRenderObject() as RenderBox?; final subject = l10n.shareWith; diff --git a/lib/dashboard/home/tab_bar/credentials/list/cubit/credential_list_cubit.dart b/lib/dashboard/home/tab_bar/credentials/list/cubit/credential_list_cubit.dart index 98a35dbe8..97c43f2f2 100644 --- a/lib/dashboard/home/tab_bar/credentials/list/cubit/credential_list_cubit.dart +++ b/lib/dashboard/home/tab_bar/credentials/list/cubit/credential_list_cubit.dart @@ -102,6 +102,7 @@ class CredentialListCubit extends Cubit { case CredentialSubjectType.fantomPooAddress: case CredentialSubjectType.polygonPooAddress: case CredentialSubjectType.binancePooAddress: + case CredentialSubjectType.euDiplomaCard: break; } @@ -292,6 +293,7 @@ class CredentialListCubit extends Cubit { case CredentialSubjectType.fantomPooAddress: case CredentialSubjectType.polygonPooAddress: case CredentialSubjectType.binancePooAddress: + case CredentialSubjectType.euDiplomaCard: break; } @@ -593,6 +595,7 @@ class CredentialListCubit extends Cubit { case CredentialSubjectType.walletCredential: case CredentialSubjectType.bloometaPass: case CredentialSubjectType.dogamiPass: + case CredentialSubjectType.euDiplomaCard: case CredentialSubjectType.troopezPass: case CredentialSubjectType.bunnyPass: case CredentialSubjectType.pigsPass: diff --git a/lib/dashboard/home/tab_bar/credentials/models/eu_diploma_card/eu_diploma_card_model.dart b/lib/dashboard/home/tab_bar/credentials/models/eu_diploma_card/eu_diploma_card_model.dart new file mode 100644 index 000000000..6714d8463 --- /dev/null +++ b/lib/dashboard/home/tab_bar/credentials/models/eu_diploma_card/eu_diploma_card_model.dart @@ -0,0 +1,27 @@ +import 'package:altme/app/app.dart'; +import 'package:altme/dashboard/dashboard.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'eu_diploma_card_model.g.dart'; + +@JsonSerializable(explicitToJson: true) +class EUDiplomaCardModel extends CredentialSubjectModel { + EUDiplomaCardModel({ + this.expires, + super.id, + super.type, + super.issuedBy, + }) : super( + credentialSubjectType: CredentialSubjectType.euDiplomaCard, + credentialCategory: CredentialCategory.othersCards, + ); + + factory EUDiplomaCardModel.fromJson(Map json) => + _$EUDiplomaCardModelFromJson(json); + + @JsonKey(defaultValue: '') + final String? expires; + + @override + Map toJson() => _$EUDiplomaCardModelToJson(this); +} diff --git a/lib/dashboard/home/tab_bar/credentials/models/home_credential/home_credential.dart b/lib/dashboard/home/tab_bar/credentials/models/home_credential/home_credential.dart index 0cf5d089e..e0fead4ce 100644 --- a/lib/dashboard/home/tab_bar/credentials/models/home_credential/home_credential.dart +++ b/lib/dashboard/home/tab_bar/credentials/models/home_credential/home_credential.dart @@ -277,6 +277,7 @@ class HomeCredential extends Equatable { case CredentialSubjectType.fantomPooAddress: case CredentialSubjectType.polygonPooAddress: case CredentialSubjectType.binancePooAddress: + case CredentialSubjectType.euDiplomaCard: break; } diff --git a/lib/dashboard/home/tab_bar/credentials/models/model.dart b/lib/dashboard/home/tab_bar/credentials/models/model.dart index 61993dbb9..145960b7a 100644 --- a/lib/dashboard/home/tab_bar/credentials/models/model.dart +++ b/lib/dashboard/home/tab_bar/credentials/models/model.dart @@ -27,6 +27,7 @@ export 'email_pass/email_pass_model.dart'; export 'ethereum_associated_address/ethereum_associated_address_credential.dart'; 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 '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'; 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 8bf5d30e3..3b2c8ce37 100644 --- a/lib/dashboard/home/tab_bar/credentials/widgets/credential_display.dart +++ b/lib/dashboard/home/tab_bar/credentials/widgets/credential_display.dart @@ -113,6 +113,9 @@ class CredentialDisplay extends StatelessWidget { case CredentialSubjectType.linkedInCard: return LinkedinCardWidget(credentialModel: credentialModel); + case CredentialSubjectType.euDiplomaCard: + return EUDiplomaCardWidget(credentialModel: credentialModel); + case CredentialSubjectType.learningAchievement: switch (credDisplayType) { case CredDisplayType.List: diff --git a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/credential_widget.dart b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/credential_widget.dart index af7f0873d..fa2c82575 100644 --- a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/credential_widget.dart +++ b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/credential_widget.dart @@ -20,6 +20,7 @@ export 'ecole_42_learning_achievement_widget.dart'; export 'email_pass_widget.dart'; export 'ethereum_associated_address_widget.dart'; export 'ethereum_poo_address_widget.dart'; +export 'eu_diploma_card_widget.dart'; export 'fantom_associated_address_widget.dart'; export 'fantom_poo_address_widget.dart'; export 'gender_widget.dart'; diff --git a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/eu_diploma_card_widget.dart b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/eu_diploma_card_widget.dart new file mode 100644 index 000000000..f27116ee6 --- /dev/null +++ b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/eu_diploma_card_widget.dart @@ -0,0 +1,83 @@ +import 'package:altme/app/app.dart'; +import 'package:altme/dashboard/dashboard.dart'; +import 'package:altme/l10n/l10n.dart'; +import 'package:altme/theme/theme.dart'; +import 'package:flutter/material.dart'; + +class EUDiplomaCardWidget extends StatelessWidget { + const EUDiplomaCardWidget({ + super.key, + required this.credentialModel, + }); + + final CredentialModel credentialModel; + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + return CredentialImage( + image: ImageStrings.euDiplomaCard, + child: AspectRatio( + aspectRatio: Sizes.credentialAspectRatio, + child: CustomMultiChildLayout( + delegate: EUDiplomaCardWidgetDelegate(position: Offset.zero), + children: [ + LayoutId( + id: 'issued-on', + child: FractionallySizedBox( + heightFactor: 0.10, + widthFactor: 0.4, + child: MyText( + l10n.issuedOn, + style: Theme.of(context).textTheme.identitiyBaseLightText, + ), + ), + ), + LayoutId( + id: 'issued-on-value', + child: FractionallySizedBox( + heightFactor: 0.10, + widthFactor: 0.4, + child: MyText( + UiDate.formatDateForCredentialCard( + credentialModel.credentialPreview.issuanceDate, + ), + style: Theme.of(context).textTheme.identitiyBaseBoldText, + ), + ), + ), + ], + ), + ), + ); + } +} + +class EUDiplomaCardWidgetDelegate extends MultiChildLayoutDelegate { + EUDiplomaCardWidgetDelegate({this.position = Offset.zero}); + + final Offset position; + + @override + void performLayout(Size size) { + if (hasChild('issued-on')) { + layoutChild('issued-on', BoxConstraints.loose(size)); + positionChild( + 'issued-on', + Offset(size.width * 0.5, size.height * 0.70), + ); + } + if (hasChild('issued-on-value')) { + layoutChild('issued-on-value', BoxConstraints.loose(size)); + positionChild( + 'issued-on-value', + Offset(size.width * 0.5, size.height * 0.82), + ); + } + } + + @override + bool shouldRelayout(EUDiplomaCardWidgetDelegate oldDelegate) { + return oldDelegate.position != position; + } +} diff --git a/lib/ebsi/add_ebsi_credential.dart b/lib/ebsi/add_ebsi_credential.dart index 5910662fa..c54b402f7 100644 --- a/lib/ebsi/add_ebsi_credential.dart +++ b/lib/ebsi/add_ebsi_credential.dart @@ -20,6 +20,11 @@ Future addEbsiCredential( Map.from(credentialFromEbsi); newCredential['jwt'] = encodedCredentialFromEbsi['credential']; newCredential['credentialPreview'] = credentialFromEbsi; + + /// added id as type to recognise the card + newCredential['credentialPreview']['credentialSubject']['type'] = + credentialFromEbsi['credentialSchema']['id']; + final String credentialSchema = uri.queryParameters['credential_type'] ?? ''; final issuerAndCode = uri.queryParameters['issuer']; final issuerAndCodeUri = Uri.parse(issuerAndCode!); diff --git a/pubspec.lock b/pubspec.lock index 194c1b85d..db3c0e363 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2343,5 +2343,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.19.0 <3.7.0" + dart: ">=2.19.0 <3.0.0" flutter: ">=3.7.0-0" From 640f0c74661782a77ef0de0b174cdfd4f2206f51 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Mon, 13 Feb 2023 21:24:04 +0330 Subject: [PATCH 007/190] Impossible to display an ethereum NFT #1339 --- .../home/tab_bar/nft/cubit/nft_cubit.dart | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/dashboard/home/tab_bar/nft/cubit/nft_cubit.dart b/lib/dashboard/home/tab_bar/nft/cubit/nft_cubit.dart index cbe5e8c0c..941b56148 100644 --- a/lib/dashboard/home/tab_bar/nft/cubit/nft_cubit.dart +++ b/lib/dashboard/home/tab_bar/nft/cubit/nft_cubit.dart @@ -147,7 +147,7 @@ class NftCubit extends Cubit { return []; } - return List.from( + final nftList = List.from( result.map((dynamic e) { return EthereumNftModel( name: e['name'] as String, @@ -162,6 +162,18 @@ class NftCubit extends Cubit { ); }), ).toList(); + + for (final element in nftList) { + if (element.thumbnailUri == null) { + await client.get( + '${Urls.moralisBaseUrl}/nft/${element.contractAddress}/${element.tokenId}/metadata/resync', + headers: { + 'X-API-KEY': moralisApiKey, + }, + ); + } + } + return nftList; } catch (e, s) { getLogger(toString()).e('e: $e, s: $s'); return []; From be1e6558ce89512eb73d579c8abf26f8f630a902 Mon Sep 17 00:00:00 2001 From: hawkbee <49282360+hawkbee1@users.noreply.github.com> Date: Tue, 14 Feb 2023 06:33:37 +0100 Subject: [PATCH 008/190] version: 1.9.9+151 + bug correction (#1352) --- .../qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart | 4 ++-- pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) 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 94c3253e1..3bb626ca9 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 @@ -205,11 +205,11 @@ class QRCodeScanCubit extends Cubit { ), ); } else { - var claims = - r'{"id_token": {"email": "None"}, "vp_token": {"presentation_definition": {"id": "0db80a1b-a3ac-11ed-bbb4-0a1628958560", "input_descriptors": [{"id": "0db80b58-a3ac-11ed-9a0b-0a1628958560", "name": "Input descriptor 1", "purpose": " ", "constraints": {"fields": [{"path": ["$..credentialSchema.id"], "filter": {"type": "string", "pattern": "https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"}}]}}], "format": {"jwt_vp": {"alg": ["ES256K", "ES256", "PS256", "RS256"]}}}}}'; + var claims = uri.queryParameters['claims'] ?? ''; // TODO(hawkbee): change when correction is done on verifier claims = claims.replaceAll("'email': None", "'email': 'None'"); + claims = claims.replaceAll("'", '"'); final jsonPath = JsonPath(r'$..input_descriptors'); final outputDescriptors = diff --git a/pubspec.yaml b/pubspec.yaml index beaa00682..021824b7e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: altme description: AltMe Flutter App -version: 1.9.8+150 +version: 1.9.9+151 publish_to: none environment: From a742dc4dc584815882071067dcc4462c6b3845d0 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Tue, 14 Feb 2023 12:18:34 +0530 Subject: [PATCH 009/190] comment to make claims of openID dynamic later --- .../qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart | 3 +++ 1 file changed, 3 insertions(+) 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 94c3253e1..150f3acb8 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 @@ -205,6 +205,9 @@ class QRCodeScanCubit extends Cubit { ), ); } else { + //print(uri?.queryParameters['claims']); + + // TODO(bibash): make the claims dynamic var claims = r'{"id_token": {"email": "None"}, "vp_token": {"presentation_definition": {"id": "0db80a1b-a3ac-11ed-bbb4-0a1628958560", "input_descriptors": [{"id": "0db80b58-a3ac-11ed-9a0b-0a1628958560", "name": "Input descriptor 1", "purpose": " ", "constraints": {"fields": [{"path": ["$..credentialSchema.id"], "filter": {"type": "string", "pattern": "https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"}}]}}], "format": {"jwt_vp": {"alg": ["ES256K", "ES256", "PS256", "RS256"]}}}}}'; From f599a7c307f16a6cc5951d23de8b54f1b31ce6c6 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Tue, 14 Feb 2023 14:00:06 +0530 Subject: [PATCH 010/190] EBSI diploma card model and UI update #1349 --- assets/image/eu_diploma_card.png | Bin 82403 -> 69306 bytes .../eu_diploma_card_model.dart | 125 +++++++++++++++++- .../credential_base_widget.dart | 4 +- .../eu_diploma_card_widget.dart | 80 +++-------- 4 files changed, 139 insertions(+), 70 deletions(-) diff --git a/assets/image/eu_diploma_card.png b/assets/image/eu_diploma_card.png index 5adb1de9f424495eabf693e05500dbfb1445d072..839339928773adb898e85cdc058215e643e45f79 100644 GIT binary patch literal 69306 zcmXV0byQSOya#FNkd#nLy1P3h1f)TvrMqK6S~?_zr9-5%8hkik@(p7c?0(y1*#djFp_^q}-F-GtHlAKyAr=)R$mg@~dOTJ)iBB=&KHO%GvT} zN9C8=>w+V?Sbmv9obA7K;*-~oSn0frNO*c%=|x)Qh#X@wh=&yu-ZZPcVL4R2&+P(| zLDd4oXVWqvPQZ19_zbwgX-egszrU86!4#YXNQj6GHSl-0iy%ye4>GzXNB@0apr^FucU5{ zbSVipW!^vdm#)xUJV2A4xh)50?V=4!u^v?9)1xjDQD~~KFXj(3RCh!{6Wg}KkW(1+ z5>zCb4by*ysqE)T`t8@mP=jETT^*pmJ3y~~u!(c6DYMSW2-Le1^zfu10HBGCjZcDc z8f7A=*(d@_Du)-E_-i?5`$P{@C_r6$mOm3M46mxhlkZ6JUq=3#Q+I6CqFtT${thvr z7js!G$;FlA1i|@gJT&qM3|elhuxO_^DqWNT#jO4Is~D5qi;M)dgJ20I`oyZatP@Yu zH3e|Q!>#y~1*&874rmxin@RwL(1Ajr7u?~4n9vzU%pI(_q`2tsmLrwJhv~JC*Syr ziM{*%J72&0Vcbb;l|9<#`Y|1Yk=F0kx$}j{A!WFpYanskf-EMwxHn$fV2^Qsnn3BItagc<`ox{OP3;3w9GI4)EQDKirY4p zYH#>>0zeIn-;r#BR?2l6G9ZYk4V$Qc!F&sAzOD2KTWJ{3LXl8p5+2)oH0s4~E=RHa z=q;Monas{+>LT0AL=|Y$M|6(W_o_lb{&=Zcutqd|&AQ!&t?VP^XCV->tzoAhCB|{#PT)G(xFnnc zswJqMVX^{_eBG74opPckHWVF_gkm9sTIv>IIX-PB>%{vSCvZ7v1CTQkslpPKrElX z5z&d>ZL%@}ZiJ490l^$-pc z2|{Y8QADwrcdEmH>kyYG-y7hAn0j!7=$54#lC9N6M|BkWi>}7knqD(8Yg>uIUs!fN z(y%mYvn*(O!_Q$E)PN=rBC!+#DhIAQK!?@=h$c|&0vhhXXHVgV<^-eIxUiT{pFJld z?~GuJJDGoxrk>vU4rQ186qkovQ+KMb2yoi*srz zy<9E(VklcRQ{z+igzvtB`UZ?=W#pTxT)6ZR`vN*<=(!Ee@^)&oit1ZlgXDWtrMUIh z7bKZ<70Zc%-34UU6*tDsM?%&D8{M zMTmod^;xW#@%hnZH|w7g!s*`Jg9MIl498z1Ub(ijzWuDLH`gdWs2#Uw-bCar^JvLS z!Ob(lNMxN$lTK)HYmKznUI8z#npY;xdpgO@$(F?upztiY zAiBUh4eMH~c@gZ0p(G^Js9BpyYC5g|!u91-!v@bpDLq~BNT;A2Mq8Crw3%kDiRKcN z_miQhRMg8)V-`7|osGHaD7x9scH$*I_h+Z~B8j^%Y=CFA=@D zh+^LoGdCgM)*h)AL-_nl6J!u3<xL*w!e!P!O?`Be3n z$`7i_OS$RveO8?bL$=<>JPpZeOxYC39yV0sLj+aBHacGQf8~dpf(7X?!<{W>dCdX; zErLXw9<#9_FI%3E;7$skS8VB~JS+XH4qKY)C`Ehr-4|<@T>oW3XRE3YvXar9mD6G^ zd#LEHIrNSKP^G&`#)IDU(C;eXxHcAAJa_g5myxU3Kip?}ns)BNeC3}|sr5Jftx=2$ zPTaOVYdgp7KVxTnkcN1+&oyv2M@W)~C^mmIH;&k0c=%Ql)Q86jSmYEpEXfb;N}_y( zU4O|@Qzms8;VqGv^>zX_i##>kH;v17cna1UTL*3^m_JUA{R2^GQoOk@|t+tww_VTrk_eh%kU~PbJH&e&}RYzt0tlAv4N5?MHJCsLv!mAM%VkBeCAfa*Aud^_PK+nH+_59?GTk)6Lumow{16|HjdcV=+bX>x>{3!=ZcS z96o1T$1-Fxq%MdiE^VSZlF@xx(L0t0Q%hKXp9Cu3nDH4CZwtla0xLfE&y%*# z$~ZUgyb|cm4$k6#?;}<&&3yk`EPmj{ytkv;vMgrz(>-h&}%7EZv2GN+Ml z6EYzDFza&)2~}#Ad1=h10!0Lzp$DW^tAICOHXq{@yPe7ra2?J7|z`jfB! z(a2KYsnMrE(@i`EV*W!*53rRe@?Y7bb6ggG^Ctckn*iQsXJM6y;Uj0t>oV>5yZ=Is zvCV>g+_3}bZ32Y}>FJ8y&VwoUrq%@wUtv2)l+5r}%^HKUO{By2U)(*1BEM;Hw2#Sa zm3KTE@;9ubXTF!_R&g);b*Y5k4wjoJy)d0HeqRaAr+MjZT4wX8&d#kL&E@3kFaGsU z@zxEh)wlYO+5rTco|P$t0?wZa^Rq&s@s?vw@j=a3LZ!~NHr53)uovJ6PR$=)>Ku#S zetgjw^N^YF);VYMhDO^A4#(-BGHJ5Z%CDdFh@jKG6xMCX^M952Qh!KJSkCReq4lzC zbC{k4&+>@=u4Wz!^M@f4fKLI#KmuU=&*T9vQU>ZQUmBCw+a_EX4X70JarrZ&9533i zT&lwe4jIjHb`2kFOT?B&vTqwRwq`lJ<-+Vc9{^Xk}dza%miTd8z48$ zoiz4Rcl=s84E;@44+evts&_|g@h0-}r46-r+3m*BB4n;$OQR>6AHcNLb|6YOJ zn>|Y+|Jv*%$&pm_o_7J}n%1>b^nc!tIj-g(>_t5o67WtL{h7OWLaR39IKfEGq_8L? z=egw&XFHd%d6R_!cB(k@*A57w+0@{uaw>DrT>5?RqFXR6%4u97hdf>Mix4qIkI!0v$Q(j-qD&?pLr3=jYcuaB=u45xhL6)M z#GmwJ&aqdM98Ygd_OW)`H-_J-WDBetJ;>NA8vqd4(dV&~0$urKk zFv?Ubkr2f+~+TMc;9K72guXoH&-z@4`(tT$j}(pWnIF=#t|a!fZ=teSfjC_(WO?+;bgGl0 zg^#M@vlAw{qGDQUQ2R{P_=bf3iu((qX)iQ;FO%cHRruf5=j44yG?$T)0X%*LE5)TWJn9{_{7fs)!4s_ z3Bz6h8D#@pQmZ*9&N+CzUdc?+h~>;;Lx{O?i?kYbSxA%a@a}ZZr5Y|H!}K{~7&C`l zfZ`f&`Otto4y>sb8iRus@kNe6ev@iLi#^qeL!7kTxxvqb>Y86tP)GBzwlHm} z2e+)9WZb`_=udtIk3JGUTOCI4pdjBoifBylrx%AIe_1^DEE64y-PpT*+}#B1*v%Mt zP8~z``R@jX7~YPetiq@VGcTN`(Zt^8mHxHxs|->5XFdf*(E~>R(|DRv2z(6@)a0y? z(|2#aTBor$2d5RFHejHL4lQLPFX^Sbbn;N*qLc@(29NDp>kNBS2R?EvpjTDCVV$e^ zT;*-#xFxN>zxUmBm$96*<~dr+VsLjK{@Z3WhPHG{82fE zJDE)t6h9>H=xW;@r{NMQ)}dJp#2xR7m^gpF zRg6pLe9`bN@R46``uqfw(?TnwwBTE)C2n8)$2z8d2c46KZ{XBXhT7jj_0CG#CIqgs zcNXi6j&+D^o|)TtTS>oP3Z_H(5g*;><`u{66N)*yBDM1m9*Rd?ntnGl9Mz-^YBQIs z{8H4nC)A0LJW;d^dL#YvsJus~g9F1aJ|kyj*VMTA&VoL4!MUD4FYYp9 zlaELTKdAp%GCVY~9OsSKHGt=%T=Vvd$v1uARc8GnbRTP%g*TrvuO`fiddAGa6^tQv z7<(Bl2H&2o;k9(HjhVu&=Nw9VGUy8HIb|nu%kT~{z&~)6^~uZMpg>WTeovLyZprS! zUKpszTS7&$+c4j&t;itvfDJz@AiPBHNBp039j_V&LgQ?}hAj#cBwI)y^!OvfYtWLF#DF-mef;zRT_d^y_4aRn}dSG?y1Y ziBK4>|IY&DQNU>fF@WE=dqc!13W0GBlAn<*Ns&+{ten@A^oUT&Z7&q1S_*=K0aA_> zNU+k+m=F2yrh7N>L}G>yZ%k5olx3^)iu*TnzEZQebDRlWd3+Ltv07_V2-{J>{zONq z-SH%q942+AUagMDTNkp0BT3gJO6QEIy6NxoC($cb4?d$zNXz94#aYLZU3*lDCE<&3 zPhQI}a$%N&*GQ`a2J=Gc#5pkB!zR#H$S)O^{p40pVM0mj-f;at|7-&*-0WRUImPC; z67cW0%HgjoEXw1sKC;M7w93tg7cMBtQY_Q#=Dt}$uJkCUQY`A?+dCqBh0v(3Vc43t z$a@iEOxaaMm5w5y=%ZS)^{!0a|K5+1ILwY5hTEm%3;OP^7<)1%no^m*{VuJ8tOOlv z39GCy@__bHR#kPiD= zDe12>UB{;T0i{E^KT*bZZ;5?!k40LFEOhPR2iBfhV$NUxatA9n&bk--TwY?djS|P2 zf}|9liGh-Q8ffO8nBuqlp8e>3ZV}*k3+*rg)6bi=$inMkxhYD?N(`2I4o@XXuiO8rE6}g!%nM-81$h1N z1A2p22ARre3Eqgk;&-NfDA(i!oj>7&WD%27kgJbdW>EOlb4VSXECViem!XTeRK_^tB9w%5`Dr75S(DDIzU#YZx8emN7b~VwneY?6K%ry>EYH>E_j#Ek%VueJqEJ6~ z>SOr>MxI}tpOmPHMr^V*0#um?q5A@9Zol#T*Fq8&X(xbj4A47G15d4kB?HnmEaQS z8@?Hqgef|d8k{R3s)1J)YDcx|Ep|1`wd%8M{MGB#5rRi4!}QPDkKXU4>dS8o*d*P$ zguGCH_)6}4td5_-*r0n4PMvsmd9Euz`7JqgGZ843Q^tNQsMeWq_Md6UJ4qRKd5wU= zw@n{HffJWxpTI5Kai_JFKaWyRF7W8UtMBhwK}0aA=)HMqxC&zbm8O&)Ewg`j$lW%G zXomD$3vXpPfuTivYJug)JL9b}!OQE}S7c$Ez2@q(6}$!)DbsF$M(t|q2iufW^gM@d zg~cT@uqiHmRlS{(T?4pw2!!rlEh{zPC9d%ZT21pg zVY)}sN~71D_@A)j2?TD{Df3c2qI?Rq)3ea&VRpn|bK@CcT=~Erm=gFwskW#U^u%@e z=`v)0YbVZ@tuFm-2yIAi0aVPn4R|5-A5C&y4O~F0f4ex?ZZtDJwd8vGQeJ?PDB&r$ z;8%w@?RWJ%G!!8nN%Yfd(Buu()wM60nT>M%U9(qwGd4!Ik1;D_1uNUibQ}eP zV%l!pWZ^M1QG8+TXbxE+M(bN*l2=om?Caay@)nskoZw#N|FmR=cOo(11cqmK-(l}H zyFhQ6%oxt$%u@F;J3vxV1IZ+JD0Tc88M=V-lZw|}I&_V(EZs@Or%}7~t;bb~^t*R6 zcurNrA%AxkBn9&$qk)+m` zL_zF6ap{2|z43gTE%UKom12n4ucw%p8;~i$zEkTjL9$_rt+F9!nlcoDdj)DoVo^CUH}_=V|U9--H$>+ z-Qsp|+MI6uFB!xytvRu1hDS~@v3iv#hdZO)u^1p9Qop6)5LpisOw;i#R-iV1jewK# z5*=D;~)Y&%q7O z*PvR7tEZUp?3kr0Oe!<~n%Wq!6?Qdmpp+k3IVLz$z=2-=Xpb(ggXi_f0Q%qNX!#H> z*B=AcyZ41az3tbim!LZLxz}i(N?awRyZ)AFuZ<}D?CfkAqX#k0WfS{Q{qU`o(qA!_sxjvI;br;2 zr5a#nf5C^HZ;Kh7Fi5{bm7;)r%irr+kR5C&`_x#oxXnQVNEQb9eC6k_r>B%|D1=3e zN;`1Bn@l(VOPR|q0UW=;jbrnsKze8@==4B}np&UlN(_n+K>_E*h?+JR#rK0Zx+SQ} zBOuq90r~|T7rLH{3c3HN1zTXRR^$2pJO>tdQzl86sIdDdHZGe2YgsW4^tc*Ktm_B8 ze-4MwoIe>y*xqLhXG405m^4GpZdSR6LhE_QqZ?{AvTbfT>3e54bXO{oO+cc(Aj;D-XQxHhwMJKzF&ll`fpnmf?f zz&CPo)Ic@ZqIM*I?w>QPZNNgJ_9sePQb1Py)_cZ_BH>7`XM&>8b7^c?)8s*y8K5~* zw6pmN1S&fS_N1H;(wGia+f|3I#C{ z_knxHyHvqX#=A1j_~7blD3Bq^L<`ddF=6uv{So(CL5!UHJxTxfip23eUTYCSrA1uwhG4rKQ+^;xm=~?YQeC+U*k=M*k!ASft6T!5x$ml zenYw+>u$KjtkLSuVhTLGmj}-v_D8r*m9^{B^?^ugEI{!l3x$CH+gJHP7p_14S{+8Bg`?( z+w%`$oh^yJoSN!!qC@wgJ_@BwFez#G zYW;hXxv-S#*bPkJO}08>muHKXbts5*`mfh}3yKEODUkdRb^~ZAuAF!ey`GEv1(LBHRpM_)YjBd7Q^KXJ z*UPtS-JaKJaP57%W3I8*q-sJ}1T%b+UaAoTMl1!-W3aF>j;Lt0rb~2e-(=zwCip(MzqBVP`7Z2vk?xwgfJh~aB!Xu%eGqQ17APBOt zPAlvI=cnj?fIc~tO+J19GPke=d4N9GWYp`*-`hR1IFIq*iO>7}q2CD+omH0K@HUR> zwEu0T*i4*A81XrZchul&7Zq1VkL?Ckv}K-3Y2;7MPYCj1v`~1?6y|1+rZ9=#XoBFh z2ZhBcA-^`w>FX|HSqYpTmk{$%j{&uR`OGpqi~enYz|f~3pjexS*M}p<*+z&^`m`w@ zAPmPI>AUQ3R-(k5kGI@?jVKu|ew78j+v$b8sw3`5yn6e$d^wjJ!Ii04lfOx3Dq{vqDfTxZHoXilC+aj-R^_xQx@VV)z&X@->rhLDI4h2=SJucnuZa z=+jHjS3RlGeOuiUgQz}-0bD{oe(~M{8_uNgA)lZb&qp{_@T&2irPTemz=Qkevp!od z#BlImovRZBzSO&OSfS83vnKd~`H>bq7D^sj)H4swm;Vgrsc5g>+W(Z_Wu;?G{AubH zCNbAyr&j!e#nw0Eokop&)&X0`AA;L)xpOm!?`+u*x~g%}!-Py{+vh#e?%D&AAC}12 z&N?5*Os3E2Y;<*EHx{T=(H`cVKh0g#ol2_26w~ir`E{wEJ2OpGf zMp=ECs-(dhqOrd`0FKL)5P{|6S$dkUd%yU;26{8QQVgUx{)E>G9*I&h(4Mm4)I1iL zWaf>1!LRcx^w8O-G+>%&RZ(ZqkKlK+9HM?L&Z_}#meKyMT==RXy)H)de)qz2i}t;m zO_B`wvM=ib>Nh}uzD->hOpRD%`}^H>zO3_-0EQ7zJSpqBCKwhRsoyFKdFugR?1Twl z(i%~W4?jwYt!6ILa3PLFJPBj8zTQOgza=4r!_Xd36rm@|R=P;3;UEw4)T+s^7Byuu(gW|_ z`|M?J+z!fhoWmqsGJKwU@l{}2`oUGfPv2%##T(41_*=W}Kb;pUTU_xy&xM>B-nD>c zY{&m8fwu8w@9$7_f^o}$5+OOUA4e2UBEh^p9^osh+3>Tv-j-lQY(eZbaM4HWOO(W` zxbcX_bzA&B_<5j=Q4JPE_46N@Y|p$mGnvE^mzhczKH^#v9{%?IAP>ad?fDY&@5vbl-Ls1PWRju(aO_?gtdqX5L|c zzl@vXG5w6Bdo_ooiTYie_|hsmltl;8GeMLjE}N!2t0LY}!^J#W0 z-|L^U(cN+Z&3FwH%KYoi%|nn&L@q5%e2L zpJ2<)6e)tPW6r@s2bjDVocXZPGQqiM3bc>v!BCp8k2xnswWg|}5=VfENGUFS7=&JnS>^yy%oW-_HMD=41r4DYoI)%;$q} zs=e$7{_2m_2%APQpBJ|<%2FW-Ja^yYBlagI+nk&fi;osvFE(n-_C)m|xUEKDA&77C zOVuGAF-JaR00KH^6T(o{9k$sy2fXv!rUkBAn4@mws(T+Z7T(6(UB95-nF|3G1^1#K zNZ=r@)$L@#5E7$om=NLXY>OO8c0~EFr_KC9MFtA0a~l`3=HuZgw%G zt5bHv6SGR_0wm4Nn)`iQo@zJusITWoP77Ahy7LPx`dHcOl69>MPXuM65pzAxS1PKz z_6~0SwyN=DQI(kG9DDQSGIy`;;4-HTw~pqgi=m<~W{A5f#3Ts$YGI+Z5gAY=NAh_Rj4AESuQV$_bG@j<`wDjEf z)$~m}Ghsh9f5a@u2MA9>HM*~^Y=kyed8I`>POK}TK8ud zh!L?e{k`$i@uTB>#`LOtj7Aydj=az7m7Yv%C0nnAA5qV37I?Qo+pt6pn}Lb`?Y#2{ z6>8@=P;!#k*p~4TeiV>4=x6e2d6oj=`Y09!Iz^kd;S%?G!zBTFGhBp<;6Y#0e=~;) zyqqhTSqNX@5u=in*Ou{o2+Yw#&#m{o8>ieqs1!8ZTv`udh<{I`* zMJ!{nRR4(k{b0-0+0l>LQ?Te=(WR`j4xk9|#=6t&bZ?t~zA}4yFSM8h*&jz}<;v7L zI7am^=zQq(**?&X)KU6}Vr`NM?@rXMpiecRWx6mk3ds30NXI5o`HzzyCy@A@F>aas zw}Gj@jqP%fh8DIuj_VZ<2Y#7~02M%NxzxtxAlG*975&7Emtp{Qnqj9sYUtZp84A#fD>~on>f)F#C>=@w zyc6pV_}ry(6baMZ3IDCCn2OOa^=j3!?dra9(OzBR;Q6TOUjg=8 z-FOP&3vav?{$K*Z?7yiNNB1A93uzwKFk#4Dk(T=X4=ePR&{~TcF+0$IB^YJ)D$Cg2 zM0=Smsmch6=B1Mx6IFs-veKG5nidH)>(|zP@AW_Gy>QA7sV|k+9}oU+_g+7y9f!Oj z+tjK1LH&Lg{m<8G>CY3*3p~v%KW~?Bnbtc`T5AS#BH&@XW?2w|M@fLX z%z{{~7#DbBW7WfB!p85_o$wN3l)c3Vj2uOdIjFwfnA=$m(wj)_YZ$gUPOi({^O@eVMsF@|>j zRAQAA&)O!o$R$&kRVURGCCZ{kzH@d>!gHJcF3Q>DU42u+%~_e3qnd>=Z>y8ZLXD*9 z`c(6teZX7W)B>+?K8lrjz^lQpssTO%x_5gw8n^D8kmYm^#DdDkdqsmTYT)a3&qDXe zr68S-<2oWM@Gxp!vWqn30x-ipzoe!Z7Q#N z->lUq%4WIRKLG7{F?8Bw7#TgHJ5dr{`hj&HT&Qi z3*mr+J#>kVz(2x-Eij_V(`r_Q#geyi0hYy|2Vf+4abL?`nv<9;25fj@?;4Ik+Lj+R z)m97QEarAc&f)=8!K>uD)ud7kA$i)A0+nW+w$dV_J#JTooEfMLu#cG}v!=_7jyhY2Tqg{_~VS~m}=Oudr zxx;_w1l6H84_SFn^H=wEd*|O7EHr*kJ{t013)kW35IT_4uf!zIJ_#I ze`mMxhCCme)-BSNeerY*@fmp28#!I|L5Yl!XUe!;FIQg9CRQGA>pOE?JWw$_u zzzbvN>k>FZ-Cf?mw~Iu{Xo27hjOFG7GB$tbEvUMB@W;hh7d5-$qsRpk1BY8PjxJwp z@x1NZ=@C5pzw)djK@l<;J2I0yfNbGT#grpyFsCD6ij}wr&Q!yi@gZ#(ZIk@v^_upn zO>OX|{COQF19^$wGYoZT@& z{=al@4>DI*4>eRAVZ+~nzG`kFuZT2)@aAPA#D#`#O+ojT94y!G}y`o+&i=#XP z9qO@1ff-RXlg;tu)cJ8r2Z3$j{3_%%um^(dVFl zT-ER5aGFJ&(yiI@o{$#Gm8X9~4EHg`ZxGJhgl6#Q+0d9G)I>KYeVH)^`6Pv)E3p~f z*Yhlp2X&vl)hTf!;}#c$C7=r`lXFgd99wINI}kzaw30MsJb%W}@amdGM{w02Q|#Qe z_PewU^>ioM>diA~x-LgkZI$cfo={Gy~S z$hp7UBk=koj>y1Io!^tC-2>+;JJ3nIZ)q2Ko=WiSG*J?LOe~-(1%)> z&y%iWi`{jToX_LYY5A?PhaDAk22M5~mQN$f+5?!IW zefc;_-u_OY6Z?h5unG=AzqUN<4jIo##fXC+t`cHwAvavUxfJ=GePNBV!43h}@9&AE z(FJoxm7^rmYvPsEMw(gF232B1eFA63ulfOm3&<4g7vHdwMP>1_?#PMTSc(JI62qR- z8McSAo#*+Q=nVIwX1Z~f#Ju#M;fy~354&fGrpr;WJ$^prm^;&U428TD9U*Vwv>Zr_ zYscZQ!1WByJ>627xCD~2p_wbqiR?n7!H1`o#fepXc>`SApr&$dwf3OL_vY=is#8Vu z&lCtQ10t6r4Sc&ppNGYW)0~CmQm>b!bLhz4x~;0xQ)Z9|&LH8F{7I!Z;&t+>ETT@1 zkjwJ5Im`0AQVs;#hm(wYKD7niW<1y1HFzKRh_BH3$M0{3QE-nd zYBm@Mu9m7(SZNTySg<8MZdQ{KGfV2F>$Wj2I&kPaumOFrx^ITu+E-ZY4`vT=QJv6= zRh0wfpq=qjluU0(4+bzcHO9R4J`{SP3$gH1t95>Pxk1lf`)gXRV5%|$qg0ObH;JnT zjS%8HIx_lImn59I#uq<`lA+CI3skGMB7PhE0e;`BTo!qdIK0E_73_AvJt#G{9k|+X zpg&H1Wpv@vt1@%`;>iuu{in)4i$oac%lR$S&7!YXZ|Hu!tDlX7{_avtK$&ERHYjKA zbUX-E;_kAr(20;OMT97WhMX-(RQ_`>j^eyA4AMgv&+YMAnaSf#(&uR-iy6^OX8)_} z%(oOKiy{ks!W3$xF&o@J*~=^>kU%yb_AuGEaDRBU2$$G5o9}x&RH;>7l)_aO7{8r&HS+Jb7zixRSADsyqO%VOP!8EUK3>Yrq+*J}Bk zg<>a~_b&PF1(JSqFUD5{DF5~yKd(%`tlbn{Rf*Ygpcmfu3=)|*o$0)PK7z17AIbnl z^@yuU9b3ent=06;MCxxlE!$~(@sqpdy%4lJ@_&C0_VsV(c`|ia8%acgexi}i3IndN zJg|yPM!*oB2YimZ`TTWF9QPJa=W!xJzJ`o?W;or?9vf&qh%dzdaTqbq9y1;VU72VR zI+~~b_ia~jM2kk0e_W?Qy@g_#NV+2AD%x!KO&ky==7NU2k%dWD`&Sa>4P%6YIWOGl zXZDqiHaA0io!|DWG$lfY<<5f)>&L?9;J~5=*p*Kj8?BsQ?T-I-a??z0MWGl`>VW9D{o?N?ut1Lv*lDKg>hP3)S%C#C~_e7}t)&m-)f zZxx#fp2gQW!$1{X&J%a61ZJvr-QsUsg7{3g1eRwPO?>8PkQuBuomuRO^qHzQ^Z^}6 z;Lq8>`|k5+s^W}A=mxi3CCG2^wScnTxhs7)on9^;9NUv7?(Cm5IaH1dr^0Zeg3(&~ z;mWp5^lzE~3ZF~>o* zm8&mQG^7)A&;6;5(^>=L(}@8ih0dH`Kv?~P;cw?ndzp(#ow91;AD9dG@Pw98$0t{i zBU=wrNWYnhSie_V6~}^M97c!B13Y8*2{@FJaGIlm&ZbPi_FQyYog3G7%nqIV5jY_w zkjD~Dk?deF{7U`+=%syFF3`w!cBa+hNu;t|?LP2AL zG+o2a%}@-KoDo#?FUZl$&eUD&t+7yhwHh^(nJuZmR5r;T85UbBT#^}97WH+;(3RcC zu@}!bud@JiIU)> zX@cHH6W$GEe;Ut>gBNSI*s6Byq2A0-3yy;rpxtv-209T-_nnH?(^@N?eG?^q)(nRg zCzZM(T9c#OA64*i(AfdF#L$eq;jh?|2;SikpZI;+W*!D!$nl`y!G+6vCGQVuT;Aox zH}@@NVE54~iN*C#3twUSNq?;0)32NiKqBC0zu+Zgf7yLnrY`cllcQf@qiX~N^_v8E ziL0+&s0gHx$eR_?%MEZQxrZ(~_~L|@RI(rsnG*gNdEtE>0-?Kre%xnf=vHYhpP7wDIE?#?QqscK`*y6gTalb^OJb;5E_ zg2>dp*H$hAK#LhU3)4WUaG~*<(@{&kKzpAIg~iK25tnJE9iHuyC&W`Gl5TM{R}l5QbVTO%9aXv)LDW+#owjvSPLgeiDWjCn`AxvFfmzU*>Po!MhT_NfYsIwJG-!*XeG8e1uIatB z5gq|82fM31yOVZSmsyLPi!uqAYHekbOp8bd$Suo1+Fj08iO-tm6J4l0Vt%Qi3!b?B zAbdU#LuT}+2{VS3b*#}7Mh`@Dhca$DQi#Skfk$g6=ZItNU$SR@+zl-2{!{*`B?w9M zCK2d=x&niaT}E0!J2h0t99%cTxRlMME+=;d8xJiUr#dzAFY5f_<`|4uf1Ugf03Jc% zzRmUh=6~z2u6Kw-K9vCHxy}?MF*`f;^`0Brrgg^4KnF-)H(2xJ?Tvo#zx`KF1FV1g z^~vBjmtCV*XBmPR0Og#(*f9l}GmWrnn>nZ*dfQru-UggM^FMq_-tq-6UHd$3TZZ`L zEBB+LQ0#aTVziHCv60+Q7XOAa08cCNJm{$F2T9sgb?Y441!C*iy^aWvH(atNQ|tU* zJPia62U+eEzr4zK!;+6d<{&5!kNW{xOUpL1ig^>y2!!S=;4u0G?HtD>P&J6#D7z+ULnx0_rNY0LrCKN>?Fd-Uo zN&$I2YNrN6VIJ)Y?|t6kJml7<*?;f9^gNw-~1Ql`)aoEXAYq%CFZ@%Ch<>g;Mi=WMK{_N{2w43h!6(s`kQN* zE1kCXd0c<3%=gETWNjys(?VK!jVb2QZ0nQ|SnssG?@#^pQ-}OlH`Nl*)@<>0-}0CH^XJ{?(Sw{50Drg2 zN*jZ$q{r~cgC1N(nDC(C!o+lNmCWL`dBx@FHEOMGKso_Y}cx9Ro6=?eD#X?ps# z%XNw1wf^U2Uf%xf$bLb(_eqg?k0HJxj&df&zN*wYzxEw}^@Z5k;?G!<;o#%YGO|@PgtgPBq?T1fSf?xjYeyRM+ zr)kdF6sT{lQ~AJO_^G91i!Wbwd0j=CK8lesI7%EuWjgH!TjSq4fe-!o&&!XT`2YD2 z|Lii=8k>`%4_3no-J%RYw(W%4I_Wl$*;>zSa4o?;;)_j|JwB4k9J?h_x!n^VBKKY zkG97t>oqo^0>sY~$3HwbU#Am0>zL*hU-u0ebm>UiH^H67`bDxkOQ-Rv&#ddYtut+% zd6l94+ph9+ia!*i#Ec!^!{Yz`{j1Yonka0LMvJzsyv-tHDoY`nT%SJal|JY@kY7xS ziIE23pPS?T5#+Nj<66h!*J-sjZHm3U9LGPFi0OjdeXmLy+8l_y4L>8Z53Mu z|Gm4FT*g=fd%an4wFgeSQ$HpgCo}x(Kl0D~hsK@iS{Z|3pjUN}k~k;vS=(OMkB>CD zBds)T!dkZ4Lpz`9#P^A}joev|w95Q?@Gl)R zM;x5a-e_6kE^j<#o^6VMnH`Z4|4r_8C%p6dsXy~>d6u@%`CtEk9ti&rT7Dh=mz&~f zwCe!|LPada%lSp>z%544?eLf%X&46iUdKi{vjpUI;}JE8+#z2?aQ37QOqVIKey+*q z`z?94PV2++3lg@_VhtFjlgu{HZdiw}I3|^Yl+B7a`BZub*$9fE5Fc5AI@fF;Jq>U^ z`je9{ta%R7$r$7)WvUIE^(z;&N3eHQTFcKns875|OL1+1wf{#%Vp{7RYS zRi(3glK5{k2eYZ^xSPLkuNVIY_Khp=87&}(Q^V;0X3utm;tc<;%T(59V0%#f<5l~k z!2e?1;q91?HZgK~OfiDM>v7KeuOl}n1Lue~({cKn^*Zv7n0(a;%)N40(7(Cs&vhOE z{jLYCff|F$;}CyG16s&xMP9VgYLlXyjCR~g5CZ32AGEQA{jK$ObcN~9V;kC$>a(nnAB)Wd;_51sp&z>Xbe;WRj6v6N}|klo{I~N$MD& z&L?=-kRPLNI5q8Hoj{!`4#LKVC__v!%X($uSH8nzMY3My1Tz-w%m%g`eAl(E_RzG3 zb|L-4F{{8f=WVV-O6>B+GqW{NGvGibNVGtERI4T$5hHzT9AKixcVNpX(~$k4ZVby) z4qv|=Y~g&>V81t?XL7swS-1GlKuCs~Qv6~DfFVyV&9j_i{q-%uBKc+= z9e}Q&c-?#wC!cHefR#+d%8$CpL~J_mJ7z+6hRXRsyw-Y=b0!^(jQ|pIBG-##6;~xI z$m-p!k*JCg9(OnKxZlkz6FQU1DPKQ!yZPO~7#Fm}0RdXChApmAD#NWFX?t6BM;p!R zFu**qE6C#9*WU>}>#6iu=^Bsbk4&rbuNbm|J$$>`AOjKry#o`^2>y?!*t){lzXASn zy8UUzf4fRY{9nQ+Rv!ueO<{nbEU2Ws4}$;8tK*IF|4eRA2>*u|G@v911Q~-=>pmq8 zNG?Y{rzo6x-4!G#AgT*8&(cGGPIPk`2avxeq#5m5&Zz55b$~P2Sw^{* z*&H#MTx@}SqvpOgO!A9xcRKhOr~LD;Tp$f{*dR}_1Jy|zP~C#%wQ4eWR`B2MByM-L z-%))a{6E*<68ALYzjcmB?+B&cDSv$Q_%CrZtACar>@P3PiI2r3ueJ^Ne-^hVi2s8# zL}EBdqq`V~fW)g}mS7P80L94?r?$!sIT&0~#zoG{uL4}LDkCMHp1=jbxdPVU34#@| zHTZ~FPw2T0SQPc&mjPK2LA;M&%pMi@qgh|e41vsY7ra> zB%w8N=OYjaY@X}qY0E?iu0LW@FcG3h2~)vP+b|LN-I$Hu}R-lkYR2mU#Liir{Qzs4 zGRgE9bQAd)-v%**`j=kE5`(z5D$r(F^OW0*|F*W=KCI#$Hsjw&{&VQ+@5;%!cPbxO_YF0W;kUwND zWy0X#`yLD|$GTdv$JoIt%h@aW6~b&?3+)#EQvrJm@%rG0|9koMKlbxf*7n=;-U@Uy zBucxi3df0YEAo!9An{0{x5p%h@s%-Y#EufV^BeqPL!!kvNRU|i@0d3lg*~D9%$95` z3;GXcr6 z?I^F^zj~_h-`3IF47{(OKT7-$WtQ!IiRm2v+n}nw;`N|1M%Oj?Z|}1B$3AcV;;7rU zEpP99!uVgmwgxZXN9NI14T6t@XdF@GRMHa26;*x)+wt&_&U>KYRS|Nl5rkLk9z8Y4 zD!621zuv(MxqbHV`f6nKqzAv~1}%1sGGV#~bpss54qj)PywLS(a~q0HpAk*_ zK)hRsC?3N@jJRsEc3P4y_zpwCr?Q#;8aUL(ggZv;e4x&4!+(3$yuG8G^8wR(J@{{5 zUu&N-rI*dvlgEF1GhEu`(cxcN#5i%d2LEmEh35{7KhU-hg8%>Iul!y47vJ?OC(xfcJek7|4_?x;L1_`f_JpBnr(yR^0PyZuEzUVp?t*iAnmOxzwD@X{I}m`*EYr8 z>~_`tAeMu~cIdunbq+D&9`WBk8T_Uf@5*oZ)t@7`dHbF}^S9;C{LsgqJpMoDw|&4y zPRJhE#OO^0Z|G1FE{AC|1yY8I6i6>LJkr*KY(JH4q>+qFX1btU)K(|-RCsh+1XFNDs`B<3-5~VlgNCA$N zWMZk4*Kex{-T#o;-ze)D3oM&>O|NNz|xwML8(MJ5{0x4hC& zk!#w_?6FRJW>Oyg%9MtXy@m?z281u9n1; zLtX{mWSil##=~0g*}{K&X-s=Ay885gdgZ?O6#I_m7t&7x7$C{Z#;DQ z-tq-6$$S6zZ$DkF{}OrouY5YM*xNEedot*QKk~Ek;lK9tz4Yn8|7pto{V@l!I@%^v zjS7_4a-VL(Q*nqxkPuivR0u1Z6jP{YHIgZS3`P-wVGGL|r)U^uTk6qtdFGS#d=jn+h358Z zI^5C)3`fmTt*I^(IAtX5V>Nr@lC&%|M^+@J3TqX@?;3rtG|P=`7_+2{@ctIrz# z+aql+y>ORet$ozwl}}&Fj(>fu_;2?=EHfq$Kd*e|vai3RPCNcz5B@Iz5%HX}Iw8R% zj6&HE_@uu#GGlp{HomrIq;tr>uyH4G6_+XO1PKAcfaMSfK372i07@j2p9%;hq07Rf zmkqv?Y!mg00njIkMwB9h@oyccXyJ8$NJH7{+Q*Fzpl1H$;lC7qpz#nn)Edt z2Rayo)a?PXD7hb#pkGBARL<7aIJTRW55K^`Xx{|y zhOPwTu^nL=I^kE#R7;0Z{W+3ILN+;3_T6n&HWFlmrybA7|9}ONF-WcA65Hs|#1K!D zHkJ3Ygn$41sN;Y67|V`-((7^He;r)qx%TF}9slQfJq-TW*Tq7nRoMz|OFDnODMIq{-;JgDyBJ$RFo6+dr-wlpBVFSu zl_>AW!P{lEB7?Uxp`p1!U7C)eqzF?yey$rP9FS4b#Y_4=^%HXBmtfMa@g}Z`b1iv? z9A+ixuwK$t<@8K1^-x&z5sfzMGoyLDv>R+=QJBZVdRgA2WMWh|e2j6GqOpFVxDMy| zY68l^4ta%QV@#A-4IdC%(+*WWj4iGDOQ{#UkfxDOX_^=K#WXVFuJREfEGd?2ZNu_E z%lKbAj+J)%O4*KoiF1z||I3GEWPM-2{rgw4TwXA z0NyO(chJJEJIXhLNs2ivgN2ETydFnRJ72?TMP^~UhaEJN2O9$fD!Z0=_u_+{TgSw< zHWVrq8<{hvXQlf3*~R~|etU6{%cJf1e}ed5UN&=Fug;VHN^8geqrv|nMoY zAu5(2v@W2jU@jT-isMP3qNb7~J>L!@rKHm_5t)&Do!ercW2Mi*NuSS(uUjTaQv|`L%LxiZ_|t)_unzU}Jlm$@lEzzkSeTc{AMcd^~S$JN~bi z=_dGZb=nfhef+@8j{iZl*MtAVB5;5K3}_MwfJPFCej4~$I>Zx)%0>b}9#-)iLZ+Zb zbxk1HtSX6)B1;o}0Ts0H9{39Y67AG?2@J)CT6EQZGOlD$##ABO;jxIwl-@)akS|8+ z8&Tu0zT|Zw8+8!YXGo$|58`EDtf~hzF8Ej}jf&*e`?(@|W_ zFdOuMf!T1?mP-#Z`Y4zIiAg>89aRVUgLaA$4cqRsj{o+kT6>wyRxIuKe|-3V?X>K% z+?YsuK;z<{EuV#MJN^m(7yg}EueaEK34k0!C=y>9Yay7U=%eJwD(DRXyAq1G5-HYR zjo{_A*|F? z@Gg!t*Cw645?D`K|0qI-UZ}CqupHS?%*L;#p)uMmsVG>6j{zviKgd*>kWPZ1I~bHJ z_nY|+f^>zGqZFM&R;*h$lOc#ePgGO8M7~Oefv~nqyQj^o4w?ju7&=@&fvzS2FcK=8 zQ`6~46X}giD6ZmrO+D_cGAMKug4~bIW;Q}_TA{^04Bi4s$|OdOAz)u2h7=vuf7u{T8I3lE8SBD;QIr(|yV72FG=o)yg35nYSme-ol5os2NuX`! zatqlmfNTN)!bAXoot!h24H#fhqoYxdWq^lkf<=aqOGep1yU<0*=cOHs$A0PmSVGI6;5!Xp90Nzdtr*wnaY) zxD3+4KeOqM|NPzY4|2&U8)z50?D&6r@PAkh3e1#=L(4u2TFZQYF%XuE?5V8ZN*5AJ(M!UP#fRcPKhk+jUJ`td5r-~^p<|%);VDv!_)Pd@+=(WCO59fE z&e)|Z4oh8B*bW)zLNPAJbg?6n9sfK2cl`GTS@*W$|H7$hFRoK|t8sCCCYqNpkUsM1 z(*b3@3nSc$GjLq)yxbVcTL@%sJSy?v2YxuB&T)I571YgeO2xgDy0RUttPdq;@fLW> zLgNxa8aA>L;31nSnq5qeg`)=r$m;B2ZEZO|TCP)tja@7YOSPaIV&dQdDx#;HnAZ}w$h@e`qhGBT)Nn1A!XP=GIsINnv7Y@fII$o{O|Zr8=_>l3;zc(2t&*1iWv2F z!U=x=`z?Q_+WhlTW1nsl6l2RabO8Tv~P=T1!@9%a#_+q zz%TSoAU0FZ_%)G`IP8hOGqADi!10x|EHJjp?(Ic({O|bR@&D}L|01Sbpq|eQDd@ix zcdz19NSzMBU#}B%1woOOV+2;}3dj~k&YKjIanqufW!qI5;dY53VvRC|t~xpzvKp-; zdfWO@3e<Havx30sCr~!zOdx2?DS^15v~ZHZWS}e`%P9R0%n}Cz z@^r%WZCd053=rUf{!}ajIn6jeO{}c(lDQ!fTKJe`QPjgycQk-ucp24KNqZUWQ@jHf ztk^`=26@OX3fOR3nsTjm(wsiAe9FB)HS(0>|3Co_LaoXeD+{BaLQ5E525p3t6cnT;19wgs-6qB^ z!BN-~UrA?^!*z8K62QRFs+WyS4BD!c^I#)#2nd)mS;V#&vlGLFWKss#q}Ot+PVdNm zG6Q->{`K?zA9h+8XVVRQjlQBlHMWmU>N4m%U+}%5K*>8aV z_Vu$DU${8^9t!;({}dy(wa0lPSnzc&nfw@T|~DnPOV!*kv&*^}#m6 zQ&C!M5kY7yo0@?=puDYH9noGzuL>S1HVT{a)DKRR2KddVy9_{Erq+P2SL_R9#N?ar@%*pRX+oKB}OT$fSSQif@(2f8bHYL7+|1c$haO*f-(~vhPxAT z)>7f-pA2dmO;J1=$egq=_$6*&LOrBAABzjf&{iS7>qOTIkUS)pQ3^}EO4863*`s2- zu1e;U{=tW^1%sYq#*A%7ZtLfCm6`jIw4ITFT6wR&9TC{J(bpYN6GxUS4|s;xu)Bkn8Z@uF79{ z?#`(*zgYG!``5NW>>dBBay$NQ^7jzQ!!bc zjY&x1bah}2V)_NTGzFz8KJk_HjYrE%{XKC=wMl|5Y-aU5I~o;PF|{wjzK7 z@u|Kxl%ZVJz90_ANq6S~@@w@gh8D(7#Xy}}9E9qzWiD5DsM|L~%UEKa+kyNfU}J-L zGE|@t2^siYh>ZGl<5;JSfCN>bNeK{{;0@@8{3wJ7*?7REJQ)7lm5<$9ul8EJzv-0x zKJ)68y!zS|h?@9sF|kguXK*ZEKHKrX;~$y40r+o&&vupF&a?q&n;H7_XRg*a)`fg2 zLaXz9s}K0Ue|5Yy_&kQ572k!)7)G!E2(4QR8u)TAJG-*O^4vt_CvNRHpT6F35tZ~*BgZ`0;J|v$3cuJ-mb@F<^y2Km#6lFD>ddzkipGq=0V-uKGt_cF;F80B% zRp$r5|I#5t{kFd(%hQy4`&4P`*W3Q{_r|r$NRY28CtGp*A>O2Q@)pe+oZ z5Y^al0ZLzh_xcJiVemC4n$DM4A9B~=<2qdh;ITHSBB#+Zcm#ceM)p0;sZ4ev6$IUN zm&?b^z-aS2Zu@oGO$L7oo_A5uW>DUiMsRJ=>8;bKDpmoxAwPmDrm#V!Ru&&-=#7mt0{+2e|Wp~P2@EIJmV1Agf>4n;Q_ z`D@xNV;{mwyEZj8+;7W1@CG~uL)7^i{NF!a`7E7v#s4WowyT`9u?+lPyF3!F9sgWD zZae;cdl>w;tMrT0;IiVs(fX{H?w-Z}H4RLLaD&Jf_>mDcxv?YrSgxqK4YvV4JL?{mxd(Tb*tl z@e0*jlZ%l}qgu@(Qhn%gMFA!CRTC;1>>2ah_CxKTzGGvMnN{90 z)N`E&=vZGDobiN&rNT~DL3^ zJMqxW-0P*{^|C7SuX2Tj z3@yp)KS4v|!F>XmVNkl9!9xVTUcmvRBDj{j((AvSFs)#KAJPV?y8)ZvnDGDF{Y&6?GyJ!=%-uHpU%10e*cf5l*v<8mB%%Z;?UWSSdK<6j zuZ#>KrLr_WXJg%Q>c|6eU0a*QO`SGOd0F54$o`pQjE>bfe$q5H*abY% z6Ew)`{s=!<=pYyanu#ZD*w>z45B^){*6@s-+;#YOx~T7#4N#;V|2zI)AO7`$@sIUy z3;qu#5Y!6qm4e{K;PSvRL4|cNYz{sk2n0vCnnL=o)zOL9%nvZcc{*E?$)uhTAD2y_ zEzmM-pGNlf9H7Y!6P9Mv*v}D_P<@ou=vaY=P;!lcH<^Oi%cUd@Eq}xbgQ0CltY^y< z9m>pCJ{ETL>8a||kkgDn)1ei%q{Ie%(x$0w=#o=EA(IDr@FQJ~?b`o|M^x}6#D8{J zDSP!J#uVB%053MRJ=qEPI1ht**!fUr>46Q-Og|-0OhONX|MnjC_H4OEmXRCbzr9?h zJs0dR8`<%{D&|~S<08Y zA_W)-4$^YOq8NBb!K(>_`gHf6bR*KJRD;$+M>+6w7e|9x9(t*A1H~&6Ch3W57%}(C zinM#foQddqppwGlFBBzZ0$%Kzsc$eDBRvNanW?I8;2#s7>YIrs>0|%(fRzbd1-T{o z_t3PQ;uQ!~DLSJaah3;Pip2}uec~Iz25W^DgklQE zvdwxL`4#7gkxY?PVKf*#CH0U9N(mWhr0l(rd7$J~YP@aBa(XnUpHgSHmkQfNt1bq5 zfZl0W6GV>78Sm4UI%4Oz@tE+)3l#uebLe0;U5)&>*bJ9LWlIlptg7B;A7-xAjgB?za_|);Mz010{RQq_p)p7 z-x5!IWT*`ccKpkZ|3{4f<+a2wUdSDl*Ny)cqi=d~ebeF7j{ggXfX9fMame}O1eWUv zsf{CwgA_rR(T9^workwG6PQ#8ev(Jgih>Dj;?C7*V<3-!oRI~DralZLbPyzK5pX!F zKG;T57lYAT5SAX0E+x=Pkv(Cd9-1;3*wadL1Gz}++NnQM8=^Ry*KDb|shNG3e(y!#c^Dz|Rhlw&A; zL(|PBgPmzAyzhj!E&WN)P#bi3y`mZFgvnMR*0jWiMpC#5{@cr9?%g}|H@=xXD*U&P zyxhGwU9ErS5J#W;-2rGJUH>ujat0sLlCTGz$jUt!kxpR6AU~l2&TI;G zENtT-=6VP^E58WE2mr7(>tiKvI{%bIC-w^&4HJ3isZ}eZ#HQ5FV%iZw8Tx~a_#S8q zcAr->XczHf7_)0z@|)zBQsW;s8Ws z$N!H12f}~*NKkwG+r^#38;1Wj0DY-_k~OD?vdvR~fBoDq`+r6nu%Z|aXM9)6sn|yX zwVtwK%_X5k&5ZLT-`vEdc+snPIW8>0dvvxixv9O6d6sEQQ>7=UD*i5 zAu{T%lXE(@XOM2xpZQcunQZuRWI42-6weFE=#qZeh^w2#-mFc4PB-FsiwB4{Ujy`!m*G+1|Fz32dG*z+C5P?!FT8gAkAma%t)x;+fHOe)bv*JD-QNsdt0xyyDPEQBkz<_pW>eoba`eki*l=JOJS4kcl zoOeCVKo@~v>2=f}HDRQ>O>!-<)97#5I@J7P=;vM()6gK3RvQ(4#o&rYg#17twjmO5 z%e!EEedA1}5M$GU#A^jH=#Kw9 z-0{E4&-lNihvoeS?UTIP1#y4HKIq>*%yKv!rue_SwDp556c-_I+EH2Orv?8PBG`Gu z9KI%LHE7kJ=ZjH6?udpq3T+asT82%^UUJk>zEuTfAj*^yl4V{19|gC*mO4(!fknZ2 z@w}~~Y%om)l6NDOrw;jREs^-Wn`IA6rX}yK(8Bok!j!G!vFnCCx~F1;s3bY z{r*{Z4^#ZNH_Ekv!KzcaqA55TAhF|LcKl1Uo#FooU+!c~L5!jewS8|ufv1E$+BXXD z4Ty<B9d3gF<`52of+}#+I*9s?^1L*U>PryCK(DO{L>cm`^b4hN)NiSYJa} ziRW4=rurd{_p!WFNh4{>pI8QMkaQ&yT#b>G)v8!<^;N2Bp7*vHQbzuyW8@DZcnBY6wE$1d{iYDs zh0tO)H-v(T+~*lg>kiq|F+pvF{NQB-5$He;=y&uZ*;d_c;Fop(fO?`n)mNCDUnl<>F{z1PT|MJA}-#Tq?fcS6Yg8TS7<6DUT!xT(Xj3}TOIiLd=8TtEGMHEwoyZx9%5?g0mvvxI3i*xiW0+$AjIa$Zh4o1QL9&@6%tB;Nzr~ysHma zIZXTui@siLr4I2zISVkQ0}~Uts$3iVK}&V?DcN08QI&~b7#CQss)eV@qs0G`42$jC zrz?*@&%vp3?Ce#U1 zJsa9R2>vhc9|sy&?MqiCJN|*jY}@hQ&pjsmUtV5G#ebVgy6yNs&`>ZyP(bI=fy{h( zXqIgZ9#I*R1-HXT7-)?H1Ihe`35&XEWC8j~TS2bb6lX|V8-;U*j87gg5xs8{)quyXDFU>MnNy@A#J;{{nJ8dHgTa z?qI9dH(p)EXVh*j{_ni>%fIdq`xF7XDqN;u^yzt}X#$lD#Lffi@hpNCI_I`)PE$R- zX=T~gt8I2oJ(brl^8<)F6M%($RCTXuTYVSr=l58Ue$_(LQ=_8ptKmvUnb$e7Zn7y< zhGk!ndC~@_@g&>EuNC@3dx`$E);TBl@<~g%eyj9FM;p5iGscL;M~VOTHm$oC`C8al zU%iBSJN_T0&URzj@qdGD3;*r)!|mDiR@QEW!<*r5EB@ChcNhS9W#8-?!Etn^P!{K@ zp3tB_Xj-sfcu6Y-C4cTRbZK-@nXHPPPQOG3fMby;w|4+F%1k0IMy z$ByfC$(Sf4bWpHB`-!^=wFU}iIw2=x&=?{7SpcpOAV?+@3dY5_mJS2MSg%9ITN}VqNj8Gl9LRix)$4V_HJM0h8#Y15M5Lt- zl&B=!0LJe!>byOUQr)IzkZk)&w{<3M4SOPK91Rce<_^Ko!ID0c+4H&z@@gguGokfQ zY!NRcLY>e~5E}-z!-NuM(>TX?$m4ivD1SL2q(`5Ma(tLo`PMbHcw|(ej$ph?#z$v) zTx9WQO3Smxp!;eg_P6Wg(|y*Ho+;}i9W#Y==pq6khvyA;=w&>Q7XQbq)8DBVyRs{h z9sfK2pJM#Cm(e)SX953rUi{_n`onU~1~{W&;*^l*CG4P6TG#QCftLub%iA?=<5%$KuhUaymfi*(A;Hx1Zm&@6{)7H5e{+pcb{RwoJ|1HMs2@Cv%9b*8B~q?qp0hFc13+`l-~md z*Ft0ZDcT#AH$BphFhVXe>PCC%^~qLNbkM%9OpAPQobZ#>7kwS-m{H82e^C}IycF6G zphBJc!7L#gmSLgw7GBqk!wTq=%R|oeEyh0SRyI$}7~7-^#8st*aU;cU9$Sv3ik(Vt zz@*#^|Lu9+&-6!tcKnO%_aRj$uQTm8Y>vfRw<)a~3w2uo)#oj%cQ7GX^q%UTR_xkEh-lSk{TJ z8VTAX{aOROvi8_RzeYO@d@mQLFA>ifQz!%1$B{ewDrp$&0J4+3nl@p-nB)y)8Pg$? zG4GTSiEvzmZNONF?KcAd`E`{Y|L&k2|4$$OkH=>V{}(waql1B@3z&8XD=q+c3&OAE zssZB0Z(d0SG?D|4_7NzWZF|kUn>E)O1SD7m?bi+9U>xPTLe4LKp}xNmA9*EEF?bAQ z)owEtjP3~X{woARBYI&2NEiq3MaGN!%>@!EzjPzka>ys% zAiBh^8?;sSPqybtFZXJE)7NH`VkDrDC|o2s37q;`L^!#{<3WXzD@Jqp2}E$)i5dAb ze5lnCDtYAqC>FT3HME7Wox6Mw31r9rj(^$l-^GyIj{ieY zlyw~e0nZAlSL>q&rI9EK%SM(VaFVA0{Vc;{l{91wh%B9zGQls_6-;6V+PWTvo){!Q zr2uf?R(dY$a3#>198IVuBs?)sJ`%LuVN0D!WG8lu=R2JkaifeT@onL-jlflo%Ol3W zZV%v(iE~I);`}DJl06saQ;p#*?d;dtHgzvF+$|82+rf#9waSpyhg z;4YzytH{YlgQ(lzxSaGi=7;6pUR7iqRZ2|dt^1lXitv?*GXUqPPTZp|gOQA`=a&4M z*(dGbwqopqpA7U(r{$7GX(#D$K4*jNHT;QzgtYqIv=jT&z9pz4p;+CwiHyY#l2b_G z1nM-#*udaVxj4zpViuoE4*r&Gm$DLTTJ(>Ys&Q-c{Y;t?Q+uc%%HW%VBEd^t2O0b| z`Y_uL`YGE)Af6XIYqVq=Np08bQ@tJkRsJ3SRYuwI|CsTguYLvKM$l^39!8&G^&=?e z>pg5ZPbL8}FSeQ_SU}C;#}M#R&Wo#ny0{KjZjs;%zNdC$xEtEaEghPOrK@5msycmN@iVpA_ z$;_B%O(@CV$Uau(lJp1GtMaY>0$)K#5bae4o1{#o(D4%fwJw5DY=&H#SAbCm5(IX1 zy>W?+MVMK$N`ok1iO zB2{^5XfsH;;~#WRU6;B${$wi0`MwZ}A3u1vdS8awzGs2W}sCY>00qd$s{?bIPI50vFlxVg=WzEIC~ z9m;q7%Z`89@qdf)e<5Yl@*9GwY>xW8!2yMpNgWZPHwH-`5s8m5q<>LnY@Q>so^(!? zj)g{5&e-#FnYunI=eLlVhE2UZY+^)nU}mTf?1NJzT?1oWUAK-J+je6ojcv2BZ99!^ zr*S54)7WWj+qTo#*36xL_ZQ5Zv)4XL55|B}P7D$SVk@veHHL?WaO#|lQSRquL|wP3!-xr>LgDpJ6H>+G{^-QSbPUM@fNc`-!=^E^}{6 z&=(QJxCLAZH^`CY`&j=AehTJ8omw_J#9mjd!Yp`$RAh&ek>pW-ffFr;dU2a{qOfQO z-M;vc_9Ky1!Mi_u`ZFzmn1m54+fLuvLJeWfudwj(gZ!z#Aj>m1kmV5gpPnO)HVwA_5uqa-JyI zTiDFPL-*%=v<-B18tr)Jw2oemg>EIEveGaTS1}Z91R+nU8v7`(f!2~k_GfY+J~Fb@rBB+Rn=fxqyuL&B;*br! zmD*8bx2vBvNHy5@AU3B`XHvZ_4w1vGFlIAN9%o`W**kvu!GX06Hq2=_0C<8;gc4dk`-e2g9&83SAs;ZIW1+A_ z23W8Z*A$$RiQ*D%hxY&VgvrTTAyIW0_DQJ6n~0J*lJSfFQV5;h?lYm!gVe3;R0o}r z?eBR>6MU?~Ty%XESfqE`H3WpuuuzTDcs^X>K*JZwoeUtuG{g{b{UZ06RsZ@By;>)` zzQCBM>?+IlC#*L@O3*fT=@XtOT`cdbkjk&&dNZtT0pO4Kf92uPG8jPMAFeFeA|;gE zhcNELGUJ??ozS>oqbv6%osX&#Csc6!)KW_4n9s;jah=+B4V_#?|1LATGYkWhXUXZd znLK&z2#F*_mKs0$YEk1azk&UNTc;)5^M54+%|58kHmMl#qkonwZ#m;*XBwZq#Ae(h zr%DHqJvbSvICxc(H6jHmU$|T3C*&K_Y~Sm*;Rej+X8E!FD3+`Ct=YaR4dwJ%NTJo)GRG z|9rTJH+P(uy0Y7uT;x<^BeWCMigmh$)iVdyg!E>iOsd*J1d}&Q=cOBnfOHH^E63 z49dwE1b%J?(#x5RgkmO~bE?X0gVTrw=F`=&|70RU{o8=f7ucS911A7Qj9#Dl8Y`28 za0)vHY;6&;*PzbEKf7u0w-KCnBUUZGC~DyIqsmwE{&^;++QOG=B0@|0vlPvd{j8c=+ibA<@ z@{F3ycrhZ>90!R;F|^GX)QNLr)b`d)zeTfCbW7v2>0#kKs2s=1^3Z$QY&kPeI8)^A za$Uk4SZc659;JFBz(`tEa zDaIM4LoUo#atvwoIq_8J!44Pa2{{nlt(f@!`F%+x<2od@T9`ixUg%oHwAdiBF>D6T z!wg2_ddqvMrfsQ(pDTOYAgpB!e+zCEq&~dqYb43P*F4_&|}wCe%3& zSE2$7b+W$N{abGt(>zIo5pz4RAI9IQ{&Np4bt^Q(iXc}q$3BKysB=tB>~(7oqY{D@ zbM*u=gr#WovM4|vzlo8f`efpqmcAi!(XPc$?OK@WZ zUf=9;DdZ|z;LryKOKQ5w4#G5-1{?&Z#qf$D?VBmEWD}c*sHMyy5%3nwofF8EoDP&< zS~TXWHi@^ivIy7MwJR(RO+y`;rnVp~qb zgeE3xG(6&edRjieSxO52u=?5;>c}>#Cgc8~b2o6$@EUjbBRJI@B^2c%dkhgYPe+Hf z{T-YC8F3v1^YL|w)x&{8;jaRIv4&X~Wn*!h`YF~|5>+pZ_>c6Z{Va3pBea4Xugcqy zyH_}TQNg!e=+gw8e%@V;j6Ss3BC&H% zoy_`G!6IanemZS)WD8JcCw!dZ9W=_}eH5gIch7-OjkQq6<-IPaECKAoT^>0$xuY+r z!$Hs_*D0{c&!VT4t1w}jto=16`?j3vl|S#W!_qu+1*_X!o(>zMG(9$%B1K`+qnqI1iBBg_u@q)9baWn9+H1mOdFPk!k#38xkp$%W>A%PP>+}w7X_|wSkBu~ zK~6eO(IqjV=}!H($Qn~>5?nwG2bp+;AUFR`7>i2^g!=BmxjTkxe;+evJB>uLZN|<- z$EG}LLe42GErLg1z%T41Y5(snU+~Rf{fnF0i^;Pt==XBt$KVb{v!M-8=VQ#&kU4y+ zsK^=@>Tq<=8uhHA$AXRl zJyfjuHpI2_Z2!V=N9)5l9~PNaFo- z3IKhh#F8$m@!wu*Q>U7dt0|?!sfB%-;|k-pul*qgu<;V5uuL-wIvVR$waYzD``I#l zuVCA8aAT3)`ASP!*s?$gkX%Er!_T0Ov%f&qe2Q;_#bWz?3R9YslXo@psI1T_pe+m1 zLf@_G%tesRtjL8cdZ-i=xo+-*xYEnA*d91#9gjSD-mZBUXB*QnOdda5%&n<++$xwY zKl!da3u(S)CmE(3>!HeuAUXd=lf~BRSHd7ie~4r7L-Ex==>zdrjvci=y57~(Gac{B zFt0{F@ov17HKO)+%k7R))LVZXUMXYF06hZf%1q$ZKM(B|B2e|kU3kngy>XsZf$-z$ z`*~F`7#lDUvvxmY%%uG~g{J)}^a-{PgrjiL#UDl?EufNXx07);r#Jm9Hm#2@!a3a` zOr(JorF&PC`EN8e0h=mh5<4D!+@I;ZG3M7Mlox^R>^kP!ujUmwT_chz1hqSPai5$7 z%rCM@4aXT8hStzT5?G}ZJiWh3#3yg0>Tymm0(l8<)jn__QaAWUOM}pKIxb18>#TO# zFULqnJa={okKRvn!X*qm#ndNOt$)aR$QqVX4W@3WWel8+3b#OV`YJ>Hz+H$_$fG;? z`$Wa*BWOEIaG*&+vwZOWT>Eozmj8sO%jG$J^vWO(^y67s`>2}pdh7yd&zsc#Z}|lW z*oX#AypLEub7^IOWqE1@y|7?Hv@%4R_yb0QK#mx`W{)1Ci?|xD! zowX5lMhF<-R}GApgm-jQCM&%+ND5n*)n49Zhxn+-Jny(7rmd*!{HCU__6@-%WkY71 z<4?r&!#+9fkI7~~BMt4vp^rSiJ<8J2vL8D$tniN-OHq{RWN0})y6@i(`1goOFvFck zQXc}wC)h>3Jl{26Ica4P2yC2*$DLdrpdY?@W<;E1}$27Z{#BI&%H@ zeFy?ec)+%yz-K_d9U7pk8%*~ zZl4JERnDl3Mohd3e47RGtf~Pu#W|3oP9HWTdN&6?p&{BFXJ1zNmcDAfY6Lcnr!+X6 z#J0bg@QHIZnRKpZC_{PUyUl0ROVaR9%D|K45N85L&eE*+e)!-xIu@BhMI6$zpFI_L zZ#Wwv__oeit>Zc^=Ek)om)534`$XLe(gII$?6Gwwv0WC1nu&15tP9yH! z>r3GCm6)LN`r=!zQM*pfZb}eQa$|87n06h9%msbbwtS>s?ptT?*7RsVKH&dwkP8Uo zR^~wvbxa7>NhE2slo03}IU~ao{EhDv;DuA=&t4;;e0!zqS53Ynp#8YU;HP$X0WdOq z{+jp-9k?;MgyaZYIg)BTw2@+|YgP5J%YFbtR}>0)JSZXpJ8D2CmZ6Ea_PD@Mq(Yj} z$4lc{WFwEelgz7bKaQxwRWqOGdR02cK~Nmui#}x(V99|9c4n8=-dvV{a3PVKgZxRaF7LaV zC<-`|hw>`0(Ws9$*5fPty?-zG0PSK&tL;KjOzsv7NHwyYu?fqN^x3S(}7 zo^yh&%WhB7;?uiZ?+3Y;GGg{HfK^;|88>gq-v5{8S+n2+#b1K1woTixgjYkd9PObG zX(K>Ln6g#}+-MH5p4&_^I!<)PA_%!t(xX~g{mYp00dSHy9K8_dk#txKN9M6}3`Lfd zH=fB^z%)l+m@~1Ex`c|dWF7vXmY(~czX~?q=q%sRbSlKj5y3^uoG-nJhq=Zt!+o65 zjtsNHq&_Me01Ba$RsK>KwY4hy{zROfu1Zbd7fW~+8;MWlgb`C?UFquE^kMaglMv;y zzoU?YSbR|CW*glNZV>oIr#9^EH?|8`mo?jPeL=Z)&!Q3KZ9 z_HfXEgu#CzkRP@7C)_*@6t2`v99|dP|Ao0_PerhFjG7~=hwPs98I#l$cO`9l#5>Lx z!KzmVq5pQ}ZHJ(o~4;=>@2eLSlGx+Esvu3x0I9AZM*r8 zWE%JGLHnGI!KxK0k1KP?t>XrVuYLLsB6lo$e10)#P7F}4WWVF0m~YjZSD(mlQaLBn zY@|6FwkJ}GYdI3coO=OwB)&;5#RIu!2&-=of|iyB`;ss~6X`#cn}Rdqq3K~N#bSz_ z;Q1Y}io~iUQ*paJI)DCmFI0zZ;WE1%pML3YVJ^M~>meF=d2Qp16|cD_PJ1k{GBAtV zFD5v;d09N$Eez*iMUXWOedyZmB?yHADWWNW)buqCySvhbPgh`_>d}pW9Tzz|cY*1S z?uFTb@nT2Ey(o*N{TH(u>~7Jl-hZEV2k9C6YnJG;4!b`X*`qyESyW!-n=&R28b7|( z*kaEkm|I0xezTX#hTKY)>T+iRo>(-4C6W%eZm<21{Vra5y1@8--*mVkP`kgS$w<{b z?+qG2C4*N?`Fcb+qWP^+kuTa}w^lfcxzzaQAk@zNROTbJ>}^TBD(RV0e6b}<<+5(V zoO6L%%9|Tf=Q(bvMdQMtEoGijpSCdN)rS>wqXaB<%y>)*R*N9tlJ=$+1$rLsM%*q6 z^W`&yyXns|6cQ*O1{j9RxBcpr(E;)hrK5YL{C2{J8%}UZ0%oskJn0c;a5M6O97`nKFsUe(orlgPC?on{V zpdqOa%Kw2X*9JjdaRSg&;FRh*S0I*28wqNi$T$B}%Lma6V}m+rSVlJsgGtR#0%KX~ z!ZC(bAE>!HufRoZD_LK7A%ree2II*RLdyYDeSZ&TY5ZQGG~k!lO**QU!6b<=`!Uwr z-_*$vhV#myW1uAblLCJMmE%9nfc$m^l~Im$i}e`M4)q|Er*y|PX0Ii+aJ6l9Bc4uq zmy8qL{qCe=>j&{gdaRhl!~=0C8-+Hv2GJk1wh5~q!&XG?u(%NqB#sb{#B21!cm9`1 ztPe7ahkZ9Yq${>Olhw{y8@FVS;BMOPgEh-1FhEXmUD_!e^aOnJVjX9!HFy#$64d%2 zv(Up3J?a3823QwHu#pg{WHTMh|E8PeVN)z7f^BiDS&<>{*!LoajESv`=SWkAQpRqS zQ-0K0;Y{Uj7C;Qn3lWZ+m(~CIM75z0l!AaP7X!p>o5Ow4{w`(x;r@>Qk;=q9{KaBC zHC%(fk|&g5+?3kB7`?mxa(B4d0{VpQOuxEeT9yh^^~nCusfiQM(AeP$N@r@5SfZ|Q z%DzZCiaGy2&RNs()CSQG%N1RQ2s|*8XR@iqHs9tF+;j(R++NS*V$uA2Y_?ln=k|_H zwOiK%P=6QhbVQ+kf=peON(<}d3(N8mNsrHS`Oc-aCD%Uk`yxidPb5RZp1X)<767@O zn;osyk?fNLUc18GJ$+hhg&Ia4Y5#vV$`rThn941M7lmeRnETwe-tdYlfkRdbpZLJ@ zQKSd;#V^yPb}TY0a)q0hRkqd<7wFB4%)DN+{oidLRfZF#xAG{OZ!!wE!c@@pH|Wp1 zLrsK^`D?wHfoX#fL;3J8EiImeh35HMld-xZLmu6>9K~6fSrw=T;w8O|UpaLSnCedi z*NJx4-z)O>&-;50iT)Y|RCn3@ZT+`Es_qYB^i~|D0tK@N$%-)ML%F12I!DTp5wcZ(ssXYmg)qi8>j(V` zzl~2(T%MbVBS<+ko(_&%Tbf*r**BgNfJ`v6UA~g;-779~f(kqN|8pkdXL=F%PUd#^ zWFh^=q=}t+tH+y=k3!NijTz(yI~=drFRII)O%P!okT}s#>*FjN_{+UirJfA6ASh&+ z3Tz8<^7>%SlF8a8SN?A<4O)l>aSs9ak-YH~Cq1e*z^Z|!`<%5#e;HGEL_b))`>V8{ zn+YrPoAM4aA6=sBGbRNfUMniqH(Tg-3Zq&575<1#E#S5!F;>cG#SPfY$1^d zvU%?e$HXiyCA!QG`S1G{br?}6(Y#q~jjLW;*yX*DoA>}XUgDyJN_%3+kGTrNM~ z()4)8Y!gBhJvodH8%h$}TRNCQaEeq7qmX}ir#rHM{Vo>a@IrUHW7G4{&%f1r00=Ew z058tC_Ij8Fx=xEb6iR{8dp$~hJue?`dqq5iAUt6XE_`KBxq>6GG^-VvJ!`j z)QEoiiAHFA*JS8l^xHujOCzU6Hs(OIiJ3teHU(x_p?bb0jpdP$!f1+@;C_y!FfK07 zNqRauXcF-%Gpq+G7%{kGJ*P>uvpQu)iOctjC@#_$p~Yd23?F*6!*fnf2K`C;4G4%L`n#;+cLlzr#upO2KRREWwclC0vv|U`i9u4%=|@b! z70a3@mKT+td3K5-2e4iyUSN8XO7i4`(iZvb3I#`-ZX`5pZp$eZrW6`}7wv>{K6fU;z;GY>6 z`_Vi_FGD^^*@JKAN)$gSBD|1hrS}>Sh?>8$@4rgb48a!}ZMiKQEgRi2blu zL)-UWmz*3wuQIe1Bbj8mu&ifB#GbB6ST;RH-!H7C&f@QSFGESUi^{U7 z&G+<~yfSwgFwjT*I;M{pk*1SG2rv8aN5k9;7=O!4&uyT_u)jjP#NuR&(NvLeD+;{p zR(9X{xaqH-Snr#1wnJH>FMP_^1s>x&R&U|bXKrx$mYyw(yk~JJEa6?+MPuPK%F(cY zXx?!DAlZE?w7ZT*|U%ULC#67+(O4hzta-9b%^zIr!7 z=me~vUwii>EdoQzLa`8rB`ETyxMO=#1PqXhc?)^V5TVKpgSG0!irIdR|M5n3y6ov3~TSfX+_@2+Mxf@iXe)} zK!iv^)Un~83*pXiG#pCZUbM!g=6ZwgAtXpk`=XGKWNrxv?SDNnAR{_j2>GcRNIdOb z$hagH$n6+bulgApqHrWNOZDQ=+9yR*2-)*rHtd2~KV8gVW!gm~&u#D0VlsXR&+s8N zx1mx0p$*h9?u(GG74hT^NH3^GVE^CDN&qNjVOEQ+>h&Q~1cn^TZ}`4`CL+|bsNjR) z=21xyf-iXyF%G^3`DR6yg$1ZtIWZOq ziRiz=!MBlOKL56`h76Z3Fa12Z~bEfjrjRX)yU1(I)nO^zN* zq1|b z^p;8s!QFGI5B9TiuTF3{sj~3Tkhp=7m}XTk#v)pM!fUtAOg}GPGOe*e&)3Ecvoz;R zZGu8CBC3`O?900v6fV2T4yvsT=Inmok6pOnt*{99A!`qF#c5S>hzl=}>%oLpM&~wp zndOvgCVW@N@TgtFH@EDts8s6pAr}i2FZ_#gxgF;Q_T-)wb~hK4$VLg}^IN|elXG~s zxN`cKUYHF9C|KuhbY2mfV+3P}uWRG;v=i{!YN$8HgT8MlSUN9u2tM5mx4*O15pPCX zn-?Mdo%-;IW-btVFLr4P@}t_7sDwGhs$HRdAp9|cugu^o2A0 zG#?1|{}+8hA@?(wz@*m0MaOj~%zzxQN|dx5)%dOnVF(j@$!Rd%z#JG9DlG1;wy;Ym zJ@k#XuLF9{q};UHq2fI`qjr z2})#su-mYNoKvwy$Q$zAL+GrMP6^^_Hu7tkMCs-9NN*ZUhxqKS&e&8!5Ah_!n(O2x z1Vci-I@XuBIx-EnG!4sq~7nGqYPikNF2y`eS0of1Rwp{@%uz-7n-`tzAw{CW_uiyE%Mh z6!G$nvY^XTU2l_M9fPru>WR(7G#3IUTxeV}V#eTqG_0?2d)_Hi1C}z%S+0y3>zgA$ z++C|53nhfCwTWurDlQRZ3|3IU=XO9MZDefQ) z#|-k6kn|bt%!;T(W}jCMy|5WN7+RqEz_V)8WBLvz!;=xMq!m_D5Un?!k#_#BX(m`R z{vj=1V9WYdh}~T(-h|kjA5JbH0Bq%yUEg#^V3I0xiQ&hQW2(;77x9C&F4%~C%na1QI3L&k`ZFGHqK&c=>XF3I zI<06s3yNzK|8W9K&zqXBg=+gLU}+3WJkHp?GvmQx?0BsYR@UtLf4d~_4}q;_Y|qwv zkA2YYdWy-&T$zmldP-izkrVpwqWBo5C=sSV>U^4P5P$?p+D^GdJ#Wg64WSL9F*?~1 zX%Yi&EM==j+?OG-T50ZY%_w(i{aBvFhuImqN~s>@GKF$_86`Srd9GhV7>bfofQs8q z1_vMV{1Nz;jcQh=uR=dv9}ERh-k@J$JaMAk&DRR)%zewue|JEzHUe6;XXIZ42woch)TVDaeJaHO!O=#FDO88(=9Y*S*&(dFw6Ra2>IJcL6|nSCs!-MI`Ss6n>xVkESvNB1=%hQ z`6bjzC)SqYIwKmWG2XP_H2XeNC2hSTzo0)>(D3lr=%&3QHfdr@AlrP-kU|P<&Pn$V z?2@pBu6Yw)#CEGjtn%;;qUyw&)Se-gu1!C@L}qhH##o7yzOum?i@n#G!s?urj<-jK zm?j{nW=#xFPHr!HlgiVcPk3QXqpwRY8TExt}d|D8Nuu8C|2 z&KYx*w;Z)N23S9|aCv>vwjSGZ+$n#abv^;0xl{;@v(CWFyqDx8GmL zdeGLOZ>0gB`cJWm^%Rb2riWb@0?{Oj!{L$TywF3Ee=1CW{O7hP6>H>MtN{^q+0Ux? z!HFO~;6}Ap^`c`{Z{nX(s4AyJC>5pI%6chIp7IJ0%K2$*ua& zMolG2N}S)G>$7yS;#OmlrmEdJHlNNYc1Z8|qjg#%bNLfLunmkW&+VKl4Md>jK*vwp zhH|MEW0(Sj*_unWjI9G=es+rx&J%uW0uzNBCIgif%cSE2=vH5<36p z8_~K$w$CYj(T>K;v@0D!j40t`-O{kn1t_}t+HrJ`crK5sIybl5_Y7kEwL?z?R1jC=%LICN zJ2$0p)o8Y}g~a~VafZ2-gT-+U?zSJRv=M)c{Z9hootqLcl#tRY0p@0vlxfwu807}Y zZ=fQKH#u;w%Q-2&k!R`jj|FiM{(F{@C)O|!o}mx~mu^~Ufko7;Z?5MWA7$;_*GPwF z>~PY-8wJxoq`2|uJ+35+0GQq`3!3Dm_oSXF$JyAmLH81NT4g90W7AN2Wk`dWRqc?BEl|w@c>*DHSH%>5ahCf&Go+kiv zH96pt9iBo#r2S0zRix$X32|mQnlcdZ5k&IQQwM=!fuzb?LEX=%t4`E}qYz9ylsrjX z-a>s_von!%(4LM~I(&lVyQSEr>E=h#FC@-?E|K7rf7m*Kt(|E&=n^sS=)onhlrX&N zpPj!?b-h&mO!XmNcLR;fYw?*ZfV0!PftWi=t^1m7!A&3hPM~?e#ZGjbt|h0!3}J|7 z;DnkRP3-7^;`Xge6AmV^11ZomIPJQn#c=6))Luc+s5^cW_AJNp3<)b1NoRFBOTwwg zDxb~uPUKSXb>}T(GYwRm21=3%B4vNr335tsq$9_3E`p2$WMuJfie_G>VbcUkM%o7y z#U7OcJ%;FQAJ)1sf&N}xIU3Xuk?Gf@@aMu-UIhu_=w((JAkD^mWOwrIfVte(fhU-+-vjY4gxTz+{5S&?Gj}ps6E7cgp2=^KTZ|eP z#CO0^s&G;AQQ6wJ6i%@)gI^;dKj~gi_f*!gda3!1!j2T_M(!LaFYp6z;`O6j`p3be z2aSVB5YNved87<|k*V3@$}jx8 zyo@6NF~&?5(D?-~2N_?OW64)BL2qC;HsbIkTWaBJcW-}Bf}~P++Zmwq@=Y5qgZ%s6 z9@NZWB7MnuI^+HG)em67Ep^yE)1vjRv~aR5GiL5eQ?O=9hHH$r zd#rYMY}?jgoNI&5hRJ*{>B=a z@?UC}1G^sTy!;vTlQ*A)MHRx4(Oq?m^H{5q!NtiSz#@@FAyta$iMmtyPPcR~(a=Qq zv*XD;v_8@Ha0^B&kr$)!O`eqoCx$PORzdRlpcKzRL#SiTE*DWQci&yf$WX_6Pja}} zZMq!)#}VS`pNp!y1=LzV`ea1b$I*FcAODtdg;)L6lGFaz3Bgz6fPWD)f;R=~j0`k@ zV?UGZt=jZ%EM!D7FH+mF&0_C1uH~)iNT7%{uKoS{H_vux5RW`shICMr!@A(#qRlEN zVNu7ZAsCq|vX`&*%o@KlE|xC0Kgwx1m{y`7vaS&@K)y#O-)V?~l_&^0th= zVZI5QEZ7qJmHsgS`0vI-(%{GM`@RrklRvESK-d z)DY>LOF`B!LY)6NFQnU+FW2>o+M03RbqrZ-cF*(IdcALlwqk+SUIu@rFVnSI*n7X5 zXpoKE9)iX|X&_@R5Lr^;CWZg1!h6N1OBs&*HU60O&uNdku$Rv|Wv*>~b@qx)UJIRp zejmHW19kj2FKBbGCsW|`Lhi!ab{3{Psu@R`$eMRP`V!7zWS7v?MP-*&ih4xd4i6q_ z!n1W3y;}CePFpfaLmW@j`N0e}_I}CB?A~cAUz=R%=a{Z>J^$z+I*9zb-unK1x6K8D zSfn)N8b6JDXJKsZ7TH{XgGnb~I+HvZ+k>Hzfa=5z^o3lBcCq$iV9;dB83XMA=IHzf zVbK`rHtgd$OUnuapdRpHyur}=FSdIAbix9S`}rWFBZ?AWd|~#4!!E|-h516>$ef?q zH>X3{mzxq&4_5i!47vz?zBIaYoLD&PFU)z?+*Is{tG^~~^l8@c&QzKNv!k=;6EcZQ zaxXnr6u>zXu0 z$JWxJDG(hIS#5mhc67#Z^enj-zZ8v?eayhiOTv;-yJa#Ky$}K?;T~`@hU^{s;RQ%5?vJ}G^&{Gg_f zBlUFC)Y_)S#+K`@)D}=fWV6D;Ybp+BwhohwOk8~@G)o%H#I9K)_0tu3+?t|6B{KsH zX$4UP0O`_Qb&(*gd>5}{qE0VM_nKmkC(Ix;U_go^pu~!ya;67h6jN$b>#oX63}~BR zDt%HHIfb>+Q~FE6XqAC926L;uc6PRc3{}X-@sZt*eCDgVvBxUVP^tnggQZnOvxr(` z4fy{$BS_~d;to`wnyYd>Vea2$(pp&3ubLP&N>{09u?sIKG}u=d<8qR?v*wD4r{oYZ zbA*U?wvT6gPxlLt*NwoMH)(h?_Xk3YuAwvZV+^XW|FCX*kZPSoREC}YpoIAOH_Rii zJ!w@)4gKW6xzqmFb#-5KWMqzjv#zob>D)ObV*RYH{Y|{(O9&iycw6c~n^)5;cO1c{ zl2=_|OqlvnA(+$A$JC+b=HlV@l!VnbSAFz>ioCSUzQL#2Rp;z*&>)t#^Qpo0;`@=5 z2nYkYnFxc=4e)bi7!;-den2|CG6hmVlQymj^4{+Lcs!2dfC?v5TNgQ~g;E$c)#Uxo zeZZp$BRa#i8?fjU-lso5GqHVDAz3zA?6-$P)wSA}%f12p{E9Nw5+?|FdkWC9V$p9K z&01g(c3h*hz=QMy;1puKc#WsVg`lo{M;j^YPcc^zyxJzlz*tA?w z$Uf9%>Og(<2gZ|IfwYrpN*WffNt+Scx=T!gnuPGe?bBLYbA#~%MC%9$KinDaaYTlJ z28%Ud41_ZAywf$*vLm=Zuz*7g3P}UKD7}F8KJNoncig^}fPzp#WTeO<{yiv0tUhlp zpOi14EU_(|4eFPy)RqwcR!4ltbaZyZzYtp#z85Xr>35fgp+!XjsxeF^WQLh1@y+ar1bI2DL-oLPt6^US-!ka3)~}tPW`K zZPZMmePV=S0VzIhtf$r@83xCvJB7L5E>^UnrZz?*92ohicbcNJHv8>tUCtoFeN(LC z*>mc$X}TL0t6Osd=DbcgQ3e{#NnKKvPpTg;xfE(z^uk0S>BY6!+s9lN(PsXPHlVkasxzc%(Mnf=Xr6%orekxw#; ztFfl&ZzXzEfbI{b_KA^E(-L7<<^fk{xk>wY`JIhBvWX?@rFBlPUXN?FbIX5Zm$*Os zoE@l0psktk&KuyACnug7*-CXXL2Ct00_MMv$vOUX(s6P^S74EOc*W??b(}T#x+U=l zzs8D634?LcM`kfsnKz(;OLEeQ4W6WzfoJFQ;XJE=)!Qk@v-=L`9gEF^f+Vref0?`I zD+u5WcMwqLX5TP{7;rxowmK-h3T!TayaqdrKTUmI65fI>Fh&Hix^8@W^?_7>Rb0an zj72HZJm=6TBrgSF9(bC6;TS!O4c*;j#%hiaPhK@da8y!ZuKv!2S^ZAjrAJC-1d+^r z2*fGZ3M?*sw3?D(k5$>%yC=bW&PGf7M#>v7tbw4F``+%ahf%+&%X{~iOwL1_pfS}t zI@?)wCy9a9nob#L(;ZVjIWc0Fzd|yb;Xs~B0-)a!cwpe(kNP;{!&eE4Oz`&cuK$Z? zk{E?8Fxg5s5`+M1ft>gHNjkA?El}3ekw51F6xlr6e{QjzXxVslORYykR0hs$1gC+N zK(%|IC`VlQqg-$2?RZ-c#g8C?l4CL(nY^PQ`j4LPsf&h*B>sxEvb+2~V-}OQ1aAC% ziu#uN1eV9ri`?gTynHa?UPmOBXd8s6UQKb1(hxX&J z0&#bzyn}Hg!*!Z*NOKCBnv*ODG{-BA6&1!nTEQpy2fh1BPtoqiBa-Kc?TNpS%{3VxZfuNi^gUS zKt_k_4O_vDB%RMh(I0flJ8B`wJvYI-ha%Ldyss)8fkL1b0|BeBUqi0AzyY+DKR4cJ z5(Rr3&SbZjUeNiPHBLb*$9yx!oFK9eD4@_5%cz4}j&*Rw=@+huLoKP`F7z>p9yDwu z;xf5dXj=n`Y??zZ?JpY*N;IOjFueG#TT>N#d-RGJFdJ4V6PQ^h6Xy|hB7@)9F!}@^ z1>EjwgAGM?mUN40WjZ<(HBtvoFp`>+5yDmYW7>%&2i5U5)B=U{8oK*w+v>P_4p

    $-RJ?bg_uB!}u*>EKjImNKiLO_FV-+J_+YDjXfk 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

    Kb$AmF7fZOobC8OeE9gJj=>k~!>%0D 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 bH4s4GNo*azSPy|`sE^_&HQ5@OFQNYjHLFAf diff --git a/lib/dashboard/home/tab_bar/credentials/models/eu_diploma_card/eu_diploma_card_model.dart b/lib/dashboard/home/tab_bar/credentials/models/eu_diploma_card/eu_diploma_card_model.dart index 6714d8463..0e0c4233b 100644 --- a/lib/dashboard/home/tab_bar/credentials/models/eu_diploma_card/eu_diploma_card_model.dart +++ b/lib/dashboard/home/tab_bar/credentials/models/eu_diploma_card/eu_diploma_card_model.dart @@ -7,7 +7,15 @@ part 'eu_diploma_card_model.g.dart'; @JsonSerializable(explicitToJson: true) class EUDiplomaCardModel extends CredentialSubjectModel { EUDiplomaCardModel({ - this.expires, + this.expires = '', + this.awardingOpportunity, + this.dateOfBirth = '', + this.familyName = '', + this.givenNames = '', + this.gradingScheme, + this.identifier = '', + this.learningAchievement, + this.learningSpecification, super.id, super.type, super.issuedBy, @@ -19,9 +27,120 @@ class EUDiplomaCardModel extends CredentialSubjectModel { factory EUDiplomaCardModel.fromJson(Map json) => _$EUDiplomaCardModelFromJson(json); - @JsonKey(defaultValue: '') - final String? expires; + final String expires; + final AwardingOpportunity? awardingOpportunity; + final String dateOfBirth; + final String familyName; + final String givenNames; + final GradingScheme? gradingScheme; + final String identifier; + final LearningAchievement? learningAchievement; + final LearningSpecification? learningSpecification; @override Map toJson() => _$EUDiplomaCardModelToJson(this); } + +@JsonSerializable(explicitToJson: true) +class AwardingOpportunity { + AwardingOpportunity({ + this.awardingBody, + this.endedAtTime = '', + this.id = '', + this.identifier = '', + this.location = '', + this.startedAtTime = '', + }); + + factory AwardingOpportunity.fromJson(Map json) => + _$AwardingOpportunityFromJson(json); + + final AwardingBody? awardingBody; + final String endedAtTime; + final String id; + final String identifier; + final String location; + final String startedAtTime; + + Map toJson() => _$AwardingOpportunityToJson(this); +} + +@JsonSerializable(explicitToJson: true) +class AwardingBody { + AwardingBody({ + this.eidasLegalIdentifier = '', + this.homepage = '', + this.id = '', + this.preferredName = '', + this.registration = '', + }); + + factory AwardingBody.fromJson(Map json) => + _$AwardingBodyFromJson(json); + + final String eidasLegalIdentifier; + final String homepage; + final String id; + final String preferredName; + final String registration; + + Map toJson() => _$AwardingBodyToJson(this); +} + +@JsonSerializable(explicitToJson: true) +class GradingScheme { + GradingScheme({ + this.id = '', + this.title = '', + }); + + factory GradingScheme.fromJson(Map json) => + _$GradingSchemeFromJson(json); + + final String id; + final String title; + + Map toJson() => _$GradingSchemeToJson(this); +} + +@JsonSerializable(explicitToJson: true) +class LearningAchievement { + LearningAchievement({ + this.additionalNote, + this.description = '', + this.id = '', + this.title = '', + }); + + factory LearningAchievement.fromJson(Map json) => + _$LearningAchievementFromJson(json); + + final List? additionalNote; + final String description; + final String id; + final String title; + + Map toJson() => _$LearningAchievementToJson(this); +} + +@JsonSerializable(explicitToJson: true) +class LearningSpecification { + LearningSpecification({ + this.ectsCreditPoints, + this.eqfLevel, + this.id = '', + this.iscedfCode, + this.nqfLevel, + }); + + factory LearningSpecification.fromJson(Map json) => + _$LearningSpecificationFromJson(json); + + int? ectsCreditPoints; + int? eqfLevel; + String id; + List? iscedfCode; + List? nqfLevel; + + Map toJson() => _$LearningSpecificationToJson(this); +} diff --git a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/credential_base_widget.dart b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/credential_base_widget.dart index b4e4cd78a..61b3c3338 100644 --- a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/credential_base_widget.dart +++ b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/credential_base_widget.dart @@ -48,8 +48,8 @@ class CredentialBaseWidget extends StatelessWidget { LayoutId( id: 'value', child: FractionallySizedBox( - widthFactor: 0.65, - heightFactor: 0.12, + widthFactor: 0.8, + heightFactor: 0.2, child: MyText( value!, style: Theme.of(context).textTheme.identitiyBaseBoldText, diff --git a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/eu_diploma_card_widget.dart b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/eu_diploma_card_widget.dart index f27116ee6..c4d03597a 100644 --- a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/eu_diploma_card_widget.dart +++ b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/eu_diploma_card_widget.dart @@ -1,7 +1,5 @@ import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; -import 'package:altme/l10n/l10n.dart'; -import 'package:altme/theme/theme.dart'; import 'package:flutter/material.dart'; class EUDiplomaCardWidget extends StatelessWidget { @@ -14,70 +12,22 @@ class EUDiplomaCardWidget extends StatelessWidget { @override Widget build(BuildContext context) { - final l10n = context.l10n; - return CredentialImage( - image: ImageStrings.euDiplomaCard, - child: AspectRatio( - aspectRatio: Sizes.credentialAspectRatio, - child: CustomMultiChildLayout( - delegate: EUDiplomaCardWidgetDelegate(position: Offset.zero), - children: [ - LayoutId( - id: 'issued-on', - child: FractionallySizedBox( - heightFactor: 0.10, - widthFactor: 0.4, - child: MyText( - l10n.issuedOn, - style: Theme.of(context).textTheme.identitiyBaseLightText, - ), - ), - ), - LayoutId( - id: 'issued-on-value', - child: FractionallySizedBox( - heightFactor: 0.10, - widthFactor: 0.4, - child: MyText( - UiDate.formatDateForCredentialCard( - credentialModel.credentialPreview.issuanceDate, - ), - style: Theme.of(context).textTheme.identitiyBaseBoldText, - ), - ), - ), - ], - ), + final euDiplomaCardModel = credentialModel + .credentialPreview.credentialSubjectModel as EUDiplomaCardModel; + return CredentialBaseWidget( + cardBackgroundImagePath: ImageStrings.euDiplomaCard, + // issuerName: credentialModel + // .credentialPreview.credentialSubjectModel.issuedBy?.name, + issuerName: + euDiplomaCardModel.awardingOpportunity?.awardingBody?.preferredName ?? + '', + value: euDiplomaCardModel.learningAchievement?.title ?? '', + issuanceDate: UiDate.formatDateForCredentialCard( + credentialModel.credentialPreview.issuanceDate, ), + expirationDate: credentialModel.expirationDate == null + ? '--' + : UiDate.formatDateForCredentialCard(credentialModel.expirationDate!), ); } } - -class EUDiplomaCardWidgetDelegate extends MultiChildLayoutDelegate { - EUDiplomaCardWidgetDelegate({this.position = Offset.zero}); - - final Offset position; - - @override - void performLayout(Size size) { - if (hasChild('issued-on')) { - layoutChild('issued-on', BoxConstraints.loose(size)); - positionChild( - 'issued-on', - Offset(size.width * 0.5, size.height * 0.70), - ); - } - if (hasChild('issued-on-value')) { - layoutChild('issued-on-value', BoxConstraints.loose(size)); - positionChild( - 'issued-on-value', - Offset(size.width * 0.5, size.height * 0.82), - ); - } - } - - @override - bool shouldRelayout(EUDiplomaCardWidgetDelegate oldDelegate) { - return oldDelegate.position != position; - } -} From 7ecd3cd2daa3fdcd4e4dac508f9ff7a7844a96b7 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Tue, 14 Feb 2023 12:27:17 +0330 Subject: [PATCH 011/190] update buy icon #1353 --- assets/icon/cash-in-hand.png | Bin 1341 -> 688 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/assets/icon/cash-in-hand.png b/assets/icon/cash-in-hand.png index 85825302d7bf97c2a04df67b790b4dc8d1c194b8..0dfaad0ea2c257471f765c467765dff3cbf11380 100644 GIT binary patch literal 688 zcmV;h0#E&kP)2FVEB;J9mSa7{t=pC<87IyqHq z_r2!qvz!8%Q|$90A}YDL;JDQ7Ovj;EajZE$ITl4xtPlbVpL5hSJ}t-GN4TPC2zO~Y zl&!VcC7zp_Lt&b999NuH(rz2bK)j)YHgV*<@rLP(7ZiG}KX-uoN%DrgxVH_o$m7?y zX*LE}ysYg@WQdsqB{IbrxJITJ1Bcue928?fAs*SUoN}C@-^UC>syiU!rLe4Zg={bo zM~LTJyu8rW`~m}EmQg|&TjKRtCHzuS%w8iVjVT<31n@XBj&}+mHieR`UV84#5ioaR zQ>cTrwbf(YVE?)zsrPF7>-4MP3&qj8sl>0BKK-qJGx8+GDx^4S$3%zyQRc#44E7~9 zg={*GCysND%A=nMw|B)}T2dMaCdNQ)3N1Kpbvqsu&P@0Bo_r3EdYlLHBr1hsZv75A z1XSZO4K(QMzzrfuv5bbWV((G_W&S)Pf|Le)*6_|;u!#zhCX#IC__`uL?i>xi{WruK zsF08l6DbOrV_-aWp2WtM#7wXTJqTn$e5CV-uGLqE2j$z!%`vvCWStcDaJt9W6TM#F`x@kgZ$WfM)raHcy^ys z%9%`wJZNM~?rtUS;ddl579D!6`9J9XwVBgzTw$7%x`A_)`7hM}*`uZLsp*eDdwc=x WkEUKQZD6SY0000KFRPK%lUWL6e!PFNHLS+=M| z(@fYNN(*6|RMG}TDwLF!NfefCwo*`(nI)BBT0@4UZPTu$r8()mIPkr3xcuIm=e+Cl zJ(w?C-u%wJzxz9PIp=)O8R$U|vd|xx3-qJP@d}UwegVc%rMN_k_*cKyW&^uZMK}Oh zC*{utE(F%=c{Ok%Re)Z=0x3Pm$s2+9B<}=f06S2@xJAl-2h>M>wZLRxlg4}w45tDy zTuN>MMim%$D6mY6=>TriA}a!RO_oc!X+@0R5x7T-ws$@N4x$1uT?(}UI~5q$0PN+S zPX&I^yMF?$Y)70V1GZ~&WPx$xfz7~1pvlQ?Ve_1uejeDHGGk8}wkdFH7h~Ua%086Q z-wgbr{vUzUDI=cJhd%=9fC0b}zz{uqw8|zn=@hfj=wi@IlnLWpT(Fq&t=n;H8-Qnl zC1ez`jwA@w9Bg{yx0O zxyL(><*)1t$?r|cTo-B5w{*un#;R|PvcE94SXy4|PQ|Fc;Q>7_OUZ^OInNUtWxaw_ zPA6oTrDr33=+w-v3gKnFW5MneU0I7O!%{7x(Rr8A8U2r_?_FoCr%Cpc0%EY8ZuaNXKPB>n>zu8WjQ%b{Uy#v1)p?pek{L67M{;AbEgA2H9XB>S zP^oQaw2)wHN${K|eLbVEL5c!DkrlTkcBq4>$!}IiWz+}ob5iA&)e9JE_Mn8 z{5aS&MDAuncmlKu>p0!hmt?uUBykwqk%htrOr)u>i&CoS_*2N3tt?a*v+L7 zLW`V_0Y4XDtv8Skce&tb1TnyPSceFq&(n*>+fk&Qp<2^i$N6g$I*m|#7#YQ&FG?kZ z*Jy$l@-R?aXbKE zSCZ{f?xt%P&A8Mp-uTwF(U{RLW?5dJ9Ovg@x1*eEj4@c1j+ruKUubJ9Q0eHWjkDAs zIr{3jH%67d-mYML9&n?mbs&bmZBU9jZYy}%Ho<~Ctn-;X)TLe5W3uYD7NIKw*Gs`i zBYCs?2g@|Y70;6@4nrNG>lIzQ%_=JFN|j)x7BfPzqe%fXq_SsHC7A9idZA_;nw3Z| zqDpXbq`WQY4xLa|b-ZHH2Y3~@T7~K!P)7U*WGKgI0a*_n00000NkvXXu0mjfa>96d From f6893a7419c08a6304f6660c3dd165976d9730c0 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Tue, 14 Feb 2023 13:22:42 +0330 Subject: [PATCH 012/190] update whatsNewPopUp ui #1360 #1359 #1358 --- lib/app/shared/widget/altme_logo.dart | 8 +- .../dashboard/view/dashboard_page.dart | 3 +- .../dashboard/widgets/new_feature.dart | 11 +- .../dashboard/widgets/what_is_new_dialog.dart | 595 +++++++++++------- .../widgets/white_close_button.dart | 3 +- lib/theme/app_theme/app_theme.dart | 6 +- 6 files changed, 374 insertions(+), 252 deletions(-) diff --git a/lib/app/shared/widget/altme_logo.dart b/lib/app/shared/widget/altme_logo.dart index 2015db8c4..7d9677aa8 100644 --- a/lib/app/shared/widget/altme_logo.dart +++ b/lib/app/shared/widget/altme_logo.dart @@ -4,9 +4,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class AltMeLogo extends StatelessWidget { - const AltMeLogo({super.key, this.size = Sizes.logoLarge}); + const AltMeLogo({ + super.key, + this.size = Sizes.logoLarge, + this.color, + }); final double size; + final Color? color; @override Widget build(BuildContext context) { @@ -19,6 +24,7 @@ class AltMeLogo extends StatelessWidget { : ImageStrings.splash, width: size, height: size, + color: color, fit: BoxFit.fitHeight, ); } diff --git a/lib/dashboard/dashboard/view/dashboard_page.dart b/lib/dashboard/dashboard/view/dashboard_page.dart index a787ae103..8ee730c98 100644 --- a/lib/dashboard/dashboard/view/dashboard_page.dart +++ b/lib/dashboard/dashboard/view/dashboard_page.dart @@ -43,7 +43,8 @@ class _DashboardViewState extends State { /// If there is a deepLink we give do as if it coming from QRCode context.read().deepLink(); context.read().startBeacon(); - + // TODO(Taleb): remove this after test + WhatIsNewDialog.show(context); if (context.read().state.isNewVersion) { WhatIsNewDialog.show(context); } diff --git a/lib/dashboard/dashboard/widgets/new_feature.dart b/lib/dashboard/dashboard/widgets/new_feature.dart index afe57ed99..9dd23bf33 100644 --- a/lib/dashboard/dashboard/widgets/new_feature.dart +++ b/lib/dashboard/dashboard/widgets/new_feature.dart @@ -15,11 +15,11 @@ class NewFeature extends StatelessWidget { child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Padding( - padding: const EdgeInsets.only(top: 3), + const Padding( + padding: EdgeInsets.only(top: 3), child: Icon( Icons.check_circle, - color: Theme.of(context).colorScheme.primary, + color: Colors.white, size: 20, ), ), @@ -27,7 +27,10 @@ class NewFeature extends StatelessWidget { Expanded( child: Text( feature, - style: Theme.of(context).textTheme.defaultDialogBody, + textAlign: TextAlign.left, + style: Theme.of(context).textTheme.defaultDialogBody.copyWith( + color: Colors.white, + ), ), ), ], diff --git a/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart b/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart index 1d573e6a8..8006737e9 100644 --- a/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart +++ b/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart @@ -14,6 +14,7 @@ class WhatIsNewDialog extends StatelessWidget { static void show(BuildContext context) { showDialog( context: context, + useSafeArea: true, builder: (_) => const WhatIsNewDialog(), ); } @@ -25,252 +26,360 @@ class WhatIsNewDialog extends StatelessWidget { final l10n = context.l10n; - return AlertDialog( - backgroundColor: Theme.of(context).colorScheme.onBackground, - contentPadding: const EdgeInsets.symmetric( - horizontal: Sizes.spaceNormal, - vertical: Sizes.spaceSmall, - ), - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(25)), - ), - content: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, + return SafeArea( + child: AlertDialog( + backgroundColor: Theme.of(context).colorScheme.whatsNewPopupBackground, + contentPadding: const EdgeInsets.all(Sizes.spaceXSmall), + insetPadding: const EdgeInsets.symmetric( + horizontal: Sizes.spaceSmall, + vertical: Sizes.spaceNormal, + ), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all( + Radius.circular(Sizes.normalRadius), + ), + ), + content: Column( + mainAxisSize: MainAxisSize.max, children: [ - const DialogCloseButton(), - const SizedBox(height: Sizes.spaceSmall), - const AltMeLogo(), - Text( - l10n.whatsNew, - style: Theme.of(context).textTheme.defaultDialogTitle, - textAlign: TextAlign.center, - ), - const SizedBox(height: Sizes.spaceNormal), - Text( - versionNumber, - style: Theme.of(context).textTheme.defaultDialogSubtitle, - textAlign: TextAlign.center, - ), - const SizedBox(height: Sizes.spaceXSmall), - const NewFeature( - 'Integration of Matrix.org to give users access to a decentralized chat in Altme', // ignore: lines_longer_than_80_chars - ), - const NewFeature( - 'Compliance with EBSI and support of new official ID documents (diplomas...)', // ignore: lines_longer_than_80_chars - ), - Text( - '1.8.13', - style: Theme.of(context).textTheme.defaultDialogSubtitle, - textAlign: TextAlign.center, - ), - const SizedBox(height: Sizes.spaceXSmall), - const NewFeature( - 'Integration of an on-ramp solution to buy crypto', - ), - const NewFeature( - 'New features : Help center', - ), - const NewFeature( - 'New wallet certificate credential', - ), - Text( - '1.7.6', - style: Theme.of(context).textTheme.defaultDialogSubtitle, - textAlign: TextAlign.center, - ), - const SizedBox(height: Sizes.spaceXSmall), - const NewFeature( - 'Bug correction', - ), - Text( - '1.7.5', - style: Theme.of(context).textTheme.defaultDialogSubtitle, - textAlign: TextAlign.center, - ), - const SizedBox(height: Sizes.spaceXSmall), - const NewFeature( - 'New Chainborn gaming membership card', - ), - const NewFeature( - 'Credential manifest input descriptors update', - ), - const NewFeature( - 'Beacon pairing improvement', - ), - Text( - '1.7.1', - style: Theme.of(context).textTheme.defaultDialogSubtitle, - textAlign: TextAlign.center, - ), - const SizedBox(height: Sizes.spaceXSmall), - const NewFeature( - 'Improve compatibility with more wallets', - ), - const NewFeature( - 'Update Altme’s privacy, terms and conditions', - ), - const NewFeature( - 'Update NFT detail screen information', - ), - const NewFeature( - 'New category for Professional credentials', - ), - Text( - '1.6.5', - style: Theme.of(context).textTheme.defaultDialogSubtitle, - textAlign: TextAlign.center, - ), - const SizedBox(height: Sizes.spaceXSmall), - const NewFeature( - 'Bug correction', - ), - Text( - '1.6.3', - style: Theme.of(context).textTheme.defaultDialogSubtitle, - textAlign: TextAlign.center, - ), - const SizedBox(height: Sizes.spaceXSmall), - const NewFeature( - 'Support SBT (Soulbound Tokens)', - ), - const NewFeature( - 'New Drawer', - ), - const NewFeature( - 'New Device Info credential', - ), - const NewFeature( - 'Bug fix', - ), - Text( - '1.5.7', - style: Theme.of(context).textTheme.defaultDialogSubtitle, - textAlign: TextAlign.center, - ), - const SizedBox(height: Sizes.spaceXSmall), - const NewFeature( - 'Upgrade Beacon behavior', - ), - Text( - '1.5.6', - style: Theme.of(context).textTheme.defaultDialogSubtitle, - textAlign: TextAlign.center, - ), - const SizedBox(height: Sizes.spaceXSmall), - const NewFeature( - 'Age range with Al as 551 issuer', - ), - const NewFeature('Al issuer optimization'), - Text( - '1.5.1', - style: Theme.of(context).textTheme.defaultDialogSubtitle, - textAlign: TextAlign.center, - ), - const SizedBox(height: Sizes.spaceXSmall), - const NewFeature( - 'Al verification to get Over13 and Over18 pass', - ), - const NewFeature( - 'Ethereum support', - ), - const NewFeature( - 'Privacy and terms update', - ), - const NewFeature( - 'Enforced security', - ), - const NewFeature('User experience improvements'), - Text( - '1.4.8', - style: Theme.of(context).textTheme.defaultDialogSubtitle, - textAlign: TextAlign.center, - ), - const SizedBox(height: Sizes.spaceXSmall), - const NewFeature( - 'Add Tezotopia membership card in Discover', - ), - const NewFeature('Update design of credentials'), - Text( - '1.4.4', - style: Theme.of(context).textTheme.defaultDialogSubtitle, - textAlign: TextAlign.center, - ), - const SizedBox(height: Sizes.spaceXSmall), - const NewFeature( - 'Add the possibility to SEND an NFT to tezos blockchain address', - ), - const NewFeature( - 'Improvements of user experience', - ), - Text( - '1.4.1', - style: Theme.of(context).textTheme.defaultDialogSubtitle, - textAlign: TextAlign.center, - ), - const SizedBox(height: Sizes.spaceXSmall), - const NewFeature('New feature : NFT display in wallet'), - Text( - '1.3.7', - style: Theme.of(context).textTheme.defaultDialogSubtitle, - textAlign: TextAlign.center, - ), - const SizedBox(height: Sizes.spaceXSmall), - const NewFeature( - 'FA1.2 and FA2 token support', - ), - const NewFeature( - 'Beacon integration to connect to Tezos dApps', - ), - const NewFeature( - 'Get multiple identity credentials after identity verification (OpenID for VC Issuance)', // ignore: lines_longer_than_80_chars - ), - const NewFeature( - 'Choose card categories to display', - ), - const NewFeature( - 'New cards design', - ), - const NewFeature( - 'Nationality card', - ), - const NewFeature( - 'Age range card', - ), - const NewFeature( - 'Liveness test', - ), - const NewFeature( - 'Display card and token history', - ), - Text( - '1.1.0', - style: Theme.of(context).textTheme.defaultDialogSubtitle, - textAlign: TextAlign.center, - ), - const SizedBox(height: Sizes.spaceXSmall), - const NewFeature( - 'USD value of tokens', - ), - const NewFeature( - 'Multiple credentials presentation', - ), - const NewFeature( - 'Wording', - ), - const NewFeature( - 'Bug correction', + const Align( + alignment: Alignment.topRight, + child: WhiteCloseButton(), ), const SizedBox(height: Sizes.spaceSmall), - MyElevatedButton( - text: l10n.okGotIt, - verticalSpacing: 12, - fontSize: 18, - borderRadius: 20, - backgroundColor: Theme.of(context).colorScheme.primary, - onPressed: () { - Navigator.of(context).pop(); - }, + Expanded( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: Sizes.spaceSmall, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const AltMeLogo( + color: Colors.white, + ), + Text( + l10n.whatsNew, + style: Theme.of(context) + .textTheme + .defaultDialogTitle + .copyWith( + color: Colors.white, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: Sizes.spaceNormal), + Text( + versionNumber, + style: Theme.of(context) + .textTheme + .defaultDialogSubtitle + .copyWith( + color: Colors.white, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: Sizes.spaceXSmall), + const NewFeature( + 'Integration of Matrix.org to give users access to a decentralized chat in Altme', // ignore: lines_longer_than_80_chars + ), + const NewFeature( + 'Compliance with EBSI and support of new official ID documents (diplomas...)', // ignore: lines_longer_than_80_chars + ), + Text( + '1.8.13', + style: Theme.of(context) + .textTheme + .defaultDialogSubtitle + .copyWith( + color: Colors.white, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: Sizes.spaceXSmall), + const NewFeature( + 'Integration of an on-ramp solution to buy crypto', + ), + const NewFeature( + 'New features : Help center', + ), + const NewFeature( + 'New wallet certificate credential', + ), + Text( + '1.7.6', + style: Theme.of(context) + .textTheme + .defaultDialogSubtitle + .copyWith( + color: Colors.white, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: Sizes.spaceXSmall), + const NewFeature( + 'Bug correction', + ), + Text( + '1.7.5', + style: Theme.of(context) + .textTheme + .defaultDialogSubtitle + .copyWith( + color: Colors.white, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: Sizes.spaceXSmall), + const NewFeature( + 'New Chainborn gaming membership card', + ), + const NewFeature( + 'Credential manifest input descriptors update', + ), + const NewFeature( + 'Beacon pairing improvement', + ), + Text( + '1.7.1', + style: Theme.of(context) + .textTheme + .defaultDialogSubtitle + .copyWith( + color: Colors.white, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: Sizes.spaceXSmall), + const NewFeature( + 'Improve compatibility with more wallets', + ), + const NewFeature( + 'Update Altme’s privacy, terms and conditions', + ), + const NewFeature( + 'Update NFT detail screen information', + ), + const NewFeature( + 'New category for Professional credentials', + ), + Text( + '1.6.5', + style: Theme.of(context) + .textTheme + .defaultDialogSubtitle + .copyWith( + color: Colors.white, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: Sizes.spaceXSmall), + const NewFeature( + 'Bug correction', + ), + Text( + '1.6.3', + style: Theme.of(context) + .textTheme + .defaultDialogSubtitle + .copyWith( + color: Colors.white, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: Sizes.spaceXSmall), + const NewFeature( + 'Support SBT (Soulbound Tokens)', + ), + const NewFeature( + 'New Drawer', + ), + const NewFeature( + 'New Device Info credential', + ), + const NewFeature( + 'Bug fix', + ), + Text( + '1.5.7', + style: Theme.of(context) + .textTheme + .defaultDialogSubtitle + .copyWith( + color: Colors.white, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: Sizes.spaceXSmall), + const NewFeature( + 'Upgrade Beacon behavior', + ), + Text( + '1.5.6', + style: Theme.of(context) + .textTheme + .defaultDialogSubtitle + .copyWith( + color: Colors.white, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: Sizes.spaceXSmall), + const NewFeature( + 'Age range with Al as 551 issuer', + ), + const NewFeature('Al issuer optimization'), + Text( + '1.5.1', + style: Theme.of(context) + .textTheme + .defaultDialogSubtitle + .copyWith( + color: Colors.white, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: Sizes.spaceXSmall), + const NewFeature( + 'Al verification to get Over13 and Over18 pass', + ), + const NewFeature( + 'Ethereum support', + ), + const NewFeature( + 'Privacy and terms update', + ), + const NewFeature( + 'Enforced security', + ), + const NewFeature('User experience improvements'), + Text( + '1.4.8', + style: Theme.of(context) + .textTheme + .defaultDialogSubtitle + .copyWith( + color: Colors.white, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: Sizes.spaceXSmall), + const NewFeature( + 'Add Tezotopia membership card in Discover', + ), + const NewFeature('Update design of credentials'), + Text( + '1.4.4', + style: Theme.of(context) + .textTheme + .defaultDialogSubtitle + .copyWith( + color: Colors.white, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: Sizes.spaceXSmall), + const NewFeature( + 'Add the possibility to SEND an NFT to tezos blockchain address', + ), + const NewFeature( + 'Improvements of user experience', + ), + Text( + '1.4.1', + style: Theme.of(context) + .textTheme + .defaultDialogSubtitle + .copyWith( + color: Colors.white, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: Sizes.spaceXSmall), + const NewFeature('New feature : NFT display in wallet'), + Text( + '1.3.7', + style: Theme.of(context) + .textTheme + .defaultDialogSubtitle + .copyWith( + color: Colors.white, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: Sizes.spaceXSmall), + const NewFeature( + 'FA1.2 and FA2 token support', + ), + const NewFeature( + 'Beacon integration to connect to Tezos dApps', + ), + const NewFeature( + 'Get multiple identity credentials after identity verification (OpenID for VC Issuance)', // ignore: lines_longer_than_80_chars + ), + const NewFeature( + 'Choose card categories to display', + ), + const NewFeature( + 'New cards design', + ), + const NewFeature( + 'Nationality card', + ), + const NewFeature( + 'Age range card', + ), + const NewFeature( + 'Liveness test', + ), + const NewFeature( + 'Display card and token history', + ), + Text( + '1.1.0', + style: Theme.of(context) + .textTheme + .defaultDialogSubtitle + .copyWith( + color: Colors.white, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: Sizes.spaceXSmall), + const NewFeature( + 'USD value of tokens', + ), + const NewFeature( + 'Multiple credentials presentation', + ), + const NewFeature( + 'Wording', + ), + const NewFeature( + 'Bug correction', + ), + const SizedBox(height: Sizes.spaceSmall), + ], + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: Sizes.spaceLarge, + vertical: Sizes.spaceXSmall, + ), + child: MyElevatedButton( + text: l10n.okGotIt, + verticalSpacing: 12, + fontSize: 18, + borderRadius: 20, + backgroundColor: Theme.of(context).colorScheme.primary, + onPressed: () { + Navigator.of(context).pop(); + }, + ), ) ], ), diff --git a/lib/dashboard/home/tab_bar/credentials/widgets/white_close_button.dart b/lib/dashboard/home/tab_bar/credentials/widgets/white_close_button.dart index 6dc64217d..4b7938af9 100644 --- a/lib/dashboard/home/tab_bar/credentials/widgets/white_close_button.dart +++ b/lib/dashboard/home/tab_bar/credentials/widgets/white_close_button.dart @@ -1,3 +1,4 @@ +import 'package:altme/app/app.dart'; import 'package:flutter/material.dart'; class WhiteCloseButton extends StatelessWidget { @@ -16,7 +17,7 @@ class WhiteCloseButton extends StatelessWidget { child: Icon( Icons.close, color: Theme.of(context).colorScheme.background, - size: 20, + size: Sizes.icon, ), ), ); diff --git a/lib/theme/app_theme/app_theme.dart b/lib/theme/app_theme/app_theme.dart index c25c6ccde..f9cdba97a 100644 --- a/lib/theme/app_theme/app_theme.dart +++ b/lib/theme/app_theme/app_theme.dart @@ -112,6 +112,8 @@ extension CustomColorScheme on ColorScheme { Color get greyText => const Color(0xFFD1CCE3); + Color get whatsNewPopupBackground => const Color(0xFF271C38); + Color get cardHighlighted => const Color(0xFF251F38); Color get defaultDialogDark => const Color(0xFF322643); @@ -682,8 +684,8 @@ extension CustomTextTheme on TextTheme { ); TextStyle get defaultDialogBody => GoogleFonts.nunito( - fontSize: 15, - fontWeight: FontWeight.w600, + fontSize: 14, + fontWeight: FontWeight.w400, color: const Color(0xFF5F556F), ); From ec5e4a5f9555c8a0ef2f01196e3b3cb0d61a7fe1 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Tue, 14 Feb 2023 13:23:44 +0330 Subject: [PATCH 013/190] remove show popup for which added for test --- lib/dashboard/dashboard/view/dashboard_page.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/dashboard/dashboard/view/dashboard_page.dart b/lib/dashboard/dashboard/view/dashboard_page.dart index 8ee730c98..c7b5c802d 100644 --- a/lib/dashboard/dashboard/view/dashboard_page.dart +++ b/lib/dashboard/dashboard/view/dashboard_page.dart @@ -43,8 +43,6 @@ class _DashboardViewState extends State { /// If there is a deepLink we give do as if it coming from QRCode context.read().deepLink(); context.read().startBeacon(); - // TODO(Taleb): remove this after test - WhatIsNewDialog.show(context); if (context.read().state.isNewVersion) { WhatIsNewDialog.show(context); } From a0f8e23e9d9a1595517520adb9ca9b8fd11c0d78 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Tue, 14 Feb 2023 13:46:58 +0330 Subject: [PATCH 014/190] decrese space between title and subtitle in credential and discover #1356 --- .../credentials/list/widgets/home_credential_widget.dart | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/dashboard/home/tab_bar/credentials/list/widgets/home_credential_widget.dart b/lib/dashboard/home/tab_bar/credentials/list/widgets/home_credential_widget.dart index f740365e9..7006c63c9 100644 --- a/lib/dashboard/home/tab_bar/credentials/list/widgets/home_credential_widget.dart +++ b/lib/dashboard/home/tab_bar/credentials/list/widgets/home_credential_widget.dart @@ -28,22 +28,21 @@ class HomeCredentialWidget extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), + padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 0), child: Text( '${fromDiscover ? l10n.get : l10n.my} ${title.toLowerCase()}', style: Theme.of(context).textTheme.credentialCategoryTitle, ), ), - const SizedBox(height: 4), Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), + padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 0), child: Text( categorySubtitle, maxLines: 3, style: Theme.of(context).textTheme.credentialCategorySubTitle, ), ), - const SizedBox(height: 8), + const SizedBox(height: 4), GridView.builder( physics: const NeverScrollableScrollPhysics(), shrinkWrap: true, From cae2e85ceccb8cf532f5445b82aeb76505563635 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Tue, 14 Feb 2023 14:50:04 +0330 Subject: [PATCH 015/190] remove line below tabbar #1354 --- .../view/tab_controller_page.dart | 143 ++++++++++-------- .../tab_controller/widgets/tab_bar.dart | 12 +- 2 files changed, 85 insertions(+), 70 deletions(-) diff --git a/lib/dashboard/home/tab_bar/tab_controller/view/tab_controller_page.dart b/lib/dashboard/home/tab_bar/tab_controller/view/tab_controller_page.dart index e0c601b72..5a2870984 100644 --- a/lib/dashboard/home/tab_bar/tab_controller/view/tab_controller_page.dart +++ b/lib/dashboard/home/tab_bar/tab_controller/view/tab_controller_page.dart @@ -1,6 +1,7 @@ import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/l10n/l10n.dart'; +import 'package:altme/theme/theme.dart'; import 'package:altme/wallet/cubit/wallet_cubit.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -55,73 +56,87 @@ class _TabControllerViewState extends State children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: Sizes.spaceSmall), - child: TabBar( - controller: _tabController, - padding: - const EdgeInsets.symmetric(horizontal: Sizes.space2XSmall), - indicatorPadding: EdgeInsets.zero, - labelPadding: - const EdgeInsets.symmetric(horizontal: Sizes.space2XSmall), - indicatorWeight: 0.0001, - indicatorColor: Colors.transparent, - indicatorSize: TabBarIndicatorSize.label, - tabs: [ - MyTab( - text: l10n.cards, - icon: - state == 0 ? IconStrings.cards : IconStrings.cardsBlur, - isSelected: state == 0, - onPressed: () { - if (context.read().state.homeStatus == - HomeStatus.hasNoWallet) { - showDialog( - context: context, - builder: (_) => const WalletDialog(), - ); - return; - } - _tabController.animateTo(0); - context.read().setIndex(0); - }, + child: Theme( + data: ThemeData(), + child: TabBar( + controller: _tabController, + padding: const EdgeInsets.symmetric( + horizontal: Sizes.space2XSmall, ), - MyTab( - text: l10n.nfts, - icon: - state == 1 ? IconStrings.ghost : IconStrings.ghostBlur, - isSelected: state == 1, - onPressed: () { - if (context.read().state.homeStatus == - HomeStatus.hasNoWallet) { - showDialog( - context: context, - builder: (_) => const WalletDialog(), - ); - return; - } - _tabController.animateTo(1); - context.read().setIndex(1); - }, + indicatorPadding: EdgeInsets.zero, + labelPadding: const EdgeInsets.symmetric( + horizontal: Sizes.space2XSmall, ), - MyTab( - text: l10n.tokens, - icon: state == 2 - ? IconStrings.health - : IconStrings.healthBlur, - isSelected: state == 2, - onPressed: () { - if (context.read().state.homeStatus == - HomeStatus.hasNoWallet) { - showDialog( - context: context, - builder: (_) => const WalletDialog(), - ); - return; - } - _tabController.animateTo(2); - context.read().setIndex(2); - }, + indicatorWeight: 0.0000001, + indicator: BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.zero, + border: Border.all( + color: Colors.transparent, + width: 0, + ), ), - ], + indicatorSize: TabBarIndicatorSize.label, + tabs: [ + MyTab( + text: l10n.cards, + icon: state == 0 + ? IconStrings.cards + : IconStrings.cardsBlur, + isSelected: state == 0, + onPressed: () { + if (context.read().state.homeStatus == + HomeStatus.hasNoWallet) { + showDialog( + context: context, + builder: (_) => const WalletDialog(), + ); + return; + } + _tabController.animateTo(0); + context.read().setIndex(0); + }, + ), + MyTab( + text: l10n.nfts, + icon: state == 1 + ? IconStrings.ghost + : IconStrings.ghostBlur, + isSelected: state == 1, + onPressed: () { + if (context.read().state.homeStatus == + HomeStatus.hasNoWallet) { + showDialog( + context: context, + builder: (_) => const WalletDialog(), + ); + return; + } + _tabController.animateTo(1); + context.read().setIndex(1); + }, + ), + MyTab( + text: l10n.tokens, + icon: state == 2 + ? IconStrings.health + : IconStrings.healthBlur, + isSelected: state == 2, + onPressed: () { + if (context.read().state.homeStatus == + HomeStatus.hasNoWallet) { + showDialog( + context: context, + builder: (_) => const WalletDialog(), + ); + return; + } + _tabController.animateTo(2); + context.read().setIndex(2); + }, + ), + ], + ), ), ), const SizedBox(height: Sizes.spaceSmall), diff --git a/lib/dashboard/home/tab_bar/tab_controller/widgets/tab_bar.dart b/lib/dashboard/home/tab_bar/tab_controller/widgets/tab_bar.dart index 84265fe01..3e64add61 100644 --- a/lib/dashboard/home/tab_bar/tab_controller/widgets/tab_bar.dart +++ b/lib/dashboard/home/tab_bar/tab_controller/widgets/tab_bar.dart @@ -32,8 +32,8 @@ class MyTab extends StatelessWidget { gradient: isSelected ? LinearGradient( colors: [ - Theme.of(context).colorScheme.primary, - Theme.of(context).colorScheme.secondary, + AppTheme.darkThemeData.colorScheme.primary, + AppTheme.darkThemeData.colorScheme.secondary, ], begin: Alignment.bottomLeft, end: Alignment.topRight, @@ -42,7 +42,7 @@ class MyTab extends StatelessWidget { : null, color: isSelected ? null - : Theme.of(context).colorScheme.tabBarNotSelected, + : AppTheme.darkThemeData.colorScheme.tabBarNotSelected, ), child: Row( mainAxisAlignment: MainAxisAlignment.center, @@ -56,9 +56,9 @@ class MyTab extends StatelessWidget { text, maxLines: 1, minFontSize: 12, - style: Theme.of(context).textTheme.title.copyWith( - color: isSelected ? null : Colors.grey[400], - ), + style: AppTheme.darkThemeData.textTheme.title.copyWith( + color: isSelected ? null : Colors.grey[400], + ), overflow: TextOverflow.fade, ), ], From 6580163041de252191c9fcaa0c05e308e3a2a42c Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Tue, 14 Feb 2023 20:18:18 +0330 Subject: [PATCH 016/190] fix minor bug --- lib/dashboard/home/tab_bar/nft/cubit/nft_cubit.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/dashboard/home/tab_bar/nft/cubit/nft_cubit.dart b/lib/dashboard/home/tab_bar/nft/cubit/nft_cubit.dart index 941b56148..3d182c07b 100644 --- a/lib/dashboard/home/tab_bar/nft/cubit/nft_cubit.dart +++ b/lib/dashboard/home/tab_bar/nft/cubit/nft_cubit.dart @@ -150,7 +150,9 @@ class NftCubit extends Cubit { final nftList = List.from( result.map((dynamic e) { return EthereumNftModel( - name: e['name'] as String, + name: (e['name'] as String? ?? + e['normalized_metadata']['name'] as String?) ?? + '', symbol: e['symbol'] as String?, description: e['normalized_metadata']['description'] as String?, tokenId: e['token_id'] as String, From 9cb0333ede4d186e2c739d22c8480631b726aa28 Mon Sep 17 00:00:00 2001 From: hawkbee1 Date: Wed, 15 Feb 2023 09:25:51 +0100 Subject: [PATCH 017/190] version: 1.9.3+144 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 1389b5cd1..51db77bad 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: altme description: AltMe Flutter App -version: 1.9.2+143 +version: 1.9.3+144 publish_to: none environment: From d5d1137262dade1764fb8c0faf7b52b395872e8d Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Wed, 15 Feb 2023 13:29:36 +0330 Subject: [PATCH 018/190] catch exception if resync failed --- .../home/tab_bar/nft/cubit/nft_cubit.dart | 16 ++++++++++------ pubspec.lock | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/dashboard/home/tab_bar/nft/cubit/nft_cubit.dart b/lib/dashboard/home/tab_bar/nft/cubit/nft_cubit.dart index 3d182c07b..f0d916ef9 100644 --- a/lib/dashboard/home/tab_bar/nft/cubit/nft_cubit.dart +++ b/lib/dashboard/home/tab_bar/nft/cubit/nft_cubit.dart @@ -167,12 +167,16 @@ class NftCubit extends Cubit { for (final element in nftList) { if (element.thumbnailUri == null) { - await client.get( - '${Urls.moralisBaseUrl}/nft/${element.contractAddress}/${element.tokenId}/metadata/resync', - headers: { - 'X-API-KEY': moralisApiKey, - }, - ); + try { + await client.get( + '${Urls.moralisBaseUrl}/nft/${element.contractAddress}/${element.tokenId}/metadata/resync', + headers: { + 'X-API-KEY': moralisApiKey, + }, + ); + } catch (e, s) { + getLogger(toString()).e('failed to resync e: $e s: $s'); + } } } return nftList; diff --git a/pubspec.lock b/pubspec.lock index db3c0e363..194c1b85d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2343,5 +2343,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.19.0 <3.0.0" + dart: ">=2.19.0 <3.7.0" flutter: ">=3.7.0-0" From cf8d271f5074e00788e011ab7730a691dfc4fd56 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Mon, 13 Feb 2023 14:51:05 +0530 Subject: [PATCH 019/190] add functioinality to pass ssikey to ebsi #1341 --- packages/ebsi/lib/src/ebsi.dart | 3 --- packages/ebsi/lib/src/token_parameters.dart | 18 +++++++++--------- packages/ebsi/pubspec.yaml | 3 ++- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/packages/ebsi/lib/src/ebsi.dart b/packages/ebsi/lib/src/ebsi.dart index f0a7c994e..3aa1029be 100644 --- a/packages/ebsi/lib/src/ebsi.dart +++ b/packages/ebsi/lib/src/ebsi.dart @@ -9,13 +9,10 @@ import 'package:ebsi/src/issuer_token_parameters.dart'; import 'package:ebsi/src/token_parameters.dart'; import 'package:ebsi/src/verifier_token_parameters.dart'; import 'package:flutter/foundation.dart'; -// ignore: depend_on_referenced_packages import 'package:hex/hex.dart'; import 'package:jose/jose.dart'; import 'package:json_path/json_path.dart'; import 'package:secp256k1/secp256k1.dart'; -// ignore: implementation_imports -// ignore: implementation_imports, unnecessary_import /// {@template ebsi} /// EBSI wallet compliance diff --git a/packages/ebsi/lib/src/token_parameters.dart b/packages/ebsi/lib/src/token_parameters.dart index e032b36ac..c19587512 100644 --- a/packages/ebsi/lib/src/token_parameters.dart +++ b/packages/ebsi/lib/src/token_parameters.dart @@ -48,19 +48,19 @@ class TokenParameters { /// we use crv P-256K in the rest of the package to ensure compatibility /// with jose dart package. In fact our crv is secp256k1 wich change the /// fingerprint - // ignore: inference_failure_on_instance_creation - final tmpPublic = Map.from(publicJWK); - /// this test is to be crv agnostic and respect https://www.rfc-editor.org/rfc/rfc7638 - if (tmpPublic['crv'] == 'P-256K') { - tmpPublic['crv'] = 'secp256k1'; - } - - tmpPublic + final sortedJwk = Map.fromEntries( + publicJWK.entries.toList()..sort((e1, e2) => e1.key.compareTo(e2.key)), + ) ..removeWhere((key, value) => key == 'use') ..removeWhere((key, value) => key == 'alg'); - final jsonString = jsonEncode(tmpPublic); + /// this test is to be crv agnostic and respect https://www.rfc-editor.org/rfc/rfc7638 + if (sortedJwk['crv'] == 'P-256K') { + sortedJwk['crv'] = 'secp256k1'; + } + + final jsonString = jsonEncode(sortedJwk); final bytesToHash = utf8.encode(jsonString); final sha256Digest = sha256.convert(bytesToHash); diff --git a/packages/ebsi/pubspec.yaml b/packages/ebsi/pubspec.yaml index 64271886e..d3c9654ef 100644 --- a/packages/ebsi/pubspec.yaml +++ b/packages/ebsi/pubspec.yaml @@ -19,8 +19,9 @@ dependencies: fast_base58: ^0.2.1 flutter: sdk: flutter + hex: ^0.2.0 http_mock_adapter: ^0.3.3 - jose: ^0.3.2 + jose: ^0.3.3 json_path: ^0.4.2 pinenacl: ^0.3.3 # tezart from git depends on pinenacl ^0.3.3 secp256k1: ^0.3.0 From d3312795670f6db52304c7a8ea2e8408f329ec63 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Wed, 15 Feb 2023 13:43:49 +0530 Subject: [PATCH 020/190] prevent regression by override test #1316 --- .../lib/src/verifier_token_parameters.dart | 41 ---- .../src/issuer_token_parameters_test.dart | 43 +++- .../ebsi/test/src/token_parameters_test.dart | 159 ++---------- .../src/verifier_token_parameters_test.dart | 63 +++-- packages/ebsi/test/test_class.dart | 230 ++++++++++++++++++ 5 files changed, 336 insertions(+), 200 deletions(-) create mode 100644 packages/ebsi/test/test_class.dart diff --git a/packages/ebsi/lib/src/verifier_token_parameters.dart b/packages/ebsi/lib/src/verifier_token_parameters.dart index 79794a6a2..36a741029 100644 --- a/packages/ebsi/lib/src/verifier_token_parameters.dart +++ b/packages/ebsi/lib/src/verifier_token_parameters.dart @@ -33,45 +33,4 @@ class VerifierTokenParameters extends TokenParameters { final claims = jwt.claims; return claims['iss'] as String; } - - // @visibleForTesting - // @override - // Map get publicJWK { - // const privateKey = { - // 'crv': 'P-256', - // 'd': 'ccWWNSjGiv1iWlNh4kfhWvwG3yyQMe8o31Du0uKRzrs', - // 'kty': 'EC', - // 'x': 'J4vQtLUyrVUiFIXRrtEq4xurmBZp2eq9wJmXkIA_stI', - // 'y': 'EUU6vXoG3BGX2zzwjXrGDcr4EyDD0Vfk3_5fg5kSgKE' - // }; - // return privateKey; - // } - - // @visibleForTesting - // @override - // String get didKey { - // const didKey = 'did:ebsi:zo4FR1YfAKFP3Q6dvqhxcXxnfeDiJDP97kmnqhyAUSACj'; - // return didKey; - // } - - // @visibleForTesting - // @override - // String get kid { - // const kid = - // '''did:ebsi:zo9FR1YfAKFP3Q6dvrhxcXxnfeDiJDP97kmnqhyAUSACj#Cgcg1y9xj9uWFw56PMc29XBd9EReixzvnftBz8JwQFiB''';// ignore: lines_longer_than_80_chars - - // return kid; - // } - - // @visibleForTesting - // @override - // String get alg { - // return 'ES256K'; - // } - - // @visibleForTesting - // @override - // List get thumbprint { - // return [1, 2, 3]; - // } } diff --git a/packages/ebsi/test/src/issuer_token_parameters_test.dart b/packages/ebsi/test/src/issuer_token_parameters_test.dart index 30d21ebb4..cc25ba0b6 100644 --- a/packages/ebsi/test/src/issuer_token_parameters_test.dart +++ b/packages/ebsi/test/src/issuer_token_parameters_test.dart @@ -5,4 +5,45 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -void main() {} +import 'package:flutter_test/flutter_test.dart'; + +import '../test_class.dart'; + +void main() { + group('override test', () { + final issuerTokenParameterTest = IssuerTokenParameterTest(); + + test( + 'public key is P-256K private key without d parameter', + issuerTokenParameterTest.publicKeyTest, + ); + + test('did EBSI', issuerTokenParameterTest.didTest); + + test('kID EBSI', issuerTokenParameterTest.keyIdTest); + + group('algorithm test', () { + test( + "algorithm is ES256K when key's curve is not P-256", + issuerTokenParameterTest.algorithmIsES256KTest, + ); + + test( + "algorithm is ES256 when key's curve is P-256", + issuerTokenParameterTest.algorithmIsES256Test, + ); + }); + + group('thumbprint test', () { + test( + 'thumbprint of the public Key', + issuerTokenParameterTest.thumprintOfKey, + ); + + test( + 'thumbrprint of the Key from exemple in rfc 7638', + issuerTokenParameterTest.thumprintOfKeyForrfc7638, + ); + }); + }); +} diff --git a/packages/ebsi/test/src/token_parameters_test.dart b/packages/ebsi/test/src/token_parameters_test.dart index 6f7ed9ac4..b9f5da79f 100644 --- a/packages/ebsi/test/src/token_parameters_test.dart +++ b/packages/ebsi/test/src/token_parameters_test.dart @@ -1,3 +1,5 @@ +// ignore: unnecessary_lambdas + // Copyright (c) 2022, Very Good Ventures // https://verygood.ventures // @@ -10,151 +12,42 @@ import 'package:ebsi/ebsi.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; +import '../test_class.dart'; + class MockDio extends Mock implements Dio {} void main() { group('TokenParameters', () { - const privateKey = { - 'crv': 'P-256K', - 'd': 'ccWWNSjGiv1iWlNh4kfhWvwG3yyQMe8o31Du0uKRzrs', - 'kty': 'EC', - 'x': 'J4vQtLUyrVUiFIXRrtEq4xurmBZp2eq9wJmXkIA_stI', - 'y': 'EUU6vXoG3BGX2zzwjXrGDcr4EyDD0Vfk3_5fg5kSgKE' - }; - - const publicJWK = { - 'crv': 'P-256K', - 'kty': 'EC', - 'x': 'J4vQtLUyrVUiFIXRrtEq4xurmBZp2eq9wJmXkIA_stI', - 'y': 'EUU6vXoG3BGX2zzwjXrGDcr4EyDD0Vfk3_5fg5kSgKE' - }; - - const didKey = 'did:ebsi:zo9FR1YfAKFP3Q6dvqhxcXxnfeDiJDP97kmnqhyAUSACj'; - - const kid = - '''did:ebsi:zo9FR1YfAKFP3Q6dvqhxcXxnfeDiJDP97kmnqhyAUSACj#Cgcg1y9xj9uWFw56PMc29XBd9EReixzvnftBz8JwQFiB'''; - - const ES256Alg = 'ES256'; - - const ES256KAlg = 'ES256K'; - - const thumbprint = [ - 173, - 150, - 139, - 1, - 202, - 186, - 32, - 132, - 51, - 6, - 216, - 230, - 103, - 154, - 26, - 196, - 52, - 23, - 248, - 132, - 91, - 7, - 58, - 174, - 149, - 38, - 148, - 157, - 199, - 122, - 118, - 36, - ]; - - final tokenParameters = TokenParameters(privateKey); - - test('public key is P-256K private key without d parameter', () { - expect(tokenParameters.publicJWK, publicJWK); - }); + final tokenParametersTest = TokenParameterTest(); - test('did EBSI', () { - expect(tokenParameters.didKey, didKey); - }); + test( + 'public key is P-256K private key without d parameter', + tokenParametersTest.publicKeyTest, + ); - test('kid EBSI', () { - expect(tokenParameters.kid, kid); - }); + test('did EBSI', tokenParametersTest.didTest); - group('algorithm test', () { - test("algorithm is ES256K when key's curve is not P-256", () { - expect(tokenParameters.alg, ES256KAlg); - }); + test('kID EBSI', tokenParametersTest.keyIdTest); - test("algorithm is ES256 when key's curve is P-256", () { - const privateKey2 = { - 'crv': 'P-256', - 'd': 'ccWWNSjGiv1iWlNh4kfhWvwG3yyQMe8o31Du0uKRzrs', - 'kty': 'EC', - 'x': 'J4vQtLUyrVUiFIXRrtEq4xurmBZp2eq9wJmXkIA_stI', - 'y': 'EUU6vXoG3BGX2zzwjXrGDcr4EyDD0Vfk3_5fg5kSgKE' - }; - final tokenParameters2 = TokenParameters(privateKey2); - expect(tokenParameters2.alg, ES256Alg); - }); + group('algorithm test', () { + test( + "algorithm is ES256K when key's curve is not P-256", + tokenParametersTest.algorithmIsES256KTest, + ); + + test( + "algorithm is ES256 when key's curve is P-256", + tokenParametersTest.algorithmIsES256Test, + ); }); group('thumbprint test', () { - test('thumbprint of the public Key', () { - expect(tokenParameters.thumbprint, thumbprint); - }); + test('thumbprint of the public Key', tokenParametersTest.thumprintOfKey); - test('thumbrprint of the Key from exemple in rfc 7638', () { - const rfc7638Jwk = { - 'e': 'AQAB', - 'kty': 'RSA', - 'n': - // ignore: lines_longer_than_80_chars - '0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw' - }; - const expectedThumbprint = [ - 55, - 54, - 203, - 177, - 120, - 124, - 184, - 48, - 156, - 119, - 238, - 140, - 55, - 5, - 197, - 225, - 111, - 251, - 158, - 133, - 151, - 21, - 144, - 31, - 30, - 76, - 89, - 177, - 17, - 130, - 245, - 123 - ]; - final tokenParameters2 = TokenParameters(rfc7638Jwk); - expect(tokenParameters2.thumbprint, expectedThumbprint); - }); + test( + 'thumbrprint of the Key from exemple in rfc 7638', + tokenParametersTest.thumprintOfKeyForrfc7638, + ); }); group('more didKey test', () { diff --git a/packages/ebsi/test/src/verifier_token_parameters_test.dart b/packages/ebsi/test/src/verifier_token_parameters_test.dart index 330bb5661..58b67003f 100644 --- a/packages/ebsi/test/src/verifier_token_parameters_test.dart +++ b/packages/ebsi/test/src/verifier_token_parameters_test.dart @@ -8,6 +8,8 @@ import 'package:ebsi/ebsi.dart'; import 'package:flutter_test/flutter_test.dart'; +import '../test_class.dart'; + void main() { group('Verifier TokenParameters', () { const url = @@ -51,30 +53,41 @@ void main() { expect(verifierTokenParameters.audience, audience); }); - // group('override test', () { - // test('publicJWK', () { - // const privateKey = { - // 'crv': 'P-256', - // 'd': 'ccWWNSjGiv1iWlNh4kfhWvwG3yyQMe8o31Du0uKRzrs', - // 'kty': 'EC', - // 'x': 'J4vQtLUyrVUiFIXRrtEq4xurmBZp2eq9wJmXkIA_stI', - // 'y': 'EUU6vXoG3BGX2zzwjXrGDcr4EyDD0Vfk3_5fg5kSgKE' - // }; - // expect(verifierTokenParameters.publicJWK, privateKey); - // }); - - // test('didKey', () { - // const didKey = 'did:ebsi:zo4FR1YfAKFP3Q6dvqhxcXxnfeDiJDP97kmnqhyAUSACj'; // ignore: lines_longer_than_80_chars - // expect(verifierTokenParameters.didKey, didKey); - // }); - - // test('alg', () { - // expect(verifierTokenParameters.alg, 'ES256K'); - // }); - - // test('thumbprint', () { - // expect(verifierTokenParameters.thumbprint, [1, 2, 3]); - // }); - // }); + group('override test', () { + final verifierTokenParametersTest = VerifierTokenParametersTest(); + + test( + 'public key is P-256K private key without d parameter', + verifierTokenParametersTest.publicKeyTest, + ); + + test('did EBSI', verifierTokenParametersTest.didTest); + + test('kID EBSI', verifierTokenParametersTest.keyIdTest); + + group('algorithm test', () { + test( + "algorithm is ES256K when key's curve is not P-256", + verifierTokenParametersTest.algorithmIsES256KTest, + ); + + test( + "algorithm is ES256 when key's curve is P-256", + verifierTokenParametersTest.algorithmIsES256Test, + ); + }); + + group('thumbprint test', () { + test( + 'thumbprint of the public Key', + verifierTokenParametersTest.thumprintOfKey, + ); + + test( + 'thumbrprint of the Key from exemple in rfc 7638', + verifierTokenParametersTest.thumprintOfKeyForrfc7638, + ); + }); + }); }); } diff --git a/packages/ebsi/test/test_class.dart b/packages/ebsi/test/test_class.dart new file mode 100644 index 000000000..b97aca614 --- /dev/null +++ b/packages/ebsi/test/test_class.dart @@ -0,0 +1,230 @@ +import 'package:ebsi/ebsi.dart'; +import 'package:flutter_test/flutter_test.dart'; + +const privateKey = { + 'crv': 'P-256K', + 'd': 'ccWWNSjGiv1iWlNh4kfhWvwG3yyQMe8o31Du0uKRzrs', + 'kty': 'EC', + 'x': 'J4vQtLUyrVUiFIXRrtEq4xurmBZp2eq9wJmXkIA_stI', + 'y': 'EUU6vXoG3BGX2zzwjXrGDcr4EyDD0Vfk3_5fg5kSgKE' +}; + +const privateKey2 = { + 'crv': 'P-256', + 'd': 'ccWWNSjGiv1iWlNh4kfhWvwG3yyQMe8o31Du0uKRzrs', + 'kty': 'EC', + 'x': 'J4vQtLUyrVUiFIXRrtEq4xurmBZp2eq9wJmXkIA_stI', + 'y': 'EUU6vXoG3BGX2zzwjXrGDcr4EyDD0Vfk3_5fg5kSgKE' +}; + +const publicJWK = { + 'crv': 'P-256K', + 'kty': 'EC', + 'x': 'J4vQtLUyrVUiFIXRrtEq4xurmBZp2eq9wJmXkIA_stI', + 'y': 'EUU6vXoG3BGX2zzwjXrGDcr4EyDD0Vfk3_5fg5kSgKE' +}; + +const didKey = 'did:ebsi:zo9FR1YfAKFP3Q6dvqhxcXxnfeDiJDP97kmnqhyAUSACj'; + +const kid = + '''did:ebsi:zo9FR1YfAKFP3Q6dvqhxcXxnfeDiJDP97kmnqhyAUSACj#Cgcg1y9xj9uWFw56PMc29XBd9EReixzvnftBz8JwQFiB'''; + +const ES256Alg = 'ES256'; + +const ES256KAlg = 'ES256K'; + +const thumbprint = [ + 173, + 150, + 139, + 1, + 202, + 186, + 32, + 132, + 51, + 6, + 216, + 230, + 103, + 154, + 26, + 196, + 52, + 23, + 248, + 132, + 91, + 7, + 58, + 174, + 149, + 38, + 148, + 157, + 199, + 122, + 118, + 36, +]; + +const rfc7638Jwk = { + 'e': 'AQAB', + 'kty': 'RSA', + 'n': + // ignore: lines_longer_than_80_chars + '0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw' +}; +const expectedThumbprintForrfc7638Jwk = [ + 55, + 54, + 203, + 177, + 120, + 124, + 184, + 48, + 156, + 119, + 238, + 140, + 55, + 5, + 197, + 225, + 111, + 251, + 158, + 133, + 151, + 21, + 144, + 31, + 30, + 76, + 89, + 177, + 17, + 130, + 245, + 123 +]; + +class TokenParameterTest { + final tokenParameters = TokenParameters(privateKey); + + void publicKeyTest() { + expect(tokenParameters.publicJWK, publicJWK); + } + + void didTest() { + expect(tokenParameters.didKey, didKey); + } + + void keyIdTest() { + expect(tokenParameters.kid, kid); + } + + void algorithmIsES256KTest() { + expect(tokenParameters.alg, ES256KAlg); + } + + void algorithmIsES256Test() { + final tokenParameters = TokenParameters(privateKey2); + expect(tokenParameters.alg, ES256Alg); + } + + void thumprintOfKey() { + expect(tokenParameters.thumbprint, thumbprint); + } + + void thumprintOfKeyForrfc7638() { + final tokenParameters2 = TokenParameters(rfc7638Jwk); + expect(tokenParameters2.thumbprint, expectedThumbprintForrfc7638Jwk); + } +} + +class IssuerTokenParameterTest extends TokenParameterTest { + final issuerTokenParameters = IssuerTokenParameters(privateKey, ''); + + @override + void publicKeyTest() { + expect(issuerTokenParameters.publicJWK, publicJWK); + } + + @override + void didTest() { + expect(tokenParameters.didKey, didKey); + } + + @override + void keyIdTest() { + expect(tokenParameters.kid, kid); + } + + @override + void algorithmIsES256KTest() { + expect(tokenParameters.alg, ES256KAlg); + } + + @override + void algorithmIsES256Test() { + final tokenParameters = IssuerTokenParameters(privateKey2, ''); + expect(tokenParameters.alg, ES256Alg); + } + + @override + void thumprintOfKey() { + expect(tokenParameters.thumbprint, thumbprint); + } + + @override + void thumprintOfKeyForrfc7638() { + final tokenParameters2 = IssuerTokenParameters(rfc7638Jwk, ''); + expect(tokenParameters2.thumbprint, expectedThumbprintForrfc7638Jwk); + } +} + +class VerifierTokenParametersTest extends TokenParameterTest { + final verifierTokenParameters = + VerifierTokenParameters(privateKey, Uri.parse(''), []); + + @override + void publicKeyTest() { + expect(verifierTokenParameters.publicJWK, publicJWK); + } + + @override + void didTest() { + expect(tokenParameters.didKey, didKey); + } + + @override + void keyIdTest() { + expect(tokenParameters.kid, kid); + } + + @override + void algorithmIsES256KTest() { + expect(tokenParameters.alg, ES256KAlg); + } + + @override + void algorithmIsES256Test() { + final tokenParameters = + VerifierTokenParameters(privateKey2, Uri.parse(''), []); + expect(tokenParameters.alg, ES256Alg); + } + + @override + void thumprintOfKey() { + expect(tokenParameters.thumbprint, thumbprint); + } + + @override + void thumprintOfKeyForrfc7638() { + final tokenParameters2 = + VerifierTokenParameters(rfc7638Jwk, Uri.parse(''), []); + expect(tokenParameters2.thumbprint, expectedThumbprintForrfc7638Jwk); + } +} From 238ae155665b253d889091b6289c827e84e3a49b Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Wed, 15 Feb 2023 14:12:48 +0530 Subject: [PATCH 021/190] add small test --- packages/ebsi/test/src/ebsi_test.dart | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/ebsi/test/src/ebsi_test.dart b/packages/ebsi/test/src/ebsi_test.dart index 4b1ad74d9..f1dd81166 100644 --- a/packages/ebsi/test/src/ebsi_test.dart +++ b/packages/ebsi/test/src/ebsi_test.dart @@ -96,6 +96,11 @@ void main() { expect(jsonDecode(jwk), expectedJwk); }); + test('privateKey from mnemonic', () async { + final jwk = await ebsi.getPrivateKey(mnemonic, null); + expect(jwk, expectedJwk); + }); + test('JWK from seeds', () { final jwk = ebsi.jwkFromSeed(seedBytes: Uint8List.fromList(seedBytes)); expect(jwk, expectedJwk); @@ -259,8 +264,18 @@ void main() { final issuer = ebsi.getIssuer(credentialRequest); expect(expectedIssuer, issuer); }); - - test('get readTokenEndPoint with openidConfigurationResponse', () async {}); + test('get readTokenEndPoint with openidConfigurationResponse', () async { + const openidConfigurationResponse = + r'{"authorization_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/authorize","batch_credential_endpoint":null,"credential_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/credential","credential_issuer":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl","credential_manifests":[{"id":"VerifiableDiploma_1","issuer":{"id":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","name":"Test EBSILUX"},"output_descriptors":[{"display":{"description":{"fallback":"This card is a proof that you passed this diploma successfully. You can use this card when you need to prove this information to services that have adopted EU EBSI framework.","path":[],"schema":{"type":"string"}},"properties":[{"fallback":"Unknown","label":"First name","path":["$.credentialSubject.firstName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Last name","path":["$.credentialSubject.familyName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Birth date","path":["$.credentialSubject.dateOfBirth"],"schema":{"format":"date","type":"string"}},{"fallback":"Unknown","label":"Grading scheme","path":["$.credentialSubject.gradingScheme.title"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Title","path":["$.credentialSubject.learningAchievement.title"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Description","path":["$.credentialSubject.learningAchievement.description"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"ECTS Points","path":["$.credentialSubject.learningSpecification.ectsCreditPoints"],"schema":{"type":"number"}},{"fallback":"Unknown","label":"Issue date","path":["$.issuanceDate"],"schema":{"format":"date","type":"string"}},{"fallback":"Unknown","label":"Issued by","path":["$.credentialSubject.awardingOpportunity.awardingBody.preferredName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Registration","path":["$.credentialSubject.awardingOpportunity.awardingBody.registration"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Website","path":["$.credentialSubject.awardingOpportunity.awardingBody.homepage"],"schema":{"format":"uri","type":"string"}}],"subtitle":{"fallback":"EBSI Verifiable diploma","path":[],"schema":{"type":"string"}},"title":{"fallback":"Diploma","path":[],"schema":{"type":"string"}}},"id":"diploma_01","schema":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"}],"spec_version":"https://identity.foundation/credential-manifest/spec/v1.0.0/"}],"credential_supported":[{"cryptographic_binding_methods_supported":["did"],"cryptographic_suites_supported":["ES256K","ES256","ES384","ES512","RS256"],"display":[{"locale":"en-US","name":"Issuer Talao"}],"format":"jwt_vc","id":"VerifiableDiploma","types":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"}],"pre-authorized_grant_anonymous_access_supported":true,"subject_syntax_types_supported":["did:ebsi"],"token_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/token"}'; + + final issuer = ebsi.readTokenEndPoint( + Response( + requestOptions: RequestOptions(path: ''), + data: jsonDecode(openidConfigurationResponse) as Map, + ), + ); + expect(issuer, tokenUrl); + }); }); test('sandbox', () { From 3fee4b9fdb0c7c041ca2685ebadcebe7e6f8eb75 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Wed, 15 Feb 2023 17:02:50 +0530 Subject: [PATCH 022/190] add comments --- packages/ebsi/test/src/ebsi_test.dart | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/packages/ebsi/test/src/ebsi_test.dart b/packages/ebsi/test/src/ebsi_test.dart index f1dd81166..b12eebe52 100644 --- a/packages/ebsi/test/src/ebsi_test.dart +++ b/packages/ebsi/test/src/ebsi_test.dart @@ -300,4 +300,26 @@ void main() { // ignore: unused_local_variable final token = jwt.sign(SecretKey('secret passphrase')); }); + + // test('generateToken', () { + // final jwk = { + // 'kty': 'OKP', + // 'crv': 'Ed25519', + // 'd': '-_9OD-PMHKE2mFKEVH5R1vDhEMimtXPUX-2w1Xa0hQ0=', + // 'x': '1tknP1Fx-YIQBzw3xXtCwLGgYoTb-nSsNn-k9uzWpuw=', + // }; + + // final vpTokenPayload = { + // 'iss': 'did:ebsi:zrDzYQPDztwjQ8HdXto1B4FVB14fxoiNawZd8eyEuhR7K', + // 'nonce': '8dd8d00a-ad17-11ed-9fe9-0a1628958560', + // 'iat': '1676455223753366', + // 'aud': 'https://talao.co/sandbox/ebsi/issuer/vgvghylozl', + // }; + + // final ebsi = Ebsi(client); + + // final tokenParameters = TokenParameters(jwk); + // final token = ebsi.generateToken(vpTokenPayload, tokenParameters); + + // }); } From e4a1bbb97cbef5d88be14d116a0ddfe9e98a5036 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Wed, 15 Feb 2023 16:57:51 +0330 Subject: [PATCH 023/190] NFT details on Ethereum, Binance, Fantom Polygon #1365 --- .../home/tab_bar/nft/cubit/nft_cubit.dart | 2 + .../nft/models/ethereum_nft_model.dart | 8 ++ .../tab_bar/nft/view/nft_details_page.dart | 126 ++++++++++++++---- lib/l10n/arb/app_en.arb | 5 +- lib/l10n/untranslated.json | 20 ++- 5 files changed, 133 insertions(+), 28 deletions(-) diff --git a/lib/dashboard/home/tab_bar/nft/cubit/nft_cubit.dart b/lib/dashboard/home/tab_bar/nft/cubit/nft_cubit.dart index f0d916ef9..959320b44 100644 --- a/lib/dashboard/home/tab_bar/nft/cubit/nft_cubit.dart +++ b/lib/dashboard/home/tab_bar/nft/cubit/nft_cubit.dart @@ -159,6 +159,8 @@ class NftCubit extends Cubit { contractAddress: e['token_address'] as String, balance: e['amount'] as String, type: e['contract_type'] as String, + minterAddress: e['minter_address'] as String?, + lastMetadataSync: e['last_metadata_sync'] as String?, image: e['normalized_metadata']['image'] as String?, animationUrl: e['normalized_metadata']['animation_url'] as String?, ); diff --git a/lib/dashboard/home/tab_bar/nft/models/ethereum_nft_model.dart b/lib/dashboard/home/tab_bar/nft/models/ethereum_nft_model.dart index 96a9a85b0..6c6c7e6fa 100644 --- a/lib/dashboard/home/tab_bar/nft/models/ethereum_nft_model.dart +++ b/lib/dashboard/home/tab_bar/nft/models/ethereum_nft_model.dart @@ -17,6 +17,8 @@ class EthereumNftModel extends NftModel { String? image, super.description, required this.type, + this.minterAddress, + this.lastMetadataSync, }) : super( displayUri: animationUrl, thumbnailUri: image, @@ -26,6 +28,10 @@ class EthereumNftModel extends NftModel { _$EthereumNftModelFromJson(json); final String type; + @JsonKey(name: 'minter_address') + final String? minterAddress; + @JsonKey(name: 'last_metadata_sync') + final String? lastMetadataSync; @override Map toJson() => _$EthereumNftModelToJson(this); @@ -55,5 +61,7 @@ class EthereumNftModel extends NftModel { balance, type, isTransferable, + minterAddress, + lastMetadataSync, ]; } diff --git a/lib/dashboard/home/tab_bar/nft/view/nft_details_page.dart b/lib/dashboard/home/tab_bar/nft/view/nft_details_page.dart index e75685cae..e5cd92d44 100644 --- a/lib/dashboard/home/tab_bar/nft/view/nft_details_page.dart +++ b/lib/dashboard/home/tab_bar/nft/view/nft_details_page.dart @@ -81,23 +81,27 @@ class _NftDetailsViewState extends State { maxLines: 1, minFontSize: 16, ), + MyText( + widget.nftModel.symbol ?? '--', + style: Theme.of(context).textTheme.bodySmall2, + maxLines: 1, + minFontSize: 12, + ), + if (widget.nftModel.description != null && + widget.nftModel.description!.isNotEmpty) ...[ + const SizedBox(height: Sizes.spaceNormal), + if (widget.nftModel.description?.contains('

    ') ?? false) + Html(data: widget.nftModel.description ?? '') + else + Text( + widget.nftModel.description ?? '', + style: Theme.of(context).textTheme.bodyLarge, + ), + ], if (widget.nftModel is TezosNftModel) - MyText( - (widget.nftModel as TezosNftModel).symbol ?? '--', - style: Theme.of(context).textTheme.bodySmall2, - maxLines: 1, - minFontSize: 12, - ), - const SizedBox(height: Sizes.spaceNormal), - if (widget.nftModel.description?.contains('

    ') ?? false) - Html(data: widget.nftModel.description ?? '') + ...buildTezosMoreDetails(l10n) else - Text( - widget.nftModel.description ?? '', - style: Theme.of(context).textTheme.bodyLarge, - ), - if (widget.nftModel is TezosNftModel) - ...buildTezosMoreDetails(l10n), + ...buildEthereumMoreDetails(l10n), // Text( // l10n.seeMoreNFTInformationOn, // style: Theme.of(context).textTheme.bodyText1, @@ -152,9 +156,24 @@ class _NftDetailsViewState extends State { List buildTezosMoreDetails(AppLocalizations l10n) { final nftModel = widget.nftModel as TezosNftModel; return [ - if (nftModel.identifier != null) + const SizedBox(height: Sizes.spaceNormal), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '${l10n.contractAddress} : ', + style: Theme.of(context).textTheme.titleMedium, + ), + Flexible( + child: Text( + nftModel.contractAddress, + style: Theme.of(context).textTheme.bodySmall3, + ), + ) + ], + ), + if (nftModel.identifier != null) ...[ const SizedBox(height: Sizes.spaceNormal), - if (nftModel.identifier != null) Row( children: [ Text( @@ -167,11 +186,11 @@ class _NftDetailsViewState extends State { ) ], ), - if (nftModel.creators != null) + ], + if (nftModel.creators != null) ...[ const SizedBox( height: Sizes.spaceXSmall, ), - if (nftModel.creators != null) Row( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, @@ -188,11 +207,11 @@ class _NftDetailsViewState extends State { ), ], ), - if (nftModel.publishers != null) + ], + if (nftModel.publishers != null) ...[ const SizedBox( height: Sizes.spaceXSmall, ), - if (nftModel.publishers != null) Row( children: [ Text( @@ -205,11 +224,11 @@ class _NftDetailsViewState extends State { ) ], ), - if (nftModel.date != null) + ], + if (nftModel.date != null) ...[ const SizedBox( height: Sizes.spaceXSmall, ), - if (nftModel.date != null) Row( children: [ Text( @@ -222,6 +241,67 @@ class _NftDetailsViewState extends State { ), ], ), + ] + ]; + } + + List buildEthereumMoreDetails(AppLocalizations l10n) { + final nftModel = widget.nftModel as EthereumNftModel; + return [ + const SizedBox(height: Sizes.spaceNormal), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '${l10n.contractAddress} : ', + style: Theme.of(context).textTheme.titleMedium, + ), + Flexible( + child: Text( + nftModel.contractAddress, + style: Theme.of(context).textTheme.bodySmall3, + ), + ) + ], + ), + if (nftModel.minterAddress != null && nftModel.type != 'ERC1155') ...[ + const SizedBox( + height: Sizes.spaceXSmall, + ), + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '${l10n.creator} : ', + style: Theme.of(context).textTheme.titleMedium, + ), + Flexible( + child: Text( + nftModel.minterAddress ?? '?', + style: Theme.of(context).textTheme.bodySmall3, + ), + ), + ], + ), + ], + if (nftModel.lastMetadataSync != null) ...[ + const SizedBox( + height: Sizes.spaceXSmall, + ), + Row( + children: [ + Text( + '${l10n.lastMetadataSync} : ', + style: Theme.of(context).textTheme.titleMedium, + ), + Text( + UiDate.normalFormat(nftModel.lastMetadataSync) ?? '?', + style: Theme.of(context).textTheme.bodySmall3, + ), + ], + ), + ] ]; } } diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 0760b9672..c2dd6ce6e 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -795,5 +795,8 @@ "backToHome": "Back to home", "help": "Help", "searchCredentials": "Search credentials", - "supportChatWelcomeMessage": "Welcome to our chat support! We're here to assist you with any questions or concerns you may have about Altme." + "supportChatWelcomeMessage": "Welcome to our chat support! We're here to assist you with any questions or concerns you may have about Altme.", + "creator": "Creator", + "contractAddress": "Contract address", + "lastMetadataSync": "Last metadata sync" } \ No newline at end of file diff --git a/lib/l10n/untranslated.json b/lib/l10n/untranslated.json index a4ed13467..b0909da3f 100644 --- a/lib/l10n/untranslated.json +++ b/lib/l10n/untranslated.json @@ -731,7 +731,10 @@ "backToHome", "help", "searchCredentials", - "supportChatWelcomeMessage" + "supportChatWelcomeMessage", + "creator", + "contractAddress", + "lastMetadataSync" ], "es": [ @@ -1466,7 +1469,10 @@ "backToHome", "help", "searchCredentials", - "supportChatWelcomeMessage" + "supportChatWelcomeMessage", + "creator", + "contractAddress", + "lastMetadataSync" ], "fr": [ @@ -1479,7 +1485,10 @@ "backToHome", "help", "searchCredentials", - "supportChatWelcomeMessage" + "supportChatWelcomeMessage", + "creator", + "contractAddress", + "lastMetadataSync" ], "it": [ @@ -2214,6 +2223,9 @@ "backToHome", "help", "searchCredentials", - "supportChatWelcomeMessage" + "supportChatWelcomeMessage", + "creator", + "contractAddress", + "lastMetadataSync" ] } From 44006b4581579003b00bd7fca9bd3d151acc9633 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Wed, 15 Feb 2023 20:12:24 +0330 Subject: [PATCH 024/190] fix send token on Ethereum #1370 --- lib/app/shared/constants/urls.dart | 5 +- .../confirm_token_transaction_cubit.dart | 64 +++++++++++-------- .../view/insert_withdrawal_amount_page.dart | 2 +- .../widgets/token_amount_calculator.dart | 2 +- 4 files changed, 44 insertions(+), 29 deletions(-) diff --git a/lib/app/shared/constants/urls.dart b/lib/app/shared/constants/urls.dart index 51434b017..2bd1236be 100644 --- a/lib/app/shared/constants/urls.dart +++ b/lib/app/shared/constants/urls.dart @@ -58,6 +58,10 @@ class Urls { //Moralis static const moralisBaseUrl = 'https://deep-index.moralis.io/api/v2'; + //Infura + static const infuraBaseUrl = + 'https://mainnet.infura.io/v3/'; + static const objktUrl = 'https://objkt.com/'; static const raribleUrl = 'https://rarible.com/'; @@ -78,5 +82,4 @@ class Urls { static const matrixHomeServer = 'https://matrix.talao.co'; static const getNonce = 'https://talao.co/matrix/nonce'; static const registerToMatrix = 'https://talao.co/matrix/register'; - } diff --git a/lib/dashboard/home/tab_bar/tokens/confirm_token_transaction/cubit/confirm_token_transaction_cubit.dart b/lib/dashboard/home/tab_bar/tokens/confirm_token_transaction/cubit/confirm_token_transaction_cubit.dart index e9bbfb20b..ec5b4f71e 100644 --- a/lib/dashboard/home/tab_bar/tokens/confirm_token_transaction/cubit/confirm_token_transaction_cubit.dart +++ b/lib/dashboard/home/tab_bar/tokens/confirm_token_transaction/cubit/confirm_token_transaction_cubit.dart @@ -273,34 +273,46 @@ class ConfirmTokenTransactionCubit extends Cubit { } Future sendContractInvocationOperation() async { - emit(state.copyWith(status: AppStatus.init)); - if (manageNetworkCubit.state.network is TezosNetwork) { - await _sendContractInvocationOperationTezos( - tokenAmount: state.totalAmount, - selectedAccountSecretKey: state.selectedAccountSecretKey, - token: state.selectedToken, - ); - } else { - final selectedEthereumNetwork = manageNetworkCubit.state.network; - - await dotenv.load(); - final ethRpcUrl = dotenv.get('WEB3_RPC_MAINNET_URL'); - - String chainRpcUrl = ethRpcUrl; - if (selectedEthereumNetwork is PolygonNetwork || - selectedEthereumNetwork is BinanceNetwork || - selectedEthereumNetwork is FantomNetwork) { - chainRpcUrl = selectedEthereumNetwork.rpcNodeUrl; + try { + emit(state.copyWith(status: AppStatus.init)); + if (manageNetworkCubit.state.network is TezosNetwork) { + await _sendContractInvocationOperationTezos( + tokenAmount: state.totalAmount, + selectedAccountSecretKey: state.selectedAccountSecretKey, + token: state.selectedToken, + ); } else { - chainRpcUrl = ethRpcUrl; + final selectedEthereumNetwork = manageNetworkCubit.state.network; + + await dotenv.load(); + final infuraApiKey = dotenv.get('INFURA_API_KEY'); + final ethRpcUrl = Urls.infuraBaseUrl + infuraApiKey; + + String chainRpcUrl = ethRpcUrl; + if (selectedEthereumNetwork is PolygonNetwork || + selectedEthereumNetwork is BinanceNetwork || + selectedEthereumNetwork is FantomNetwork) { + chainRpcUrl = selectedEthereumNetwork.rpcNodeUrl; + } else { + chainRpcUrl = ethRpcUrl; + } + await _sendContractInvocationOperationEthereum( + tokenAmount: state.totalAmount, + selectedAccountSecretKey: state.selectedAccountSecretKey, + token: state.selectedToken, + chainId: (selectedEthereumNetwork as EthereumNetwork).chainId, + chainRpcUrl: chainRpcUrl, + ethRpcUrl: ethRpcUrl, + ); } - await _sendContractInvocationOperationEthereum( - tokenAmount: state.totalAmount, - selectedAccountSecretKey: state.selectedAccountSecretKey, - token: state.selectedToken, - chainId: (selectedEthereumNetwork as EthereumNetwork).chainId, - chainRpcUrl: chainRpcUrl, - ethRpcUrl: ethRpcUrl, + } catch (e, s) { + logger.e('e: $e s: $s'); + emit( + state.error( + messageHandler: ResponseMessage( + ResponseString.RESPONSE_STRING_SOMETHING_WENT_WRONG_TRY_AGAIN_LATER, + ), + ), ); } } diff --git a/lib/dashboard/home/tab_bar/tokens/insert_withdrawal_amount/view/insert_withdrawal_amount_page.dart b/lib/dashboard/home/tab_bar/tokens/insert_withdrawal_amount/view/insert_withdrawal_amount_page.dart index d61feb693..aad89a4bb 100644 --- a/lib/dashboard/home/tab_bar/tokens/insert_withdrawal_amount/view/insert_withdrawal_amount_page.dart +++ b/lib/dashboard/home/tab_bar/tokens/insert_withdrawal_amount/view/insert_withdrawal_amount_page.dart @@ -79,7 +79,7 @@ class _InsertWithdrawalAmountViewState style: Theme.of(context).textTheme.titleLarge, ), const SizedBox( - height: Sizes.spaceNormal, + height: Sizes.spaceSmall, ), TokenSelectBoxView(selectedToken: state.selectedToken), TokenAmountCalculatorView(selectedToken: state.selectedToken), diff --git a/lib/dashboard/home/tab_bar/tokens/insert_withdrawal_amount/widgets/token_amount_calculator.dart b/lib/dashboard/home/tab_bar/tokens/insert_withdrawal_amount/widgets/token_amount_calculator.dart index 520362e3d..5a79c79a9 100644 --- a/lib/dashboard/home/tab_bar/tokens/insert_withdrawal_amount/widgets/token_amount_calculator.dart +++ b/lib/dashboard/home/tab_bar/tokens/insert_withdrawal_amount/widgets/token_amount_calculator.dart @@ -99,7 +99,7 @@ class _TokenAmountCalculatorPageState extends State { mainAxisSize: MainAxisSize.max, children: [ const SizedBox( - height: Sizes.spaceLarge, + height: Sizes.spaceSmall, ), BlocBuilder( builder: (context, state) { From 290b5e535b589db96a495f50ca62f1853a61693b Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Thu, 16 Feb 2023 11:22:29 +0530 Subject: [PATCH 025/190] button to display private key as qr #1285 --- .../manage_accounts/manage_accounts.dart | 1 + .../view/account_private_key_page.dart | 16 +++ .../view/private_key_qr_page.dart | 120 ++++++++++++++++++ .../detail/view/credentials_details_page.dart | 4 +- .../view/tab_controller_page.dart | 1 - .../qr_display/view/qr_display_page.dart | 58 +++++---- pubspec.lock | 2 +- 7 files changed, 176 insertions(+), 26 deletions(-) create mode 100644 lib/dashboard/drawer/manage_accounts/view/private_key_qr_page.dart diff --git a/lib/dashboard/drawer/manage_accounts/manage_accounts.dart b/lib/dashboard/drawer/manage_accounts/manage_accounts.dart index fe184508c..c8e88aae1 100644 --- a/lib/dashboard/drawer/manage_accounts/manage_accounts.dart +++ b/lib/dashboard/drawer/manage_accounts/manage_accounts.dart @@ -2,4 +2,5 @@ export 'cubit/manage_account_dialog_cubit.dart'; export 'cubit/manage_accounts_cubit.dart'; export 'view/account_public_address_page.dart'; export 'view/manage_accounts_page.dart'; +export 'view/private_key_qr_page.dart'; export 'widgets/widgets.dart'; diff --git a/lib/dashboard/drawer/manage_accounts/view/account_private_key_page.dart b/lib/dashboard/drawer/manage_accounts/view/account_private_key_page.dart index 566988ab8..56ed1807a 100644 --- a/lib/dashboard/drawer/manage_accounts/view/account_private_key_page.dart +++ b/lib/dashboard/drawer/manage_accounts/view/account_private_key_page.dart @@ -1,4 +1,5 @@ import 'package:altme/app/app.dart'; +import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/l10n/l10n.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -63,6 +64,21 @@ class _AccountPrivateKeyPageState extends State scrollView: false, titleLeading: const BackLeadingButton(), secureScreen: true, + titleTrailing: IconButton( + onPressed: () { + Navigator.of(context).push( + PrivateKeyQrPage.route( + title: l10n.privateKey, + data: widget.privateKey, + secondsLeft: animation.value, + ), + ); + }, + icon: Icon( + Icons.qr_code, + color: Theme.of(context).colorScheme.onBackground, + ), + ), body: BackgroundCard( height: double.infinity, width: double.infinity, diff --git a/lib/dashboard/drawer/manage_accounts/view/private_key_qr_page.dart b/lib/dashboard/drawer/manage_accounts/view/private_key_qr_page.dart new file mode 100644 index 000000000..d815f0292 --- /dev/null +++ b/lib/dashboard/drawer/manage_accounts/view/private_key_qr_page.dart @@ -0,0 +1,120 @@ +import 'package:altme/app/app.dart'; +import 'package:altme/l10n/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:qr_flutter/qr_flutter.dart'; + +class PrivateKeyQrPage extends StatefulWidget { + const PrivateKeyQrPage({ + super.key, + required this.title, + required this.data, + required this.secondsLeft, + }); + + final String title; + final String data; + final double secondsLeft; + + static Route route({ + required String title, + required String data, + required double secondsLeft, + }) => + MaterialPageRoute( + builder: (context) => PrivateKeyQrPage( + data: data, + title: title, + secondsLeft: secondsLeft, + ), + settings: const RouteSettings(name: '/PrivateKeyQrPage'), + ); + + @override + State createState() => _PrivateKeyQrPageState(); +} + +class _PrivateKeyQrPageState extends State + with SingleTickerProviderStateMixin { + late Animation animation; + late AnimationController animationController; + + @override + void initState() { + super.initState(); + animationController = AnimationController( + vsync: this, + duration: Duration(seconds: widget.secondsLeft.toInt()), + ); + + final Tween rotationTween = + Tween(begin: widget.secondsLeft, end: 0); + + animation = rotationTween.animate(animationController) + ..addStatusListener((status) { + if (status == AnimationStatus.completed) { + Navigator.pop(context); + } + }); + animationController.forward(); + } + + @override + void dispose() { + animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + return BasePage( + title: widget.title, + titleAlignment: Alignment.topCenter, + titleLeading: const BackLeadingButton(), + scrollView: false, + body: BackgroundCard( + child: Column( + children: [ + const SizedBox(height: 20), + Center( + child: QrImage( + data: widget.data, + size: 200, + foregroundColor: Theme.of(context).colorScheme.onBackground, + ), + ), + const SizedBox(height: 10), + CopyButton( + onTap: () async { + await Clipboard.setData( + ClipboardData(text: widget.data), + ); + AlertMessage.showStateMessage( + context: context, + stateMessage: StateMessage.success( + stringMessage: l10n.copiedToClipboard, + ), + ); + }, + ), + Expanded( + child: Center( + child: AnimatedBuilder( + animation: animation, + builder: (BuildContext context, Widget? child) { + return Text( + timeFormatter(timeInSecond: animation.value.toInt()), + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.displayMedium, + ); + }, + ), + ), + ), + ], + ), + ), + ); + } +} 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 5f9041e91..d1e1ef83a 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 @@ -215,8 +215,8 @@ class _CredentialsDetailsViewState extends State { onPressed: () { Navigator.of(context).push( QrCodeDisplayPage.route( - widget.credentialModel.id, - widget.credentialModel, + title: '', + data: widget.credentialModel.shareLink, ), ); }, diff --git a/lib/dashboard/home/tab_bar/tab_controller/view/tab_controller_page.dart b/lib/dashboard/home/tab_bar/tab_controller/view/tab_controller_page.dart index 5a2870984..00a20834f 100644 --- a/lib/dashboard/home/tab_bar/tab_controller/view/tab_controller_page.dart +++ b/lib/dashboard/home/tab_bar/tab_controller/view/tab_controller_page.dart @@ -1,7 +1,6 @@ import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/l10n/l10n.dart'; -import 'package:altme/theme/theme.dart'; import 'package:altme/wallet/cubit/wallet_cubit.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; diff --git a/lib/dashboard/qr_code/qr_display/view/qr_display_page.dart b/lib/dashboard/qr_code/qr_display/view/qr_display_page.dart index cc84dfabd..8eaf5f28c 100644 --- a/lib/dashboard/qr_code/qr_display/view/qr_display_page.dart +++ b/lib/dashboard/qr_code/qr_display/view/qr_display_page.dart @@ -1,46 +1,60 @@ import 'package:altme/app/app.dart'; -import 'package:altme/dashboard/dashboard.dart'; +import 'package:altme/l10n/l10n.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:qr_flutter/qr_flutter.dart'; class QrCodeDisplayPage extends StatelessWidget { const QrCodeDisplayPage({ super.key, - required this.name, + required this.title, required this.data, }); - final String name; - final CredentialModel data; + final String title; + final String data; - static Route route(String name, CredentialModel data) => + static Route route({required String title, required String data}) => MaterialPageRoute( - builder: (context) => QrCodeDisplayPage(name: name, data: data), - settings: const RouteSettings(name: '/qrCodeDisplay'), + builder: (context) => QrCodeDisplayPage(data: data, title: title), + settings: const RouteSettings(name: '/QrCodeDisplayPage'), ); @override Widget build(BuildContext context) { + final l10n = context.l10n; return BasePage( - title: ' ', + title: title, + titleAlignment: Alignment.topCenter, titleLeading: const BackLeadingButton(), scrollView: false, - body: Column( - children: [ - Expanded( - child: Center( - child: Container( - alignment: Alignment.center, - padding: const EdgeInsets.all(32), - child: QrImage( - data: data.shareLink, - version: QrVersions.auto, - foregroundColor: Theme.of(context).colorScheme.primary, - ), + body: BackgroundCard( + child: Column( + children: [ + const SizedBox(height: 20), + Center( + child: QrImage( + data: data, + size: 200, + foregroundColor: Theme.of(context).colorScheme.onBackground, ), ), - ), - ], + const SizedBox(height: 10), + CopyButton( + onTap: () async { + await Clipboard.setData( + ClipboardData(text: data), + ); + AlertMessage.showStateMessage( + context: context, + stateMessage: StateMessage.success( + stringMessage: l10n.copiedToClipboard, + ), + ); + }, + ), + ], + ), ), ); } diff --git a/pubspec.lock b/pubspec.lock index 194c1b85d..db3c0e363 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2343,5 +2343,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.19.0 <3.7.0" + dart: ">=2.19.0 <3.0.0" flutter: ">=3.7.0-0" From 3057cfd0c4007ff19fb39afcaec4d3fa38419aab Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Thu, 16 Feb 2023 14:00:44 +0530 Subject: [PATCH 026/190] correct verifiable credential format #1366 --- packages/ebsi/lib/src/ebsi.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ebsi/lib/src/ebsi.dart b/packages/ebsi/lib/src/ebsi.dart index 3aa1029be..61dbad206 100644 --- a/packages/ebsi/lib/src/ebsi.dart +++ b/packages/ebsi/lib/src/ebsi.dart @@ -424,11 +424,12 @@ class Ebsi { 'id': 'http://example.org/presentations/talao/01', 'type': ['VerifiablePresentation'], 'holder': tokenParameters.didKey, - 'verifiableCredential': [jsonEncode(tokenParameters.jwtsOfCredentials)] + 'verifiableCredential': tokenParameters.jwtsOfCredentials }, 'nonce': tokenParameters.nonce }; final verifierVpJwt = generateToken(vpTokenPayload, tokenParameters); + return verifierVpJwt; } From d5901676e7db55f3fd108eeae59d87d4bbb72de1 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Thu, 16 Feb 2023 13:45:14 +0330 Subject: [PATCH 027/190] check if user is logged for matrix --- lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart index be26b145c..aed08f9c1 100644 --- a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart +++ b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart @@ -416,6 +416,8 @@ class LiveChatCubit extends Cubit { required String username, required String password, }) async { + final isLogged = client.isLogged(); + if (isLogged) return client.userID!; client.homeserver = Uri.parse(Urls.matrixHomeServer); final loginResonse = await client.login( LoginType.mLoginPassword, From 92b69e8bbed8734a381f8f42170d9788ec964f71 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Thu, 16 Feb 2023 17:35:41 +0530 Subject: [PATCH 028/190] attempt to present both eu diploma and email pass #1367 --- .../cubit/qr_code_scan_cubit.dart | 1 + .../verify_credentials_to_be_presented.dart | 46 +++++++++++++++++++ lib/scan/cubit/scan_cubit.dart | 17 +++++-- packages/ebsi/lib/src/ebsi.dart | 1 - 4 files changed, 59 insertions(+), 6 deletions(-) create mode 100644 lib/ebsi/verify_credentials_to_be_presented.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 3bb626ca9..bc6ffc543 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 @@ -308,6 +308,7 @@ class QRCodeScanCubit extends Cubit { emit(state.acceptHost(uri: uri!)); } } catch (e) { + log.e(e); if (e is MessageHandler) { emit(state.error(messageHandler: e)); } else { diff --git a/lib/ebsi/verify_credentials_to_be_presented.dart b/lib/ebsi/verify_credentials_to_be_presented.dart new file mode 100644 index 000000000..b8be91361 --- /dev/null +++ b/lib/ebsi/verify_credentials_to_be_presented.dart @@ -0,0 +1,46 @@ +import 'dart:convert'; + +import 'package:altme/app/app.dart'; +import 'package:altme/dashboard/dashboard.dart'; +import 'package:ebsi/ebsi.dart'; +import 'package:secure_storage/secure_storage.dart'; + +Future> verifyCredentialsToBePresented({ + required List credentialsToBePresented, + required Ebsi ebsi, +}) async { + final String p256PrivateKey = await getRandomP256PrivateKey(getSecureStorage); + + for (int i = 0; i < credentialsToBePresented.length; i++) { + if (credentialsToBePresented[i].jwt == null) { + final TokenParameters tokenParameters = TokenParameters( + jsonDecode(p256PrivateKey) as Map, + ); + + final Map credentialDatatMap = credentialsToBePresented[i] + .data + .map((key, value) => MapEntry(key, value as Object)); + + final jwt = ebsi.generateToken(credentialDatatMap, tokenParameters); + + final newCredentialModel = CredentialModel( + id: credentialsToBePresented[i].id, + image: credentialsToBePresented[i].image, + data: credentialsToBePresented[i].data, + shareLink: credentialsToBePresented[i].shareLink, + display: credentialsToBePresented[i].display, + credentialPreview: credentialsToBePresented[i].credentialPreview, + expirationDate: credentialsToBePresented[i].expirationDate, + credentialManifest: credentialsToBePresented[i].credentialManifest, + receivedId: credentialsToBePresented[i].receivedId, + challenge: credentialsToBePresented[i].challenge, + domain: credentialsToBePresented[i].domain, + activities: credentialsToBePresented[i].activities, + jwt: jwt, + ); + + credentialsToBePresented[i] = newCredentialModel; + } + } + return credentialsToBePresented; +} diff --git a/lib/scan/cubit/scan_cubit.dart b/lib/scan/cubit/scan_cubit.dart index 2ae676923..2e328ead8 100644 --- a/lib/scan/cubit/scan_cubit.dart +++ b/lib/scan/cubit/scan_cubit.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/dashboard/home/tab_bar/credentials/models/activity/activity.dart'; +import 'package:altme/ebsi/verify_credentials_to_be_presented.dart'; import 'package:altme/wallet/wallet.dart'; import 'package:bloc/bloc.dart'; import 'package:did_kit/did_kit.dart'; @@ -56,19 +57,25 @@ class ScanCubit extends Cubit { try { if (uri.queryParameters['scope'] == 'openid') { + final String p256PrivateKey = + await getRandomP256PrivateKey(secureStorageProvider); // final mnemonic = // await secureStorageProvider.get(SecureStorageKeys.ssiMnemonic); - final credentialList = credentialsToBePresented! + + final siopv2CredentialsToBePresented = + await verifyCredentialsToBePresented( + credentialsToBePresented: credentialsToBePresented!, + ebsi: ebsi, + ); + + final credentialList = siopv2CredentialsToBePresented .map((e) => jsonEncode(e.toJson())) .toList(); - final String p256PrivateKey = - await getRandomP256PrivateKey(secureStorageProvider); - await ebsi.sendPresentation(uri, credentialList, null, p256PrivateKey); await presentationActivity( - credentialModels: credentialsToBePresented, + credentialModels: siopv2CredentialsToBePresented, issuer: issuer, ); diff --git a/packages/ebsi/lib/src/ebsi.dart b/packages/ebsi/lib/src/ebsi.dart index 61dbad206..2aa191774 100644 --- a/packages/ebsi/lib/src/ebsi.dart +++ b/packages/ebsi/lib/src/ebsi.dart @@ -433,7 +433,6 @@ class Ebsi { return verifierVpJwt; } - @visibleForTesting String generateToken( Map vpTokenPayload, TokenParameters tokenParameters, From cdfe63f6c1cb300e7b90a65c3d234d959795b512 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Thu, 16 Feb 2023 19:34:17 +0530 Subject: [PATCH 029/190] file origanisation for test readability --- packages/ebsi/test/src/const_values.dart | 119 +++++++++ .../issuer_token_parameters_class.dart | 52 ++++ .../issuer_token_parameters_test.dart | 14 +- .../token_parameters_class.dart | 43 ++++ .../token_parameters_test.dart | 7 +- .../verifier_token_parameters_class.dart | 56 +++++ .../verifier_token_parameters_test.dart | 7 +- packages/ebsi/test/test_class.dart | 230 ------------------ 8 files changed, 288 insertions(+), 240 deletions(-) create mode 100644 packages/ebsi/test/src/const_values.dart create mode 100644 packages/ebsi/test/src/issuer_token_parameters/issuer_token_parameters_class.dart rename packages/ebsi/test/src/{ => issuer_token_parameters}/issuer_token_parameters_test.dart (80%) create mode 100644 packages/ebsi/test/src/token_parameters/token_parameters_class.dart rename packages/ebsi/test/src/{ => token_parameters}/token_parameters_test.dart (96%) create mode 100644 packages/ebsi/test/src/verifier_token_parameters/verifier_token_parameters_class.dart rename packages/ebsi/test/src/{ => verifier_token_parameters}/verifier_token_parameters_test.dart (99%) delete mode 100644 packages/ebsi/test/test_class.dart diff --git a/packages/ebsi/test/src/const_values.dart b/packages/ebsi/test/src/const_values.dart new file mode 100644 index 000000000..1fae13684 --- /dev/null +++ b/packages/ebsi/test/src/const_values.dart @@ -0,0 +1,119 @@ +const privateKey = { + 'crv': 'P-256K', + 'd': 'ccWWNSjGiv1iWlNh4kfhWvwG3yyQMe8o31Du0uKRzrs', + 'kty': 'EC', + 'x': 'J4vQtLUyrVUiFIXRrtEq4xurmBZp2eq9wJmXkIA_stI', + 'y': 'EUU6vXoG3BGX2zzwjXrGDcr4EyDD0Vfk3_5fg5kSgKE' +}; + +const privateKey2 = { + 'crv': 'P-256', + 'd': 'ccWWNSjGiv1iWlNh4kfhWvwG3yyQMe8o31Du0uKRzrs', + 'kty': 'EC', + 'x': 'J4vQtLUyrVUiFIXRrtEq4xurmBZp2eq9wJmXkIA_stI', + 'y': 'EUU6vXoG3BGX2zzwjXrGDcr4EyDD0Vfk3_5fg5kSgKE' +}; + +const publicJWK = { + 'crv': 'P-256K', + 'kty': 'EC', + 'x': 'J4vQtLUyrVUiFIXRrtEq4xurmBZp2eq9wJmXkIA_stI', + 'y': 'EUU6vXoG3BGX2zzwjXrGDcr4EyDD0Vfk3_5fg5kSgKE' +}; + +const keyWithAlg = { + 'crv': 'P-256K', + 'd': 'ccWWNSjGiv1iWlNh4kfhWvwG3yyQMe8o31Du0uKRzrs', + 'kty': 'EC', + 'x': 'J4vQtLUyrVUiFIXRrtEq4xurmBZp2eq9wJmXkIA_stI', + 'y': 'EUU6vXoG3BGX2zzwjXrGDcr4EyDD0Vfk3_5fg5kSgKE', + 'alg': 'HS256', +}; + +const didKey = 'did:ebsi:zo9FR1YfAKFP3Q6dvqhxcXxnfeDiJDP97kmnqhyAUSACj'; + +const kid = + '''did:ebsi:zo9FR1YfAKFP3Q6dvqhxcXxnfeDiJDP97kmnqhyAUSACj#Cgcg1y9xj9uWFw56PMc29XBd9EReixzvnftBz8JwQFiB'''; + +const ES256Alg = 'ES256'; + +const ES256KAlg = 'ES256K'; + +const HS256Alg = 'HS256'; + +const thumbprint = [ + 173, + 150, + 139, + 1, + 202, + 186, + 32, + 132, + 51, + 6, + 216, + 230, + 103, + 154, + 26, + 196, + 52, + 23, + 248, + 132, + 91, + 7, + 58, + 174, + 149, + 38, + 148, + 157, + 199, + 122, + 118, + 36, +]; + +const rfc7638Jwk = { + 'e': 'AQAB', + 'kty': 'RSA', + 'n': + // ignore: lines_longer_than_80_chars + '0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw' +}; +const expectedThumbprintForrfc7638Jwk = [ + 55, + 54, + 203, + 177, + 120, + 124, + 184, + 48, + 156, + 119, + 238, + 140, + 55, + 5, + 197, + 225, + 111, + 251, + 158, + 133, + 151, + 21, + 144, + 31, + 30, + 76, + 89, + 177, + 17, + 130, + 245, + 123 +]; diff --git a/packages/ebsi/test/src/issuer_token_parameters/issuer_token_parameters_class.dart b/packages/ebsi/test/src/issuer_token_parameters/issuer_token_parameters_class.dart new file mode 100644 index 000000000..c8ee0397a --- /dev/null +++ b/packages/ebsi/test/src/issuer_token_parameters/issuer_token_parameters_class.dart @@ -0,0 +1,52 @@ +import 'package:ebsi/ebsi.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../const_values.dart'; +import '../token_parameters/token_parameters_class.dart'; + +class IssuerTokenParameterTest extends TokenParameterTest { + final issuerTokenParameters = IssuerTokenParameters(privateKey, ''); + + @override + void publicKeyTest() { + expect(issuerTokenParameters.publicJWK, publicJWK); + } + + @override + void didTest() { + expect(tokenParameters.didKey, didKey); + } + + @override + void keyIdTest() { + expect(tokenParameters.kid, kid); + } + + @override + void algorithmIsES256KTest() { + expect(tokenParameters.alg, ES256KAlg); + } + + @override + void algorithmIsES256Test() { + final tokenParameters = IssuerTokenParameters(privateKey2, ''); + expect(tokenParameters.alg, ES256Alg); + } + + @override + void algorithmIsNotNullTest() { + final tokenParameters = IssuerTokenParameters(keyWithAlg, ''); + expect(tokenParameters.alg, HS256Alg); + } + + @override + void thumprintOfKey() { + expect(tokenParameters.thumbprint, thumbprint); + } + + @override + void thumprintOfKeyForrfc7638() { + final tokenParameters2 = IssuerTokenParameters(rfc7638Jwk, ''); + expect(tokenParameters2.thumbprint, expectedThumbprintForrfc7638Jwk); + } +} diff --git a/packages/ebsi/test/src/issuer_token_parameters_test.dart b/packages/ebsi/test/src/issuer_token_parameters/issuer_token_parameters_test.dart similarity index 80% rename from packages/ebsi/test/src/issuer_token_parameters_test.dart rename to packages/ebsi/test/src/issuer_token_parameters/issuer_token_parameters_test.dart index cc25ba0b6..5de3e5960 100644 --- a/packages/ebsi/test/src/issuer_token_parameters_test.dart +++ b/packages/ebsi/test/src/issuer_token_parameters/issuer_token_parameters_test.dart @@ -1,13 +1,6 @@ -// Copyright (c) 2022, Very Good Ventures -// https://verygood.ventures -// -// Use of this source code is governed by an MIT-style -// license that can be found in the LICENSE file or at -// https://opensource.org/licenses/MIT. - import 'package:flutter_test/flutter_test.dart'; -import '../test_class.dart'; +import 'issuer_token_parameters_class.dart'; void main() { group('override test', () { @@ -32,6 +25,11 @@ void main() { "algorithm is ES256 when key's curve is P-256", issuerTokenParameterTest.algorithmIsES256Test, ); + + test( + 'if alg is not null then return as it is', + issuerTokenParameterTest.algorithmIsNotNullTest, + ); }); group('thumbprint test', () { diff --git a/packages/ebsi/test/src/token_parameters/token_parameters_class.dart b/packages/ebsi/test/src/token_parameters/token_parameters_class.dart new file mode 100644 index 000000000..3c1d6deef --- /dev/null +++ b/packages/ebsi/test/src/token_parameters/token_parameters_class.dart @@ -0,0 +1,43 @@ +import 'package:ebsi/src/token_parameters.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../const_values.dart'; + +class TokenParameterTest { + final tokenParameters = TokenParameters(privateKey); + + void publicKeyTest() { + expect(tokenParameters.publicJWK, publicJWK); + } + + void didTest() { + expect(tokenParameters.didKey, didKey); + } + + void keyIdTest() { + expect(tokenParameters.kid, kid); + } + + void algorithmIsES256KTest() { + expect(tokenParameters.alg, ES256KAlg); + } + + void algorithmIsES256Test() { + final tokenParameters = TokenParameters(privateKey2); + expect(tokenParameters.alg, ES256Alg); + } + + void algorithmIsNotNullTest() { + final tokenParameters = TokenParameters(keyWithAlg); + expect(tokenParameters.alg, HS256Alg); + } + + void thumprintOfKey() { + expect(tokenParameters.thumbprint, thumbprint); + } + + void thumprintOfKeyForrfc7638() { + final tokenParameters = TokenParameters(rfc7638Jwk); + expect(tokenParameters.thumbprint, expectedThumbprintForrfc7638Jwk); + } +} diff --git a/packages/ebsi/test/src/token_parameters_test.dart b/packages/ebsi/test/src/token_parameters/token_parameters_test.dart similarity index 96% rename from packages/ebsi/test/src/token_parameters_test.dart rename to packages/ebsi/test/src/token_parameters/token_parameters_test.dart index b9f5da79f..59003c46a 100644 --- a/packages/ebsi/test/src/token_parameters_test.dart +++ b/packages/ebsi/test/src/token_parameters/token_parameters_test.dart @@ -12,7 +12,7 @@ import 'package:ebsi/ebsi.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -import '../test_class.dart'; +import 'token_parameters_class.dart'; class MockDio extends Mock implements Dio {} @@ -39,6 +39,11 @@ void main() { "algorithm is ES256 when key's curve is P-256", tokenParametersTest.algorithmIsES256Test, ); + + test( + 'if alg is not null then return as it is', + tokenParametersTest.algorithmIsNotNullTest, + ); }); group('thumbprint test', () { diff --git a/packages/ebsi/test/src/verifier_token_parameters/verifier_token_parameters_class.dart b/packages/ebsi/test/src/verifier_token_parameters/verifier_token_parameters_class.dart new file mode 100644 index 000000000..80fef2d45 --- /dev/null +++ b/packages/ebsi/test/src/verifier_token_parameters/verifier_token_parameters_class.dart @@ -0,0 +1,56 @@ +import 'package:ebsi/ebsi.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../const_values.dart'; +import '../token_parameters/token_parameters_class.dart'; + +class VerifierTokenParametersTest extends TokenParameterTest { + final verifierTokenParameters = + VerifierTokenParameters(privateKey, Uri.parse(''), []); + + @override + void publicKeyTest() { + expect(verifierTokenParameters.publicJWK, publicJWK); + } + + @override + void didTest() { + expect(tokenParameters.didKey, didKey); + } + + @override + void keyIdTest() { + expect(tokenParameters.kid, kid); + } + + @override + void algorithmIsES256KTest() { + expect(tokenParameters.alg, ES256KAlg); + } + + @override + void algorithmIsES256Test() { + final tokenParameters = + VerifierTokenParameters(privateKey2, Uri.parse(''), []); + expect(tokenParameters.alg, ES256Alg); + } + + @override + void algorithmIsNotNullTest() { + final tokenParameters = + VerifierTokenParameters(keyWithAlg, Uri.parse(''), []); + expect(tokenParameters.alg, HS256Alg); + } + + @override + void thumprintOfKey() { + expect(tokenParameters.thumbprint, thumbprint); + } + + @override + void thumprintOfKeyForrfc7638() { + final tokenParameters2 = + VerifierTokenParameters(rfc7638Jwk, Uri.parse(''), []); + expect(tokenParameters2.thumbprint, expectedThumbprintForrfc7638Jwk); + } +} diff --git a/packages/ebsi/test/src/verifier_token_parameters_test.dart b/packages/ebsi/test/src/verifier_token_parameters/verifier_token_parameters_test.dart similarity index 99% rename from packages/ebsi/test/src/verifier_token_parameters_test.dart rename to packages/ebsi/test/src/verifier_token_parameters/verifier_token_parameters_test.dart index 58b67003f..ed2b4cfb1 100644 --- a/packages/ebsi/test/src/verifier_token_parameters_test.dart +++ b/packages/ebsi/test/src/verifier_token_parameters/verifier_token_parameters_test.dart @@ -8,7 +8,7 @@ import 'package:ebsi/ebsi.dart'; import 'package:flutter_test/flutter_test.dart'; -import '../test_class.dart'; +import 'verifier_token_parameters_class.dart'; void main() { group('Verifier TokenParameters', () { @@ -75,6 +75,11 @@ void main() { "algorithm is ES256 when key's curve is P-256", verifierTokenParametersTest.algorithmIsES256Test, ); + + test( + 'if alg is not null then return as it is', + verifierTokenParametersTest.algorithmIsNotNullTest, + ); }); group('thumbprint test', () { diff --git a/packages/ebsi/test/test_class.dart b/packages/ebsi/test/test_class.dart deleted file mode 100644 index b97aca614..000000000 --- a/packages/ebsi/test/test_class.dart +++ /dev/null @@ -1,230 +0,0 @@ -import 'package:ebsi/ebsi.dart'; -import 'package:flutter_test/flutter_test.dart'; - -const privateKey = { - 'crv': 'P-256K', - 'd': 'ccWWNSjGiv1iWlNh4kfhWvwG3yyQMe8o31Du0uKRzrs', - 'kty': 'EC', - 'x': 'J4vQtLUyrVUiFIXRrtEq4xurmBZp2eq9wJmXkIA_stI', - 'y': 'EUU6vXoG3BGX2zzwjXrGDcr4EyDD0Vfk3_5fg5kSgKE' -}; - -const privateKey2 = { - 'crv': 'P-256', - 'd': 'ccWWNSjGiv1iWlNh4kfhWvwG3yyQMe8o31Du0uKRzrs', - 'kty': 'EC', - 'x': 'J4vQtLUyrVUiFIXRrtEq4xurmBZp2eq9wJmXkIA_stI', - 'y': 'EUU6vXoG3BGX2zzwjXrGDcr4EyDD0Vfk3_5fg5kSgKE' -}; - -const publicJWK = { - 'crv': 'P-256K', - 'kty': 'EC', - 'x': 'J4vQtLUyrVUiFIXRrtEq4xurmBZp2eq9wJmXkIA_stI', - 'y': 'EUU6vXoG3BGX2zzwjXrGDcr4EyDD0Vfk3_5fg5kSgKE' -}; - -const didKey = 'did:ebsi:zo9FR1YfAKFP3Q6dvqhxcXxnfeDiJDP97kmnqhyAUSACj'; - -const kid = - '''did:ebsi:zo9FR1YfAKFP3Q6dvqhxcXxnfeDiJDP97kmnqhyAUSACj#Cgcg1y9xj9uWFw56PMc29XBd9EReixzvnftBz8JwQFiB'''; - -const ES256Alg = 'ES256'; - -const ES256KAlg = 'ES256K'; - -const thumbprint = [ - 173, - 150, - 139, - 1, - 202, - 186, - 32, - 132, - 51, - 6, - 216, - 230, - 103, - 154, - 26, - 196, - 52, - 23, - 248, - 132, - 91, - 7, - 58, - 174, - 149, - 38, - 148, - 157, - 199, - 122, - 118, - 36, -]; - -const rfc7638Jwk = { - 'e': 'AQAB', - 'kty': 'RSA', - 'n': - // ignore: lines_longer_than_80_chars - '0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw' -}; -const expectedThumbprintForrfc7638Jwk = [ - 55, - 54, - 203, - 177, - 120, - 124, - 184, - 48, - 156, - 119, - 238, - 140, - 55, - 5, - 197, - 225, - 111, - 251, - 158, - 133, - 151, - 21, - 144, - 31, - 30, - 76, - 89, - 177, - 17, - 130, - 245, - 123 -]; - -class TokenParameterTest { - final tokenParameters = TokenParameters(privateKey); - - void publicKeyTest() { - expect(tokenParameters.publicJWK, publicJWK); - } - - void didTest() { - expect(tokenParameters.didKey, didKey); - } - - void keyIdTest() { - expect(tokenParameters.kid, kid); - } - - void algorithmIsES256KTest() { - expect(tokenParameters.alg, ES256KAlg); - } - - void algorithmIsES256Test() { - final tokenParameters = TokenParameters(privateKey2); - expect(tokenParameters.alg, ES256Alg); - } - - void thumprintOfKey() { - expect(tokenParameters.thumbprint, thumbprint); - } - - void thumprintOfKeyForrfc7638() { - final tokenParameters2 = TokenParameters(rfc7638Jwk); - expect(tokenParameters2.thumbprint, expectedThumbprintForrfc7638Jwk); - } -} - -class IssuerTokenParameterTest extends TokenParameterTest { - final issuerTokenParameters = IssuerTokenParameters(privateKey, ''); - - @override - void publicKeyTest() { - expect(issuerTokenParameters.publicJWK, publicJWK); - } - - @override - void didTest() { - expect(tokenParameters.didKey, didKey); - } - - @override - void keyIdTest() { - expect(tokenParameters.kid, kid); - } - - @override - void algorithmIsES256KTest() { - expect(tokenParameters.alg, ES256KAlg); - } - - @override - void algorithmIsES256Test() { - final tokenParameters = IssuerTokenParameters(privateKey2, ''); - expect(tokenParameters.alg, ES256Alg); - } - - @override - void thumprintOfKey() { - expect(tokenParameters.thumbprint, thumbprint); - } - - @override - void thumprintOfKeyForrfc7638() { - final tokenParameters2 = IssuerTokenParameters(rfc7638Jwk, ''); - expect(tokenParameters2.thumbprint, expectedThumbprintForrfc7638Jwk); - } -} - -class VerifierTokenParametersTest extends TokenParameterTest { - final verifierTokenParameters = - VerifierTokenParameters(privateKey, Uri.parse(''), []); - - @override - void publicKeyTest() { - expect(verifierTokenParameters.publicJWK, publicJWK); - } - - @override - void didTest() { - expect(tokenParameters.didKey, didKey); - } - - @override - void keyIdTest() { - expect(tokenParameters.kid, kid); - } - - @override - void algorithmIsES256KTest() { - expect(tokenParameters.alg, ES256KAlg); - } - - @override - void algorithmIsES256Test() { - final tokenParameters = - VerifierTokenParameters(privateKey2, Uri.parse(''), []); - expect(tokenParameters.alg, ES256Alg); - } - - @override - void thumprintOfKey() { - expect(tokenParameters.thumbprint, thumbprint); - } - - @override - void thumprintOfKeyForrfc7638() { - final tokenParameters2 = - VerifierTokenParameters(rfc7638Jwk, Uri.parse(''), []); - expect(tokenParameters2.thumbprint, expectedThumbprintForrfc7638Jwk); - } -} From e12456d84208d885f0ce7755b2ae3cd308d80529 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Thu, 16 Feb 2023 21:38:30 +0530 Subject: [PATCH 030/190] added test - 81.7percent coverage --- packages/ebsi/test/src/ebsi_test.dart | 254 +++++++++++++++++++++----- 1 file changed, 204 insertions(+), 50 deletions(-) diff --git a/packages/ebsi/test/src/ebsi_test.dart b/packages/ebsi/test/src/ebsi_test.dart index b12eebe52..63c9ba3d7 100644 --- a/packages/ebsi/test/src/ebsi_test.dart +++ b/packages/ebsi/test/src/ebsi_test.dart @@ -19,17 +19,6 @@ import 'package:mocktail/mocktail.dart'; class MockDio extends Mock implements Dio {} void main() { - // const wellKnownContent = - // r'{"authorization_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/authorize","batch_credential_endpoint":null,"credential_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/credential","credential_issuer":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl","credential_manifests":[{"id":"VerifiableDiploma_1","issuer":{"id":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","name":"Test EBSILUX"},"output_descriptors":[{"display":{"description":{"fallback":"This card is a proof that you passed this diploma successfully. You can use this card when you need to prove this information to services that have adopted EU EBSI framework.","path":[],"schema":{"type":"string"}},"properties":[{"fallback":"Unknown","label":"First name","path":["$.credentialSubject.firstName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Last name","path":["$.credentialSubject.familyName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Birth date","path":["$.credentialSubject.dateOfBirth"],"schema":{"format":"date","type":"string"}},{"fallback":"Unknown","label":"Grading scheme","path":["$.credentialSubject.gradingScheme.title"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Title","path":["$.credentialSubject.learningAchievement.title"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Description","path":["$.credentialSubject.learningAchievement.description"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"ECTS Points","path":["$.credentialSubject.learningSpecification.ectsCreditPoints"],"schema":{"type":"number"}},{"fallback":"Unknown","label":"Issue date","path":["$.issuanceDate"],"schema":{"format":"date","type":"string"}},{"fallback":"Unknown","label":"Issued by","path":["$.credentialSubject.awardingOpportunity.awardingBody.preferredName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Registration","path":["$.credentialSubject.awardingOpportunity.awardingBody.registration"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Website","path":["$.credentialSubject.awardingOpportunity.awardingBody.homepage"],"schema":{"format":"uri","type":"string"}}],"subtitle":{"fallback":"EBSI Verifiable diploma","path":[],"schema":{"type":"string"}},"title":{"fallback":"Diploma","path":[],"schema":{"type":"string"}}},"id":"diploma_01","schema":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"}],"spec_version":"https://identity.foundation/credential-manifest/spec/v1.0.0/"}],"credential_supported":[{"cryptographic_binding_methods_supported":["did"],"cryptographic_suites_supported":["ES256K","ES256","ES384","ES512","RS256"],"display":[{"locale":"en-US","name":"Issuer Talao"}],"format":"jwt_vc","id":"VerifiableDiploma","types":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"}],"pre-authorized_grant_anonymous_access_supported":false,"subject_syntax_types_supported":["did:ebsi"],"token_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/token"}'; - // const initialQrCodeUrl = 'https://talao.co/sandbox/ebsi/issuer/vgvghylozl'; - // const QRCodeContent = - // 'openid://initiate_issuance?issuer=https://talao.co/sandbox/ebsi/issuer/vgvghylozl&credential_type=https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd&op_stat=46cc5d84-9b29-11ed-ae36-0a1628958560'; - // const authorizationEndpoint = - // 'https://talao.co/sandbox/ebsi/issuer/vgvghylozl/authorize'; - // const tokenEndpoint = 'https://talao.co/sandbox/ebsi/issuer/vgvghylozl/token'; - // const credentialEndpoint = - // 'https://talao.co/sandbox/ebsi/issuer/vgvghylozl/credential'; - final client = Dio(); final dioAdapter = DioAdapter(dio: Dio(BaseOptions()), matcher: const UrlRequestMatcher()); @@ -41,6 +30,11 @@ void main() { const issuer = 'https://talao.co/sandbox/ebsi/issuer/vgvghylozl/.well-known/openid-configuration'; + const givenOpenIdRequest = + 'openid://initiate_issuance?issuer=https%3A%2F%2Ftalao.co%2Fsandbox%2Febsi%2Fissuer%2Fvgvghylozl&credential_type=https%3A%2F%2Fapi.preprod.ebsi.eu%2Ftrusted-schemas-registry%2Fv1%2Fschemas%2F0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd&issuer_state=c8d25b7e-9bd2-11ed-9d05-0a1628958560'; + + const tokenUrl = 'https://talao.co/sandbox/ebsi/issuer/vgvghylozl/token'; + test('EBSI class can be instantiated', () { final ebsi = Ebsi(client); expect(ebsi, isNotNull); @@ -96,9 +90,23 @@ void main() { expect(jsonDecode(jwk), expectedJwk); }); - test('privateKey from mnemonic', () async { - final jwk = await ebsi.getPrivateKey(mnemonic, null); - expect(jwk, expectedJwk); + group('getPrivateKey', () { + test('privateKey from mnemonic', () async { + final jwk = await ebsi.getPrivateKey(mnemonic, null); + expect(jwk, expectedJwk); + }); + + test('privateKey from mnemonic', () async { + const key = { + 'crv': 'P-256K', + 'd': 'ccWWNSjGiv1iWlNh4kfhWvwG3yyQMe8o31Du0uKRzrs', + 'kty': 'EC', + 'x': 'J4vQtLUyrVUiFIXRrtEq4xurmBZp2eq9wJmXkIA_stI', + 'y': 'EUU6vXoG3BGX2zzwjXrGDcr4EyDD0Vfk3_5fg5kSgKE' + }; + final jwk = await ebsi.getPrivateKey(null, jsonEncode(key)); + expect(jwk, expectedJwk); + }); }); test('JWK from seeds', () { @@ -122,33 +130,69 @@ void main() { }); group('EBSI: getAuthorizationUriForIssuer', () { - const givenOpenIdRequest = - 'openid://initiate_issuance?issuer=https%3A%2F%2Ftalao.co%2Fsandbox%2Febsi%2Fissuer%2Fvgvghylozl&credential_type=https%3A%2F%2Fapi.preprod.ebsi.eu%2Ftrusted-schemas-registry%2Fv1%2Fschemas%2F0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd&issuer_state=c8d25b7e-9bd2-11ed-9d05-0a1628958560'; const givenredirectUrl = 'app.altme.io/app/download/callback'; test( - 'given Url of openid request we return Uri for authentication endpoint', - () async { - const openidConfiguration = - r'{"authorization_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/authorize","batch_credential_endpoint":null,"credential_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/credential","credential_issuer":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl","credential_manifests":[{"id":"VerifiableDiploma_1","issuer":{"id":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","name":"Test EBSILUX"},"output_descriptors":[{"display":{"description":{"fallback":"This card is a proof that you passed this diploma successfully. You can use this card when you need to prove this information to services that have adopted EU EBSI framework.","path":[],"schema":{"type":"string"}},"properties":[{"fallback":"Unknown","label":"First name","path":["$.credentialSubject.firstName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Last name","path":["$.credentialSubject.familyName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Birth date","path":["$.credentialSubject.dateOfBirth"],"schema":{"format":"date","type":"string"}},{"fallback":"Unknown","label":"Grading scheme","path":["$.credentialSubject.gradingScheme.title"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Title","path":["$.credentialSubject.learningAchievement.title"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Description","path":["$.credentialSubject.learningAchievement.description"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"ECTS Points","path":["$.credentialSubject.learningSpecification.ectsCreditPoints"],"schema":{"type":"number"}},{"fallback":"Unknown","label":"Issue date","path":["$.issuanceDate"],"schema":{"format":"date","type":"string"}},{"fallback":"Unknown","label":"Issued by","path":["$.credentialSubject.awardingOpportunity.awardingBody.preferredName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Registration","path":["$.credentialSubject.awardingOpportunity.awardingBody.registration"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Website","path":["$.credentialSubject.awardingOpportunity.awardingBody.homepage"],"schema":{"format":"uri","type":"string"}}],"subtitle":{"fallback":"EBSI Verifiable diploma","path":[],"schema":{"type":"string"}},"title":{"fallback":"Diploma","path":[],"schema":{"type":"string"}}},"id":"diploma_01","schema":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"}],"spec_version":"https://identity.foundation/credential-manifest/spec/v1.0.0/"}],"credential_supported":[{"cryptographic_binding_methods_supported":["did"],"cryptographic_suites_supported":["ES256K","ES256","ES384","ES512","RS256"],"display":[{"locale":"en-US","name":"Issuer Talao"}],"format":"jwt_vc","id":"VerifiableDiploma","types":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"}],"pre-authorized_grant_anonymous_access_supported":false,"subject_syntax_types_supported":["did:ebsi"],"token_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/token"}'; - final expectedAuthorizationEndpointdUri = Uri.parse( - 'https://talao.co/sandbox/ebsi/issuer/vgvghylozl/authorize?scope=openid&client_id=app.altme.io%2Fapp%2Fdownload%2Fcallback&response_type=code&authorization_details=%5B%7B%22type%22%3A%22openid_credential%22%2C%22credential_type%22%3A%22https%3A%2F%2Fapi.preprod.ebsi.eu%2Ftrusted-schemas-registry%2Fv1%2Fschemas%2F0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd%22%2C%22format%22%3A%22jwt_vc%22%7D%5D&redirect_uri=app.altme.io%2Fapp%2Fdownload%2Fcallback%3Fcredential_type%3Dhttps%3A%2F%2Fapi.preprod.ebsi.eu%2Ftrusted-schemas-registry%2Fv1%2Fschemas%2F0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd%26issuer%3Dhttps%3A%2F%2Ftalao.co%2Fsandbox%2Febsi%2Fissuer%2Fvgvghylozl&state&op_state', - ); + 'given Url of openid request we return Uri for authentication endpoint', + () async { + const openidConfiguration = + r'{"authorization_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/authorize","batch_credential_endpoint":null,"credential_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/credential","credential_issuer":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl","credential_manifests":[{"id":"VerifiableDiploma_1","issuer":{"id":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","name":"Test EBSILUX"},"output_descriptors":[{"display":{"description":{"fallback":"This card is a proof that you passed this diploma successfully. You can use this card when you need to prove this information to services that have adopted EU EBSI framework.","path":[],"schema":{"type":"string"}},"properties":[{"fallback":"Unknown","label":"First name","path":["$.credentialSubject.firstName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Last name","path":["$.credentialSubject.familyName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Birth date","path":["$.credentialSubject.dateOfBirth"],"schema":{"format":"date","type":"string"}},{"fallback":"Unknown","label":"Grading scheme","path":["$.credentialSubject.gradingScheme.title"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Title","path":["$.credentialSubject.learningAchievement.title"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Description","path":["$.credentialSubject.learningAchievement.description"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"ECTS Points","path":["$.credentialSubject.learningSpecification.ectsCreditPoints"],"schema":{"type":"number"}},{"fallback":"Unknown","label":"Issue date","path":["$.issuanceDate"],"schema":{"format":"date","type":"string"}},{"fallback":"Unknown","label":"Issued by","path":["$.credentialSubject.awardingOpportunity.awardingBody.preferredName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Registration","path":["$.credentialSubject.awardingOpportunity.awardingBody.registration"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Website","path":["$.credentialSubject.awardingOpportunity.awardingBody.homepage"],"schema":{"format":"uri","type":"string"}}],"subtitle":{"fallback":"EBSI Verifiable diploma","path":[],"schema":{"type":"string"}},"title":{"fallback":"Diploma","path":[],"schema":{"type":"string"}}},"id":"diploma_01","schema":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"}],"spec_version":"https://identity.foundation/credential-manifest/spec/v1.0.0/"}],"credential_supported":[{"cryptographic_binding_methods_supported":["did"],"cryptographic_suites_supported":["ES256K","ES256","ES384","ES512","RS256"],"display":[{"locale":"en-US","name":"Issuer Talao"}],"format":"jwt_vc","id":"VerifiableDiploma","types":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"}],"pre-authorized_grant_anonymous_access_supported":false,"subject_syntax_types_supported":["did:ebsi"],"token_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/token"}'; + final expectedAuthorizationEndpointdUri = Uri.parse( + 'https://talao.co/sandbox/ebsi/issuer/vgvghylozl/authorize?scope=openid&client_id=app.altme.io%2Fapp%2Fdownload%2Fcallback&response_type=code&authorization_details=%5B%7B%22type%22%3A%22openid_credential%22%2C%22credential_type%22%3A%22https%3A%2F%2Fapi.preprod.ebsi.eu%2Ftrusted-schemas-registry%2Fv1%2Fschemas%2F0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd%22%2C%22format%22%3A%22jwt_vc%22%7D%5D&redirect_uri=app.altme.io%2Fapp%2Fdownload%2Fcallback%3Fcredential_type%3Dhttps%3A%2F%2Fapi.preprod.ebsi.eu%2Ftrusted-schemas-registry%2Fv1%2Fschemas%2F0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd%26issuer%3Dhttps%3A%2F%2Ftalao.co%2Fsandbox%2Febsi%2Fissuer%2Fvgvghylozl&state&op_state', + ); + + dioAdapter.onGet( + issuer, + (request) => request.reply(200, jsonDecode(openidConfiguration)), + ); + final ebsi = Ebsi(client); + + final authorizationEndpointdUri = + await ebsi.getAuthorizationUriForIssuer( + givenOpenIdRequest, + givenredirectUrl, + ); + expect(authorizationEndpointdUri, expectedAuthorizationEndpointdUri); + }, + ); - dioAdapter.onGet( - issuer, - (request) => request.reply(200, jsonDecode(openidConfiguration)), - ); - final ebsi = Ebsi(client); + test( + 'throw Exception with when request is not ebsi initiate issuance url', + () async { + final ebsi = Ebsi(client); + + expect( + () async => ebsi.getAuthorizationUriForIssuer( + 'www.example.com', + givenredirectUrl, + ), + throwsA( + isA().having( + (p0) => p0.toString(), + 'toString()', + 'Exception: Not a valid openid url to initiate issuance', + ), + ), + ); + }, + ); - final authorizationEndpointdUri = await ebsi.getAuthorizationUriForIssuer( - givenOpenIdRequest, - givenredirectUrl, - ); - expect(authorizationEndpointdUri, expectedAuthorizationEndpointdUri); - }); + test( + 'throw Exception when random url is passed', + () async { + final ebsi = Ebsi(client); + + expect( + () async => ebsi.getAuthorizationUriForIssuer( + 'openid://initiate_issuance?', + givenredirectUrl, + ), + throwsA(isA()), + ); + }, + ); - test('get correct issuer from openid request', () async { + test('get correct issuer from openid request', () { const expectedIssuer = 'https://talao.co/sandbox/ebsi/issuer/vgvghylozl'; final ebsi = Ebsi(client); @@ -158,7 +202,7 @@ void main() { expect(issuer, expectedIssuer); }); - test('get correct Autorization RequestParameters', () async { + test('get correct Autorization RequestParameters', () { const parameters = { 'scope': 'openid', 'client_id': 'app.altme.io/app/download/callback', @@ -206,11 +250,12 @@ void main() { 'https://app.altme.io/app/download?credential_type=https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd&issuer=https://talao.co/sandbox/ebsi/issuer/vgvghylozl?code=cb803d46-9c88-11ed-bdb3-0a1628958560&state=0d189873-9c87-11ed-8dbf-0a1628958560', ); + final credentialRequestWithPreAuthorizedCode = Uri.parse( + 'openid://initiate_issuance?issuer=https%3A%2F%2Ftalao.co%2Fsandbox%2Febsi%2Fissuer%2Fvgvghylozl&credential_type=https%3A%2F%2Fapi.preprod.ebsi.eu%2Ftrusted-schemas-registry%2Fv1%2Fschemas%2F0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd&op_state=test&pre-authorized_code=ff8e73c5-ae07-11ed-b1f7-0a1628958560&user_pin_required=False'); + const issuerResponse = r'{"authorization_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/authorize","batch_credential_endpoint":null,"credential_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/credential","credential_issuer":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl","credential_manifests":[{"id":"VerifiableDiploma_1","issuer":{"id":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","name":"Test EBSILUX"},"output_descriptors":[{"display":{"description":{"fallback":"This card is a proof that you passed this diploma successfully. You can use this card when you need to prove this information to services that have adopted EU EBSI framework.","path":[],"schema":{"type":"string"}},"properties":[{"fallback":"Unknown","label":"First name","path":["$.credentialSubject.firstName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Last name","path":["$.credentialSubject.familyName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Birth date","path":["$.credentialSubject.dateOfBirth"],"schema":{"format":"date","type":"string"}},{"fallback":"Unknown","label":"Grading scheme","path":["$.credentialSubject.gradingScheme.title"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Title","path":["$.credentialSubject.learningAchievement.title"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Description","path":["$.credentialSubject.learningAchievement.description"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"ECTS Points","path":["$.credentialSubject.learningSpecification.ectsCreditPoints"],"schema":{"type":"number"}},{"fallback":"Unknown","label":"Issue date","path":["$.issuanceDate"],"schema":{"format":"date","type":"string"}},{"fallback":"Unknown","label":"Issued by","path":["$.credentialSubject.awardingOpportunity.awardingBody.preferredName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Registration","path":["$.credentialSubject.awardingOpportunity.awardingBody.registration"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Website","path":["$.credentialSubject.awardingOpportunity.awardingBody.homepage"],"schema":{"format":"uri","type":"string"}}],"subtitle":{"fallback":"EBSI Verifiable diploma","path":[],"schema":{"type":"string"}},"title":{"fallback":"Diploma","path":[],"schema":{"type":"string"}}},"id":"diploma_01","schema":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"}],"spec_version":"https://identity.foundation/credential-manifest/spec/v1.0.0/"}],"credential_supported":[{"cryptographic_binding_methods_supported":["did"],"cryptographic_suites_supported":["ES256K","ES256","ES384","ES512","RS256"],"display":[{"locale":"en-US","name":"Issuer Talao"}],"format":"jwt_vc","id":"VerifiableDiploma","types":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"}],"pre-authorized_grant_anonymous_access_supported":true,"subject_syntax_types_supported":["did:ebsi"],"token_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/token"}'; - const tokenUrl = 'https://talao.co/sandbox/ebsi/issuer/vgvghylozl/token'; - const tokenResponse = '{"access_token":"7a07dd19-a879-11ed-ad95-0a1628958560","c_nonce":"7a07de0f-a879-11ed-822b-0a1628958560","token_type":"Bearer","expires_in":1000}'; // ignore: lines_longer_than_80_chars @@ -250,20 +295,43 @@ void main() { expect(jsonEncode(credential), credentialRequestResponse); }); - test('get token data with credentialRequestUri', () async { - const expectedTokenData = - '{"code":"cb803d46-9c88-11ed-bdb3-0a1628958560","grant_type":"authorization_code"}'; // ignore: lines_longer_than_80_chars - final tokenData = ebsi.buildTokenData( - credentialRequest, - ); - expect(jsonEncode(tokenData), expectedTokenData); + group('build token data', () { + test('get token data with credentialRequestUri', () async { + const expectedTokenData = + '{"code":"cb803d46-9c88-11ed-bdb3-0a1628958560","grant_type":"authorization_code"}'; // ignore: lines_longer_than_80_chars + final tokenData = ebsi.buildTokenData( + credentialRequest, + ); + expect(jsonEncode(tokenData), expectedTokenData); + }); + + test('get token data with credentialRequestUri', () { + const expectedTokenData = + '{"pre-authorized_code":"ff8e73c5-ae07-11ed-b1f7-0a1628958560","grant_type":"urn:ietf:params:oauth:grant-type:pre-authorized_code"}'; // ignore: lines_longer_than_80_chars + final tokenData = + ebsi.buildTokenData(credentialRequestWithPreAuthorizedCode); + expect(jsonEncode(tokenData), expectedTokenData); + }); }); - test('get issuer with credentialRequestUri', () async { - const expectedIssuer = 'https://talao.co/sandbox/ebsi/issuer/vgvghylozl'; - final issuer = ebsi.getIssuer(credentialRequest); - expect(expectedIssuer, issuer); + group('getIssuer', () { + test('get issuer with credentialRequestUri', () { + const expectedIssuer = + 'https://talao.co/sandbox/ebsi/issuer/vgvghylozl'; + final issuer = ebsi.getIssuer(credentialRequest); + expect(expectedIssuer, issuer); + }); + + test( + 'get issuer with credentialRequestUri when PreAuthorizedCode is given', + () { + const expectedIssuer = + 'https://talao.co/sandbox/ebsi/issuer/vgvghylozl'; + final issuer = ebsi.getIssuer(credentialRequestWithPreAuthorizedCode); + expect(expectedIssuer, issuer); + }); }); + test('get readTokenEndPoint with openidConfigurationResponse', () async { const openidConfigurationResponse = r'{"authorization_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/authorize","batch_credential_endpoint":null,"credential_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/credential","credential_issuer":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl","credential_manifests":[{"id":"VerifiableDiploma_1","issuer":{"id":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","name":"Test EBSILUX"},"output_descriptors":[{"display":{"description":{"fallback":"This card is a proof that you passed this diploma successfully. You can use this card when you need to prove this information to services that have adopted EU EBSI framework.","path":[],"schema":{"type":"string"}},"properties":[{"fallback":"Unknown","label":"First name","path":["$.credentialSubject.firstName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Last name","path":["$.credentialSubject.familyName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Birth date","path":["$.credentialSubject.dateOfBirth"],"schema":{"format":"date","type":"string"}},{"fallback":"Unknown","label":"Grading scheme","path":["$.credentialSubject.gradingScheme.title"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Title","path":["$.credentialSubject.learningAchievement.title"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Description","path":["$.credentialSubject.learningAchievement.description"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"ECTS Points","path":["$.credentialSubject.learningSpecification.ectsCreditPoints"],"schema":{"type":"number"}},{"fallback":"Unknown","label":"Issue date","path":["$.issuanceDate"],"schema":{"format":"date","type":"string"}},{"fallback":"Unknown","label":"Issued by","path":["$.credentialSubject.awardingOpportunity.awardingBody.preferredName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Registration","path":["$.credentialSubject.awardingOpportunity.awardingBody.registration"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Website","path":["$.credentialSubject.awardingOpportunity.awardingBody.homepage"],"schema":{"format":"uri","type":"string"}}],"subtitle":{"fallback":"EBSI Verifiable diploma","path":[],"schema":{"type":"string"}},"title":{"fallback":"Diploma","path":[],"schema":{"type":"string"}}},"id":"diploma_01","schema":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"}],"spec_version":"https://identity.foundation/credential-manifest/spec/v1.0.0/"}],"credential_supported":[{"cryptographic_binding_methods_supported":["did"],"cryptographic_suites_supported":["ES256K","ES256","ES384","ES512","RS256"],"display":[{"locale":"en-US","name":"Issuer Talao"}],"format":"jwt_vc","id":"VerifiableDiploma","types":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"}],"pre-authorized_grant_anonymous_access_supported":true,"subject_syntax_types_supported":["did:ebsi"],"token_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/token"}'; @@ -278,6 +346,92 @@ void main() { }); }); + group('getCredentialRequest', () { + final ebsi = Ebsi(client); + + test('extract correct credential type url from openId', () async { + final url = ebsi.getCredentialRequest(givenOpenIdRequest); + expect( + url, + 'https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd', + ); + }); + + test('credential type url is empty when url is not OK', () async { + final url = ebsi.getCredentialRequest('www.example.com'); + expect(url, ''); + }); + }); + + test('extract correct credential type url from openId', () async { + final ebsi = Ebsi(client); + final url = ebsi.getCredentialRequest(givenOpenIdRequest); + expect( + url, + 'https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd', + ); + }); + + group('get token', () { + test('get correct token ', () async { + final ebsi = Ebsi(client); + + final json = { + 'code': 'cb803d46-9c88-11ed-bdb3-0a1628958560', + 'grant_type': 'authorization_code' + }; + + const expectedValue = + '{"access_token":"7a07dd19-a879-11ed-ad95-0a1628958560","c_nonce":"7a07de0f-a879-11ed-822b-0a1628958560","token_type":"Bearer","expires_in":1000}'; + + final token = await ebsi.getToken(tokenUrl, json); + expect(jsonEncode(token), expectedValue); + }); + + test('throw expection when invalid value is sent', () async { + expect( + () async { + dioAdapter.onPost( + tokenUrl, + (request) => request.throws( + 401, + DioError(requestOptions: RequestOptions(path: tokenUrl)), + ), + ); + final ebsi = Ebsi(client); + + final json = {}; + + await ebsi.getToken(tokenUrl, json); + }, + throwsA(isA()), + ); + }); + }); + + group('get did', () { + final ebsi = Ebsi(client); + + const expectedDid = + 'did:ebsi:zo9FR1YfAKFP3Q6dvqhxcXxnfeDiJDP97kmnqhyAUSACj'; + test('from mnemonic ', () async { + final did = await ebsi.getDidFromMnemonic(mnemonic, null); + expect(did, expectedDid); + }); + + test('from privateKey ', () async { + const key = { + 'crv': 'P-256K', + 'd': 'ccWWNSjGiv1iWlNh4kfhWvwG3yyQMe8o31Du0uKRzrs', + 'kty': 'EC', + 'x': 'J4vQtLUyrVUiFIXRrtEq4xurmBZp2eq9wJmXkIA_stI', + 'y': 'EUU6vXoG3BGX2zzwjXrGDcr4EyDD0Vfk3_5fg5kSgKE' + }; + final did = await ebsi.getDidFromMnemonic(null, jsonEncode(key)); + expect(did, expectedDid); + }); + }); + test('sandbox', () { const cNonce = 'cNonce'; const did = 'did'; @@ -296,7 +450,7 @@ void main() { issuer: issuer, ); -// Sign it (default with HS256 algorithm) + // Sign it (default with HS256 algorithm) // ignore: unused_local_variable final token = jwt.sign(SecretKey('secret passphrase')); }); From 1d2e74eeead131cff717db112a55cdabd9918ac4 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Thu, 16 Feb 2023 22:41:39 +0530 Subject: [PATCH 031/190] 100% test in ebsi --- packages/ebsi/test/src/ebsi_test.dart | 53 +++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/packages/ebsi/test/src/ebsi_test.dart b/packages/ebsi/test/src/ebsi_test.dart index 63c9ba3d7..b549b86cb 100644 --- a/packages/ebsi/test/src/ebsi_test.dart +++ b/packages/ebsi/test/src/ebsi_test.dart @@ -430,6 +430,59 @@ void main() { final did = await ebsi.getDidFromMnemonic(null, jsonEncode(key)); expect(did, expectedDid); }); + + group('send Presentation', () { + const url = + 'https://talao.co/sandbox/ebsi/login/endpoint/f1722b9e-ae19-11ed-ac9f-0a1628958560'; + + final uri = Uri.parse( + 'openid://?scope=openid&response_type=id_token&client_id=xjcqarovuv&redirect_uri=https%3A%2F%2Ftalao.co%2Fsandbox%2Febsi%2Flogin%2Fendpoint%2Ff1722b9e-ae19-11ed-ac9f-0a1628958560&claims=%7B%27id_token%27%3A+%7B%27email%27%3A+None%7D%2C+%27vp_token%27%3A+%7B%27presentation_definition%27%3A+%7B%27id%27%3A+%27f17247aa-ae19-11ed-9241-0a1628958560%27%2C+%27input_descriptors%27%3A+%5B%7B%27constraints%27%3A+%7B%27fields%27%3A+%5B%7B%27path%27%3A+%5B%27%24.credentialSchema.id%27%5D%2C+%27filter%27%3A+%7B%27type%27%3A+%27string%27%2C+%27pattern%27%3A+%27https%3A%2F%2Fapi.preprod.ebsi.eu%2Ftrusted-schemas-registry%2Fv1%2Fschemas%2F0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd%27%7D%7D%5D%7D%2C+%27id%27%3A+%27f1724e6c-ae19-11ed-a3cf-0a1628958560%27%2C+%27name%27%3A+%27Input+descriptor+1%27%2C+%27purpose%27%3A+%27+%27%7D%5D%2C+%27format%27%3A+%7B%27jwt_vp%27%3A+%7B%27alg%27%3A+%5B%27ES256K%27%2C+%27ES256%27%2C+%27PS256%27%2C+%27RS256%27%5D%7D%7D%7D%7D%7D&nonce=f17239d6-ae19-11ed-8550-0a1628958560'); + + final credentialToBePresented = [ + r'{"id":"urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236","receivedId":"urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236","image":null,"data":{"@context":["https://www.w3.org/2018/credentials/v1"],"credentialSchema":{"id":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd","type":"JsonSchemaValidator2018"},"credentialStatus":{"id":"https://essif.europa.eu/status/education#higherEducation#392ac7f6-399a-437b-a268-4691ead8f176","type":"CredentialStatusList2020"},"credentialSubject":{"awardingOpportunity":{"awardingBody":{"eidasLegalIdentifier":"Unknown","homepage":"https://leaston.bcdiploma.com/","id":"did:ebsi:zdRvvKbXhVVBsXhatjuiBhs","preferredName":"Leaston University","registration":"0597065J"},"endedAtTime":"2020-06-26T00:00:00Z","id":"https://leaston.bcdiploma.com/law-economics-management#AwardingOpportunity","identifier":"https://certificate-demo.bcdiploma.com/check/87ED2F2270E6C41456E94B86B9D9115B4E35BCCAD200A49B846592C14F79C86BV1Fnbllta0NZTnJkR3lDWlRmTDlSRUJEVFZISmNmYzJhUU5sZUJ5Z2FJSHpWbmZZ","location":"FRANCE","startedAtTime":"2019-09-02T00:00:00Z"},"dateOfBirth":"1993-04-08","familyName":"DOE","givenNames":"Jane","gradingScheme":{"id":"https://leaston.bcdiploma.com/law-economics-management#GradingScheme","title":"2 year full-time programme / 4 semesters"},"id":"did:ebsi:zhFWcvr8DFL3cAVdheCpjHg3sPn1WUh9Gynm6hevPFzpw","identifier":"0904008084H","learningAchievement":{"additionalNote":["DISTRIBUTION MANAGEMENT"],"description":"The Master in Information and Computer Sciences (MICS) at the University of Luxembourg enables students to acquire deeper knowledge in computer science by understanding its abstract and interdisciplinary foundations, focusing on problem solving and developing lifelong learning skills.","id":"https://leaston.bcdiploma.com/law-economics-management#LearningAchievment","title":"Master in Information and Computer Sciences"},"learningSpecification":{"ectsCreditPoints":120,"eqfLevel":7,"id":"https://leaston.bcdiploma.com/law-economics-management#LearningSpecification","iscedfCode":["7"],"nqfLevel":["7"]},"type":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"},"evidence":{"documentPresence":["Physical"],"evidenceDocument":["Passport"],"id":"https://essif.europa.eu/tsr-va/evidence/f2aeec97-fc0d-42bf-8ca7-0548192d5678","subjectPresence":"Physical","type":["DocumentVerification"],"verifier":"did:ebsi:2962fb784df61baa267c8132497539f8c674b37c1244a7a"},"id":"urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236","issuanceDate":"2023-02-16T12:04:19Z","issued":"2023-02-16T12:04:19Z","issuer":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","proof":{"created":"2022-04-27T12:25:07Z","creator":"did:ebsi:zdRvvKbXhVVBsXhatjuiBhs","domain":"https://api.preprod.ebsi.eu","jws":"eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzI1NksifQ..mIBnM8XDQqSYKQNX_LvaJhmsbyCr5OZ5cU2Zk-ReqLpr4doFsgmoobkO5128tZy-8KimVjJkGw0wL1uBWnMLWQ","nonce":"3ea68dae-d07a-4daa-932b-fbb58f5c20c4","type":"EcdsaSecp256k1Signature2019"},"type":["VerifiableCredential","VerifiableAttestation","VerifiableDiploma"],"validFrom":"2023-02-16T12:04:19Z"},"shareLink":"","credentialPreview":{"id":"urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236","type":["VerifiableCredential","VerifiableAttestation","VerifiableDiploma"],"issuer":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","description":[],"name":[],"issuanceDate":"2023-02-16T12:04:19Z","proof":[{"type":"EcdsaSecp256k1Signature2019","proofPurpose":null,"verificationMethod":null,"created":"2022-04-27T12:25:07Z","jws":"eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzI1NksifQ..mIBnM8XDQqSYKQNX_LvaJhmsbyCr5OZ5cU2Zk-ReqLpr4doFsgmoobkO5128tZy-8KimVjJkGw0wL1uBWnMLWQ"}],"credentialSubject":{"id":"did:ebsi:zhFWcvr8DFL3cAVdheCpjHg3sPn1WUh9Gynm6hevPFzpw","type":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd","issuedBy":{"name":""},"expires":"","awardingOpportunity":{"awardingBody":{"eidasLegalIdentifier":"Unknown","homepage":"https://leaston.bcdiploma.com/","id":"did:ebsi:zdRvvKbXhVVBsXhatjuiBhs","preferredName":"Leaston University","registration":"0597065J"},"endedAtTime":"2020-06-26T00:00:00Z","id":"https://leaston.bcdiploma.com/law-economics-management#AwardingOpportunity","identifier":"https://certificate-demo.bcdiploma.com/check/87ED2F2270E6C41456E94B86B9D9115B4E35BCCAD200A49B846592C14F79C86BV1Fnbllta0NZTnJkR3lDWlRmTDlSRUJEVFZISmNmYzJhUU5sZUJ5Z2FJSHpWbmZZ","location":"FRANCE","startedAtTime":"2019-09-02T00:00:00Z"},"dateOfBirth":"1993-04-08","familyName":"DOE","givenNames":"Jane","gradingScheme":{"id":"https://leaston.bcdiploma.com/law-economics-management#GradingScheme","title":"2 year full-time programme / 4 semesters"},"identifier":"0904008084H","learningAchievement":{"additionalNote":["DISTRIBUTION MANAGEMENT"],"description":"The Master in Information and Computer Sciences (MICS) at the University of Luxembourg enables students to acquire deeper knowledge in computer science by understanding its abstract and interdisciplinary foundations, focusing on problem solving and developing lifelong learning skills.","id":"https://leaston.bcdiploma.com/law-economics-management#LearningAchievment","title":"Master in Information and Computer Sciences"},"learningSpecification":{"ectsCreditPoints":120,"eqfLevel":7,"id":"https://leaston.bcdiploma.com/law-economics-management#LearningSpecification","iscedfCode":["7"],"nqfLevel":["7"]}},"evidence":[{"id":"https://essif.europa.eu/tsr-va/evidence/f2aeec97-fc0d-42bf-8ca7-0548192d5678","type":["DocumentVerification"]}],"credentialStatus":{"id":"https://essif.europa.eu/status/education#higherEducation#392ac7f6-399a-437b-a268-4691ead8f176","type":"CredentialStatusList2020","revocationListIndex":"","revocationListCredential":""}},"display":{"backgroundColor":"","icon":"","nameFallback":"","descriptionFallback":""},"expirationDate":null,"credential_manifest":{"id":"VerifiableDiploma_1","issuer":{"id":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","name":"Test EBSILUX"},"output_descriptors":[{"id":"diploma_01","schema":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd","name":null,"description":null,"styles":null,"display":{"title":{"path":[],"schema":{"type":"string","format":null},"fallback":"Diploma"},"subtitle":{"path":[],"schema":{"type":"string","format":null},"fallback":"EBSI Verifiable diploma"},"description":{"path":[],"schema":{"type":"string","format":null},"fallback":"This card is a proof that you passed this diploma successfully. You can use this card when you need to prove this information to services that have adopted EU EBSI framework."},"properties":[{"label":"First name","path":["$.credentialSubject.firstName"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Last name","path":["$.credentialSubject.familyName"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Birth date","path":["$.credentialSubject.dateOfBirth"],"schema":{"type":"string","format":"date"},"fallback":"Unknown"},{"label":"Grading scheme","path":["$.credentialSubject.gradingScheme.title"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Title","path":["$.credentialSubject.learningAchievement.title"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Description","path":["$.credentialSubject.learningAchievement.description"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"ECTS Points","path":["$.credentialSubject.learningSpecification.ectsCreditPoints"],"schema":{"type":"number","format":null},"fallback":"Unknown"},{"label":"Issue date","path":["$.issuanceDate"],"schema":{"type":"string","format":"date"},"fallback":"Unknown"},{"label":"Issued by","path":["$.credentialSubject.awardingOpportunity.awardingBody.preferredName"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Registration","path":["$.credentialSubject.awardingOpportunity.awardingBody.registration"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Website","path":["$.credentialSubject.awardingOpportunity.awardingBody.homepage"],"schema":{"type":"string","format":"uri"},"fallback":"Unknown"}]}}],"presentation_definition":null},"challenge":null,"domain":null,"activities":[{"acquisitionAt":"2023-02-16T17:34:24.679713","presentation":null},{"acquisitionAt":null,"presentation":{"issuer":{"preferredName":"","did":[],"organizationInfo":{"id":"","legalName":"","currentAddress":"","website":"domain","issuerDomain":[]}},"presentedAt":"2023-02-16T17:34:47.206600"}}],"jwt":"eyJhbGciOiJFUzI1NksiLCJraWQiOiJkaWQ6ZWJzaTp6aFN3NXJQWGtjSGp2cXV3blZjVHp6QiM1MTVhOWM0MzZjMGYyYWQzYWI2NWQ2Y2VmYzVjMWYwNmMwNWI4YWRmY2Y1NGVlMDZkYzgwNTQzMjA0NzBmZmFmIiwidHlwIjoiSldUIn0.eyJleHAiOjE2NzY1NTAwNTkuMjMzMzY3LCJpYXQiOjE2NzY1NDkwNTkuMjMzMzYsImlzcyI6ImRpZDplYnNpOnpoU3c1clBYa2NIanZxdXduVmNUenpCIiwianRpIjoidXJuOnV1aWQ6NmIxZDg0MTEtOWVkNS00NTY2LTljN2YtNGMyNDE2NWZmMjM2IiwibmJmIjoxNjc2NTQ5MDU5LjIzMzM2NCwibm9uY2UiOiIwYTc4MDg2MC1hZGYyLTExZWQtYmIzZS0wYTE2Mjg5NTg1NjAiLCJzdWIiOiJkaWQ6ZWJzaTp6aEZXY3ZyOERGTDNjQVZkaGVDcGpIZzNzUG4xV1VoOUd5bm02aGV2UEZ6cHciLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJjcmVkZW50aWFsU2NoZW1hIjp7ImlkIjoiaHR0cHM6Ly9hcGkucHJlcHJvZC5lYnNpLmV1L3RydXN0ZWQtc2NoZW1hcy1yZWdpc3RyeS92MS9zY2hlbWFzLzB4YmY3OGZjMDhhN2E5ZjI4ZjU0NzlmNThkZWEyNjlkMzY1N2Y1NGYxM2NhMzdkMzgwY2Q0ZTkyMjM3ZmI2OTFkZCIsInR5cGUiOiJKc29uU2NoZW1hVmFsaWRhdG9yMjAxOCJ9LCJjcmVkZW50aWFsU3RhdHVzIjp7ImlkIjoiaHR0cHM6Ly9lc3NpZi5ldXJvcGEuZXUvc3RhdHVzL2VkdWNhdGlvbiNoaWdoZXJFZHVjYXRpb24jMzkyYWM3ZjYtMzk5YS00MzdiLWEyNjgtNDY5MWVhZDhmMTc2IiwidHlwZSI6IkNyZWRlbnRpYWxTdGF0dXNMaXN0MjAyMCJ9LCJjcmVkZW50aWFsU3ViamVjdCI6eyJhd2FyZGluZ09wcG9ydHVuaXR5Ijp7ImF3YXJkaW5nQm9keSI6eyJlaWRhc0xlZ2FsSWRlbnRpZmllciI6IlVua25vd24iLCJob21lcGFnZSI6Imh0dHBzOi8vbGVhc3Rvbi5iY2RpcGxvbWEuY29tLyIsImlkIjoiZGlkOmVic2k6emRSdnZLYlhoVlZCc1hoYXRqdWlCaHMiLCJwcmVmZXJyZWROYW1lIjoiTGVhc3RvbiBVbml2ZXJzaXR5IiwicmVnaXN0cmF0aW9uIjoiMDU5NzA2NUoifSwiZW5kZWRBdFRpbWUiOiIyMDIwLTA2LTI2VDAwOjAwOjAwWiIsImlkIjoiaHR0cHM6Ly9sZWFzdG9uLmJjZGlwbG9tYS5jb20vbGF3LWVjb25vbWljcy1tYW5hZ2VtZW50I0F3YXJkaW5nT3Bwb3J0dW5pdHkiLCJpZGVudGlmaWVyIjoiaHR0cHM6Ly9jZXJ0aWZpY2F0ZS1kZW1vLmJjZGlwbG9tYS5jb20vY2hlY2svODdFRDJGMjI3MEU2QzQxNDU2RTk0Qjg2QjlEOTExNUI0RTM1QkNDQUQyMDBBNDlCODQ2NTkyQzE0Rjc5Qzg2QlYxRm5ibGx0YTBOWlRuSmtSM2xEV2xSbVREbFNSVUpFVkZaSVNtTm1ZekpoVVU1c1pVSjVaMkZKU0hwV2JtWloiLCJsb2NhdGlvbiI6IkZSQU5DRSIsInN0YXJ0ZWRBdFRpbWUiOiIyMDE5LTA5LTAyVDAwOjAwOjAwWiJ9LCJkYXRlT2ZCaXJ0aCI6IjE5OTMtMDQtMDgiLCJmYW1pbHlOYW1lIjoiRE9FIiwiZ2l2ZW5OYW1lcyI6IkphbmUiLCJncmFkaW5nU2NoZW1lIjp7ImlkIjoiaHR0cHM6Ly9sZWFzdG9uLmJjZGlwbG9tYS5jb20vbGF3LWVjb25vbWljcy1tYW5hZ2VtZW50I0dyYWRpbmdTY2hlbWUiLCJ0aXRsZSI6IjIgeWVhciBmdWxsLXRpbWUgcHJvZ3JhbW1lIC8gNCBzZW1lc3RlcnMifSwiaWQiOiJkaWQ6ZWJzaTp6aEZXY3ZyOERGTDNjQVZkaGVDcGpIZzNzUG4xV1VoOUd5bm02aGV2UEZ6cHciLCJpZGVudGlmaWVyIjoiMDkwNDAwODA4NEgiLCJsZWFybmluZ0FjaGlldmVtZW50Ijp7ImFkZGl0aW9uYWxOb3RlIjpbIkRJU1RSSUJVVElPTiBNQU5BR0VNRU5UIl0sImRlc2NyaXB0aW9uIjoiVGhlIE1hc3RlciBpbiBJbmZvcm1hdGlvbiBhbmQgQ29tcHV0ZXIgU2NpZW5jZXMgKE1JQ1MpIGF0IHRoZSBVbml2ZXJzaXR5IG9mIEx1eGVtYm91cmcgZW5hYmxlcyBzdHVkZW50cyB0byBhY3F1aXJlIGRlZXBlciBrbm93bGVkZ2UgaW4gY29tcHV0ZXIgc2NpZW5jZSBieSB1bmRlcnN0YW5kaW5nIGl0cyBhYnN0cmFjdCBhbmQgaW50ZXJkaXNjaXBsaW5hcnkgZm91bmRhdGlvbnMsIGZvY3VzaW5nIG9uIHByb2JsZW0gc29sdmluZyBhbmQgZGV2ZWxvcGluZyBsaWZlbG9uZyBsZWFybmluZyBza2lsbHMuIiwiaWQiOiJodHRwczovL2xlYXN0b24uYmNkaXBsb21hLmNvbS9sYXctZWNvbm9taWNzLW1hbmFnZW1lbnQjTGVhcm5pbmdBY2hpZXZtZW50IiwidGl0bGUiOiJNYXN0ZXIgaW4gSW5mb3JtYXRpb24gYW5kIENvbXB1dGVyIFNjaWVuY2VzIn0sImxlYXJuaW5nU3BlY2lmaWNhdGlvbiI6eyJlY3RzQ3JlZGl0UG9pbnRzIjoxMjAsImVxZkxldmVsIjo3LCJpZCI6Imh0dHBzOi8vbGVhc3Rvbi5iY2RpcGxvbWEuY29tL2xhdy1lY29ub21pY3MtbWFuYWdlbWVudCNMZWFybmluZ1NwZWNpZmljYXRpb24iLCJpc2NlZGZDb2RlIjpbIjciXSwibnFmTGV2ZWwiOlsiNyJdfX0sImV2aWRlbmNlIjp7ImRvY3VtZW50UHJlc2VuY2UiOlsiUGh5c2ljYWwiXSwiZXZpZGVuY2VEb2N1bWVudCI6WyJQYXNzcG9ydCJdLCJpZCI6Imh0dHBzOi8vZXNzaWYuZXVyb3BhLmV1L3Rzci12YS9ldmlkZW5jZS9mMmFlZWM5Ny1mYzBkLTQyYmYtOGNhNy0wNTQ4MTkyZDU2NzgiLCJzdWJqZWN0UHJlc2VuY2UiOiJQaHlzaWNhbCIsInR5cGUiOlsiRG9jdW1lbnRWZXJpZmljYXRpb24iXSwidmVyaWZpZXIiOiJkaWQ6ZWJzaToyOTYyZmI3ODRkZjYxYmFhMjY3YzgxMzI0OTc1MzlmOGM2NzRiMzdjMTI0NGE3YSJ9LCJpZCI6InVybjp1dWlkOjZiMWQ4NDExLTllZDUtNDU2Ni05YzdmLTRjMjQxNjVmZjIzNiIsImlzc3VhbmNlRGF0ZSI6IjIwMjMtMDItMTZUMTI6MDQ6MTlaIiwiaXNzdWVkIjoiMjAyMy0wMi0xNlQxMjowNDoxOVoiLCJpc3N1ZXIiOiJkaWQ6ZWJzaTp6aFN3NXJQWGtjSGp2cXV3blZjVHp6QiIsInByb29mIjp7ImNyZWF0ZWQiOiIyMDIyLTA0LTI3VDEyOjI1OjA3WiIsImNyZWF0b3IiOiJkaWQ6ZWJzaTp6ZFJ2dktiWGhWVkJzWGhhdGp1aUJocyIsImRvbWFpbiI6Imh0dHBzOi8vYXBpLnByZXByb2QuZWJzaS5ldSIsImp3cyI6ImV5SmlOalFpT21aaGJITmxMQ0pqY21sMElqcGJJbUkyTkNKZExDSmhiR2NpT2lKRlV6STFOa3NpZlEuLm1JQm5NOFhEUXFTWUtRTlhfTHZhSmhtc2J5Q3I1T1o1Y1UyWmstUmVxTHByNGRvRnNnbW9vYmtPNTEyOHRaeS04S2ltVmpKa0d3MHdMMXVCV25NTFdRIiwibm9uY2UiOiIzZWE2OGRhZS1kMDdhLTRkYWEtOTMyYi1mYmI1OGY1YzIwYzQiLCJ0eXBlIjoiRWNkc2FTZWNwMjU2azFTaWduYXR1cmUyMDE5In0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJWZXJpZmlhYmxlQXR0ZXN0YXRpb24iLCJWZXJpZmlhYmxlRGlwbG9tYSJdLCJ2YWxpZEZyb20iOiIyMDIzLTAyLTE2VDEyOjA0OjE5WiJ9fQ.7Bmdrkp-hpM7BcNASc7ngjia3cjrZr-nqmP0Plsfnefn26G_yR5fBJV8BT_U2zsMlcqWsxuJ9pS2Vyiq_SrooQ"}' + ]; + + test('successfully send presentation', () async { + const response = + '{"access":"ok","created":1676566727.680238,"credential_status":"signature check bypassed","holder_did_status":"ok","id_token_status":"ok","qrcode_status":"ok","response_format":"ok","status_code":200,"vp_token_status":"ok"}'; + dioAdapter.onGet( + url, + (request) => request.reply(200, jsonDecode(response)), + ); + final ebsi = Ebsi(client); + + expect( + () async { + await ebsi.sendPresentation( + uri, + credentialToBePresented, + mnemonic, + null, + ); + }, + returnsNormally, + ); + }); + + test('throw exception', () async { + expect( + () async { + dioAdapter.onPost( + url, + (request) => request.throws( + 401, + DioError(requestOptions: RequestOptions(path: tokenUrl)), + ), + ); + final ebsi = Ebsi(client); + + await ebsi.sendPresentation( + uri, credentialToBePresented, mnemonic, null); + }, + throwsA(isA()), + ); + }); + }); }); test('sandbox', () { From ff947471da862e9050e6cb1b32597000c0bd76d5 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Thu, 16 Feb 2023 22:43:34 +0530 Subject: [PATCH 032/190] linter update --- packages/ebsi/test/src/ebsi_test.dart | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/ebsi/test/src/ebsi_test.dart b/packages/ebsi/test/src/ebsi_test.dart index b549b86cb..9a05350e9 100644 --- a/packages/ebsi/test/src/ebsi_test.dart +++ b/packages/ebsi/test/src/ebsi_test.dart @@ -251,7 +251,8 @@ void main() { ); final credentialRequestWithPreAuthorizedCode = Uri.parse( - 'openid://initiate_issuance?issuer=https%3A%2F%2Ftalao.co%2Fsandbox%2Febsi%2Fissuer%2Fvgvghylozl&credential_type=https%3A%2F%2Fapi.preprod.ebsi.eu%2Ftrusted-schemas-registry%2Fv1%2Fschemas%2F0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd&op_state=test&pre-authorized_code=ff8e73c5-ae07-11ed-b1f7-0a1628958560&user_pin_required=False'); + 'openid://initiate_issuance?issuer=https%3A%2F%2Ftalao.co%2Fsandbox%2Febsi%2Fissuer%2Fvgvghylozl&credential_type=https%3A%2F%2Fapi.preprod.ebsi.eu%2Ftrusted-schemas-registry%2Fv1%2Fschemas%2F0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd&op_state=test&pre-authorized_code=ff8e73c5-ae07-11ed-b1f7-0a1628958560&user_pin_required=False', + ); const issuerResponse = r'{"authorization_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/authorize","batch_credential_endpoint":null,"credential_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/credential","credential_issuer":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl","credential_manifests":[{"id":"VerifiableDiploma_1","issuer":{"id":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","name":"Test EBSILUX"},"output_descriptors":[{"display":{"description":{"fallback":"This card is a proof that you passed this diploma successfully. You can use this card when you need to prove this information to services that have adopted EU EBSI framework.","path":[],"schema":{"type":"string"}},"properties":[{"fallback":"Unknown","label":"First name","path":["$.credentialSubject.firstName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Last name","path":["$.credentialSubject.familyName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Birth date","path":["$.credentialSubject.dateOfBirth"],"schema":{"format":"date","type":"string"}},{"fallback":"Unknown","label":"Grading scheme","path":["$.credentialSubject.gradingScheme.title"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Title","path":["$.credentialSubject.learningAchievement.title"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Description","path":["$.credentialSubject.learningAchievement.description"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"ECTS Points","path":["$.credentialSubject.learningSpecification.ectsCreditPoints"],"schema":{"type":"number"}},{"fallback":"Unknown","label":"Issue date","path":["$.issuanceDate"],"schema":{"format":"date","type":"string"}},{"fallback":"Unknown","label":"Issued by","path":["$.credentialSubject.awardingOpportunity.awardingBody.preferredName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Registration","path":["$.credentialSubject.awardingOpportunity.awardingBody.registration"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Website","path":["$.credentialSubject.awardingOpportunity.awardingBody.homepage"],"schema":{"format":"uri","type":"string"}}],"subtitle":{"fallback":"EBSI Verifiable diploma","path":[],"schema":{"type":"string"}},"title":{"fallback":"Diploma","path":[],"schema":{"type":"string"}}},"id":"diploma_01","schema":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"}],"spec_version":"https://identity.foundation/credential-manifest/spec/v1.0.0/"}],"credential_supported":[{"cryptographic_binding_methods_supported":["did"],"cryptographic_suites_supported":["ES256K","ES256","ES384","ES512","RS256"],"display":[{"locale":"en-US","name":"Issuer Talao"}],"format":"jwt_vc","id":"VerifiableDiploma","types":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"}],"pre-authorized_grant_anonymous_access_supported":true,"subject_syntax_types_supported":["did:ebsi"],"token_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/token"}'; @@ -323,7 +324,7 @@ void main() { }); test( - 'get issuer with credentialRequestUri when PreAuthorizedCode is given', + 'get issuer with credentialRequestUri when PreAuthorizedCode is given', // ignore: lines_longer_than_80_chars () { const expectedIssuer = 'https://talao.co/sandbox/ebsi/issuer/vgvghylozl'; @@ -382,7 +383,7 @@ void main() { }; const expectedValue = - '{"access_token":"7a07dd19-a879-11ed-ad95-0a1628958560","c_nonce":"7a07de0f-a879-11ed-822b-0a1628958560","token_type":"Bearer","expires_in":1000}'; + '{"access_token":"7a07dd19-a879-11ed-ad95-0a1628958560","c_nonce":"7a07de0f-a879-11ed-822b-0a1628958560","token_type":"Bearer","expires_in":1000}'; // ignore: lines_longer_than_80_chars final token = await ebsi.getToken(tokenUrl, json); expect(jsonEncode(token), expectedValue); @@ -436,7 +437,8 @@ void main() { 'https://talao.co/sandbox/ebsi/login/endpoint/f1722b9e-ae19-11ed-ac9f-0a1628958560'; final uri = Uri.parse( - 'openid://?scope=openid&response_type=id_token&client_id=xjcqarovuv&redirect_uri=https%3A%2F%2Ftalao.co%2Fsandbox%2Febsi%2Flogin%2Fendpoint%2Ff1722b9e-ae19-11ed-ac9f-0a1628958560&claims=%7B%27id_token%27%3A+%7B%27email%27%3A+None%7D%2C+%27vp_token%27%3A+%7B%27presentation_definition%27%3A+%7B%27id%27%3A+%27f17247aa-ae19-11ed-9241-0a1628958560%27%2C+%27input_descriptors%27%3A+%5B%7B%27constraints%27%3A+%7B%27fields%27%3A+%5B%7B%27path%27%3A+%5B%27%24.credentialSchema.id%27%5D%2C+%27filter%27%3A+%7B%27type%27%3A+%27string%27%2C+%27pattern%27%3A+%27https%3A%2F%2Fapi.preprod.ebsi.eu%2Ftrusted-schemas-registry%2Fv1%2Fschemas%2F0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd%27%7D%7D%5D%7D%2C+%27id%27%3A+%27f1724e6c-ae19-11ed-a3cf-0a1628958560%27%2C+%27name%27%3A+%27Input+descriptor+1%27%2C+%27purpose%27%3A+%27+%27%7D%5D%2C+%27format%27%3A+%7B%27jwt_vp%27%3A+%7B%27alg%27%3A+%5B%27ES256K%27%2C+%27ES256%27%2C+%27PS256%27%2C+%27RS256%27%5D%7D%7D%7D%7D%7D&nonce=f17239d6-ae19-11ed-8550-0a1628958560'); + 'openid://?scope=openid&response_type=id_token&client_id=xjcqarovuv&redirect_uri=https%3A%2F%2Ftalao.co%2Fsandbox%2Febsi%2Flogin%2Fendpoint%2Ff1722b9e-ae19-11ed-ac9f-0a1628958560&claims=%7B%27id_token%27%3A+%7B%27email%27%3A+None%7D%2C+%27vp_token%27%3A+%7B%27presentation_definition%27%3A+%7B%27id%27%3A+%27f17247aa-ae19-11ed-9241-0a1628958560%27%2C+%27input_descriptors%27%3A+%5B%7B%27constraints%27%3A+%7B%27fields%27%3A+%5B%7B%27path%27%3A+%5B%27%24.credentialSchema.id%27%5D%2C+%27filter%27%3A+%7B%27type%27%3A+%27string%27%2C+%27pattern%27%3A+%27https%3A%2F%2Fapi.preprod.ebsi.eu%2Ftrusted-schemas-registry%2Fv1%2Fschemas%2F0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd%27%7D%7D%5D%7D%2C+%27id%27%3A+%27f1724e6c-ae19-11ed-a3cf-0a1628958560%27%2C+%27name%27%3A+%27Input+descriptor+1%27%2C+%27purpose%27%3A+%27+%27%7D%5D%2C+%27format%27%3A+%7B%27jwt_vp%27%3A+%7B%27alg%27%3A+%5B%27ES256K%27%2C+%27ES256%27%2C+%27PS256%27%2C+%27RS256%27%5D%7D%7D%7D%7D%7D&nonce=f17239d6-ae19-11ed-8550-0a1628958560', + ); final credentialToBePresented = [ r'{"id":"urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236","receivedId":"urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236","image":null,"data":{"@context":["https://www.w3.org/2018/credentials/v1"],"credentialSchema":{"id":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd","type":"JsonSchemaValidator2018"},"credentialStatus":{"id":"https://essif.europa.eu/status/education#higherEducation#392ac7f6-399a-437b-a268-4691ead8f176","type":"CredentialStatusList2020"},"credentialSubject":{"awardingOpportunity":{"awardingBody":{"eidasLegalIdentifier":"Unknown","homepage":"https://leaston.bcdiploma.com/","id":"did:ebsi:zdRvvKbXhVVBsXhatjuiBhs","preferredName":"Leaston University","registration":"0597065J"},"endedAtTime":"2020-06-26T00:00:00Z","id":"https://leaston.bcdiploma.com/law-economics-management#AwardingOpportunity","identifier":"https://certificate-demo.bcdiploma.com/check/87ED2F2270E6C41456E94B86B9D9115B4E35BCCAD200A49B846592C14F79C86BV1Fnbllta0NZTnJkR3lDWlRmTDlSRUJEVFZISmNmYzJhUU5sZUJ5Z2FJSHpWbmZZ","location":"FRANCE","startedAtTime":"2019-09-02T00:00:00Z"},"dateOfBirth":"1993-04-08","familyName":"DOE","givenNames":"Jane","gradingScheme":{"id":"https://leaston.bcdiploma.com/law-economics-management#GradingScheme","title":"2 year full-time programme / 4 semesters"},"id":"did:ebsi:zhFWcvr8DFL3cAVdheCpjHg3sPn1WUh9Gynm6hevPFzpw","identifier":"0904008084H","learningAchievement":{"additionalNote":["DISTRIBUTION MANAGEMENT"],"description":"The Master in Information and Computer Sciences (MICS) at the University of Luxembourg enables students to acquire deeper knowledge in computer science by understanding its abstract and interdisciplinary foundations, focusing on problem solving and developing lifelong learning skills.","id":"https://leaston.bcdiploma.com/law-economics-management#LearningAchievment","title":"Master in Information and Computer Sciences"},"learningSpecification":{"ectsCreditPoints":120,"eqfLevel":7,"id":"https://leaston.bcdiploma.com/law-economics-management#LearningSpecification","iscedfCode":["7"],"nqfLevel":["7"]},"type":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"},"evidence":{"documentPresence":["Physical"],"evidenceDocument":["Passport"],"id":"https://essif.europa.eu/tsr-va/evidence/f2aeec97-fc0d-42bf-8ca7-0548192d5678","subjectPresence":"Physical","type":["DocumentVerification"],"verifier":"did:ebsi:2962fb784df61baa267c8132497539f8c674b37c1244a7a"},"id":"urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236","issuanceDate":"2023-02-16T12:04:19Z","issued":"2023-02-16T12:04:19Z","issuer":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","proof":{"created":"2022-04-27T12:25:07Z","creator":"did:ebsi:zdRvvKbXhVVBsXhatjuiBhs","domain":"https://api.preprod.ebsi.eu","jws":"eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzI1NksifQ..mIBnM8XDQqSYKQNX_LvaJhmsbyCr5OZ5cU2Zk-ReqLpr4doFsgmoobkO5128tZy-8KimVjJkGw0wL1uBWnMLWQ","nonce":"3ea68dae-d07a-4daa-932b-fbb58f5c20c4","type":"EcdsaSecp256k1Signature2019"},"type":["VerifiableCredential","VerifiableAttestation","VerifiableDiploma"],"validFrom":"2023-02-16T12:04:19Z"},"shareLink":"","credentialPreview":{"id":"urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236","type":["VerifiableCredential","VerifiableAttestation","VerifiableDiploma"],"issuer":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","description":[],"name":[],"issuanceDate":"2023-02-16T12:04:19Z","proof":[{"type":"EcdsaSecp256k1Signature2019","proofPurpose":null,"verificationMethod":null,"created":"2022-04-27T12:25:07Z","jws":"eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzI1NksifQ..mIBnM8XDQqSYKQNX_LvaJhmsbyCr5OZ5cU2Zk-ReqLpr4doFsgmoobkO5128tZy-8KimVjJkGw0wL1uBWnMLWQ"}],"credentialSubject":{"id":"did:ebsi:zhFWcvr8DFL3cAVdheCpjHg3sPn1WUh9Gynm6hevPFzpw","type":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd","issuedBy":{"name":""},"expires":"","awardingOpportunity":{"awardingBody":{"eidasLegalIdentifier":"Unknown","homepage":"https://leaston.bcdiploma.com/","id":"did:ebsi:zdRvvKbXhVVBsXhatjuiBhs","preferredName":"Leaston University","registration":"0597065J"},"endedAtTime":"2020-06-26T00:00:00Z","id":"https://leaston.bcdiploma.com/law-economics-management#AwardingOpportunity","identifier":"https://certificate-demo.bcdiploma.com/check/87ED2F2270E6C41456E94B86B9D9115B4E35BCCAD200A49B846592C14F79C86BV1Fnbllta0NZTnJkR3lDWlRmTDlSRUJEVFZISmNmYzJhUU5sZUJ5Z2FJSHpWbmZZ","location":"FRANCE","startedAtTime":"2019-09-02T00:00:00Z"},"dateOfBirth":"1993-04-08","familyName":"DOE","givenNames":"Jane","gradingScheme":{"id":"https://leaston.bcdiploma.com/law-economics-management#GradingScheme","title":"2 year full-time programme / 4 semesters"},"identifier":"0904008084H","learningAchievement":{"additionalNote":["DISTRIBUTION MANAGEMENT"],"description":"The Master in Information and Computer Sciences (MICS) at the University of Luxembourg enables students to acquire deeper knowledge in computer science by understanding its abstract and interdisciplinary foundations, focusing on problem solving and developing lifelong learning skills.","id":"https://leaston.bcdiploma.com/law-economics-management#LearningAchievment","title":"Master in Information and Computer Sciences"},"learningSpecification":{"ectsCreditPoints":120,"eqfLevel":7,"id":"https://leaston.bcdiploma.com/law-economics-management#LearningSpecification","iscedfCode":["7"],"nqfLevel":["7"]}},"evidence":[{"id":"https://essif.europa.eu/tsr-va/evidence/f2aeec97-fc0d-42bf-8ca7-0548192d5678","type":["DocumentVerification"]}],"credentialStatus":{"id":"https://essif.europa.eu/status/education#higherEducation#392ac7f6-399a-437b-a268-4691ead8f176","type":"CredentialStatusList2020","revocationListIndex":"","revocationListCredential":""}},"display":{"backgroundColor":"","icon":"","nameFallback":"","descriptionFallback":""},"expirationDate":null,"credential_manifest":{"id":"VerifiableDiploma_1","issuer":{"id":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","name":"Test EBSILUX"},"output_descriptors":[{"id":"diploma_01","schema":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd","name":null,"description":null,"styles":null,"display":{"title":{"path":[],"schema":{"type":"string","format":null},"fallback":"Diploma"},"subtitle":{"path":[],"schema":{"type":"string","format":null},"fallback":"EBSI Verifiable diploma"},"description":{"path":[],"schema":{"type":"string","format":null},"fallback":"This card is a proof that you passed this diploma successfully. You can use this card when you need to prove this information to services that have adopted EU EBSI framework."},"properties":[{"label":"First name","path":["$.credentialSubject.firstName"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Last name","path":["$.credentialSubject.familyName"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Birth date","path":["$.credentialSubject.dateOfBirth"],"schema":{"type":"string","format":"date"},"fallback":"Unknown"},{"label":"Grading scheme","path":["$.credentialSubject.gradingScheme.title"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Title","path":["$.credentialSubject.learningAchievement.title"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Description","path":["$.credentialSubject.learningAchievement.description"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"ECTS Points","path":["$.credentialSubject.learningSpecification.ectsCreditPoints"],"schema":{"type":"number","format":null},"fallback":"Unknown"},{"label":"Issue date","path":["$.issuanceDate"],"schema":{"type":"string","format":"date"},"fallback":"Unknown"},{"label":"Issued by","path":["$.credentialSubject.awardingOpportunity.awardingBody.preferredName"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Registration","path":["$.credentialSubject.awardingOpportunity.awardingBody.registration"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Website","path":["$.credentialSubject.awardingOpportunity.awardingBody.homepage"],"schema":{"type":"string","format":"uri"},"fallback":"Unknown"}]}}],"presentation_definition":null},"challenge":null,"domain":null,"activities":[{"acquisitionAt":"2023-02-16T17:34:24.679713","presentation":null},{"acquisitionAt":null,"presentation":{"issuer":{"preferredName":"","did":[],"organizationInfo":{"id":"","legalName":"","currentAddress":"","website":"domain","issuerDomain":[]}},"presentedAt":"2023-02-16T17:34:47.206600"}}],"jwt":"eyJhbGciOiJFUzI1NksiLCJraWQiOiJkaWQ6ZWJzaTp6aFN3NXJQWGtjSGp2cXV3blZjVHp6QiM1MTVhOWM0MzZjMGYyYWQzYWI2NWQ2Y2VmYzVjMWYwNmMwNWI4YWRmY2Y1NGVlMDZkYzgwNTQzMjA0NzBmZmFmIiwidHlwIjoiSldUIn0.eyJleHAiOjE2NzY1NTAwNTkuMjMzMzY3LCJpYXQiOjE2NzY1NDkwNTkuMjMzMzYsImlzcyI6ImRpZDplYnNpOnpoU3c1clBYa2NIanZxdXduVmNUenpCIiwianRpIjoidXJuOnV1aWQ6NmIxZDg0MTEtOWVkNS00NTY2LTljN2YtNGMyNDE2NWZmMjM2IiwibmJmIjoxNjc2NTQ5MDU5LjIzMzM2NCwibm9uY2UiOiIwYTc4MDg2MC1hZGYyLTExZWQtYmIzZS0wYTE2Mjg5NTg1NjAiLCJzdWIiOiJkaWQ6ZWJzaTp6aEZXY3ZyOERGTDNjQVZkaGVDcGpIZzNzUG4xV1VoOUd5bm02aGV2UEZ6cHciLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJjcmVkZW50aWFsU2NoZW1hIjp7ImlkIjoiaHR0cHM6Ly9hcGkucHJlcHJvZC5lYnNpLmV1L3RydXN0ZWQtc2NoZW1hcy1yZWdpc3RyeS92MS9zY2hlbWFzLzB4YmY3OGZjMDhhN2E5ZjI4ZjU0NzlmNThkZWEyNjlkMzY1N2Y1NGYxM2NhMzdkMzgwY2Q0ZTkyMjM3ZmI2OTFkZCIsInR5cGUiOiJKc29uU2NoZW1hVmFsaWRhdG9yMjAxOCJ9LCJjcmVkZW50aWFsU3RhdHVzIjp7ImlkIjoiaHR0cHM6Ly9lc3NpZi5ldXJvcGEuZXUvc3RhdHVzL2VkdWNhdGlvbiNoaWdoZXJFZHVjYXRpb24jMzkyYWM3ZjYtMzk5YS00MzdiLWEyNjgtNDY5MWVhZDhmMTc2IiwidHlwZSI6IkNyZWRlbnRpYWxTdGF0dXNMaXN0MjAyMCJ9LCJjcmVkZW50aWFsU3ViamVjdCI6eyJhd2FyZGluZ09wcG9ydHVuaXR5Ijp7ImF3YXJkaW5nQm9keSI6eyJlaWRhc0xlZ2FsSWRlbnRpZmllciI6IlVua25vd24iLCJob21lcGFnZSI6Imh0dHBzOi8vbGVhc3Rvbi5iY2RpcGxvbWEuY29tLyIsImlkIjoiZGlkOmVic2k6emRSdnZLYlhoVlZCc1hoYXRqdWlCaHMiLCJwcmVmZXJyZWROYW1lIjoiTGVhc3RvbiBVbml2ZXJzaXR5IiwicmVnaXN0cmF0aW9uIjoiMDU5NzA2NUoifSwiZW5kZWRBdFRpbWUiOiIyMDIwLTA2LTI2VDAwOjAwOjAwWiIsImlkIjoiaHR0cHM6Ly9sZWFzdG9uLmJjZGlwbG9tYS5jb20vbGF3LWVjb25vbWljcy1tYW5hZ2VtZW50I0F3YXJkaW5nT3Bwb3J0dW5pdHkiLCJpZGVudGlmaWVyIjoiaHR0cHM6Ly9jZXJ0aWZpY2F0ZS1kZW1vLmJjZGlwbG9tYS5jb20vY2hlY2svODdFRDJGMjI3MEU2QzQxNDU2RTk0Qjg2QjlEOTExNUI0RTM1QkNDQUQyMDBBNDlCODQ2NTkyQzE0Rjc5Qzg2QlYxRm5ibGx0YTBOWlRuSmtSM2xEV2xSbVREbFNSVUpFVkZaSVNtTm1ZekpoVVU1c1pVSjVaMkZKU0hwV2JtWloiLCJsb2NhdGlvbiI6IkZSQU5DRSIsInN0YXJ0ZWRBdFRpbWUiOiIyMDE5LTA5LTAyVDAwOjAwOjAwWiJ9LCJkYXRlT2ZCaXJ0aCI6IjE5OTMtMDQtMDgiLCJmYW1pbHlOYW1lIjoiRE9FIiwiZ2l2ZW5OYW1lcyI6IkphbmUiLCJncmFkaW5nU2NoZW1lIjp7ImlkIjoiaHR0cHM6Ly9sZWFzdG9uLmJjZGlwbG9tYS5jb20vbGF3LWVjb25vbWljcy1tYW5hZ2VtZW50I0dyYWRpbmdTY2hlbWUiLCJ0aXRsZSI6IjIgeWVhciBmdWxsLXRpbWUgcHJvZ3JhbW1lIC8gNCBzZW1lc3RlcnMifSwiaWQiOiJkaWQ6ZWJzaTp6aEZXY3ZyOERGTDNjQVZkaGVDcGpIZzNzUG4xV1VoOUd5bm02aGV2UEZ6cHciLCJpZGVudGlmaWVyIjoiMDkwNDAwODA4NEgiLCJsZWFybmluZ0FjaGlldmVtZW50Ijp7ImFkZGl0aW9uYWxOb3RlIjpbIkRJU1RSSUJVVElPTiBNQU5BR0VNRU5UIl0sImRlc2NyaXB0aW9uIjoiVGhlIE1hc3RlciBpbiBJbmZvcm1hdGlvbiBhbmQgQ29tcHV0ZXIgU2NpZW5jZXMgKE1JQ1MpIGF0IHRoZSBVbml2ZXJzaXR5IG9mIEx1eGVtYm91cmcgZW5hYmxlcyBzdHVkZW50cyB0byBhY3F1aXJlIGRlZXBlciBrbm93bGVkZ2UgaW4gY29tcHV0ZXIgc2NpZW5jZSBieSB1bmRlcnN0YW5kaW5nIGl0cyBhYnN0cmFjdCBhbmQgaW50ZXJkaXNjaXBsaW5hcnkgZm91bmRhdGlvbnMsIGZvY3VzaW5nIG9uIHByb2JsZW0gc29sdmluZyBhbmQgZGV2ZWxvcGluZyBsaWZlbG9uZyBsZWFybmluZyBza2lsbHMuIiwiaWQiOiJodHRwczovL2xlYXN0b24uYmNkaXBsb21hLmNvbS9sYXctZWNvbm9taWNzLW1hbmFnZW1lbnQjTGVhcm5pbmdBY2hpZXZtZW50IiwidGl0bGUiOiJNYXN0ZXIgaW4gSW5mb3JtYXRpb24gYW5kIENvbXB1dGVyIFNjaWVuY2VzIn0sImxlYXJuaW5nU3BlY2lmaWNhdGlvbiI6eyJlY3RzQ3JlZGl0UG9pbnRzIjoxMjAsImVxZkxldmVsIjo3LCJpZCI6Imh0dHBzOi8vbGVhc3Rvbi5iY2RpcGxvbWEuY29tL2xhdy1lY29ub21pY3MtbWFuYWdlbWVudCNMZWFybmluZ1NwZWNpZmljYXRpb24iLCJpc2NlZGZDb2RlIjpbIjciXSwibnFmTGV2ZWwiOlsiNyJdfX0sImV2aWRlbmNlIjp7ImRvY3VtZW50UHJlc2VuY2UiOlsiUGh5c2ljYWwiXSwiZXZpZGVuY2VEb2N1bWVudCI6WyJQYXNzcG9ydCJdLCJpZCI6Imh0dHBzOi8vZXNzaWYuZXVyb3BhLmV1L3Rzci12YS9ldmlkZW5jZS9mMmFlZWM5Ny1mYzBkLTQyYmYtOGNhNy0wNTQ4MTkyZDU2NzgiLCJzdWJqZWN0UHJlc2VuY2UiOiJQaHlzaWNhbCIsInR5cGUiOlsiRG9jdW1lbnRWZXJpZmljYXRpb24iXSwidmVyaWZpZXIiOiJkaWQ6ZWJzaToyOTYyZmI3ODRkZjYxYmFhMjY3YzgxMzI0OTc1MzlmOGM2NzRiMzdjMTI0NGE3YSJ9LCJpZCI6InVybjp1dWlkOjZiMWQ4NDExLTllZDUtNDU2Ni05YzdmLTRjMjQxNjVmZjIzNiIsImlzc3VhbmNlRGF0ZSI6IjIwMjMtMDItMTZUMTI6MDQ6MTlaIiwiaXNzdWVkIjoiMjAyMy0wMi0xNlQxMjowNDoxOVoiLCJpc3N1ZXIiOiJkaWQ6ZWJzaTp6aFN3NXJQWGtjSGp2cXV3blZjVHp6QiIsInByb29mIjp7ImNyZWF0ZWQiOiIyMDIyLTA0LTI3VDEyOjI1OjA3WiIsImNyZWF0b3IiOiJkaWQ6ZWJzaTp6ZFJ2dktiWGhWVkJzWGhhdGp1aUJocyIsImRvbWFpbiI6Imh0dHBzOi8vYXBpLnByZXByb2QuZWJzaS5ldSIsImp3cyI6ImV5SmlOalFpT21aaGJITmxMQ0pqY21sMElqcGJJbUkyTkNKZExDSmhiR2NpT2lKRlV6STFOa3NpZlEuLm1JQm5NOFhEUXFTWUtRTlhfTHZhSmhtc2J5Q3I1T1o1Y1UyWmstUmVxTHByNGRvRnNnbW9vYmtPNTEyOHRaeS04S2ltVmpKa0d3MHdMMXVCV25NTFdRIiwibm9uY2UiOiIzZWE2OGRhZS1kMDdhLTRkYWEtOTMyYi1mYmI1OGY1YzIwYzQiLCJ0eXBlIjoiRWNkc2FTZWNwMjU2azFTaWduYXR1cmUyMDE5In0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJWZXJpZmlhYmxlQXR0ZXN0YXRpb24iLCJWZXJpZmlhYmxlRGlwbG9tYSJdLCJ2YWxpZEZyb20iOiIyMDIzLTAyLTE2VDEyOjA0OjE5WiJ9fQ.7Bmdrkp-hpM7BcNASc7ngjia3cjrZr-nqmP0Plsfnefn26G_yR5fBJV8BT_U2zsMlcqWsxuJ9pS2Vyiq_SrooQ"}' @@ -444,7 +446,7 @@ void main() { test('successfully send presentation', () async { const response = - '{"access":"ok","created":1676566727.680238,"credential_status":"signature check bypassed","holder_did_status":"ok","id_token_status":"ok","qrcode_status":"ok","response_format":"ok","status_code":200,"vp_token_status":"ok"}'; + '{"access":"ok","created":1676566727.680238,"credential_status":"signature check bypassed","holder_did_status":"ok","id_token_status":"ok","qrcode_status":"ok","response_format":"ok","status_code":200,"vp_token_status":"ok"}'; // ignore: lines_longer_than_80_chars dioAdapter.onGet( url, (request) => request.reply(200, jsonDecode(response)), @@ -477,7 +479,11 @@ void main() { final ebsi = Ebsi(client); await ebsi.sendPresentation( - uri, credentialToBePresented, mnemonic, null); + uri, + credentialToBePresented, + mnemonic, + null, + ); }, throwsA(isA()), ); From 0c2c7c515a096f1a8edc69fcb94c8966d3a9139b Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Fri, 17 Feb 2023 17:18:31 +0530 Subject: [PATCH 033/190] ebsi package supports both jsonId and jws #1368 --- .../verify_credentials_to_be_presented.dart | 46 ------------------- lib/scan/cubit/scan_cubit.dart | 12 ++--- packages/ebsi/lib/src/ebsi.dart | 3 +- .../lib/src/verifier_token_parameters.dart | 31 ++++++++----- .../verifier_token_parameters_test.dart | 2 +- 5 files changed, 25 insertions(+), 69 deletions(-) delete mode 100644 lib/ebsi/verify_credentials_to_be_presented.dart diff --git a/lib/ebsi/verify_credentials_to_be_presented.dart b/lib/ebsi/verify_credentials_to_be_presented.dart deleted file mode 100644 index b8be91361..000000000 --- a/lib/ebsi/verify_credentials_to_be_presented.dart +++ /dev/null @@ -1,46 +0,0 @@ -import 'dart:convert'; - -import 'package:altme/app/app.dart'; -import 'package:altme/dashboard/dashboard.dart'; -import 'package:ebsi/ebsi.dart'; -import 'package:secure_storage/secure_storage.dart'; - -Future> verifyCredentialsToBePresented({ - required List credentialsToBePresented, - required Ebsi ebsi, -}) async { - final String p256PrivateKey = await getRandomP256PrivateKey(getSecureStorage); - - for (int i = 0; i < credentialsToBePresented.length; i++) { - if (credentialsToBePresented[i].jwt == null) { - final TokenParameters tokenParameters = TokenParameters( - jsonDecode(p256PrivateKey) as Map, - ); - - final Map credentialDatatMap = credentialsToBePresented[i] - .data - .map((key, value) => MapEntry(key, value as Object)); - - final jwt = ebsi.generateToken(credentialDatatMap, tokenParameters); - - final newCredentialModel = CredentialModel( - id: credentialsToBePresented[i].id, - image: credentialsToBePresented[i].image, - data: credentialsToBePresented[i].data, - shareLink: credentialsToBePresented[i].shareLink, - display: credentialsToBePresented[i].display, - credentialPreview: credentialsToBePresented[i].credentialPreview, - expirationDate: credentialsToBePresented[i].expirationDate, - credentialManifest: credentialsToBePresented[i].credentialManifest, - receivedId: credentialsToBePresented[i].receivedId, - challenge: credentialsToBePresented[i].challenge, - domain: credentialsToBePresented[i].domain, - activities: credentialsToBePresented[i].activities, - jwt: jwt, - ); - - credentialsToBePresented[i] = newCredentialModel; - } - } - return credentialsToBePresented; -} diff --git a/lib/scan/cubit/scan_cubit.dart b/lib/scan/cubit/scan_cubit.dart index 2e328ead8..105fd69e8 100644 --- a/lib/scan/cubit/scan_cubit.dart +++ b/lib/scan/cubit/scan_cubit.dart @@ -3,7 +3,7 @@ import 'dart:convert'; import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/dashboard/home/tab_bar/credentials/models/activity/activity.dart'; -import 'package:altme/ebsi/verify_credentials_to_be_presented.dart'; + import 'package:altme/wallet/wallet.dart'; import 'package:bloc/bloc.dart'; import 'package:did_kit/did_kit.dart'; @@ -62,20 +62,14 @@ class ScanCubit extends Cubit { // final mnemonic = // await secureStorageProvider.get(SecureStorageKeys.ssiMnemonic); - final siopv2CredentialsToBePresented = - await verifyCredentialsToBePresented( - credentialsToBePresented: credentialsToBePresented!, - ebsi: ebsi, - ); - - final credentialList = siopv2CredentialsToBePresented + final credentialList = credentialsToBePresented! .map((e) => jsonEncode(e.toJson())) .toList(); await ebsi.sendPresentation(uri, credentialList, null, p256PrivateKey); await presentationActivity( - credentialModels: siopv2CredentialsToBePresented, + credentialModels: credentialsToBePresented, issuer: issuer, ); diff --git a/packages/ebsi/lib/src/ebsi.dart b/packages/ebsi/lib/src/ebsi.dart index 2aa191774..3c9534408 100644 --- a/packages/ebsi/lib/src/ebsi.dart +++ b/packages/ebsi/lib/src/ebsi.dart @@ -424,10 +424,11 @@ class Ebsi { 'id': 'http://example.org/presentations/talao/01', 'type': ['VerifiablePresentation'], 'holder': tokenParameters.didKey, - 'verifiableCredential': tokenParameters.jwtsOfCredentials + 'verifiableCredential': tokenParameters.jsonIdOrJwtList }, 'nonce': tokenParameters.nonce }; + final verifierVpJwt = generateToken(vpTokenPayload, tokenParameters); return verifierVpJwt; diff --git a/packages/ebsi/lib/src/verifier_token_parameters.dart b/packages/ebsi/lib/src/verifier_token_parameters.dart index 36a741029..9e1d25d39 100644 --- a/packages/ebsi/lib/src/verifier_token_parameters.dart +++ b/packages/ebsi/lib/src/verifier_token_parameters.dart @@ -7,29 +7,36 @@ import 'package:jose/jose.dart'; /// for verifier interactions. class VerifierTokenParameters extends TokenParameters { /// - VerifierTokenParameters(super.privateKey, this.uri, this.jwtList); + VerifierTokenParameters(super.privateKey, this.uri, this.credentials); /// [uri] provided by verifier and containing nonce final Uri uri; - /// [jwtList] is list of credentials to be presented - final List jwtList; + /// [credentials] is list of credentials to be presented + final List credentials; /// [nonce] is a number given by verifier to handle request authentication String get nonce => uri.queryParameters['nonce'] ?? ''; - /// [jwtsOfCredentials] is list of jwt from the jwtList wich contains - /// other credential's metadata - List get jwtsOfCredentials => jwtList - .map( - (credential) => - (jsonDecode(credential) as Map)['jwt'] as String, - ) - .toList(); + /// [jsonIdOrJwtList] is list of jwt or jsonIds from the credentials + /// wich contains other credential's metadata + List get jsonIdOrJwtList { + final list = []; + + for (final credential in credentials) { + final credentialJson = jsonDecode(credential) as Map; + if (credentialJson['jwt'] != null) { + list.add(credentialJson['jwt'].toString()); + } else { + list.add(credentialJson['data'].toString()); + } + } + return list; + } /// [audience] is is from first jwt claims String get audience { - final jwt = JsonWebToken.unverified(jwtsOfCredentials[0]); + final jwt = JsonWebToken.unverified(jsonIdOrJwtList[0]); final claims = jwt.claims; return claims['iss'] as String; } diff --git a/packages/ebsi/test/src/verifier_token_parameters/verifier_token_parameters_test.dart b/packages/ebsi/test/src/verifier_token_parameters/verifier_token_parameters_test.dart index ed2b4cfb1..8e6607526 100644 --- a/packages/ebsi/test/src/verifier_token_parameters/verifier_token_parameters_test.dart +++ b/packages/ebsi/test/src/verifier_token_parameters/verifier_token_parameters_test.dart @@ -46,7 +46,7 @@ void main() { }); test('jwtsOfCredentials', () { - expect(verifierTokenParameters.jwtsOfCredentials, jwtsOfCredentials); + expect(verifierTokenParameters.jsonIdOrJwtList, jwtsOfCredentials); }); test('audience', () { From 213f7d39d4bba682c1e2a2c2c3bd4b13e0784f36 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Fri, 17 Feb 2023 19:41:36 +0530 Subject: [PATCH 034/190] update audience data --- .../ebsi/lib/src/verifier_token_parameters.dart | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/packages/ebsi/lib/src/verifier_token_parameters.dart b/packages/ebsi/lib/src/verifier_token_parameters.dart index 9e1d25d39..971c7d23c 100644 --- a/packages/ebsi/lib/src/verifier_token_parameters.dart +++ b/packages/ebsi/lib/src/verifier_token_parameters.dart @@ -20,24 +20,20 @@ class VerifierTokenParameters extends TokenParameters { /// [jsonIdOrJwtList] is list of jwt or jsonIds from the credentials /// wich contains other credential's metadata - List get jsonIdOrJwtList { - final list = []; + List get jsonIdOrJwtList { + final list = []; for (final credential in credentials) { final credentialJson = jsonDecode(credential) as Map; if (credentialJson['jwt'] != null) { - list.add(credentialJson['jwt'].toString()); + list.add(credentialJson['jwt']); } else { - list.add(credentialJson['data'].toString()); + list.add(credentialJson['data']); } } return list; } - /// [audience] is is from first jwt claims - String get audience { - final jwt = JsonWebToken.unverified(jsonIdOrJwtList[0]); - final claims = jwt.claims; - return claims['iss'] as String; - } + /// [audience] is is client id of the request + String get audience => uri.queryParameters['client_id'] ?? ''; } From df5a784925432833eb4c97119cccab70423ac1f1 Mon Sep 17 00:00:00 2001 From: Taleb <40627325+TalebRafiepour@users.noreply.github.com> Date: Fri, 17 Feb 2023 19:58:59 +0330 Subject: [PATCH 035/190] Matrix db#1336 (#1373) * init database builder for matrix * enable database recording of the messages #1336 --- lib/app/view/app.dart | 8 + .../live_chat/cubit/live_chat_cubit.dart | 144 ++++++++++-------- pubspec.lock | 2 +- 3 files changed, 91 insertions(+), 63 deletions(-) diff --git a/lib/app/view/app.dart b/lib/app/view/app.dart index a7c5b99e7..8c9604c28 100644 --- a/lib/app/view/app.dart +++ b/lib/app/view/app.dart @@ -30,6 +30,7 @@ import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:jwt_decode/jwt_decode.dart'; import 'package:key_generator/key_generator.dart'; import 'package:matrix/matrix.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:secure_storage/secure_storage.dart' as secure_storage; import 'package:secure_storage/secure_storage.dart'; @@ -179,6 +180,13 @@ class App extends StatelessWidget { secureStorageProvider: getSecureStorage, client: Client( 'AltMeUser', + databaseBuilder: (_) async { + final dir = await getApplicationSupportDirectory(); + final db = + HiveCollectionsDatabase('matrix_support_chat', dir.path); + await db.open(); + return db; + }, ), ), ), diff --git a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart index aed08f9c1..d4607d10d 100644 --- a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart +++ b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart @@ -207,10 +207,12 @@ class LiveChatCubit extends Cubit { ); logger.i('roomId : $_roomId'); _subscribeToEventsOfRoom(); + final retrivedMessageFromDB = await _retriveMessagesFromDB(_roomId); emit( state.copyWith( status: AppStatus.init, user: User(id: userId), + messages: retrivedMessageFromDB, ), ); } catch (e, s) { @@ -221,6 +223,7 @@ class LiveChatCubit extends Cubit { void _subscribeToEventsOfRoom() { _onEventSubscription?.cancel(); + _onEventSubscription = client.onRoomState.stream.listen((Event event) { if (event.roomId == _roomId && event.type == 'm.room.message') { final txId = event.unsigned?['transaction_id'] as String?; @@ -237,68 +240,7 @@ class LiveChatCubit extends Cubit { newMessages[index] = updatedMessage; emit(state.copyWith(messages: newMessages)); } else { - late final Message message; - if (event.messageType == 'm.text') { - message = TextMessage( - id: event.unsigned?['transaction_id'] as String? ?? - const Uuid().v4(), - text: event.text, - createdAt: event.originServerTs.millisecondsSinceEpoch, - status: _mapEventStatusToMessageStatus(event.status), - author: User( - id: event.senderId, - ), - ); - } else if (event.messageType == 'm.image') { - message = ImageMessage( - id: const Uuid().v4(), - name: event.body, - size: event.content['info']['size'] as num, - uri: _getUrlFromUri(event.content['url'] as String), - status: _mapEventStatusToMessageStatus(event.status), - createdAt: event.originServerTs.millisecondsSinceEpoch, - author: User( - id: event.senderId, - ), - ); - } else if (event.messageType == 'm.file') { - message = FileMessage( - id: const Uuid().v4(), - name: event.body, - size: event.content['info']['size'] as num, - uri: _getUrlFromUri(event.content['url'] as String), - status: _mapEventStatusToMessageStatus(event.status), - createdAt: event.originServerTs.millisecondsSinceEpoch, - author: User( - id: event.senderId, - ), - ); - } else if (event.messageType == 'm.audio') { - message = AudioMessage( - id: const Uuid().v4(), - duration: Duration( - milliseconds: event.content['info']['duration'] as int, - ), - name: event.body, - size: event.content['info']['size'] as num, - uri: _getUrlFromUri(event.content['url'] as String), - status: _mapEventStatusToMessageStatus(event.status), - createdAt: event.originServerTs.millisecondsSinceEpoch, - author: User( - id: event.senderId, - ), - ); - } else { - message = TextMessage( - id: const Uuid().v4(), - text: event.text, - createdAt: event.originServerTs.millisecondsSinceEpoch, - status: _mapEventStatusToMessageStatus(event.status), - author: User( - id: event.senderId, - ), - ); - } + final Message message = _mapEventToMessage(event); emit( state.copyWith( messages: [message, ...state.messages], @@ -309,6 +251,84 @@ class LiveChatCubit extends Cubit { }); } + Message _mapEventToMessage(Event event) { + late final Message message; + if (event.messageType == 'm.text') { + message = TextMessage( + id: event.unsigned?['transaction_id'] as String? ?? const Uuid().v4(), + text: event.text, + createdAt: event.originServerTs.millisecondsSinceEpoch, + status: _mapEventStatusToMessageStatus(event.status), + author: User( + id: event.senderId, + ), + ); + } else if (event.messageType == 'm.image') { + message = ImageMessage( + id: const Uuid().v4(), + name: event.body, + size: event.content['info']['size'] as num, + uri: _getUrlFromUri(event.content['url'] as String), + status: _mapEventStatusToMessageStatus(event.status), + createdAt: event.originServerTs.millisecondsSinceEpoch, + author: User( + id: event.senderId, + ), + ); + } else if (event.messageType == 'm.file') { + message = FileMessage( + id: const Uuid().v4(), + name: event.body, + size: event.content['info']['size'] as num, + uri: _getUrlFromUri(event.content['url'] as String), + status: _mapEventStatusToMessageStatus(event.status), + createdAt: event.originServerTs.millisecondsSinceEpoch, + author: User( + id: event.senderId, + ), + ); + } else if (event.messageType == 'm.audio') { + message = AudioMessage( + id: const Uuid().v4(), + duration: Duration( + milliseconds: event.content['info']['duration'] as int, + ), + name: event.body, + size: event.content['info']['size'] as num, + uri: _getUrlFromUri(event.content['url'] as String), + status: _mapEventStatusToMessageStatus(event.status), + createdAt: event.originServerTs.millisecondsSinceEpoch, + author: User( + id: event.senderId, + ), + ); + } else { + message = TextMessage( + id: const Uuid().v4(), + text: event.text, + createdAt: event.originServerTs.millisecondsSinceEpoch, + status: _mapEventStatusToMessageStatus(event.status), + author: User( + id: event.senderId, + ), + ); + } + return message; + } + + Future> _retriveMessagesFromDB(String roomId) async { + final room = client.getRoomById(roomId); + if (room == null) return []; + final events = await client.database?.getEventList(room); + if (events == null || events.isEmpty) return []; + final messageEvents = + events.where((event) => event.type == 'm.room.message').toList() + ..sort( + (e1, e2) => e2.originServerTs.compareTo(e1.originServerTs), + ); + return messageEvents.map(_mapEventToMessage).toList(); + } + Future _createRoomAndInviteSupport(String name) async { final mRoomId = await secureStorageProvider.get(SecureStorageKeys.supportRoomId); diff --git a/pubspec.lock b/pubspec.lock index db3c0e363..194c1b85d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2343,5 +2343,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.19.0 <3.0.0" + dart: ">=2.19.0 <3.7.0" flutter: ">=3.7.0-0" From 3ba584a2a7b6a26592e11b8b45c165b3dc7918e3 Mon Sep 17 00:00:00 2001 From: Taleb <40627325+TalebRafiepour@users.noreply.github.com> Date: Fri, 17 Feb 2023 19:59:55 +0330 Subject: [PATCH 036/190] enable E2E encryption #1337 (#1376) * enable E2E encryption #1337 * add some verifcation method --- .../live_chat/cubit/live_chat_cubit.dart | 33 ++++++++ pubspec.lock | 80 +++++++++++++++++++ pubspec.yaml | 3 + .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 5 files changed, 120 insertions(+) diff --git a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart index d4607d10d..45e5c1e09 100644 --- a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart +++ b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart @@ -18,6 +18,7 @@ import 'package:matrix/matrix.dart' hide User; import 'package:mime/mime.dart'; import 'package:open_filex/open_filex.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:platform_device_id/platform_device_id.dart'; import 'package:secure_storage/secure_storage.dart'; import 'package:uuid/uuid.dart'; @@ -339,22 +340,52 @@ class LiveChatCubit extends Cubit { name: name, invite: ['@support:matrix.talao.co'], roomAliasName: name, + initialState: [ + StateEvent( + type: EventTypes.Encryption, + stateKey: '', + content: { + 'algorithm': 'm.megolm.v1.aes-sha2', + }, + ), + ], ); await secureStorageProvider.set( SecureStorageKeys.supportRoomId, roomId, ); + await _enableRoomEncyption(roomId); + logger.i('room created! => id: $roomId'); return roomId; } catch (e, s) { logger.e('e: $e, s: $s'); final roomId = await client.joinRoom(name); + await _enableRoomEncyption(roomId); return roomId; } } else { + await _enableRoomEncyption(mRoomId); return mRoomId; } } + Future _enableRoomEncyption(String roomId) async { + try { + final room = client.getRoomById(roomId); + if (room == null) return; + final verificationResponse = + await DeviceKeysList(client.userID!, client).startVerification(); + logger.i('verification response: $verificationResponse'); + await verificationResponse.acceptVerification(); + verificationResponse.onUpdate = () { + logger.i('on update the verifcation : ${verificationResponse.state}'); + }; + await room.enableEncryption(); + } catch (e, s) { + logger.e('error in enabling room e2e encryption, e: $e, s: $s'); + } + } + Future _initClient() async { client.homeserver = Uri.parse(Urls.matrixHomeServer); await client.init(); @@ -439,9 +470,11 @@ class LiveChatCubit extends Cubit { final isLogged = client.isLogged(); if (isLogged) return client.userID!; client.homeserver = Uri.parse(Urls.matrixHomeServer); + final deviceId = await PlatformDeviceId.getDeviceId; final loginResonse = await client.login( LoginType.mLoginPassword, password: password, + deviceId: deviceId, identifier: AuthenticationUserIdentifier(user: username), ); return loginResonse.userId!; diff --git a/pubspec.lock b/pubspec.lock index 194c1b85d..a22ad8659 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -497,6 +497,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + device_info: + dependency: transitive + description: + name: device_info + sha256: f4a8156cb7b7480d969cb734907d18b333c8f0bc0b1ad0b342cdcecf30d62c48 + url: "https://pub.dev" + source: hosted + version: "2.0.3" + device_info_platform_interface: + dependency: transitive + description: + name: device_info_platform_interface + sha256: b148e0bf9640145d09a4f8dea96614076f889e7f7f8b5ecab1c7e5c2dbc73c1b + url: "https://pub.dev" + source: hosted + version: "2.0.1" device_info_plus: dependency: "direct main" description: @@ -832,6 +848,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + flutter_olm: + dependency: "direct main" + description: + name: flutter_olm + sha256: fef0c9476d02c0df25ef0a66680bc23ac529a36b4911505910bcd8711b449c81 + url: "https://pub.dev" + source: hosted + version: "1.2.0" + flutter_openssl_crypto: + dependency: "direct main" + description: + name: flutter_openssl_crypto + sha256: b64a0825d79f10b6d5f5951f7ce2d5ddc12ed532129fc5a7e0ce472f5b97d78e + url: "https://pub.dev" + source: hosted + version: "0.1.0" flutter_parsed_text: dependency: transitive description: @@ -1568,6 +1600,54 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.0" + platform_device_id: + dependency: "direct main" + description: + name: platform_device_id + sha256: "7a12ec84de4a823bb10eba2f0e1ad29e2365abba17790489a0d78029904f562e" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + platform_device_id_linux: + dependency: transitive + description: + name: platform_device_id_linux + sha256: "994b1608593e527a629af2d5aeb241c60d308d3434bc78b0f6fcb3c1a02dff43" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + platform_device_id_macos: + dependency: transitive + description: + name: platform_device_id_macos + sha256: "968db2a504c611294b12a031b3734432d6df10553a0d3ae3b33ed21abfdbaba0" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + platform_device_id_platform_interface: + dependency: transitive + description: + name: platform_device_id_platform_interface + sha256: c61607594252aaddacf3e4c4371ab08f2ef85ff427817fa6e48a169429610c46 + url: "https://pub.dev" + source: hosted + version: "1.0.0" + platform_device_id_web: + dependency: transitive + description: + name: platform_device_id_web + sha256: "58e124594e1165db7f108395a780b1d1e1cd403021978e5228cf4289fbe736d5" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + platform_device_id_windows: + dependency: transitive + description: + name: platform_device_id_windows + sha256: dbf8dcf03ad8555320ebae2403a3081b79f137f37661874e161fe2de0a84eeeb + url: "https://pub.dev" + source: hosted + version: "1.0.0" plugin_platform_interface: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 021824b7e..91190e411 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -55,6 +55,8 @@ dependencies: sdk: flutter flutter_markdown: ^0.6.14 flutter_native_timezone: ^2.0.0 + flutter_olm: ^1.2.0 + flutter_openssl_crypto: ^0.1.0 flutter_svg: ^2.0.0+1 google_fonts: ^4.0.3 #google_mlkit_face_detection: ^0.5.0 @@ -82,6 +84,7 @@ dependencies: path: ^1.8.2 # flutter_test from sdk depends on path 1.8.2 path_provider: ^2.0.12 permission_handler: ^10.2.0 + platform_device_id: ^1.0.1 pointycastle: ^3.6.2 pretty_qr_code: ^2.0.2 qr_flutter: ^4.0.0 diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 67b1304f1..b7dfcf8b3 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -26,6 +27,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("LocalAuthPlugin")); PermissionHandlerWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); + PlatformDeviceIdWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("PlatformDeviceIdWindowsPlugin")); SecureApplicationPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("SecureApplicationPlugin")); SharePlusWindowsPluginCApiRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 27239ead3..68b90b809 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -8,6 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_secure_storage_windows local_auth_windows permission_handler_windows + platform_device_id_windows secure_application share_plus url_launcher_windows From 6caea37c6e5a1c74175a0f08ef96d50b9c42f48b Mon Sep 17 00:00:00 2001 From: hawkbee1 Date: Fri, 17 Feb 2023 17:32:41 +0100 Subject: [PATCH 037/190] version: 1.10.1+152 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 91190e411..7c945a205 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: altme description: AltMe Flutter App -version: 1.9.9+151 +version: 1.10.1+152 publish_to: none environment: From a0c4700c60088b78f7718286cc46c1db67023d91 Mon Sep 17 00:00:00 2001 From: hawkbee1 Date: Fri, 17 Feb 2023 17:58:21 +0100 Subject: [PATCH 038/190] comment android deployment --- script.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/script.sh b/script.sh index dbce97182..343de0eae 100755 --- a/script.sh +++ b/script.sh @@ -58,8 +58,8 @@ then echo "deploy android" echo "Make sure you are in right branch" fvm flutter build appbundle --flavor "production" --target "lib/main_production.dart" - cd android - fastlane deploy + # cd android + # fastlane deploy echo "app bundle deployed on internal testing track" elif [[ "$*" == *-ios* ]]; From 0f1d7c45bdafbba50d72bb2c19e4d76754237871 Mon Sep 17 00:00:00 2001 From: hawkbee1 Date: Fri, 17 Feb 2023 17:59:05 +0100 Subject: [PATCH 039/190] Revert "enable E2E encryption #1337 (#1376)" This reverts commit 3ba584a2a7b6a26592e11b8b45c165b3dc7918e3. --- .../live_chat/cubit/live_chat_cubit.dart | 33 -------- pubspec.lock | 80 ------------------- pubspec.yaml | 3 - .../flutter/generated_plugin_registrant.cc | 3 - windows/flutter/generated_plugins.cmake | 1 - 5 files changed, 120 deletions(-) diff --git a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart index 45e5c1e09..d4607d10d 100644 --- a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart +++ b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart @@ -18,7 +18,6 @@ import 'package:matrix/matrix.dart' hide User; import 'package:mime/mime.dart'; import 'package:open_filex/open_filex.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:platform_device_id/platform_device_id.dart'; import 'package:secure_storage/secure_storage.dart'; import 'package:uuid/uuid.dart'; @@ -340,52 +339,22 @@ class LiveChatCubit extends Cubit { name: name, invite: ['@support:matrix.talao.co'], roomAliasName: name, - initialState: [ - StateEvent( - type: EventTypes.Encryption, - stateKey: '', - content: { - 'algorithm': 'm.megolm.v1.aes-sha2', - }, - ), - ], ); await secureStorageProvider.set( SecureStorageKeys.supportRoomId, roomId, ); - await _enableRoomEncyption(roomId); - logger.i('room created! => id: $roomId'); return roomId; } catch (e, s) { logger.e('e: $e, s: $s'); final roomId = await client.joinRoom(name); - await _enableRoomEncyption(roomId); return roomId; } } else { - await _enableRoomEncyption(mRoomId); return mRoomId; } } - Future _enableRoomEncyption(String roomId) async { - try { - final room = client.getRoomById(roomId); - if (room == null) return; - final verificationResponse = - await DeviceKeysList(client.userID!, client).startVerification(); - logger.i('verification response: $verificationResponse'); - await verificationResponse.acceptVerification(); - verificationResponse.onUpdate = () { - logger.i('on update the verifcation : ${verificationResponse.state}'); - }; - await room.enableEncryption(); - } catch (e, s) { - logger.e('error in enabling room e2e encryption, e: $e, s: $s'); - } - } - Future _initClient() async { client.homeserver = Uri.parse(Urls.matrixHomeServer); await client.init(); @@ -470,11 +439,9 @@ class LiveChatCubit extends Cubit { final isLogged = client.isLogged(); if (isLogged) return client.userID!; client.homeserver = Uri.parse(Urls.matrixHomeServer); - final deviceId = await PlatformDeviceId.getDeviceId; final loginResonse = await client.login( LoginType.mLoginPassword, password: password, - deviceId: deviceId, identifier: AuthenticationUserIdentifier(user: username), ); return loginResonse.userId!; diff --git a/pubspec.lock b/pubspec.lock index a22ad8659..194c1b85d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -497,22 +497,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" - device_info: - dependency: transitive - description: - name: device_info - sha256: f4a8156cb7b7480d969cb734907d18b333c8f0bc0b1ad0b342cdcecf30d62c48 - url: "https://pub.dev" - source: hosted - version: "2.0.3" - device_info_platform_interface: - dependency: transitive - description: - name: device_info_platform_interface - sha256: b148e0bf9640145d09a4f8dea96614076f889e7f7f8b5ecab1c7e5c2dbc73c1b - url: "https://pub.dev" - source: hosted - version: "2.0.1" device_info_plus: dependency: "direct main" description: @@ -848,22 +832,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" - flutter_olm: - dependency: "direct main" - description: - name: flutter_olm - sha256: fef0c9476d02c0df25ef0a66680bc23ac529a36b4911505910bcd8711b449c81 - url: "https://pub.dev" - source: hosted - version: "1.2.0" - flutter_openssl_crypto: - dependency: "direct main" - description: - name: flutter_openssl_crypto - sha256: b64a0825d79f10b6d5f5951f7ce2d5ddc12ed532129fc5a7e0ce472f5b97d78e - url: "https://pub.dev" - source: hosted - version: "0.1.0" flutter_parsed_text: dependency: transitive description: @@ -1600,54 +1568,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.0" - platform_device_id: - dependency: "direct main" - description: - name: platform_device_id - sha256: "7a12ec84de4a823bb10eba2f0e1ad29e2365abba17790489a0d78029904f562e" - url: "https://pub.dev" - source: hosted - version: "1.0.1" - platform_device_id_linux: - dependency: transitive - description: - name: platform_device_id_linux - sha256: "994b1608593e527a629af2d5aeb241c60d308d3434bc78b0f6fcb3c1a02dff43" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - platform_device_id_macos: - dependency: transitive - description: - name: platform_device_id_macos - sha256: "968db2a504c611294b12a031b3734432d6df10553a0d3ae3b33ed21abfdbaba0" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - platform_device_id_platform_interface: - dependency: transitive - description: - name: platform_device_id_platform_interface - sha256: c61607594252aaddacf3e4c4371ab08f2ef85ff427817fa6e48a169429610c46 - url: "https://pub.dev" - source: hosted - version: "1.0.0" - platform_device_id_web: - dependency: transitive - description: - name: platform_device_id_web - sha256: "58e124594e1165db7f108395a780b1d1e1cd403021978e5228cf4289fbe736d5" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - platform_device_id_windows: - dependency: transitive - description: - name: platform_device_id_windows - sha256: dbf8dcf03ad8555320ebae2403a3081b79f137f37661874e161fe2de0a84eeeb - url: "https://pub.dev" - source: hosted - version: "1.0.0" plugin_platform_interface: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 7c945a205..9ef0b98dc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -55,8 +55,6 @@ dependencies: sdk: flutter flutter_markdown: ^0.6.14 flutter_native_timezone: ^2.0.0 - flutter_olm: ^1.2.0 - flutter_openssl_crypto: ^0.1.0 flutter_svg: ^2.0.0+1 google_fonts: ^4.0.3 #google_mlkit_face_detection: ^0.5.0 @@ -84,7 +82,6 @@ dependencies: path: ^1.8.2 # flutter_test from sdk depends on path 1.8.2 path_provider: ^2.0.12 permission_handler: ^10.2.0 - platform_device_id: ^1.0.1 pointycastle: ^3.6.2 pretty_qr_code: ^2.0.2 qr_flutter: ^4.0.0 diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index b7dfcf8b3..67b1304f1 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -27,8 +26,6 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("LocalAuthPlugin")); PermissionHandlerWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); - PlatformDeviceIdWindowsPluginRegisterWithRegistrar( - registry->GetRegistrarForPlugin("PlatformDeviceIdWindowsPlugin")); SecureApplicationPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("SecureApplicationPlugin")); SharePlusWindowsPluginCApiRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 68b90b809..27239ead3 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -8,7 +8,6 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_secure_storage_windows local_auth_windows permission_handler_windows - platform_device_id_windows secure_application share_plus url_launcher_windows From 1c8af74b8b879dab4e9e6ae9fc0a66ac249235aa Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Fri, 17 Feb 2023 16:22:02 +0330 Subject: [PATCH 040/190] enable E2E encryption #1337 --- .../live_chat/cubit/live_chat_cubit.dart | 22 +++++++++++++++++++ pubspec.lock | 16 ++++++++++++++ pubspec.yaml | 2 ++ 3 files changed, 40 insertions(+) diff --git a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart index d4607d10d..642268353 100644 --- a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart +++ b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart @@ -336,25 +336,47 @@ class LiveChatCubit extends Cubit { try { final roomId = await client.createRoom( isDirect: true, + preset: CreateRoomPreset.privateChat, name: name, invite: ['@support:matrix.talao.co'], roomAliasName: name, + initialState: [ + StateEvent( + type: EventTypes.Encryption, + stateKey: '', + content: { + 'algorithm': 'm.megolm.v1.aes-sha2', + }, + ), + ], ); await secureStorageProvider.set( SecureStorageKeys.supportRoomId, roomId, ); + await _enableRoomEncyption(roomId); + logger.i('room created! => id: $roomId'); return roomId; } catch (e, s) { logger.e('e: $e, s: $s'); final roomId = await client.joinRoom(name); + await _enableRoomEncyption(roomId); return roomId; } } else { + await _enableRoomEncyption(mRoomId); return mRoomId; } } + Future _enableRoomEncyption(String roomId) async { + try { + await client.getRoomById(roomId)?.enableEncryption(); + } catch (e, s) { + logger.e('error in enabling room e2e encryption, e: $e, s: $s'); + } + } + Future _initClient() async { client.homeserver = Uri.parse(Urls.matrixHomeServer); await client.init(); diff --git a/pubspec.lock b/pubspec.lock index 194c1b85d..c9116ab1f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -832,6 +832,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + flutter_olm: + dependency: "direct main" + description: + name: flutter_olm + sha256: fef0c9476d02c0df25ef0a66680bc23ac529a36b4911505910bcd8711b449c81 + url: "https://pub.dev" + source: hosted + version: "1.2.0" + flutter_openssl_crypto: + dependency: "direct main" + description: + name: flutter_openssl_crypto + sha256: b64a0825d79f10b6d5f5951f7ce2d5ddc12ed532129fc5a7e0ce472f5b97d78e + url: "https://pub.dev" + source: hosted + version: "0.1.0" flutter_parsed_text: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 9ef0b98dc..abc33c127 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -55,6 +55,8 @@ dependencies: sdk: flutter flutter_markdown: ^0.6.14 flutter_native_timezone: ^2.0.0 + flutter_olm: ^1.2.0 + flutter_openssl_crypto: ^0.1.0 flutter_svg: ^2.0.0+1 google_fonts: ^4.0.3 #google_mlkit_face_detection: ^0.5.0 From 504c5997f36255e1c4df19e6667a88a2153e9c72 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Fri, 17 Feb 2023 19:31:44 +0330 Subject: [PATCH 041/190] add some verifcation method --- .../live_chat/cubit/live_chat_cubit.dart | 15 ++++- pubspec.lock | 64 +++++++++++++++++++ pubspec.yaml | 1 + .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 5 files changed, 82 insertions(+), 2 deletions(-) diff --git a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart index 642268353..45e5c1e09 100644 --- a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart +++ b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart @@ -18,6 +18,7 @@ import 'package:matrix/matrix.dart' hide User; import 'package:mime/mime.dart'; import 'package:open_filex/open_filex.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:platform_device_id/platform_device_id.dart'; import 'package:secure_storage/secure_storage.dart'; import 'package:uuid/uuid.dart'; @@ -336,7 +337,6 @@ class LiveChatCubit extends Cubit { try { final roomId = await client.createRoom( isDirect: true, - preset: CreateRoomPreset.privateChat, name: name, invite: ['@support:matrix.talao.co'], roomAliasName: name, @@ -371,7 +371,16 @@ class LiveChatCubit extends Cubit { Future _enableRoomEncyption(String roomId) async { try { - await client.getRoomById(roomId)?.enableEncryption(); + final room = client.getRoomById(roomId); + if (room == null) return; + final verificationResponse = + await DeviceKeysList(client.userID!, client).startVerification(); + logger.i('verification response: $verificationResponse'); + await verificationResponse.acceptVerification(); + verificationResponse.onUpdate = () { + logger.i('on update the verifcation : ${verificationResponse.state}'); + }; + await room.enableEncryption(); } catch (e, s) { logger.e('error in enabling room e2e encryption, e: $e, s: $s'); } @@ -461,9 +470,11 @@ class LiveChatCubit extends Cubit { final isLogged = client.isLogged(); if (isLogged) return client.userID!; client.homeserver = Uri.parse(Urls.matrixHomeServer); + final deviceId = await PlatformDeviceId.getDeviceId; final loginResonse = await client.login( LoginType.mLoginPassword, password: password, + deviceId: deviceId, identifier: AuthenticationUserIdentifier(user: username), ); return loginResonse.userId!; diff --git a/pubspec.lock b/pubspec.lock index c9116ab1f..a22ad8659 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -497,6 +497,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + device_info: + dependency: transitive + description: + name: device_info + sha256: f4a8156cb7b7480d969cb734907d18b333c8f0bc0b1ad0b342cdcecf30d62c48 + url: "https://pub.dev" + source: hosted + version: "2.0.3" + device_info_platform_interface: + dependency: transitive + description: + name: device_info_platform_interface + sha256: b148e0bf9640145d09a4f8dea96614076f889e7f7f8b5ecab1c7e5c2dbc73c1b + url: "https://pub.dev" + source: hosted + version: "2.0.1" device_info_plus: dependency: "direct main" description: @@ -1584,6 +1600,54 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.0" + platform_device_id: + dependency: "direct main" + description: + name: platform_device_id + sha256: "7a12ec84de4a823bb10eba2f0e1ad29e2365abba17790489a0d78029904f562e" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + platform_device_id_linux: + dependency: transitive + description: + name: platform_device_id_linux + sha256: "994b1608593e527a629af2d5aeb241c60d308d3434bc78b0f6fcb3c1a02dff43" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + platform_device_id_macos: + dependency: transitive + description: + name: platform_device_id_macos + sha256: "968db2a504c611294b12a031b3734432d6df10553a0d3ae3b33ed21abfdbaba0" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + platform_device_id_platform_interface: + dependency: transitive + description: + name: platform_device_id_platform_interface + sha256: c61607594252aaddacf3e4c4371ab08f2ef85ff427817fa6e48a169429610c46 + url: "https://pub.dev" + source: hosted + version: "1.0.0" + platform_device_id_web: + dependency: transitive + description: + name: platform_device_id_web + sha256: "58e124594e1165db7f108395a780b1d1e1cd403021978e5228cf4289fbe736d5" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + platform_device_id_windows: + dependency: transitive + description: + name: platform_device_id_windows + sha256: dbf8dcf03ad8555320ebae2403a3081b79f137f37661874e161fe2de0a84eeeb + url: "https://pub.dev" + source: hosted + version: "1.0.0" plugin_platform_interface: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index abc33c127..7c945a205 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -84,6 +84,7 @@ dependencies: path: ^1.8.2 # flutter_test from sdk depends on path 1.8.2 path_provider: ^2.0.12 permission_handler: ^10.2.0 + platform_device_id: ^1.0.1 pointycastle: ^3.6.2 pretty_qr_code: ^2.0.2 qr_flutter: ^4.0.0 diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 67b1304f1..b7dfcf8b3 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -26,6 +27,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("LocalAuthPlugin")); PermissionHandlerWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); + PlatformDeviceIdWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("PlatformDeviceIdWindowsPlugin")); SecureApplicationPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("SecureApplicationPlugin")); SharePlusWindowsPluginCApiRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 27239ead3..68b90b809 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -8,6 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_secure_storage_windows local_auth_windows permission_handler_windows + platform_device_id_windows secure_application share_plus url_launcher_windows From 01a1352e61bd59ee097e8ffc8ce1d11d9c416f7f Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Mon, 20 Feb 2023 13:04:50 +0330 Subject: [PATCH 042/190] fix bug of crashing LiveChat after reset wallet #1383 --- lib/app/view/app.dart | 10 --------- .../drawer/drawer/view/reset_wallet_menu.dart | 9 ++++++-- .../live_chat/cubit/live_chat_cubit.dart | 22 +++++++++++++++---- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/lib/app/view/app.dart b/lib/app/view/app.dart index 8c9604c28..897a646d8 100644 --- a/lib/app/view/app.dart +++ b/lib/app/view/app.dart @@ -178,16 +178,6 @@ class App extends StatelessWidget { dioClient: DioClient('', Dio()), didCubit: context.read(), secureStorageProvider: getSecureStorage, - client: Client( - 'AltMeUser', - databaseBuilder: (_) async { - final dir = await getApplicationSupportDirectory(); - final db = - HiveCollectionsDatabase('matrix_support_chat', dir.path); - await db.open(); - return db; - }, - ), ), ), ], diff --git a/lib/dashboard/drawer/drawer/view/reset_wallet_menu.dart b/lib/dashboard/drawer/drawer/view/reset_wallet_menu.dart index 8411315fa..8fe5b907b 100644 --- a/lib/dashboard/drawer/drawer/view/reset_wallet_menu.dart +++ b/lib/dashboard/drawer/drawer/view/reset_wallet_menu.dart @@ -109,11 +109,16 @@ class ResetWalletView extends StatelessWidget { await getSecureStorage.get(SecureStorageKeys.pinCode); if (pinCode?.isEmpty ?? true) { await context.read().resetWallet(); + await context.read().dispose(); + await context.read().init(); } else { await Navigator.of(context).push( PinCodePage.route( - isValidCallback: () => - context.read().resetWallet(), + isValidCallback: () { + context.read().resetWallet(); + context.read().dispose(); + context.read().init(); + }, restrictToBack: false, ), ); diff --git a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart index d4607d10d..27afb9652 100644 --- a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart +++ b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart @@ -28,7 +28,6 @@ class LiveChatCubit extends Cubit { LiveChatCubit({ required this.didCubit, required this.secureStorageProvider, - required this.client, required this.dioClient, }) : super( const LiveChatState(), @@ -37,7 +36,7 @@ class LiveChatCubit extends Cubit { } final SecureStorageProvider secureStorageProvider; - final Client client; + late Client client; final DioClient dioClient; final logger = getLogger('LiveChatCubit'); String _roomId = ''; @@ -46,6 +45,10 @@ class LiveChatCubit extends Cubit { Future onSendPressed(PartialText partialText) async { try { + final room = client.getRoomById(_roomId); + if (room == null) { + await client.joinRoomById(_roomId); + } final eventId = await client.getRoomById(_roomId)?.sendTextEvent( partialText.text, txid: const Uuid().v4(), @@ -356,8 +359,18 @@ class LiveChatCubit extends Cubit { } Future _initClient() async { - client.homeserver = Uri.parse(Urls.matrixHomeServer); - await client.init(); + client = Client( + 'AltMeUser', + databaseBuilder: (_) async { + final dir = await getApplicationSupportDirectory(); + final db = HiveCollectionsDatabase('matrix_support_chat', dir.path); + await db.open(); + return db; + }, + ); + await client.init( + newHomeserver: Uri.parse(Urls.matrixHomeServer), + ); } Future _getDidAuth(String did, String nonce) async { @@ -451,6 +464,7 @@ class LiveChatCubit extends Cubit { await client.logout(); await client.dispose(); await _onEventSubscription?.cancel(); + _onEventSubscription = null; } String _getUrlFromUri(String uri) { From 0cc050ede8cdf91e132e37cefbe7963ae8054a65 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Mon, 20 Feb 2023 13:18:45 +0330 Subject: [PATCH 043/190] update welcome message of chat page and add the title about chat encyption #1382 --- .../drawer/live_chat/view/live_chat_page.dart | 35 ++++++++++++++++--- lib/l10n/arb/app_en.arb | 3 +- lib/l10n/untranslated.json | 12 ++++--- 3 files changed, 40 insertions(+), 10 deletions(-) diff --git a/lib/dashboard/drawer/live_chat/view/live_chat_page.dart b/lib/dashboard/drawer/live_chat/view/live_chat_page.dart index 90967526f..3d870b030 100644 --- a/lib/dashboard/drawer/live_chat/view/live_chat_page.dart +++ b/lib/dashboard/drawer/live_chat/view/live_chat_page.dart @@ -1,6 +1,7 @@ import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/l10n/l10n.dart'; +import 'package:altme/theme/app_theme/app_theme.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_chat_types/flutter_chat_types.dart'; @@ -89,12 +90,36 @@ class _ContactUsViewState extends State { ), if (state.messages.isEmpty) BackgroundCard( - padding: const EdgeInsets.all(Sizes.spaceNormal), + padding: const EdgeInsets.all(Sizes.spaceSmall), margin: const EdgeInsets.all(Sizes.spaceNormal), - child: Text( - l10n.supportChatWelcomeMessage, - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.bodyMedium, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + l10n.supportChatWelcomeMessage, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodyMedium, + ), + const SizedBox( + height: Sizes.spaceSmall, + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.lock, + size: Sizes.icon, + ), + const SizedBox( + width: Sizes.space2XSmall, + ), + Text( + l10n.e2eEncyptedChat, + style: Theme.of(context).textTheme.subtitle4, + ), + ], + ) + ], ), ), ], diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index c2dd6ce6e..d78815c4f 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -798,5 +798,6 @@ "supportChatWelcomeMessage": "Welcome to our chat support! We're here to assist you with any questions or concerns you may have about Altme.", "creator": "Creator", "contractAddress": "Contract address", - "lastMetadataSync": "Last metadata sync" + "lastMetadataSync": "Last metadata sync", + "e2eEncyptedChat": "Chat is encrypted from end to end." } \ No newline at end of file diff --git a/lib/l10n/untranslated.json b/lib/l10n/untranslated.json index b0909da3f..d5bffdcd2 100644 --- a/lib/l10n/untranslated.json +++ b/lib/l10n/untranslated.json @@ -734,7 +734,8 @@ "supportChatWelcomeMessage", "creator", "contractAddress", - "lastMetadataSync" + "lastMetadataSync", + "e2eEncyptedChat" ], "es": [ @@ -1472,7 +1473,8 @@ "supportChatWelcomeMessage", "creator", "contractAddress", - "lastMetadataSync" + "lastMetadataSync", + "e2eEncyptedChat" ], "fr": [ @@ -1488,7 +1490,8 @@ "supportChatWelcomeMessage", "creator", "contractAddress", - "lastMetadataSync" + "lastMetadataSync", + "e2eEncyptedChat" ], "it": [ @@ -2226,6 +2229,7 @@ "supportChatWelcomeMessage", "creator", "contractAddress", - "lastMetadataSync" + "lastMetadataSync", + "e2eEncyptedChat" ] } From ce0e9998edf62df7746faca2b573232841e35ba1 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Mon, 20 Feb 2023 17:21:46 +0330 Subject: [PATCH 044/190] add link on creator address and contract address in NFT details page #1377 --- .../helper_functions/helper_functions.dart | 29 +++++ .../tab_bar/nft/view/nft_details_page.dart | 102 ++++++++++++------ 2 files changed, 101 insertions(+), 30 deletions(-) diff --git a/lib/app/shared/helper_functions/helper_functions.dart b/lib/app/shared/helper_functions/helper_functions.dart index 518d25912..5ee738c13 100644 --- a/lib/app/shared/helper_functions/helper_functions.dart +++ b/lib/app/shared/helper_functions/helper_functions.dart @@ -44,6 +44,35 @@ Future openBlockchainExplorer( } } +Future openAddressBlockchainExplorer( + BlockchainNetwork network, + String address, +) async { + if (network is TezosNetwork) { + await LaunchUrl.launch( + 'https://tzkt.io/$address/operations', + ); + } else if (network is PolygonNetwork) { + await LaunchUrl.launch( + 'https://polygonscan.com/address/$address', + ); + } else if (network is BinanceNetwork) { + await LaunchUrl.launch( + 'https://www.bscscan.com/address/$address', + ); + } else if (network is FantomNetwork) { + await LaunchUrl.launch( + 'https://ftmscan.com/address/$address', + ); + } else if (network is EthereumNetwork) { + await LaunchUrl.launch( + 'https://etherscan.io/address/$address', + ); + } else { + UnimplementedError(); + } +} + String generateDefaultAccountName( int accountIndex, List accountNameList, diff --git a/lib/dashboard/home/tab_bar/nft/view/nft_details_page.dart b/lib/dashboard/home/tab_bar/nft/view/nft_details_page.dart index e5cd92d44..4bcfe2001 100644 --- a/lib/dashboard/home/tab_bar/nft/view/nft_details_page.dart +++ b/lib/dashboard/home/tab_bar/nft/view/nft_details_page.dart @@ -1,8 +1,10 @@ import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; +import 'package:altme/dashboard/home/tab_bar/nft/widgets/widgets.dart'; import 'package:altme/l10n/l10n.dart'; import 'package:altme/theme/theme.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_html/flutter_html.dart'; class NftDetailsPage extends StatelessWidget { @@ -102,28 +104,6 @@ class _NftDetailsViewState extends State { ...buildTezosMoreDetails(l10n) else ...buildEthereumMoreDetails(l10n), - // Text( - // l10n.seeMoreNFTInformationOn, - // style: Theme.of(context).textTheme.bodyText1, - // ), - // const SizedBox(height: Sizes.spaceSmall), - // Row( - // children: [ - // NftUrlWidget( - // text: 'Objkt.com', - // onPressed: () async { - // await LaunchUrl.launch(Urls.objktUrl); - // }, - // ), - // const SizedBox(width: 15), - // NftUrlWidget( - // text: 'Rarible.com', - // onPressed: () async { - // await LaunchUrl.launch(Urls.raribleUrl); - // }, - // ), - // ], - // ) ], ), ), @@ -169,7 +149,19 @@ class _NftDetailsViewState extends State { nftModel.contractAddress, style: Theme.of(context).textTheme.bodySmall3, ), - ) + ), + IconButton( + icon: const Icon( + Icons.open_in_new, + size: Sizes.icon, + ), + onPressed: () { + openAddressBlockchainExplorer( + context.read().state.network, + nftModel.contractAddress, + ); + }, + ), ], ), if (nftModel.identifier != null) ...[ @@ -187,7 +179,7 @@ class _NftDetailsViewState extends State { ], ), ], - if (nftModel.creators != null) ...[ + if (nftModel.creators != null && nftModel.creators!.isNotEmpty) ...[ const SizedBox( height: Sizes.spaceXSmall, ), @@ -205,10 +197,22 @@ class _NftDetailsViewState extends State { style: Theme.of(context).textTheme.bodySmall3, ), ), + IconButton( + icon: const Icon( + Icons.open_in_new, + size: Sizes.icon, + ), + onPressed: () { + openAddressBlockchainExplorer( + context.read().state.network, + nftModel.creators!.first, + ); + }, + ), ], ), ], - if (nftModel.publishers != null) ...[ + if (nftModel.publishers != null && nftModel.publishers!.isNotEmpty) ...[ const SizedBox( height: Sizes.spaceXSmall, ), @@ -218,10 +222,24 @@ class _NftDetailsViewState extends State { '${l10n.publishers} : ', style: Theme.of(context).textTheme.titleMedium, ), - Text( - nftModel.publishers?.join(', ') ?? '?', - style: Theme.of(context).textTheme.bodySmall3, - ) + Flexible( + child: Text( + nftModel.publishers?.join(', ') ?? '?', + style: Theme.of(context).textTheme.bodySmall3, + ), + ), + IconButton( + icon: const Icon( + Icons.open_in_new, + size: Sizes.icon, + ), + onPressed: () { + openAddressBlockchainExplorer( + context.read().state.network, + nftModel.publishers!.first, + ); + }, + ), ], ), ], @@ -261,7 +279,19 @@ class _NftDetailsViewState extends State { nftModel.contractAddress, style: Theme.of(context).textTheme.bodySmall3, ), - ) + ), + IconButton( + icon: const Icon( + Icons.open_in_new, + size: Sizes.icon, + ), + onPressed: () { + openAddressBlockchainExplorer( + context.read().state.network, + nftModel.contractAddress, + ); + }, + ), ], ), if (nftModel.minterAddress != null && nftModel.type != 'ERC1155') ...[ @@ -282,6 +312,18 @@ class _NftDetailsViewState extends State { style: Theme.of(context).textTheme.bodySmall3, ), ), + IconButton( + icon: const Icon( + Icons.open_in_new, + size: Sizes.icon, + ), + onPressed: () { + openAddressBlockchainExplorer( + context.read().state.network, + nftModel.minterAddress!, + ); + }, + ), ], ), ], From dd3841146780391fa8f70d986ad87fdeedca4d60 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Mon, 20 Feb 2023 20:31:39 +0330 Subject: [PATCH 045/190] fix bug of send token on Ethereum #1389 --- .../home/tab_bar/tokens/token_page/cubit/tokens_cubit.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/dashboard/home/tab_bar/tokens/token_page/cubit/tokens_cubit.dart b/lib/dashboard/home/tab_bar/tokens/token_page/cubit/tokens_cubit.dart index ef6f11cd5..d246021bf 100644 --- a/lib/dashboard/home/tab_bar/tokens/token_page/cubit/tokens_cubit.dart +++ b/lib/dashboard/home/tab_bar/tokens/token_page/cubit/tokens_cubit.dart @@ -136,6 +136,7 @@ class TokensCubit extends Cubit { balance: (json['balance'] as String?) ?? '', decimals: ((json['decimals'] as int?) ?? 0).toString(), thumbnailUri: json['thumbnail'] as String?, + standard: 'erc20', icon: icon, decimalsToShow: 2, ); From f8efc20c656845b595b51312a002c5bb256f7208 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Tue, 21 Feb 2023 10:16:36 +0530 Subject: [PATCH 046/190] attempt to verify ebsi credential using jose package --- .../cubit/qr_code_scan_cubit.dart | 14 ++-- packages/ebsi/lib/src/ebsi.dart | 84 +++++++++++++++---- packages/ebsi/test/src/ebsi_test.dart | 62 ++++++++++++++ 3 files changed, 139 insertions(+), 21 deletions(-) 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 bc6ffc543..f0f856bae 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 @@ -108,13 +108,15 @@ class QRCodeScanCubit extends Cubit { if (e is MessageHandler) { emit(state.error(messageHandler: e)); } else { + var message = + ResponseString.RESPONSE_STRING_SOMETHING_WENT_WRONG_TRY_AGAIN_LATER; + + if (e.toString() == 'Exception: VERIFICATION_ISSUE') { + message = ResponseString.RESPONSE_STRING_FAILED_TO_VERIFY_CREDENTIAL; + } + emit( - state.error( - messageHandler: ResponseMessage( - ResponseString - .RESPONSE_STRING_SOMETHING_WENT_WRONG_TRY_AGAIN_LATER, - ), - ), + state.error(messageHandler: ResponseMessage(message)), ); } } diff --git a/packages/ebsi/lib/src/ebsi.dart b/packages/ebsi/lib/src/ebsi.dart index 3c9534408..3ed231496 100644 --- a/packages/ebsi/lib/src/ebsi.dart +++ b/packages/ebsi/lib/src/ebsi.dart @@ -199,11 +199,12 @@ class Ebsi { final response = await getToken(tokenEndPoint, tokenData); + final private = await getPrivateKey(mnemonic, privateKey); + final issuerTokenParameters = IssuerTokenParameters(private, issuer); + final credentialData = await buildCredentialData( response as Map, - mnemonic, - privateKey, - issuer, + issuerTokenParameters, credentialRequestUri, ); @@ -260,15 +261,51 @@ class Ebsi { return issuer; } + Future>> getDidDocument(String didKey) async { + try { + final didDocument = await client.get>( + 'https://api-pilot.ebsi.eu/did-registry/v3/identifiers/$didKey', + ); + return didDocument; + } catch (e) { + throw Exception(e); + } + } + String readTokenEndPoint( Response> openidConfigurationResponse, ) { final jsonPath = JsonPath(r'$..token_endpoint'); + final tokenEndPoint = jsonPath.readValues(openidConfigurationResponse.data).first as String; return tokenEndPoint; } + String readIssuerDid( + Response> openidConfigurationResponse, + ) { + final jsonPath = JsonPath(r'$..issuer'); + final data = jsonPath.read(openidConfigurationResponse.data).first.value; + + return data['id'] as String; + } + + Map readPublicKeyJwk( + String issuerDid, + Response> didDocumentResponse, + ) { + final jsonPath = JsonPath(r'$..verificationMethod'); + final data = jsonPath.read(didDocumentResponse.data).first.value + ..where( + (dynamic e) => e['controller'].toString() == issuerDid, + ).toList(); + + final value = data.first['publicKeyJwk']; + + return jsonDecode(jsonEncode(value)) as Map; + } + Future> getPrivateKey( String? mnemonic, String? privateKey, @@ -281,29 +318,45 @@ class Ebsi { } else { private = jsonDecode(privateKey!) as Map; } - return private; } Future> buildCredentialData( Map response, - String? mnemonic, - String? privateKey, - String issuer, + IssuerTokenParameters issuerTokenParameters, Uri credentialRequestUri, ) async { final nonce = response['c_nonce'] as String; - final private = await getPrivateKey(mnemonic, privateKey); + final vcJwt = await getIssuerJwt(issuerTokenParameters, nonce); + + //final issuerDid = readIssuerDid(openidConfigurationResponse); + + const issuerDid = 'did:ebsi:zeFCExU2XAAshYkPCpjuahA'; + + final didDocument = + await getDidDocument('did:ebsi:zeFCExU2XAAshYkPCpjuahA'); + + final publicKeyJwk = readPublicKeyJwk(issuerDid, didDocument); + + // using jose package + final jws = JsonWebSignature.fromCompactSerialization(vcJwt); + + final keyStore = JsonWebKeyStore() + ..addKey(JsonWebKey.fromJson(publicKeyJwk)); + + final isVerified = await jws.verify(keyStore); + + if (!isVerified) { + throw Exception('VERIFICATION_ISSUE'); + } - final issuerTokenParameters = IssuerTokenParameters(private, issuer); - final jwt = await getIssuerJwt(issuerTokenParameters, nonce); final credentialType = credentialRequestUri.queryParameters['credential_type']; final credentialData = { 'type': credentialType, 'format': 'jwt_vc', - 'proof': {'proof_type': 'jwt', 'jwt': jwt} + 'proof': {'proof_type': 'jwt', 'jwt': vcJwt} }; return credentialData; } @@ -441,6 +494,9 @@ class Ebsi { final vpVerifierClaims = JsonWebTokenClaims.fromJson(vpTokenPayload); // create a builder, decoding the JWT in a JWS, so using a // JsonWebSignatureBuilder + + final key = JsonWebKey.fromJson(tokenParameters.privateKey); + final vpBuilder = JsonWebSignatureBuilder() // set the content ..jsonContent = vpVerifierClaims.toJson() @@ -450,10 +506,8 @@ class Ebsi { ..setProtectedHeader('kid', tokenParameters.kid) // add a key to sign, can only add one for JWT - ..addRecipient( - JsonWebKey.fromJson(tokenParameters.privateKey), - algorithm: tokenParameters.alg, - ); + ..addRecipient(key, algorithm: tokenParameters.alg); + // build the jws final vpJws = vpBuilder.build(); diff --git a/packages/ebsi/test/src/ebsi_test.dart b/packages/ebsi/test/src/ebsi_test.dart index 9a05350e9..e9779134a 100644 --- a/packages/ebsi/test/src/ebsi_test.dart +++ b/packages/ebsi/test/src/ebsi_test.dart @@ -16,6 +16,8 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:http_mock_adapter/http_mock_adapter.dart'; import 'package:mocktail/mocktail.dart'; +import 'const_values.dart'; + class MockDio extends Mock implements Dio {} void main() { @@ -345,6 +347,37 @@ void main() { ); expect(issuer, tokenUrl); }); + + test('get issuer did with openidConfigurationResponse', () { + const openidConfigurationResponse = + r'{"authorization_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/authorize","batch_credential_endpoint":null,"credential_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/credential","credential_issuer":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl","credential_manifests":[{"id":"VerifiableDiploma_1","issuer":{"id":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","name":"Test EBSILUX"},"output_descriptors":[{"display":{"description":{"fallback":"This card is a proof that you passed this diploma successfully. You can use this card when you need to prove this information to services that have adopted EU EBSI framework.","path":[],"schema":{"type":"string"}},"properties":[{"fallback":"Unknown","label":"First name","path":["$.credentialSubject.firstName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Last name","path":["$.credentialSubject.familyName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Birth date","path":["$.credentialSubject.dateOfBirth"],"schema":{"format":"date","type":"string"}},{"fallback":"Unknown","label":"Grading scheme","path":["$.credentialSubject.gradingScheme.title"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Title","path":["$.credentialSubject.learningAchievement.title"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Description","path":["$.credentialSubject.learningAchievement.description"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"ECTS Points","path":["$.credentialSubject.learningSpecification.ectsCreditPoints"],"schema":{"type":"number"}},{"fallback":"Unknown","label":"Issue date","path":["$.issuanceDate"],"schema":{"format":"date","type":"string"}},{"fallback":"Unknown","label":"Issued by","path":["$.credentialSubject.awardingOpportunity.awardingBody.preferredName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Registration","path":["$.credentialSubject.awardingOpportunity.awardingBody.registration"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Website","path":["$.credentialSubject.awardingOpportunity.awardingBody.homepage"],"schema":{"format":"uri","type":"string"}}],"subtitle":{"fallback":"EBSI Verifiable diploma","path":[],"schema":{"type":"string"}},"title":{"fallback":"Diploma","path":[],"schema":{"type":"string"}}},"id":"diploma_01","schema":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"}],"spec_version":"https://identity.foundation/credential-manifest/spec/v1.0.0/"}],"credential_supported":[{"cryptographic_binding_methods_supported":["did"],"cryptographic_suites_supported":["ES256K","ES256","ES384","ES512","RS256"],"display":[{"locale":"en-US","name":"Issuer Talao"}],"format":"jwt_vc","id":"VerifiableDiploma","types":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"},{"id":"VerifiableDiploma_1","issuer":{"id":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","name":"Test EBSILUX"},"output_descriptors":[{"display":{"description":{"fallback":"This card is a proof that you passed this diploma successfully. You can use this card when you need to prove this information to services that have adopted EU EBSI framework.","path":[],"schema":{"type":"string"}},"properties":[{"fallback":"Unknown","label":"First name","path":["$.credentialSubject.firstName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Last name","path":["$.credentialSubject.familyName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Birth date","path":["$.credentialSubject.dateOfBirth"],"schema":{"format":"date","type":"string"}},{"fallback":"Unknown","label":"Grading scheme","path":["$.credentialSubject.gradingScheme.title"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Title","path":["$.credentialSubject.learningAchievement.title"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Description","path":["$.credentialSubject.learningAchievement.description"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"ECTS Points","path":["$.credentialSubject.learningSpecification.ectsCreditPoints"],"schema":{"type":"number"}},{"fallback":"Unknown","label":"Issue date","path":["$.issuanceDate"],"schema":{"format":"date","type":"string"}},{"fallback":"Unknown","label":"Issued by","path":["$.credentialSubject.awardingOpportunity.awardingBody.preferredName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Registration","path":["$.credentialSubject.awardingOpportunity.awardingBody.registration"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Website","path":["$.credentialSubject.awardingOpportunity.awardingBody.homepage"],"schema":{"format":"uri","type":"string"}}],"subtitle":{"fallback":"EBSI Verifiable diploma","path":[],"schema":{"type":"string"}},"title":{"fallback":"Diploma","path":[],"schema":{"type":"string"}}},"id":"diploma_01","schema":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"}],"spec_version":"https://identity.foundation/credential-manifest/spec/v1.0.0/"}],"credential_supported":[{"cryptographic_binding_methods_supported":["did"],"cryptographic_suites_supported":["ES256K","ES256","ES384","ES512","RS256"],"display":[{"locale":"en-US","name":"Issuer Talao"}],"format":"jwt_vc","id":"VerifiableDiploma","types":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"}],"pre-authorized_grant_anonymous_access_supported":true,"subject_syntax_types_supported":["did:ebsi"],"token_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/token"}'; + + final issuer = ebsi.readIssuerDid( + Response( + requestOptions: RequestOptions(path: ''), + data: jsonDecode(openidConfigurationResponse) as Map, + ), + ); + expect(issuer, 'did:ebsi:zhSw5rPXkcHjvquwnVcTzzB'); + }); + + test('get publicKey did with didDocumentResponse', () { + const didDocumentResponse = + '{"assertionMethod":["did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53"],"authentication":["did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53"],"@context":"https://www.w3.org/ns/did/v1","id":"did:ebsi:zeFCExU2XAAshYkPCpjuahA","verificationMethod":[{"controller":"did:ebsi:zeFCExU2XAAshYkPCpjuahA","id":"did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53","publicKeyJwk":{"alg":"EdDSA","crv":"Ed25519","kid":"3623b877bbb24b08ba390f3585418f53","kty":"OKP","use":"sig","x":"pWgA8M3etXlLaqcRmgjEQkz7waseg3FKzMCzfm9Yeow"},"type":"Ed25519VerificationKey2019"}]}'; + + const issuerDid = 'did:ebsi:zeFCExU2XAAshYkPCpjuahA'; + + const expectedPublicKey = + '{alg: EdDSA, crv: Ed25519, kid: 3623b877bbb24b08ba390f3585418f53, kty: OKP, use: sig, x: pWgA8M3etXlLaqcRmgjEQkz7waseg3FKzMCzfm9Yeow}'; + final publicKey = ebsi.readPublicKeyJwk( + issuerDid, + Response( + requestOptions: RequestOptions(path: ''), + data: jsonDecode(didDocumentResponse) as Map, + ), + ); + expect(publicKey, expectedPublicKey); + }); }); group('getCredentialRequest', () { @@ -491,6 +524,35 @@ void main() { }); }); + test('get corresponding did document', () async { + const url = 'https://api-pilot.ebsi.eu/did-registry/v3/identifiers/$didKey'; + const response = { + '@context': 'https://w3id.org/did/v1', + 'id': 'did:ebsi:z24q8qN8UE1j4XAFiFKtvJbH', + 'verificationMethod': [ + { + 'id': 'did:ebsi:z24q8qN8UE1j4XAFiFKtvJbH#keys-1', + 'type': 'Secp256k1VerificationKey2018', + 'controller': 'did:ebsi:z24q8qN8UE1j4XAFiFKtvJbH', + 'publicKeyHex': + '04f9139fbd3b9780863b17e8390934a1017fc8d0f18b84f02acb04f24c9cae261e45745c6507881509b9e6d837329153ea75fafb2c1a5d504702d04f1e22a80ecc' // ignore: lines_longer_than_80_chars + } + ], + 'authentication': ['did:ebsi:z24q8qN8UE1j4XAFiFKtvJbH'], + 'assertionMethod': ['did:ebsi:z24q8qN8UE1j4XAFiFKtvJbH#keys-1'] + }; + + dioAdapter.onGet( + url, + (request) => request.reply(200, response), + ); + final ebsi = Ebsi(client); + + final value = await ebsi.getDidDocument(didKey); + + expect(value.data, response); + }); + test('sandbox', () { const cNonce = 'cNonce'; const did = 'did'; From 8de9422bbdd04eaa606b77e5df390d30d7fe6861 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Tue, 21 Feb 2023 14:33:29 +0530 Subject: [PATCH 047/190] by pass verification if verification fails --- packages/ebsi/lib/src/ebsi.dart | 27 ++++++++++++++++++++------- packages/ebsi/pubspec.yaml | 1 - 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/packages/ebsi/lib/src/ebsi.dart b/packages/ebsi/lib/src/ebsi.dart index 3ed231496..643d16e6e 100644 --- a/packages/ebsi/lib/src/ebsi.dart +++ b/packages/ebsi/lib/src/ebsi.dart @@ -339,13 +339,7 @@ class Ebsi { final publicKeyJwk = readPublicKeyJwk(issuerDid, didDocument); - // using jose package - final jws = JsonWebSignature.fromCompactSerialization(vcJwt); - - final keyStore = JsonWebKeyStore() - ..addKey(JsonWebKey.fromJson(publicKeyJwk)); - - final isVerified = await jws.verify(keyStore); + final isVerified = await verifyCredential(publicKeyJwk, vcJwt); if (!isVerified) { throw Exception('VERIFICATION_ISSUE'); @@ -361,6 +355,25 @@ class Ebsi { return credentialData; } + Future verifyCredential( + Map publicKeyJwk, + String vcJwt, + ) async { + try { + // using jose package + final jws = JsonWebSignature.fromCompactSerialization(vcJwt); + + final keyStore = JsonWebKeyStore() + ..addKey(JsonWebKey.fromJson(publicKeyJwk)); + + final isVerified = await jws.verify(keyStore); + + return isVerified; + } catch (e) { + return true; + } + } + String readCredentialEndpoint( Response> openidConfigurationResponse, ) { diff --git a/packages/ebsi/pubspec.yaml b/packages/ebsi/pubspec.yaml index d3c9654ef..8b45908d7 100644 --- a/packages/ebsi/pubspec.yaml +++ b/packages/ebsi/pubspec.yaml @@ -15,7 +15,6 @@ dependencies: dart_jsonwebtoken: ^2.7.1 dart_web3: ^0.0.3 dio: ^4.0.6 - ed25519_hd_key: ^2.2.0 fast_base58: ^0.2.1 flutter: sdk: flutter From 06702fd923f105a05d5a3d302d0a1e5097010d94 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Tue, 21 Feb 2023 14:47:54 +0530 Subject: [PATCH 048/190] unknown status added for ebsi --- .../shared/enum/status/credential_status.dart | 1 + .../shared/extension/credential_status.dart | 5 +++ .../cubit/credential_details_cubit.dart | 31 +++++++++++++------ .../detail/view/credentials_details_page.dart | 10 +++--- 4 files changed, 31 insertions(+), 16 deletions(-) diff --git a/lib/app/shared/enum/status/credential_status.dart b/lib/app/shared/enum/status/credential_status.dart index 0da6f5273..15d051832 100644 --- a/lib/app/shared/enum/status/credential_status.dart +++ b/lib/app/shared/enum/status/credential_status.dart @@ -2,4 +2,5 @@ enum CredentialStatus { active, suspended, pending, + unknown, } diff --git a/lib/app/shared/extension/credential_status.dart b/lib/app/shared/extension/credential_status.dart index d20928181..f163e042a 100644 --- a/lib/app/shared/extension/credential_status.dart +++ b/lib/app/shared/extension/credential_status.dart @@ -13,6 +13,8 @@ extension CredentialStatusExtension on CredentialStatus { return l10n.cardsProblem; case CredentialStatus.pending: return l10n.cardsPending; + case CredentialStatus.unknown: + return l10n.unknown; } } @@ -23,6 +25,7 @@ extension CredentialStatusExtension on CredentialStatus { case CredentialStatus.suspended: return Icons.error_rounded; case CredentialStatus.pending: + case CredentialStatus.unknown: return Icons.circle_outlined; } } @@ -35,6 +38,8 @@ extension CredentialStatusExtension on CredentialStatus { return Theme.of(context).colorScheme.inactiveColor; case CredentialStatus.pending: return Colors.orange; + case CredentialStatus.unknown: + return Colors.blue; } } } diff --git a/lib/dashboard/home/tab_bar/credentials/detail/cubit/credential_details_cubit.dart b/lib/dashboard/home/tab_bar/credentials/detail/cubit/credential_details_cubit.dart index b4cd3c6a9..617b2aaeb 100644 --- a/lib/dashboard/home/tab_bar/credentials/detail/cubit/credential_details_cubit.dart +++ b/lib/dashboard/home/tab_bar/credentials/detail/cubit/credential_details_cubit.dart @@ -22,10 +22,9 @@ class CredentialDetailsCubit extends Cubit { } Future verifyCredential(CredentialModel item) async { - if (isEbsiIssuer(item)) return; - emit(state.copyWith(status: AppStatus.loading)); await Future.delayed(const Duration(milliseconds: 500)); + if (item.expirationDate != null) { final DateTime dateTimeExpirationDate = DateTime.parse(item.expirationDate!); @@ -38,21 +37,33 @@ class CredentialDetailsCubit extends Cubit { ); } } - if (item.credentialPreview.credentialStatus.type != '') { - final CredentialStatus credentialStatus = - await item.checkRevocationStatus(); - if (credentialStatus == CredentialStatus.active) { - await verifyProofOfPurpose(item); - } else { + + if (isEbsiIssuer(item)) { + { emit( state.copyWith( - credentialStatus: CredentialStatus.suspended, + credentialStatus: CredentialStatus.unknown, status: AppStatus.idle, ), ); } } else { - await verifyProofOfPurpose(item); + if (item.credentialPreview.credentialStatus.type != '') { + final CredentialStatus credentialStatus = + await item.checkRevocationStatus(); + if (credentialStatus == CredentialStatus.active) { + await verifyProofOfPurpose(item); + } else { + emit( + state.copyWith( + credentialStatus: CredentialStatus.suspended, + status: AppStatus.idle, + ), + ); + } + } else { + await verifyProofOfPurpose(item); + } } } 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 d1e1ef83a..f9de2db2c 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 @@ -288,12 +288,10 @@ class _CredentialsDetailsViewState extends State { const SizedBox(height: 10), if (state.credentialDetailTabStatus == CredentialDetailTabStatus.informations) ...[ - if (!isEbsiIssuer(widget.credentialModel)) ...[ - const SizedBox(height: 10), - CredentialActiveStatus( - credentialStatus: state.credentialStatus, - ), - ], + const SizedBox(height: 10), + CredentialActiveStatus( + credentialStatus: state.credentialStatus, + ), if (outputDescriptors != null) ...[ const SizedBox(height: 10), CredentialManifestDetails( From fe1ea0b20713aa5b307ee2a0ba6165a4d0bd55e0 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Tue, 21 Feb 2023 15:28:39 +0530 Subject: [PATCH 049/190] ebsi verification status added --- .../cubit/credential_details_cubit.dart | 48 +++++++++++++++---- .../detail/view/credentials_details_page.dart | 4 +- lib/ebsi/verify_ebsi_credential.dart | 16 +++++++ packages/ebsi/lib/ebsi.dart | 1 + packages/ebsi/lib/src/ebsi.dart | 33 ++++++++----- packages/ebsi/lib/src/verification_type.dart | 5 ++ 6 files changed, 83 insertions(+), 24 deletions(-) create mode 100644 lib/ebsi/verify_ebsi_credential.dart create mode 100644 packages/ebsi/lib/src/verification_type.dart diff --git a/lib/dashboard/home/tab_bar/credentials/detail/cubit/credential_details_cubit.dart b/lib/dashboard/home/tab_bar/credentials/detail/cubit/credential_details_cubit.dart index 617b2aaeb..996e4e600 100644 --- a/lib/dashboard/home/tab_bar/credentials/detail/cubit/credential_details_cubit.dart +++ b/lib/dashboard/home/tab_bar/credentials/detail/cubit/credential_details_cubit.dart @@ -2,7 +2,10 @@ import 'dart:convert'; import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; +import 'package:altme/ebsi/verify_ebsi_credential.dart'; import 'package:did_kit/did_kit.dart'; +import 'package:dio/dio.dart'; +import 'package:ebsi/ebsi.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:json_annotation/json_annotation.dart'; @@ -12,10 +15,13 @@ part 'credential_details_cubit.g.dart'; part 'credential_details_state.dart'; class CredentialDetailsCubit extends Cubit { - CredentialDetailsCubit({required this.didKitProvider}) - : super(const CredentialDetailsState()); + CredentialDetailsCubit({ + required this.didKitProvider, + required this.client, + }) : super(const CredentialDetailsState()); final DIDKitProvider didKitProvider; + final DioClient client; void changeTabStatus(CredentialDetailTabStatus credentialDetailTabStatus) { emit(state.copyWith(credentialDetailTabStatus: credentialDetailTabStatus)); @@ -39,14 +45,38 @@ class CredentialDetailsCubit extends Cubit { } if (isEbsiIssuer(item)) { - { - emit( - state.copyWith( - credentialStatus: CredentialStatus.unknown, - status: AppStatus.idle, - ), - ); + print(item); + print(item.data); + + // TODO(bibash): dynamic //item.data['issuer'] + const issuerDid = 'did:ebsi:zeFCExU2XAAshYkPCpjuahA'; + + final VerificationType isVerified = await isEbsiCredentialVerified( + issuerDid, + client, + item.jwt!, + ); + + var credentialStatus = CredentialStatus.unknown; + + switch (isVerified) { + case VerificationType.verified: + credentialStatus = CredentialStatus.active; + break; + case VerificationType.notVerified: + credentialStatus = CredentialStatus.pending; + break; + case VerificationType.unKnown: + credentialStatus = CredentialStatus.unknown; + break; } + + emit( + state.copyWith( + credentialStatus: credentialStatus, + status: AppStatus.idle, + ), + ); } else { if (item.credentialPreview.credentialStatus.type != '') { final CredentialStatus credentialStatus = 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 f9de2db2c..ff5ddb9d3 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/l10n/l10n.dart'; import 'package:altme/theme/theme.dart'; import 'package:altme/wallet/wallet.dart'; import 'package:did_kit/did_kit.dart'; +import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; @@ -39,8 +40,7 @@ class CredentialsDetailsPage extends StatelessWidget { Widget build(BuildContext context) { return BlocProvider( create: (context) => CredentialDetailsCubit( - didKitProvider: DIDKitProvider(), - ), + didKitProvider: DIDKitProvider(), client: DioClient('', Dio())), child: CredentialsDetailsView( credentialModel: credentialModel, readOnly: readOnly, diff --git a/lib/ebsi/verify_ebsi_credential.dart b/lib/ebsi/verify_ebsi_credential.dart new file mode 100644 index 000000000..be6b28b64 --- /dev/null +++ b/lib/ebsi/verify_ebsi_credential.dart @@ -0,0 +1,16 @@ +import 'package:altme/app/shared/dio_client/dio_client.dart'; +import 'package:dio/dio.dart'; +import 'package:ebsi/ebsi.dart'; + +Future isEbsiCredentialVerified( + String issuerDid, + DioClient client, + String vcJwt, +) async { + final Ebsi ebsi = Ebsi(Dio()); + final VerificationType verificationType = await ebsi.verifyCredential( + issuerDid: issuerDid, + vcJwt: vcJwt, + ); + return verificationType; +} diff --git a/packages/ebsi/lib/ebsi.dart b/packages/ebsi/lib/ebsi.dart index bc7dbe648..9972af626 100644 --- a/packages/ebsi/lib/ebsi.dart +++ b/packages/ebsi/lib/ebsi.dart @@ -11,4 +11,5 @@ library ebsi; export 'src/ebsi.dart'; export 'src/issuer_token_parameters.dart'; export 'src/token_parameters.dart'; +export 'src/verification_type.dart'; export 'src/verifier_token_parameters.dart'; diff --git a/packages/ebsi/lib/src/ebsi.dart b/packages/ebsi/lib/src/ebsi.dart index 643d16e6e..3f2a7e9bd 100644 --- a/packages/ebsi/lib/src/ebsi.dart +++ b/packages/ebsi/lib/src/ebsi.dart @@ -7,6 +7,7 @@ import 'package:bip39/bip39.dart' as bip393; import 'package:dio/dio.dart'; import 'package:ebsi/src/issuer_token_parameters.dart'; import 'package:ebsi/src/token_parameters.dart'; +import 'package:ebsi/src/verification_type.dart'; import 'package:ebsi/src/verifier_token_parameters.dart'; import 'package:flutter/foundation.dart'; import 'package:hex/hex.dart'; @@ -332,16 +333,13 @@ class Ebsi { //final issuerDid = readIssuerDid(openidConfigurationResponse); + // TODO(bibash): dynamic const issuerDid = 'did:ebsi:zeFCExU2XAAshYkPCpjuahA'; - final didDocument = - await getDidDocument('did:ebsi:zeFCExU2XAAshYkPCpjuahA'); + final isVerified = + await verifyCredential(issuerDid: issuerDid, vcJwt: vcJwt); - final publicKeyJwk = readPublicKeyJwk(issuerDid, didDocument); - - final isVerified = await verifyCredential(publicKeyJwk, vcJwt); - - if (!isVerified) { + if (isVerified == VerificationType.notVerified) { throw Exception('VERIFICATION_ISSUE'); } @@ -355,11 +353,16 @@ class Ebsi { return credentialData; } - Future verifyCredential( - Map publicKeyJwk, - String vcJwt, - ) async { + // TODO(bibash): TEST + Future verifyCredential({ + required String issuerDid, + required String vcJwt, + }) async { + final didDocument = await getDidDocument(issuerDid); + try { + final publicKeyJwk = readPublicKeyJwk(issuerDid, didDocument); + // using jose package final jws = JsonWebSignature.fromCompactSerialization(vcJwt); @@ -368,9 +371,13 @@ class Ebsi { final isVerified = await jws.verify(keyStore); - return isVerified; + if (isVerified) { + return VerificationType.verified; + } else { + return VerificationType.notVerified; + } } catch (e) { - return true; + return VerificationType.unKnown; } } diff --git a/packages/ebsi/lib/src/verification_type.dart b/packages/ebsi/lib/src/verification_type.dart new file mode 100644 index 000000000..b1f424393 --- /dev/null +++ b/packages/ebsi/lib/src/verification_type.dart @@ -0,0 +1,5 @@ +enum VerificationType { + verified, + notVerified, + unKnown, +} From e883ac398b3722933408d2c0235244e8dcc4a49d Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Tue, 21 Feb 2023 16:00:19 +0530 Subject: [PATCH 050/190] some update --- packages/ebsi/test/src/ebsi_test.dart | 4 ++-- .../verifier_token_parameters_test.dart | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/ebsi/test/src/ebsi_test.dart b/packages/ebsi/test/src/ebsi_test.dart index e9779134a..9e0edba21 100644 --- a/packages/ebsi/test/src/ebsi_test.dart +++ b/packages/ebsi/test/src/ebsi_test.dart @@ -368,7 +368,7 @@ void main() { const issuerDid = 'did:ebsi:zeFCExU2XAAshYkPCpjuahA'; const expectedPublicKey = - '{alg: EdDSA, crv: Ed25519, kid: 3623b877bbb24b08ba390f3585418f53, kty: OKP, use: sig, x: pWgA8M3etXlLaqcRmgjEQkz7waseg3FKzMCzfm9Yeow}'; + '{"alg":"EdDSA","crv":"Ed25519","kid":"3623b877bbb24b08ba390f3585418f53","kty":"OKP","use":"sig","x":"pWgA8M3etXlLaqcRmgjEQkz7waseg3FKzMCzfm9Yeow"}'; final publicKey = ebsi.readPublicKeyJwk( issuerDid, Response( @@ -376,7 +376,7 @@ void main() { data: jsonDecode(didDocumentResponse) as Map, ), ); - expect(publicKey, expectedPublicKey); + expect(jsonEncode(publicKey), expectedPublicKey); }); }); diff --git a/packages/ebsi/test/src/verifier_token_parameters/verifier_token_parameters_test.dart b/packages/ebsi/test/src/verifier_token_parameters/verifier_token_parameters_test.dart index 8e6607526..fd7921744 100644 --- a/packages/ebsi/test/src/verifier_token_parameters/verifier_token_parameters_test.dart +++ b/packages/ebsi/test/src/verifier_token_parameters/verifier_token_parameters_test.dart @@ -34,7 +34,7 @@ void main() { const nonce = '69165b47-a851-11ed-bd52-0a1628958560'; - const audience = 'did:ebsi:zhSw5rPXkcHjvquwnVcTzzB'; + const audience = 'xjcqarovuv'; const jwtsOfCredentials = [ // ignore: lines_longer_than_80_chars From d5d819c0afcc2f6037c63e770d66b3f8b8797e1a Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Tue, 21 Feb 2023 16:04:18 +0530 Subject: [PATCH 051/190] issuer is made dynamic --- .../credentials/detail/cubit/credential_details_cubit.dart | 7 ++----- packages/ebsi/lib/src/ebsi.dart | 7 ++++--- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/dashboard/home/tab_bar/credentials/detail/cubit/credential_details_cubit.dart b/lib/dashboard/home/tab_bar/credentials/detail/cubit/credential_details_cubit.dart index 996e4e600..685132cc4 100644 --- a/lib/dashboard/home/tab_bar/credentials/detail/cubit/credential_details_cubit.dart +++ b/lib/dashboard/home/tab_bar/credentials/detail/cubit/credential_details_cubit.dart @@ -45,11 +45,8 @@ class CredentialDetailsCubit extends Cubit { } if (isEbsiIssuer(item)) { - print(item); - print(item.data); - - // TODO(bibash): dynamic //item.data['issuer'] - const issuerDid = 'did:ebsi:zeFCExU2XAAshYkPCpjuahA'; + final issuerDid = item.data['issuer']! as String; + //const issuerDid = 'did:ebsi:zeFCExU2XAAshYkPCpjuahA'; final VerificationType isVerified = await isEbsiCredentialVerified( issuerDid, diff --git a/packages/ebsi/lib/src/ebsi.dart b/packages/ebsi/lib/src/ebsi.dart index 3f2a7e9bd..4910ae01e 100644 --- a/packages/ebsi/lib/src/ebsi.dart +++ b/packages/ebsi/lib/src/ebsi.dart @@ -207,6 +207,7 @@ class Ebsi { response as Map, issuerTokenParameters, credentialRequestUri, + openidConfigurationResponse, ); final credentialEndpoint = @@ -326,15 +327,15 @@ class Ebsi { Map response, IssuerTokenParameters issuerTokenParameters, Uri credentialRequestUri, + Response> openidConfigurationResponse, ) async { final nonce = response['c_nonce'] as String; final vcJwt = await getIssuerJwt(issuerTokenParameters, nonce); - //final issuerDid = readIssuerDid(openidConfigurationResponse); + final issuerDid = readIssuerDid(openidConfigurationResponse); - // TODO(bibash): dynamic - const issuerDid = 'did:ebsi:zeFCExU2XAAshYkPCpjuahA'; + //const issuerDid = 'did:ebsi:zeFCExU2XAAshYkPCpjuahA'; final isVerified = await verifyCredential(issuerDid: issuerDid, vcJwt: vcJwt); From e459e9c3776f30da789eb306385c1bb04525e52d Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Tue, 21 Feb 2023 16:09:48 +0530 Subject: [PATCH 052/190] if issuer fails then credential is added --- packages/ebsi/lib/src/ebsi.dart | 3 +-- pubspec.lock | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/ebsi/lib/src/ebsi.dart b/packages/ebsi/lib/src/ebsi.dart index 4910ae01e..321d153a3 100644 --- a/packages/ebsi/lib/src/ebsi.dart +++ b/packages/ebsi/lib/src/ebsi.dart @@ -359,9 +359,8 @@ class Ebsi { required String issuerDid, required String vcJwt, }) async { - final didDocument = await getDidDocument(issuerDid); - try { + final didDocument = await getDidDocument(issuerDid); final publicKeyJwk = readPublicKeyJwk(issuerDid, didDocument); // using jose package diff --git a/pubspec.lock b/pubspec.lock index 194c1b85d..db3c0e363 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2343,5 +2343,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.19.0 <3.7.0" + dart: ">=2.19.0 <3.0.0" flutter: ">=3.7.0-0" From 2b3350402680d51f7104a32f0c66146c85564b6c Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Tue, 21 Feb 2023 17:55:38 +0530 Subject: [PATCH 053/190] separate diploma card into education category --- lib/app/shared/constants/parameters.dart | 1 + .../shared/constants/secure_storage_keys.dart | 1 + lib/app/shared/enum/credential_category.dart | 1 + .../discover/view/discover_page.dart | 1 + .../cubit/advance_settings_cubit.dart | 22 ++++ .../cubit/advance_settings_state.dart | 5 + .../view/advanced_settings_page.dart | 7 +- .../view/manage_accounts_page.dart | 2 +- .../list/cubit/credential_list_cubit.dart | 112 +++++++++++++++--- .../list/cubit/credential_list_state.dart | 10 ++ .../list/widgets/credential_list_data.dart | 12 +- .../eu_diploma_card_model.dart | 2 +- lib/l10n/arb/app_en.arb | 3 +- lib/l10n/arb/app_fr.arb | 1 - lib/l10n/untranslated.json | 11 +- lib/wallet/cubit/wallet_cubit.dart | 7 ++ 16 files changed, 172 insertions(+), 26 deletions(-) diff --git a/lib/app/shared/constants/parameters.dart b/lib/app/shared/constants/parameters.dart index 4cc54d493..8534adbec 100644 --- a/lib/app/shared/constants/parameters.dart +++ b/lib/app/shared/constants/parameters.dart @@ -20,6 +20,7 @@ class Parameters { isGamingEnabled: true, isIdentityEnabled: true, isBlockchainAccountsEnabled: true, + isEducationEnabled: true, isSocialMediaEnabled: true, isCommunityEnabled: true, isOtherEnabled: true, diff --git a/lib/app/shared/constants/secure_storage_keys.dart b/lib/app/shared/constants/secure_storage_keys.dart index ee8400609..dd1c944ab 100644 --- a/lib/app/shared/constants/secure_storage_keys.dart +++ b/lib/app/shared/constants/secure_storage_keys.dart @@ -12,6 +12,7 @@ class SecureStorageKeys { static const String isGamingEnabled = 'isGamingEnabled'; static const String isBlockchainAccountsEnabled = 'isBlockchainAccountsEnabled'; + static const String isEducationEnabled = 'isEducationEnabled'; static const String isPassEnabled = 'isPassEnabled'; static const String isSocialMediaEnabled = 'isSocialMediaEnabled'; static const String fingerprintEnabled = 'fingerprintEnabled'; diff --git a/lib/app/shared/enum/credential_category.dart b/lib/app/shared/enum/credential_category.dart index e1e9637fd..038377d62 100644 --- a/lib/app/shared/enum/credential_category.dart +++ b/lib/app/shared/enum/credential_category.dart @@ -3,6 +3,7 @@ enum CredentialCategory { identityCards, communityCards, blockchainAccountsCards, + educationCards, passCards, othersCards, myProfessionalCards, diff --git a/lib/dashboard/discover/view/discover_page.dart b/lib/dashboard/discover/view/discover_page.dart index e6c391b9c..0c7ab4d0e 100644 --- a/lib/dashboard/discover/view/discover_page.dart +++ b/lib/dashboard/discover/view/discover_page.dart @@ -75,6 +75,7 @@ class _DiscoverPageState extends State { state.myProfessionalCategories, ), blockchainAccountsCredentials: [], + educationCredentials: [], othersCredentials: [], ), ); diff --git a/lib/dashboard/drawer/advance_settings/cubit/advance_settings_cubit.dart b/lib/dashboard/drawer/advance_settings/cubit/advance_settings_cubit.dart index da597de7d..f35275cb4 100644 --- a/lib/dashboard/drawer/advance_settings/cubit/advance_settings_cubit.dart +++ b/lib/dashboard/drawer/advance_settings/cubit/advance_settings_cubit.dart @@ -38,6 +38,10 @@ class AdvanceSettingsCubit extends Cubit { .get(SecureStorageKeys.isBlockchainAccountsEnabled) ?? 'true') == 'true'; + final isEducationEnabled = (await secureStorageProvider + .get(SecureStorageKeys.isEducationEnabled) ?? + 'true') == + 'true'; final isSocialMediaEnabled = (await secureStorageProvider .get(SecureStorageKeys.isSocialMediaEnabled) ?? 'true') == @@ -52,6 +56,7 @@ class AdvanceSettingsCubit extends Cubit { isGamingEnabled: isGamingEnabled, isIdentityEnabled: isIdentityEnabled, isBlockchainAccountsEnabled: isBlockchainAccountsEnabled, + isEducationEnabled: isEducationEnabled, isSocialMediaEnabled: isSocialMediaEnabled, isCommunityEnabled: isCommunityEnabled, isOtherEnabled: isOtherEnabled, @@ -75,6 +80,11 @@ class AdvanceSettingsCubit extends Cubit { newState.isBlockchainAccountsEnabled.toString(), ); + await secureStorageProvider.set( + SecureStorageKeys.isEducationEnabled, + newState.isEducationEnabled.toString(), + ); + await secureStorageProvider.set( SecureStorageKeys.isSocialMediaEnabled, newState.isSocialMediaEnabled.toString(), @@ -125,6 +135,18 @@ class AdvanceSettingsCubit extends Cubit { ); } + void toggleEducationRadio() { + emit( + state.copyWith( + isEducationEnabled: !state.isEducationEnabled, + ), + ); + secureStorageProvider.set( + SecureStorageKeys.isEducationEnabled, + state.isEducationEnabled.toString(), + ); + } + void toggleSocialMediaRadio() { emit(state.copyWith(isSocialMediaEnabled: !state.isSocialMediaEnabled)); secureStorageProvider.set( diff --git a/lib/dashboard/drawer/advance_settings/cubit/advance_settings_state.dart b/lib/dashboard/drawer/advance_settings/cubit/advance_settings_state.dart index e16863e31..3ceef3b52 100644 --- a/lib/dashboard/drawer/advance_settings/cubit/advance_settings_state.dart +++ b/lib/dashboard/drawer/advance_settings/cubit/advance_settings_state.dart @@ -6,6 +6,7 @@ class AdvanceSettingsState extends Equatable { required this.isGamingEnabled, required this.isIdentityEnabled, required this.isBlockchainAccountsEnabled, + required this.isEducationEnabled, required this.isPassEnabled, required this.isSocialMediaEnabled, required this.isCommunityEnabled, @@ -18,6 +19,7 @@ class AdvanceSettingsState extends Equatable { final bool isGamingEnabled; final bool isIdentityEnabled; final bool isBlockchainAccountsEnabled; + final bool isEducationEnabled; final bool isPassEnabled; final bool isSocialMediaEnabled; final bool isCommunityEnabled; @@ -29,6 +31,7 @@ class AdvanceSettingsState extends Equatable { bool? isGamingEnabled, bool? isIdentityEnabled, bool? isBlockchainAccountsEnabled, + bool? isEducationEnabled, bool? isPassEnabled, bool? isSocialMediaEnabled, bool? isCommunityEnabled, @@ -39,6 +42,7 @@ class AdvanceSettingsState extends Equatable { isIdentityEnabled: isIdentityEnabled ?? this.isIdentityEnabled, isBlockchainAccountsEnabled: isBlockchainAccountsEnabled ?? this.isBlockchainAccountsEnabled, + isEducationEnabled: isEducationEnabled ?? this.isEducationEnabled, isPassEnabled: isPassEnabled ?? this.isPassEnabled, isSocialMediaEnabled: isSocialMediaEnabled ?? this.isSocialMediaEnabled, isCommunityEnabled: isCommunityEnabled ?? this.isCommunityEnabled, @@ -51,6 +55,7 @@ class AdvanceSettingsState extends Equatable { isGamingEnabled, isIdentityEnabled, isBlockchainAccountsEnabled, + isEducationEnabled, isPassEnabled, isSocialMediaEnabled, isCommunityEnabled, diff --git a/lib/dashboard/drawer/advance_settings/view/advanced_settings_page.dart b/lib/dashboard/drawer/advance_settings/view/advanced_settings_page.dart index fc57971c7..8d1c59ad3 100644 --- a/lib/dashboard/drawer/advance_settings/view/advanced_settings_page.dart +++ b/lib/dashboard/drawer/advance_settings/view/advanced_settings_page.dart @@ -59,11 +59,16 @@ class _AdvancedSettingsViewState extends State { onPressed: advancedSettingsCubit.toggleIdentityRadio, ), AdvanceSettingsRadioItem( - title: l10n.blockChainAccounts, + title: l10n.blockchainAccounts, isSelected: state.isBlockchainAccountsEnabled, onPressed: advancedSettingsCubit.toggleBlockchainAccountsRadio, ), + AdvanceSettingsRadioItem( + title: l10n.educationAccounts, + isSelected: state.isEducationEnabled, + onPressed: advancedSettingsCubit.toggleEducationRadio, + ), AdvanceSettingsRadioItem( title: l10n.community, isSelected: state.isCommunityEnabled, diff --git a/lib/dashboard/drawer/manage_accounts/view/manage_accounts_page.dart b/lib/dashboard/drawer/manage_accounts/view/manage_accounts_page.dart index d9e6f71bd..8eb05d152 100644 --- a/lib/dashboard/drawer/manage_accounts/view/manage_accounts_page.dart +++ b/lib/dashboard/drawer/manage_accounts/view/manage_accounts_page.dart @@ -98,7 +98,7 @@ class _ManageAccountsPageState extends State { }, builder: (context, state) { return BasePage( - title: l10n.blockChainAccounts, + title: l10n.blockchainAccounts, titleAlignment: Alignment.topCenter, scrollView: false, titleLeading: const BackLeadingButton(), diff --git a/lib/dashboard/home/tab_bar/credentials/list/cubit/credential_list_cubit.dart b/lib/dashboard/home/tab_bar/credentials/list/cubit/credential_list_cubit.dart index 97c43f2f2..6743de80b 100644 --- a/lib/dashboard/home/tab_bar/credentials/list/cubit/credential_list_cubit.dart +++ b/lib/dashboard/home/tab_bar/credentials/list/cubit/credential_list_cubit.dart @@ -20,6 +20,7 @@ class CredentialListCubit extends Cubit { final identityCredentials = []; final passCredentials = []; final blockchainAccountsCredentials = []; + final educationCredentials = []; final othersCredentials = []; final myProfessionalCredentials = []; final gamingCategories = state.gamingCategories; @@ -137,6 +138,12 @@ class CredentialListCubit extends Cubit { .add(HomeCredential.isNotDummy(credential)); break; + case CredentialCategory.educationCards: + + /// adding real credentials + educationCredentials.add(HomeCredential.isNotDummy(credential)); + break; + case CredentialCategory.passCards: /// adding real credentials @@ -146,7 +153,12 @@ class CredentialListCubit extends Cubit { case CredentialCategory.othersCards: /// adding real credentials - othersCredentials.add(HomeCredential.isNotDummy(credential)); + if (credential.credentialPreview.type.contains('VerifiableDiploma')) { + educationCredentials.add(HomeCredential.isNotDummy(credential)); + } else { + othersCredentials.add(HomeCredential.isNotDummy(credential)); + } + break; } } @@ -167,6 +179,7 @@ class CredentialListCubit extends Cubit { communityCredentials: communityCredentials, identityCredentials: identityCredentials, blockchainAccountsCredentials: blockchainAccountsCredentials, + educationCredentials: educationCredentials, passCredentials: passCredentials, othersCredentials: othersCredentials, myProfessionalCredentials: myProfessionalCredentials, @@ -314,6 +327,14 @@ class CredentialListCubit extends Cubit { emit(state.populate(blockchainAccountsCredentials: credentials)); break; + case CredentialCategory.educationCards: + + /// adding real credentials + final credentials = List.of(state.educationCredentials) + ..insert(0, HomeCredential.isNotDummy(credential)); + emit(state.populate(educationCredentials: credentials)); + break; + case CredentialCategory.passCards: /// adding real credentials @@ -325,9 +346,16 @@ class CredentialListCubit extends Cubit { case CredentialCategory.othersCards: /// adding real credentials - final credentials = List.of(state.othersCredentials) - ..insert(0, HomeCredential.isNotDummy(credential)); - emit(state.populate(othersCredentials: credentials)); + if (credential.credentialPreview.type.contains('VerifiableDiploma')) { + final credentials = List.of(state.educationCredentials) + ..insert(0, HomeCredential.isNotDummy(credential)); + emit(state.populate(educationCredentials: credentials)); + } else { + final credentials = List.of(state.othersCredentials) + ..insert(0, HomeCredential.isNotDummy(credential)); + emit(state.populate(othersCredentials: credentials)); + } + break; } } @@ -437,38 +465,71 @@ class CredentialListCubit extends Cubit { emit(state.populate(blockchainAccountsCredentials: credentials)); break; - case CredentialCategory.passCards: + case CredentialCategory.educationCards: ///finding index of updated credential - final index = state.passCredentials.indexWhere( + final index = state.educationCredentials.indexWhere( (element) => element.credentialModel?.id == credential.id, ); ///create updated credential list - final credentials = List.of(state.passCredentials) + final credentials = List.of(state.educationCredentials) ..removeWhere( (element) => element.credentialModel?.id == credential.id, ) ..insert(index, HomeCredential.isNotDummy(credential)); - emit(state.populate(passCredentials: credentials)); + emit(state.populate(educationCredentials: credentials)); break; - case CredentialCategory.othersCards: + case CredentialCategory.passCards: ///finding index of updated credential - final index = state.othersCredentials.indexWhere( + final index = state.passCredentials.indexWhere( (element) => element.credentialModel?.id == credential.id, ); ///create updated credential list - final credentials = List.of(state.othersCredentials) + final credentials = List.of(state.passCredentials) ..removeWhere( (element) => element.credentialModel?.id == credential.id, ) ..insert(index, HomeCredential.isNotDummy(credential)); - emit(state.populate(othersCredentials: credentials)); + emit(state.populate(passCredentials: credentials)); + break; + + case CredentialCategory.othersCards: + + ///finding index of updated credential + if (credential.credentialPreview.type.contains('VerifiableDiploma')) { + final index = state.educationCredentials.indexWhere( + (element) => element.credentialModel?.id == credential.id, + ); + + ///create updated credential list + final credentials = List.of(state.educationCredentials) + ..removeWhere( + (element) => element.credentialModel?.id == credential.id, + ) + ..insert(index, HomeCredential.isNotDummy(credential)); + + emit(state.populate(educationCredentials: credentials)); + } else { + final index = state.othersCredentials.indexWhere( + (element) => element.credentialModel?.id == credential.id, + ); + + ///create updated credential list + final credentials = List.of(state.othersCredentials) + ..removeWhere( + (element) => element.credentialModel?.id == credential.id, + ) + ..insert(index, HomeCredential.isNotDummy(credential)); + + emit(state.populate(othersCredentials: credentials)); + } + break; } } @@ -630,6 +691,14 @@ class CredentialListCubit extends Cubit { emit(state.populate(blockchainAccountsCredentials: credentials)); break; + case CredentialCategory.educationCards: + final credentials = List.of(state.educationCredentials) + ..removeWhere( + (element) => element.credentialModel?.id == credential.id, + ); + emit(state.populate(educationCredentials: credentials)); + break; + case CredentialCategory.passCards: final credentials = List.of(state.passCredentials) ..removeWhere( @@ -639,11 +708,19 @@ class CredentialListCubit extends Cubit { break; case CredentialCategory.othersCards: - final credentials = List.of(state.othersCredentials) - ..removeWhere( - (element) => element.credentialModel?.id == credential.id, - ); - emit(state.populate(othersCredentials: credentials)); + if (credential.credentialPreview.type.contains('VerifiableDiploma')) { + final credentials = List.of(state.educationCredentials) + ..removeWhere( + (element) => element.credentialModel?.id == credential.id, + ); + emit(state.populate(educationCredentials: credentials)); + } else { + final credentials = List.of(state.othersCredentials) + ..removeWhere( + (element) => element.credentialModel?.id == credential.id, + ); + emit(state.populate(othersCredentials: credentials)); + } break; } } @@ -655,6 +732,7 @@ class CredentialListCubit extends Cubit { communityCredentials: [], identityCredentials: [], blockchainAccountsCredentials: [], + educationCredentials: [], passCredentials: [], othersCredentials: [], myProfessionalCredentials: [], diff --git a/lib/dashboard/home/tab_bar/credentials/list/cubit/credential_list_state.dart b/lib/dashboard/home/tab_bar/credentials/list/cubit/credential_list_state.dart index e6b53ead0..a69f53736 100644 --- a/lib/dashboard/home/tab_bar/credentials/list/cubit/credential_list_state.dart +++ b/lib/dashboard/home/tab_bar/credentials/list/cubit/credential_list_state.dart @@ -9,6 +9,7 @@ class CredentialListState extends Equatable { this.communityCredentials = const [], this.identityCredentials = const [], this.blockchainAccountsCredentials = const [], + this.educationCredentials = const [], this.passCredentials = const [], this.othersCredentials = const [], this.myProfessionalCredentials = const [], @@ -33,6 +34,7 @@ class CredentialListState extends Equatable { final List communityCredentials; final List identityCredentials; final List blockchainAccountsCredentials; + final List educationCredentials; final List passCredentials; final List othersCredentials; final List myProfessionalCredentials; @@ -49,6 +51,7 @@ class CredentialListState extends Equatable { communityCredentials: communityCredentials, identityCredentials: identityCredentials, blockchainAccountsCredentials: blockchainAccountsCredentials, + educationCredentials: educationCredentials, passCredentials: passCredentials, othersCredentials: othersCredentials, myProfessionalCredentials: myProfessionalCredentials, @@ -69,6 +72,7 @@ class CredentialListState extends Equatable { communityCredentials: communityCredentials, identityCredentials: identityCredentials, blockchainAccountsCredentials: blockchainAccountsCredentials, + educationCredentials: educationCredentials, passCredentials: passCredentials, othersCredentials: othersCredentials, myProfessionalCredentials: myProfessionalCredentials, @@ -86,6 +90,7 @@ class CredentialListState extends Equatable { communityCredentials: communityCredentials, identityCredentials: identityCredentials, blockchainAccountsCredentials: blockchainAccountsCredentials, + educationCredentials: educationCredentials, passCredentials: passCredentials, othersCredentials: othersCredentials, myProfessionalCredentials: myProfessionalCredentials, @@ -104,6 +109,7 @@ class CredentialListState extends Equatable { communityCredentials: communityCredentials, identityCredentials: identityCredentials, blockchainAccountsCredentials: blockchainAccountsCredentials, + educationCredentials: educationCredentials, passCredentials: passCredentials, othersCredentials: othersCredentials, myProfessionalCredentials: myProfessionalCredentials, @@ -119,6 +125,7 @@ class CredentialListState extends Equatable { List? communityCredentials, List? identityCredentials, List? blockchainAccountsCredentials, + List? educationCredentials, List? passCredentials, List? othersCredentials, List? myProfessionalCredentials, @@ -134,6 +141,7 @@ class CredentialListState extends Equatable { identityCredentials: identityCredentials ?? this.identityCredentials, blockchainAccountsCredentials: blockchainAccountsCredentials ?? this.blockchainAccountsCredentials, + educationCredentials: educationCredentials ?? this.educationCredentials, passCredentials: passCredentials ?? this.passCredentials, othersCredentials: othersCredentials ?? this.othersCredentials, myProfessionalCredentials: @@ -159,6 +167,7 @@ class CredentialListState extends Equatable { communityCredentials: communityCredentials, identityCredentials: identityCredentials, blockchainAccountsCredentials: blockchainAccountsCredentials, + educationCredentials: educationCredentials, passCredentials: passCredentials, othersCredentials: othersCredentials, myProfessionalCredentials: myProfessionalCredentials, @@ -178,6 +187,7 @@ class CredentialListState extends Equatable { communityCredentials, identityCredentials, blockchainAccountsCredentials, + educationCredentials, passCredentials, othersCredentials, message, diff --git a/lib/dashboard/home/tab_bar/credentials/list/widgets/credential_list_data.dart b/lib/dashboard/home/tab_bar/credentials/list/widgets/credential_list_data.dart index 04d351430..4552e0221 100644 --- a/lib/dashboard/home/tab_bar/credentials/list/widgets/credential_list_data.dart +++ b/lib/dashboard/home/tab_bar/credentials/list/widgets/credential_list_data.dart @@ -61,13 +61,23 @@ class CredentialListData extends StatelessWidget { state.blockchainAccountsCredentials.isNotEmpty) ...[ /// BlockchainAccounts Credentials HomeCredentialWidget( - title: l10n.blockChainAccounts, + title: l10n.blockchainAccounts, credentials: state.blockchainAccountsCredentials, categorySubtitle: l10n.blockchainAccountsCredentialHomeSubtitle, ), const SizedBox(height: Sizes.spaceNormal), ], + if (advanceSettingsState.isEducationEnabled && + state.educationCredentials.isNotEmpty) ...[ + /// Education Credentials + HomeCredentialWidget( + title: l10n.educationAccounts, + credentials: state.educationCredentials, + categorySubtitle: l10n.educationCredentialHomeSubtitle, + ), + const SizedBox(height: Sizes.spaceNormal), + ], if (advanceSettingsState.isPassEnabled && state.passCredentials.isNotEmpty) ...[ /// Pass Credentials diff --git a/lib/dashboard/home/tab_bar/credentials/models/eu_diploma_card/eu_diploma_card_model.dart b/lib/dashboard/home/tab_bar/credentials/models/eu_diploma_card/eu_diploma_card_model.dart index 0e0c4233b..7319f1b31 100644 --- a/lib/dashboard/home/tab_bar/credentials/models/eu_diploma_card/eu_diploma_card_model.dart +++ b/lib/dashboard/home/tab_bar/credentials/models/eu_diploma_card/eu_diploma_card_model.dart @@ -21,7 +21,7 @@ class EUDiplomaCardModel extends CredentialSubjectModel { super.issuedBy, }) : super( credentialSubjectType: CredentialSubjectType.euDiplomaCard, - credentialCategory: CredentialCategory.othersCards, + credentialCategory: CredentialCategory.educationCards, ); factory EUDiplomaCardModel.fromJson(Map json) => diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index d78815c4f..d2c1cd7ec 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -336,6 +336,7 @@ "wallet": "Wallet", "manageAccounts": "Manage blockchain accounts", "blockchainAccounts": "Blockchain accounts", + "educationAccounts": "Education accounts", "security": "Security", "networkAndRegistries": "Network & Registries", "chooseNetwork": "Choose Network", @@ -423,7 +424,6 @@ "verfiedButton": "Adding the over 18 Card", "verifiedNotificationTitle": "Verification complete!", "verifiedNotificationDescription": "Congratulations! You have been successfully verified.", - "blockChainAccounts": "Blockchain accounts", "showDecentralizedID": "Show Decentralized ID", "manageDecentralizedID": "Manage Decentralized ID", "addressBook": "Address Book", @@ -553,6 +553,7 @@ "identityCredentialDiscoverSubtitle": "Prove things about yourself while protecting your data.", "myProfessionalCredentialDiscoverSubtitle": "Use your professional cards securely.", "blockchainAccountsCredentialHomeSubtitle": "Use your blockchain accounts securely.", + "educationCredentialHomeSubtitle": "Use your education accounts.", "passCredentialHomeSubtitle": "Use exclusive Passes: Supercharge your Web3 experience.", "communityCredentialHomeSubtitle": "Benefit from advantages offered by Communities you like.", "communityCredentialDiscoverSubtitle": "Get exclusive advantages offered by Communities you like.", diff --git a/lib/l10n/arb/app_fr.arb b/lib/l10n/arb/app_fr.arb index 6273ef09a..edec5aaf9 100644 --- a/lib/l10n/arb/app_fr.arb +++ b/lib/l10n/arb/app_fr.arb @@ -423,7 +423,6 @@ "verfiedButton": "Ajout de la carte de plus de 18 ans", "verifiedNotificationTitle": "Vérification terminée !", "verifiedNotificationDescription": "Félicitations ! Vous avez été vérifié avec succès.", - "blockChainAccounts": "Comptes Blockchain", "showDecentralizedID": "Afficher l'ID décentralisé", "manageDecentralizedID": "Gérer l'ID décentralisé", "addressBook": "Carnet d'adresses", diff --git a/lib/l10n/untranslated.json b/lib/l10n/untranslated.json index d5bffdcd2..0e757539f 100644 --- a/lib/l10n/untranslated.json +++ b/lib/l10n/untranslated.json @@ -286,6 +286,7 @@ "wallet", "manageAccounts", "blockchainAccounts", + "educationAccounts", "security", "networkAndRegistries", "chooseNetwork", @@ -366,7 +367,6 @@ "verfiedButton", "verifiedNotificationTitle", "verifiedNotificationDescription", - "blockChainAccounts", "showDecentralizedID", "manageDecentralizedID", "addressBook", @@ -489,6 +489,7 @@ "identityCredentialDiscoverSubtitle", "myProfessionalCredentialDiscoverSubtitle", "blockchainAccountsCredentialHomeSubtitle", + "educationCredentialHomeSubtitle", "passCredentialHomeSubtitle", "communityCredentialHomeSubtitle", "communityCredentialDiscoverSubtitle", @@ -1025,6 +1026,7 @@ "wallet", "manageAccounts", "blockchainAccounts", + "educationAccounts", "security", "networkAndRegistries", "chooseNetwork", @@ -1105,7 +1107,6 @@ "verfiedButton", "verifiedNotificationTitle", "verifiedNotificationDescription", - "blockChainAccounts", "showDecentralizedID", "manageDecentralizedID", "addressBook", @@ -1228,6 +1229,7 @@ "identityCredentialDiscoverSubtitle", "myProfessionalCredentialDiscoverSubtitle", "blockchainAccountsCredentialHomeSubtitle", + "educationCredentialHomeSubtitle", "passCredentialHomeSubtitle", "communityCredentialHomeSubtitle", "communityCredentialDiscoverSubtitle", @@ -1478,6 +1480,8 @@ ], "fr": [ + "educationAccounts", + "educationCredentialHomeSubtitle", "altmeSupport", "manageKeyDecentralizedId", "manageEbsiDecentralizedId", @@ -1781,6 +1785,7 @@ "wallet", "manageAccounts", "blockchainAccounts", + "educationAccounts", "security", "networkAndRegistries", "chooseNetwork", @@ -1861,7 +1866,6 @@ "verfiedButton", "verifiedNotificationTitle", "verifiedNotificationDescription", - "blockChainAccounts", "showDecentralizedID", "manageDecentralizedID", "addressBook", @@ -1984,6 +1988,7 @@ "identityCredentialDiscoverSubtitle", "myProfessionalCredentialDiscoverSubtitle", "blockchainAccountsCredentialHomeSubtitle", + "educationCredentialHomeSubtitle", "passCredentialHomeSubtitle", "communityCredentialHomeSubtitle", "communityCredentialDiscoverSubtitle", diff --git a/lib/wallet/cubit/wallet_cubit.dart b/lib/wallet/cubit/wallet_cubit.dart index 48df44b97..ace8f448b 100644 --- a/lib/wallet/cubit/wallet_cubit.dart +++ b/lib/wallet/cubit/wallet_cubit.dart @@ -529,6 +529,13 @@ class WalletCubit extends Cubit { } } + if (credentialCategory == CredentialCategory.educationCards && + credentialListCubit.state.educationCredentials.isEmpty) { + if (!advanceSettingsCubit.state.isEducationEnabled) { + advanceSettingsCubit.toggleEducationRadio(); + } + } + if (credentialCategory == CredentialCategory.othersCards && credentialListCubit.state.othersCredentials.isEmpty) { if (!advanceSettingsCubit.state.isOtherEnabled) { From 35c170ac1dd534565e7cd7eddf905ecd2e4cf2c4 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Tue, 21 Feb 2023 19:27:40 +0530 Subject: [PATCH 054/190] handle ebsi credential UI with type --- lib/app/shared/helper_functions/helper_functions.dart | 4 ++++ .../advance_settings/view/advanced_settings_page.dart | 2 +- .../credentials/list/cubit/credential_list_cubit.dart | 8 ++++---- .../credentials/list/widgets/credential_list_data.dart | 2 +- .../credentials/widgets/credential_background.dart | 5 +---- .../credential_manifest_card.dart | 5 +++-- .../default_credential_detail_widget.dart | 10 ++++++---- .../default_credential_list_widget.dart | 10 ++++++---- .../widgets/display_issuance_date_widget.dart | 1 + lib/l10n/arb/app_en.arb | 2 +- lib/l10n/untranslated.json | 8 ++++---- pubspec.lock | 2 +- 12 files changed, 33 insertions(+), 26 deletions(-) diff --git a/lib/app/shared/helper_functions/helper_functions.dart b/lib/app/shared/helper_functions/helper_functions.dart index 5ee738c13..bb2494b6e 100644 --- a/lib/app/shared/helper_functions/helper_functions.dart +++ b/lib/app/shared/helper_functions/helper_functions.dart @@ -322,3 +322,7 @@ Future getRandomP256PrivateKey( return p256PrivateKey; } } + +bool isEUDiploma(CredentialModel credentialModel) { + return credentialModel.credentialPreview.type.contains('VerifiableDiploma'); +} diff --git a/lib/dashboard/drawer/advance_settings/view/advanced_settings_page.dart b/lib/dashboard/drawer/advance_settings/view/advanced_settings_page.dart index 8d1c59ad3..6afbe94ce 100644 --- a/lib/dashboard/drawer/advance_settings/view/advanced_settings_page.dart +++ b/lib/dashboard/drawer/advance_settings/view/advanced_settings_page.dart @@ -65,7 +65,7 @@ class _AdvancedSettingsViewState extends State { advancedSettingsCubit.toggleBlockchainAccountsRadio, ), AdvanceSettingsRadioItem( - title: l10n.educationAccounts, + title: l10n.educationCredentials, isSelected: state.isEducationEnabled, onPressed: advancedSettingsCubit.toggleEducationRadio, ), diff --git a/lib/dashboard/home/tab_bar/credentials/list/cubit/credential_list_cubit.dart b/lib/dashboard/home/tab_bar/credentials/list/cubit/credential_list_cubit.dart index 6743de80b..6714815b1 100644 --- a/lib/dashboard/home/tab_bar/credentials/list/cubit/credential_list_cubit.dart +++ b/lib/dashboard/home/tab_bar/credentials/list/cubit/credential_list_cubit.dart @@ -153,7 +153,7 @@ class CredentialListCubit extends Cubit { case CredentialCategory.othersCards: /// adding real credentials - if (credential.credentialPreview.type.contains('VerifiableDiploma')) { + if (isEUDiploma(credential)) { educationCredentials.add(HomeCredential.isNotDummy(credential)); } else { othersCredentials.add(HomeCredential.isNotDummy(credential)); @@ -346,7 +346,7 @@ class CredentialListCubit extends Cubit { case CredentialCategory.othersCards: /// adding real credentials - if (credential.credentialPreview.type.contains('VerifiableDiploma')) { + if (isEUDiploma(credential)) { final credentials = List.of(state.educationCredentials) ..insert(0, HomeCredential.isNotDummy(credential)); emit(state.populate(educationCredentials: credentials)); @@ -502,7 +502,7 @@ class CredentialListCubit extends Cubit { case CredentialCategory.othersCards: ///finding index of updated credential - if (credential.credentialPreview.type.contains('VerifiableDiploma')) { + if (isEUDiploma(credential)) { final index = state.educationCredentials.indexWhere( (element) => element.credentialModel?.id == credential.id, ); @@ -708,7 +708,7 @@ class CredentialListCubit extends Cubit { break; case CredentialCategory.othersCards: - if (credential.credentialPreview.type.contains('VerifiableDiploma')) { + if (isEUDiploma(credential)) { final credentials = List.of(state.educationCredentials) ..removeWhere( (element) => element.credentialModel?.id == credential.id, diff --git a/lib/dashboard/home/tab_bar/credentials/list/widgets/credential_list_data.dart b/lib/dashboard/home/tab_bar/credentials/list/widgets/credential_list_data.dart index 4552e0221..40860d5ba 100644 --- a/lib/dashboard/home/tab_bar/credentials/list/widgets/credential_list_data.dart +++ b/lib/dashboard/home/tab_bar/credentials/list/widgets/credential_list_data.dart @@ -72,7 +72,7 @@ class CredentialListData extends StatelessWidget { state.educationCredentials.isNotEmpty) ...[ /// Education Credentials HomeCredentialWidget( - title: l10n.educationAccounts, + title: l10n.educationCredentials, credentials: state.educationCredentials, categorySubtitle: l10n.educationCredentialHomeSubtitle, ), diff --git a/lib/dashboard/home/tab_bar/credentials/widgets/credential_background.dart b/lib/dashboard/home/tab_bar/credentials/widgets/credential_background.dart index ce399f2d8..84ce57b12 100644 --- a/lib/dashboard/home/tab_bar/credentials/widgets/credential_background.dart +++ b/lib/dashboard/home/tab_bar/credentials/widgets/credential_background.dart @@ -22,10 +22,7 @@ class CredentialBackground extends StatelessWidget { return CredentialContainer( child: DecoratedBox( decoration: BaseBoxDecoration( - color: backgroundColor ?? - credentialModel.credentialPreview.credentialSubjectModel - .credentialSubjectType - .backgroundColor(credentialModel), + color: Colors.amber, shapeColor: Theme.of(context).colorScheme.documentShape, value: 0, shapeSize: 256, diff --git a/lib/dashboard/home/tab_bar/credentials/widgets/credential_manifest_widgets/credential_manifest_card.dart b/lib/dashboard/home/tab_bar/credentials/widgets/credential_manifest_widgets/credential_manifest_card.dart index 3f292fb08..a9180bbf5 100644 --- a/lib/dashboard/home/tab_bar/credentials/widgets/credential_manifest_widgets/credential_manifest_card.dart +++ b/lib/dashboard/home/tab_bar/credentials/widgets/credential_manifest_widgets/credential_manifest_card.dart @@ -15,8 +15,9 @@ class CredentialManifestCard extends StatelessWidget { @override Widget build(BuildContext context) { - final textColor = - getColorFromCredential(outputDescriptor.styles?.text, Colors.black); + final textColor = isEUDiploma(credentialModel) + ? Colors.white + : getColorFromCredential(outputDescriptor.styles?.text, Colors.black); final credential = Credential.fromJsonOrDummy(credentialModel.data); return FractionallySizedBox( widthFactor: 0.85, diff --git a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/default_credential_detail_widget.dart b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/default_credential_detail_widget.dart index 457895ba6..409a51e11 100644 --- a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/default_credential_detail_widget.dart +++ b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/default_credential_detail_widget.dart @@ -64,10 +64,12 @@ class DefaultCredentialDetailWidget extends StatelessWidget { child: DecoratedBox( decoration: BaseBoxDecoration( borderRadius: BorderRadius.circular(20), - color: getColorFromCredential( - outputDescriptors.first.styles?.background, - Colors.white, - ), + color: isEUDiploma(credentialModel) + ? const Color(0xff200072) + : getColorFromCredential( + outputDescriptors.first.styles?.background, + Colors.white, + ), shapeColor: Theme.of(context).colorScheme.documentShape, value: 1, anchors: showBgDecoration diff --git a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/default_credential_list_widget.dart b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/default_credential_list_widget.dart index ffa3ffa84..23839b034 100644 --- a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/default_credential_list_widget.dart +++ b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/default_credential_list_widget.dart @@ -27,10 +27,12 @@ class DefaultCredentialListWidget extends StatelessWidget { ? credentialModel .credentialPreview.credentialSubjectModel.credentialSubjectType .backgroundColor(credentialModel) - : getColorFromCredential( - outputDescriptor.styles?.background, - Colors.white, - ); + : isEUDiploma(credentialModel) + ? const Color(0xff200072) + : getColorFromCredential( + outputDescriptor.styles?.background, + Colors.white, + ); late Widget descriptionWidget; diff --git a/lib/dashboard/home/tab_bar/credentials/widgets/display_issuance_date_widget.dart b/lib/dashboard/home/tab_bar/credentials/widgets/display_issuance_date_widget.dart index d418c0442..825a284f5 100644 --- a/lib/dashboard/home/tab_bar/credentials/widgets/display_issuance_date_widget.dart +++ b/lib/dashboard/home/tab_bar/credentials/widgets/display_issuance_date_widget.dart @@ -18,6 +18,7 @@ class DisplayIssuanceDateWidget extends StatelessWidget { if (issuanceDate != null) { return Row( + crossAxisAlignment: CrossAxisAlignment.end, children: [ Expanded( flex: 6, diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index d2c1cd7ec..14249c817 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -336,7 +336,7 @@ "wallet": "Wallet", "manageAccounts": "Manage blockchain accounts", "blockchainAccounts": "Blockchain accounts", - "educationAccounts": "Education accounts", + "educationCredentials": "Education credentials", "security": "Security", "networkAndRegistries": "Network & Registries", "chooseNetwork": "Choose Network", diff --git a/lib/l10n/untranslated.json b/lib/l10n/untranslated.json index 0e757539f..2ecb20335 100644 --- a/lib/l10n/untranslated.json +++ b/lib/l10n/untranslated.json @@ -286,7 +286,7 @@ "wallet", "manageAccounts", "blockchainAccounts", - "educationAccounts", + "educationCredentials", "security", "networkAndRegistries", "chooseNetwork", @@ -1026,7 +1026,7 @@ "wallet", "manageAccounts", "blockchainAccounts", - "educationAccounts", + "educationCredentials", "security", "networkAndRegistries", "chooseNetwork", @@ -1480,7 +1480,7 @@ ], "fr": [ - "educationAccounts", + "educationCredentials", "educationCredentialHomeSubtitle", "altmeSupport", "manageKeyDecentralizedId", @@ -1785,7 +1785,7 @@ "wallet", "manageAccounts", "blockchainAccounts", - "educationAccounts", + "educationCredentials", "security", "networkAndRegistries", "chooseNetwork", diff --git a/pubspec.lock b/pubspec.lock index db3c0e363..194c1b85d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2343,5 +2343,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.19.0 <3.0.0" + dart: ">=2.19.0 <3.7.0" flutter: ">=3.7.0-0" From d570a51599ebbb0871d649b34888495a57273a09 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Tue, 21 Feb 2023 19:31:17 +0530 Subject: [PATCH 055/190] credential border radius update --- .../credential_widget/default_credential_detail_widget.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/default_credential_detail_widget.dart b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/default_credential_detail_widget.dart index 409a51e11..72d73564e 100644 --- a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/default_credential_detail_widget.dart +++ b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/default_credential_detail_widget.dart @@ -63,7 +63,7 @@ class DefaultCredentialDetailWidget extends StatelessWidget { aspectRatio: Sizes.credentialAspectRatio, child: DecoratedBox( decoration: BaseBoxDecoration( - borderRadius: BorderRadius.circular(20), + borderRadius: BorderRadius.circular(Sizes.credentialBorderRadius), color: isEUDiploma(credentialModel) ? const Color(0xff200072) : getColorFromCredential( From 3f3bdcd7a4f5e46803a147566364e5d9bf7805ea Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Tue, 21 Feb 2023 20:42:43 +0530 Subject: [PATCH 056/190] color update --- .../helper_functions/helper_functions.dart | 2 +- .../detail/view/credentials_details_page.dart | 4 +- .../list/cubit/credential_list_cubit.dart | 8 ++-- .../widgets/credential_background.dart | 5 ++- .../credential_manifest_card.dart | 2 +- .../default_credential_detail_widget.dart | 38 ++++++++++++------ .../default_credential_list_widget.dart | 40 ++++++++++++------- 7 files changed, 64 insertions(+), 35 deletions(-) diff --git a/lib/app/shared/helper_functions/helper_functions.dart b/lib/app/shared/helper_functions/helper_functions.dart index bb2494b6e..cac3f995e 100644 --- a/lib/app/shared/helper_functions/helper_functions.dart +++ b/lib/app/shared/helper_functions/helper_functions.dart @@ -323,6 +323,6 @@ Future getRandomP256PrivateKey( } } -bool isEUDiploma(CredentialModel credentialModel) { +bool isVerifiableDiplomaType(CredentialModel credentialModel) { return credentialModel.credentialPreview.type.contains('VerifiableDiploma'); } 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 ff5ddb9d3..f3b06b883 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 @@ -40,7 +40,9 @@ class CredentialsDetailsPage extends StatelessWidget { Widget build(BuildContext context) { return BlocProvider( create: (context) => CredentialDetailsCubit( - didKitProvider: DIDKitProvider(), client: DioClient('', Dio())), + didKitProvider: DIDKitProvider(), + client: DioClient('', Dio()), + ), child: CredentialsDetailsView( credentialModel: credentialModel, readOnly: readOnly, diff --git a/lib/dashboard/home/tab_bar/credentials/list/cubit/credential_list_cubit.dart b/lib/dashboard/home/tab_bar/credentials/list/cubit/credential_list_cubit.dart index 6714815b1..94f201254 100644 --- a/lib/dashboard/home/tab_bar/credentials/list/cubit/credential_list_cubit.dart +++ b/lib/dashboard/home/tab_bar/credentials/list/cubit/credential_list_cubit.dart @@ -153,7 +153,7 @@ class CredentialListCubit extends Cubit { case CredentialCategory.othersCards: /// adding real credentials - if (isEUDiploma(credential)) { + if (isVerifiableDiplomaType(credential)) { educationCredentials.add(HomeCredential.isNotDummy(credential)); } else { othersCredentials.add(HomeCredential.isNotDummy(credential)); @@ -346,7 +346,7 @@ class CredentialListCubit extends Cubit { case CredentialCategory.othersCards: /// adding real credentials - if (isEUDiploma(credential)) { + if (isVerifiableDiplomaType(credential)) { final credentials = List.of(state.educationCredentials) ..insert(0, HomeCredential.isNotDummy(credential)); emit(state.populate(educationCredentials: credentials)); @@ -502,7 +502,7 @@ class CredentialListCubit extends Cubit { case CredentialCategory.othersCards: ///finding index of updated credential - if (isEUDiploma(credential)) { + if (isVerifiableDiplomaType(credential)) { final index = state.educationCredentials.indexWhere( (element) => element.credentialModel?.id == credential.id, ); @@ -708,7 +708,7 @@ class CredentialListCubit extends Cubit { break; case CredentialCategory.othersCards: - if (isEUDiploma(credential)) { + if (isVerifiableDiplomaType(credential)) { final credentials = List.of(state.educationCredentials) ..removeWhere( (element) => element.credentialModel?.id == credential.id, diff --git a/lib/dashboard/home/tab_bar/credentials/widgets/credential_background.dart b/lib/dashboard/home/tab_bar/credentials/widgets/credential_background.dart index 84ce57b12..ce399f2d8 100644 --- a/lib/dashboard/home/tab_bar/credentials/widgets/credential_background.dart +++ b/lib/dashboard/home/tab_bar/credentials/widgets/credential_background.dart @@ -22,7 +22,10 @@ class CredentialBackground extends StatelessWidget { return CredentialContainer( child: DecoratedBox( decoration: BaseBoxDecoration( - color: Colors.amber, + color: backgroundColor ?? + credentialModel.credentialPreview.credentialSubjectModel + .credentialSubjectType + .backgroundColor(credentialModel), shapeColor: Theme.of(context).colorScheme.documentShape, value: 0, shapeSize: 256, diff --git a/lib/dashboard/home/tab_bar/credentials/widgets/credential_manifest_widgets/credential_manifest_card.dart b/lib/dashboard/home/tab_bar/credentials/widgets/credential_manifest_widgets/credential_manifest_card.dart index a9180bbf5..fd96147e1 100644 --- a/lib/dashboard/home/tab_bar/credentials/widgets/credential_manifest_widgets/credential_manifest_card.dart +++ b/lib/dashboard/home/tab_bar/credentials/widgets/credential_manifest_widgets/credential_manifest_card.dart @@ -15,7 +15,7 @@ class CredentialManifestCard extends StatelessWidget { @override Widget build(BuildContext context) { - final textColor = isEUDiploma(credentialModel) + final textColor = isVerifiableDiplomaType(credentialModel) ? Colors.white : getColorFromCredential(outputDescriptor.styles?.text, Colors.black); final credential = Credential.fromJsonOrDummy(credentialModel.data); diff --git a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/default_credential_detail_widget.dart b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/default_credential_detail_widget.dart index 72d73564e..6fd3ca62e 100644 --- a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/default_credential_detail_widget.dart +++ b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/default_credential_detail_widget.dart @@ -22,7 +22,14 @@ class DefaultCredentialDetailWidget extends StatelessWidget { credentialModel.credentialManifest?.outputDescriptors; // If outputDescriptor exist, the credential has a credential manifest // telling us what to display + + late Color backgroundColor; + if (outputDescriptors == null) { + backgroundColor = credentialModel + .credentialPreview.credentialSubjectModel.credentialSubjectType + .backgroundColor(credentialModel); + return AspectRatio( aspectRatio: Sizes.credentialAspectRatio, child: DefaultSelectionDisplayDescriptor( @@ -31,16 +38,18 @@ class DefaultCredentialDetailWidget extends StatelessWidget { ), ); } else { + backgroundColor = getColorFromCredential( + outputDescriptors.first.styles?.background, + Colors.white, + )!; + if (fromCredentialOffer) { return AspectRatio( aspectRatio: Sizes.credentialAspectRatio, child: DecoratedBox( decoration: BaseBoxDecoration( - borderRadius: BorderRadius.circular(20), - color: getColorFromCredential( - outputDescriptors.first.styles?.background, - Colors.white, - ), + borderRadius: BorderRadius.circular(Sizes.credentialAspectRatio), + color: backgroundColor, shapeColor: Theme.of(context).colorScheme.documentShape, value: 1, anchors: showBgDecoration @@ -48,7 +57,7 @@ class DefaultCredentialDetailWidget extends StatelessWidget { : const [], ), child: Padding( - padding: const EdgeInsets.all(20), + padding: const EdgeInsets.all(Sizes.credentialAspectRatio), child: CredentialManifestCard( credentialModel: credentialModel, outputDescriptor: outputDescriptors.first, @@ -64,12 +73,17 @@ class DefaultCredentialDetailWidget extends StatelessWidget { child: DecoratedBox( decoration: BaseBoxDecoration( borderRadius: BorderRadius.circular(Sizes.credentialBorderRadius), - color: isEUDiploma(credentialModel) - ? const Color(0xff200072) - : getColorFromCredential( - outputDescriptors.first.styles?.background, - Colors.white, - ), + color: backgroundColor, + gradient: isVerifiableDiplomaType(credentialModel) + ? const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Color(0xff0B67C5), + Color(0xff200072), + ], + ) + : null, shapeColor: Theme.of(context).colorScheme.documentShape, value: 1, anchors: showBgDecoration diff --git a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/default_credential_list_widget.dart b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/default_credential_list_widget.dart index 23839b034..54524d654 100644 --- a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/default_credential_list_widget.dart +++ b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/default_credential_list_widget.dart @@ -18,41 +18,51 @@ class DefaultCredentialListWidget extends StatelessWidget { @override Widget build(BuildContext context) { - final outputDescriptor = - credentialModel.credentialManifest?.outputDescriptors?.first; + final outputDescriptors = + credentialModel.credentialManifest?.outputDescriptors; // If outputDescriptor exist, the credential has a credential manifest // telling us what to display - final backgroundColor = outputDescriptor == null - ? credentialModel - .credentialPreview.credentialSubjectModel.credentialSubjectType - .backgroundColor(credentialModel) - : isEUDiploma(credentialModel) - ? const Color(0xff200072) - : getColorFromCredential( - outputDescriptor.styles?.background, - Colors.white, - ); - + late Color backgroundColor; late Widget descriptionWidget; - if (outputDescriptor == null) { + if (outputDescriptors == null) { + backgroundColor = credentialModel + .credentialPreview.credentialSubjectModel.credentialSubjectType + .backgroundColor(credentialModel); + descriptionWidget = DefaultDisplayDescriptor( credentialModel: credentialModel, descriptionMaxLine: descriptionMaxLine, ); } else { + backgroundColor = getColorFromCredential( + outputDescriptors.first.styles?.background, + Colors.white, + )!; + descriptionWidget = CredentialManifestCard( credentialModel: credentialModel, - outputDescriptor: outputDescriptor, + outputDescriptor: outputDescriptors.first, ); } + return CredentialContainer( child: AspectRatio( aspectRatio: Sizes.credentialAspectRatio, child: DecoratedBox( decoration: BaseBoxDecoration( color: backgroundColor, + gradient: isVerifiableDiplomaType(credentialModel) + ? const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Color(0xff0B67C5), + Color(0xff200072), + ], + ) + : null, shapeColor: Theme.of(context).colorScheme.documentShape, value: 1, anchors: showBgDecoration From 1c0625e6cbf1b51561929424eb157909f1f865af Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Tue, 21 Feb 2023 20:46:23 +0530 Subject: [PATCH 057/190] remove unused imports --- lib/app/view/app.dart | 2 -- .../credentials/detail/cubit/credential_details_cubit.dart | 1 - lib/dashboard/home/tab_bar/nft/view/nft_details_page.dart | 1 - packages/ebsi/lib/src/verifier_token_parameters.dart | 1 - 4 files changed, 5 deletions(-) diff --git a/lib/app/view/app.dart b/lib/app/view/app.dart index 897a646d8..9cf168bab 100644 --- a/lib/app/view/app.dart +++ b/lib/app/view/app.dart @@ -29,8 +29,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:jwt_decode/jwt_decode.dart'; import 'package:key_generator/key_generator.dart'; -import 'package:matrix/matrix.dart'; -import 'package:path_provider/path_provider.dart'; import 'package:secure_storage/secure_storage.dart' as secure_storage; import 'package:secure_storage/secure_storage.dart'; diff --git a/lib/dashboard/home/tab_bar/credentials/detail/cubit/credential_details_cubit.dart b/lib/dashboard/home/tab_bar/credentials/detail/cubit/credential_details_cubit.dart index 685132cc4..c24a0b7e8 100644 --- a/lib/dashboard/home/tab_bar/credentials/detail/cubit/credential_details_cubit.dart +++ b/lib/dashboard/home/tab_bar/credentials/detail/cubit/credential_details_cubit.dart @@ -4,7 +4,6 @@ import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/ebsi/verify_ebsi_credential.dart'; import 'package:did_kit/did_kit.dart'; -import 'package:dio/dio.dart'; import 'package:ebsi/ebsi.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; diff --git a/lib/dashboard/home/tab_bar/nft/view/nft_details_page.dart b/lib/dashboard/home/tab_bar/nft/view/nft_details_page.dart index 4bcfe2001..3925c7150 100644 --- a/lib/dashboard/home/tab_bar/nft/view/nft_details_page.dart +++ b/lib/dashboard/home/tab_bar/nft/view/nft_details_page.dart @@ -1,6 +1,5 @@ import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; -import 'package:altme/dashboard/home/tab_bar/nft/widgets/widgets.dart'; import 'package:altme/l10n/l10n.dart'; import 'package:altme/theme/theme.dart'; import 'package:flutter/material.dart'; diff --git a/packages/ebsi/lib/src/verifier_token_parameters.dart b/packages/ebsi/lib/src/verifier_token_parameters.dart index 971c7d23c..085921b96 100644 --- a/packages/ebsi/lib/src/verifier_token_parameters.dart +++ b/packages/ebsi/lib/src/verifier_token_parameters.dart @@ -1,7 +1,6 @@ import 'dart:convert'; import 'package:ebsi/src/token_parameters.dart'; -import 'package:jose/jose.dart'; /// Extends [TokenParameters] to handle additional parameters /// for verifier interactions. From 51b07838bfe41ddc42a7453201a7bf5c36d61149 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Tue, 21 Feb 2023 22:28:54 +0330 Subject: [PATCH 058/190] add functionality to read notification of Matrix --- lib/app/view/app.dart | 1 + .../dashboard/view/dashboard_page.dart | 21 +++++++++---- .../dashboard/widgets/bottom_bar_item.dart | 27 +++++++++++----- .../live_chat/cubit/live_chat_cubit.dart | 31 +++++++++++++++++++ .../drawer/live_chat/view/live_chat_page.dart | 6 +++- lib/theme/app_theme/app_theme.dart | 6 ++++ pubspec.lock | 8 +++++ pubspec.yaml | 1 + 8 files changed, 86 insertions(+), 15 deletions(-) diff --git a/lib/app/view/app.dart b/lib/app/view/app.dart index 897a646d8..2dca51952 100644 --- a/lib/app/view/app.dart +++ b/lib/app/view/app.dart @@ -174,6 +174,7 @@ class App extends StatelessWidget { ), ), BlocProvider( + lazy: false, create: (context) => LiveChatCubit( dioClient: DioClient('', Dio()), didCubit: context.read(), diff --git a/lib/dashboard/dashboard/view/dashboard_page.dart b/lib/dashboard/dashboard/view/dashboard_page.dart index c7b5c802d..a89ec6e41 100644 --- a/lib/dashboard/dashboard/view/dashboard_page.dart +++ b/lib/dashboard/dashboard/view/dashboard_page.dart @@ -48,6 +48,7 @@ class _DashboardViewState extends State { } }); }); + super.initState(); } @@ -288,12 +289,20 @@ class _DashboardViewState extends State { onTap: () => bottomTapped(2), isSelected: state.selectedIndex == 2, ), - BottomBarItem( - icon: IconStrings.messaging, - text: l10n.help, - onTap: () => bottomTapped(3), - isSelected: state.selectedIndex == 3, - ), + StreamBuilder( + stream: context + .read() + .unreadMessageCountStream, + builder: (_, snapShot) { + return BottomBarItem( + icon: IconStrings.messaging, + text: l10n.help, + badgeCount: snapShot.data ?? 0, + onTap: () => bottomTapped(3), + isSelected: state.selectedIndex == 3, + ); + }, + ) ], ), ), diff --git a/lib/dashboard/dashboard/widgets/bottom_bar_item.dart b/lib/dashboard/dashboard/widgets/bottom_bar_item.dart index fb22ef5e5..3abb2a591 100644 --- a/lib/dashboard/dashboard/widgets/bottom_bar_item.dart +++ b/lib/dashboard/dashboard/widgets/bottom_bar_item.dart @@ -1,5 +1,6 @@ import 'package:altme/theme/theme.dart'; import 'package:flutter/material.dart'; +import 'package:badges/badges.dart' as badges; class BottomBarItem extends StatelessWidget { const BottomBarItem({ @@ -8,12 +9,14 @@ class BottomBarItem extends StatelessWidget { required this.text, required this.onTap, required this.isSelected, + this.badgeCount = 0, }); final String text; final String icon; final VoidCallback onTap; final bool isSelected; + final int badgeCount; @override Widget build(BuildContext context) { @@ -25,14 +28,22 @@ class BottomBarItem extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ - Container( - margin: const EdgeInsets.only(bottom: 5), - child: ImageIcon( - AssetImage(icon), - color: isSelected - ? Theme.of(context).colorScheme.onPrimary - : Theme.of(context).colorScheme.unSelectedLabel, - size: 20, + badges.Badge( + showBadge: badgeCount > 0, + badgeContent: Text( + badgeCount.toString(), + style: Theme.of(context).textTheme.badgeStyle, + textAlign: TextAlign.center, + ), + child: Container( + margin: const EdgeInsets.only(bottom: 5), + child: ImageIcon( + AssetImage(icon), + color: isSelected + ? Theme.of(context).colorScheme.onPrimary + : Theme.of(context).colorScheme.unSelectedLabel, + size: 20, + ), ), ), Text( diff --git a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart index 27afb9652..e28c21638 100644 --- a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart +++ b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart @@ -42,6 +42,10 @@ class LiveChatCubit extends Cubit { String _roomId = ''; StreamSubscription? _onEventSubscription; final DIDCubit didCubit; + final _notificationStreamController = StreamController(); + + Stream get unreadMessageCountStream => + _notificationStreamController.stream; Future onSendPressed(PartialText partialText) async { try { @@ -209,6 +213,7 @@ class LiveChatCubit extends Cubit { username, ); logger.i('roomId : $_roomId'); + _getUnreadMessageCount(); _subscribeToEventsOfRoom(); final retrivedMessageFromDB = await _retriveMessagesFromDB(_roomId); emit( @@ -250,6 +255,7 @@ class LiveChatCubit extends Cubit { ), ); } + _getUnreadMessageCount(); } }); } @@ -259,6 +265,7 @@ class LiveChatCubit extends Cubit { if (event.messageType == 'm.text') { message = TextMessage( id: event.unsigned?['transaction_id'] as String? ?? const Uuid().v4(), + remoteId: event.eventId, text: event.text, createdAt: event.originServerTs.millisecondsSinceEpoch, status: _mapEventStatusToMessageStatus(event.status), @@ -269,6 +276,7 @@ class LiveChatCubit extends Cubit { } else if (event.messageType == 'm.image') { message = ImageMessage( id: const Uuid().v4(), + remoteId: event.eventId, name: event.body, size: event.content['info']['size'] as num, uri: _getUrlFromUri(event.content['url'] as String), @@ -281,6 +289,7 @@ class LiveChatCubit extends Cubit { } else if (event.messageType == 'm.file') { message = FileMessage( id: const Uuid().v4(), + remoteId: event.eventId, name: event.body, size: event.content['info']['size'] as num, uri: _getUrlFromUri(event.content['url'] as String), @@ -293,6 +302,7 @@ class LiveChatCubit extends Cubit { } else if (event.messageType == 'm.audio') { message = AudioMessage( id: const Uuid().v4(), + remoteId: event.eventId, duration: Duration( milliseconds: event.content['info']['duration'] as int, ), @@ -308,6 +318,7 @@ class LiveChatCubit extends Cubit { } else { message = TextMessage( id: const Uuid().v4(), + remoteId: event.eventId, text: event.text, createdAt: event.originServerTs.millisecondsSinceEpoch, status: _mapEventStatusToMessageStatus(event.status), @@ -319,6 +330,25 @@ class LiveChatCubit extends Cubit { return message; } + void _getUnreadMessageCount() { + final unreadCount = client.getRoomById(_roomId)?.notificationCount ?? 0; + _notificationStreamController.sink.add(unreadCount); + } + + Future markMessageAsRead(List? eventIds) async { + if (eventIds == null) return; + try { + for (final eventId in eventIds) { + if (eventId != null) { + await client.getRoomById(_roomId)?.setReadMarker(eventId); + } + } + _getUnreadMessageCount(); + } catch (e, s) { + logger.e('e: $e , s: $s'); + } + } + Future> _retriveMessagesFromDB(String roomId) async { final room = client.getRoomById(roomId); if (room == null) return []; @@ -463,6 +493,7 @@ class LiveChatCubit extends Cubit { Future dispose() async { await client.logout(); await client.dispose(); + await _notificationStreamController.close(); await _onEventSubscription?.cancel(); _onEventSubscription = null; } diff --git a/lib/dashboard/drawer/live_chat/view/live_chat_page.dart b/lib/dashboard/drawer/live_chat/view/live_chat_page.dart index 3d870b030..a5488ec42 100644 --- a/lib/dashboard/drawer/live_chat/view/live_chat_page.dart +++ b/lib/dashboard/drawer/live_chat/view/live_chat_page.dart @@ -65,7 +65,11 @@ class _ContactUsViewState extends State { titleLeading: widget.hideAppBar ? null : const BackLeadingButton(), titleAlignment: Alignment.topCenter, padding: const EdgeInsets.all(Sizes.spaceSmall), - body: BlocBuilder( + body: BlocConsumer( + listener: (context, state) { + final eventIds = state.messages.map((e) => e.remoteId).toList(); + liveChatCubit.markMessageAsRead(eventIds); + }, builder: (context, state) { if (state.status == AppStatus.loading) { return const Center( diff --git a/lib/theme/app_theme/app_theme.dart b/lib/theme/app_theme/app_theme.dart index f9cdba97a..6ea8a5f24 100644 --- a/lib/theme/app_theme/app_theme.dart +++ b/lib/theme/app_theme/app_theme.dart @@ -298,6 +298,12 @@ extension CustomTextTheme on TextTheme { fontWeight: FontWeight.w600, ); + TextStyle get badgeStyle => GoogleFonts.nunito( + color: const Color(0xFFEEEEEE), + fontSize: 8, + fontWeight: FontWeight.w500, + ); + TextStyle get onBoardingTitleStyle => GoogleFonts.roboto( color: const Color(0xFFFFFFFF), fontSize: 22, diff --git a/pubspec.lock b/pubspec.lock index 194c1b85d..c7df610aa 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -73,6 +73,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.0" + badges: + dependency: "direct main" + description: + name: badges + sha256: "461031a60efbb95276f52107f63d5d45008b5ca1eb7f8ca440cadda9ec2143b0" + url: "https://pub.dev" + source: hosted + version: "3.0.2" base58check: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 9ef0b98dc..95621a9d3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -8,6 +8,7 @@ environment: dependencies: auto_size_text: ^3.0.0 + badges: ^3.0.2 beacon_flutter: git: url: https://github.com/TalaoDAO/beacon.git From ef8958cf763881f78ca6a99dbb9879fc6419f7e0 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Wed, 22 Feb 2023 10:22:29 +0530 Subject: [PATCH 059/190] bypass passbase for twittercard #1378 --- .../discover/view/discover_details_page.dart | 15 +++++++++++++-- .../helper_functions/discover_credential.dart | 6 ++++-- pubspec.lock | 2 +- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/lib/dashboard/discover/view/discover_details_page.dart b/lib/dashboard/discover/view/discover_details_page.dart index 9c8784e9a..e677e5b69 100644 --- a/lib/dashboard/discover/view/discover_details_page.dart +++ b/lib/dashboard/discover/view/discover_details_page.dart @@ -104,7 +104,7 @@ class DiscoverDetailsView extends StatelessWidget { ), ), ), - _buildDetailsFields(context, l10n), + DetailFields(homeCredential: homeCredential), ], ), ), @@ -125,8 +125,19 @@ class DiscoverDetailsView extends StatelessWidget { ), ); } +} + +class DetailFields extends StatelessWidget { + const DetailFields({ + super.key, + required this.homeCredential, + }); - Widget _buildDetailsFields(BuildContext context, AppLocalizations l10n) { + final HomeCredential homeCredential; + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; if (homeCredential.credentialSubjectType.isDisabled) { return DiscoverDynamicDetial( title: l10n.credentialManifestDescription, diff --git a/lib/dashboard/home/tab_bar/credentials/helper_functions/discover_credential.dart b/lib/dashboard/home/tab_bar/credentials/helper_functions/discover_credential.dart index 98bbb1127..3c4761a36 100644 --- a/lib/dashboard/home/tab_bar/credentials/helper_functions/discover_credential.dart +++ b/lib/dashboard/home/tab_bar/credentials/helper_functions/discover_credential.dart @@ -12,8 +12,10 @@ Future discoverCredential({ List.of(DiscoverList.identityCategories); /// items to remove to bypass KYC - credentialSubjectTypeList.remove(CredentialSubjectType.emailPass); - credentialSubjectTypeList.remove(CredentialSubjectType.phonePass); + credentialSubjectTypeList + ..remove(CredentialSubjectType.emailPass) + ..remove(CredentialSubjectType.phonePass) + ..remove(CredentialSubjectType.twitterCard); if (credentialSubjectTypeList .contains(homeCredential.credentialSubjectType)) { diff --git a/pubspec.lock b/pubspec.lock index 194c1b85d..db3c0e363 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2343,5 +2343,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.19.0 <3.7.0" + dart: ">=2.19.0 <3.0.0" flutter: ">=3.7.0-0" From 986ea5c8dd1e4b613b41dff2d0493993a81d872b Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Wed, 22 Feb 2023 14:58:55 +0530 Subject: [PATCH 060/190] ebsi test added --- packages/ebsi/lib/src/ebsi.dart | 12 +- packages/ebsi/test/src/ebsi_test.dart | 105 +++++++++++++++++- .../verifier_token_parameters_test.dart | 91 ++++++++++++++- 3 files changed, 200 insertions(+), 8 deletions(-) diff --git a/packages/ebsi/lib/src/ebsi.dart b/packages/ebsi/lib/src/ebsi.dart index 321d153a3..d480ac241 100644 --- a/packages/ebsi/lib/src/ebsi.dart +++ b/packages/ebsi/lib/src/ebsi.dart @@ -201,6 +201,7 @@ class Ebsi { final response = await getToken(tokenEndPoint, tokenData); final private = await getPrivateKey(mnemonic, privateKey); + final issuerTokenParameters = IssuerTokenParameters(private, issuer); final credentialData = await buildCredentialData( @@ -335,8 +336,6 @@ class Ebsi { final issuerDid = readIssuerDid(openidConfigurationResponse); - //const issuerDid = 'did:ebsi:zeFCExU2XAAshYkPCpjuahA'; - final isVerified = await verifyCredential(issuerDid: issuerDid, vcJwt: vcJwt); @@ -354,20 +353,23 @@ class Ebsi { return credentialData; } - // TODO(bibash): TEST Future verifyCredential({ required String issuerDid, required String vcJwt, }) async { try { final didDocument = await getDidDocument(issuerDid); + final publicKeyJwk = readPublicKeyJwk(issuerDid, didDocument); - // using jose package + // create a JsonWebSignature from the encoded string final jws = JsonWebSignature.fromCompactSerialization(vcJwt); + // create a JsonWebKey for verifying the signature final keyStore = JsonWebKeyStore() - ..addKey(JsonWebKey.fromJson(publicKeyJwk)); + ..addKey( + JsonWebKey.fromJson(publicKeyJwk), + ); final isVerified = await jws.verify(keyStore); diff --git a/packages/ebsi/test/src/ebsi_test.dart b/packages/ebsi/test/src/ebsi_test.dart index 9e0edba21..f2b65de34 100644 --- a/packages/ebsi/test/src/ebsi_test.dart +++ b/packages/ebsi/test/src/ebsi_test.dart @@ -267,6 +267,7 @@ void main() { const credentialRequestResponse = '{"format":"jwt_vc","credential":"eyJhbGciOiJFUzI1NksiLCJraWQiOiJkaWQ6ZWJzaTp6aFN3NXJQWGtjSGp2cXV3blZjVHp6QiM1MTVhOWM0MzZjMGYyYWQzYWI2NWQ2Y2VmYzVjMWYwNmMwNWI4YWRmY2Y1NGVlMDZkYzgwNTQzMjA0NzBmZmFmIiwidHlwIjoiSldUIn0.eyJleHAiOjE2NzU5NDcwNTguODkyNzc4LCJpYXQiOjE2NzU5NDYwNTguODkyNzcxLCJpc3MiOiJkaWQ6ZWJzaTp6aFN3NXJQWGtjSGp2cXV3blZjVHp6QiIsImp0aSI6InVybjp1dWlkOjZiMWQ4NDExLTllZDUtNDU2Ni05YzdmLTRjMjQxNjVmZjIzNiIsIm5iZiI6MTY3NTk0NjA1OC44OTI3NzYsIm5vbmNlIjoiMTFmNWY2MDAtYTg3Ni0xMWVkLTkwZjItMGExNjI4OTU4NTYwIiwic3ViIjoiZGlkOmVic2k6em94UkdWWlFuZFRmUWs1NEI3dEtkd3dOZGhhaTVnbTlGOE5hdjhlY2VOQUJhIiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwiY3JlZGVudGlhbFNjaGVtYSI6eyJpZCI6Imh0dHBzOi8vYXBpLnByZXByb2QuZWJzaS5ldS90cnVzdGVkLXNjaGVtYXMtcmVnaXN0cnkvdjEvc2NoZW1hcy8weGJmNzhmYzA4YTdhOWYyOGY1NDc5ZjU4ZGVhMjY5ZDM2NTdmNTRmMTNjYTM3ZDM4MGNkNGU5MjIzN2ZiNjkxZGQiLCJ0eXBlIjoiSnNvblNjaGVtYVZhbGlkYXRvcjIwMTgifSwiY3JlZGVudGlhbFN0YXR1cyI6eyJpZCI6Imh0dHBzOi8vZXNzaWYuZXVyb3BhLmV1L3N0YXR1cy9lZHVjYXRpb24jaGlnaGVyRWR1Y2F0aW9uIzM5MmFjN2Y2LTM5OWEtNDM3Yi1hMjY4LTQ2OTFlYWQ4ZjE3NiIsInR5cGUiOiJDcmVkZW50aWFsU3RhdHVzTGlzdDIwMjAifSwiY3JlZGVudGlhbFN1YmplY3QiOnsiYXdhcmRpbmdPcHBvcnR1bml0eSI6eyJhd2FyZGluZ0JvZHkiOnsiZWlkYXNMZWdhbElkZW50aWZpZXIiOiJVbmtub3duIiwiaG9tZXBhZ2UiOiJodHRwczovL2xlYXN0b24uYmNkaXBsb21hLmNvbS8iLCJpZCI6ImRpZDplYnNpOnpkUnZ2S2JYaFZWQnNYaGF0anVpQmhzIiwicHJlZmVycmVkTmFtZSI6IkxlYXN0b24gVW5pdmVyc2l0eSIsInJlZ2lzdHJhdGlvbiI6IjA1OTcwNjVKIn0sImVuZGVkQXRUaW1lIjoiMjAyMC0wNi0yNlQwMDowMDowMFoiLCJpZCI6Imh0dHBzOi8vbGVhc3Rvbi5iY2RpcGxvbWEuY29tL2xhdy1lY29ub21pY3MtbWFuYWdlbWVudCNBd2FyZGluZ09wcG9ydHVuaXR5IiwiaWRlbnRpZmllciI6Imh0dHBzOi8vY2VydGlmaWNhdGUtZGVtby5iY2RpcGxvbWEuY29tL2NoZWNrLzg3RUQyRjIyNzBFNkM0MTQ1NkU5NEI4NkI5RDkxMTVCNEUzNUJDQ0FEMjAwQTQ5Qjg0NjU5MkMxNEY3OUM4NkJWMUZuYmxsdGEwTlpUbkprUjNsRFdsUm1URGxTUlVKRVZGWklTbU5tWXpKaFVVNXNaVUo1WjJGSlNIcFdibVpaIiwibG9jYXRpb24iOiJGUkFOQ0UiLCJzdGFydGVkQXRUaW1lIjoiMjAxOS0wOS0wMlQwMDowMDowMFoifSwiZGF0ZU9mQmlydGgiOiIxOTkzLTA0LTA4IiwiZmFtaWx5TmFtZSI6IkRPRSIsImdpdmVuTmFtZXMiOiJKYW5lIiwiZ3JhZGluZ1NjaGVtZSI6eyJpZCI6Imh0dHBzOi8vbGVhc3Rvbi5iY2RpcGxvbWEuY29tL2xhdy1lY29ub21pY3MtbWFuYWdlbWVudCNHcmFkaW5nU2NoZW1lIiwidGl0bGUiOiIyIHllYXIgZnVsbC10aW1lIHByb2dyYW1tZSAvIDQgc2VtZXN0ZXJzIn0sImlkIjoiZGlkOmVic2k6em94UkdWWlFuZFRmUWs1NEI3dEtkd3dOZGhhaTVnbTlGOE5hdjhlY2VOQUJhIiwiaWRlbnRpZmllciI6IjA5MDQwMDgwODRIIiwibGVhcm5pbmdBY2hpZXZlbWVudCI6eyJhZGRpdGlvbmFsTm90ZSI6WyJESVNUUklCVVRJT04gTUFOQUdFTUVOVCJdLCJkZXNjcmlwdGlvbiI6IlRoZSBNYXN0ZXIgaW4gSW5mb3JtYXRpb24gYW5kIENvbXB1dGVyIFNjaWVuY2VzIChNSUNTKSBhdCB0aGUgVW5pdmVyc2l0eSBvZiBMdXhlbWJvdXJnIGVuYWJsZXMgc3R1ZGVudHMgdG8gYWNxdWlyZSBkZWVwZXIga25vd2xlZGdlIGluIGNvbXB1dGVyIHNjaWVuY2UgYnkgdW5kZXJzdGFuZGluZyBpdHMgYWJzdHJhY3QgYW5kIGludGVyZGlzY2lwbGluYXJ5IGZvdW5kYXRpb25zLCBmb2N1c2luZyBvbiBwcm9ibGVtIHNvbHZpbmcgYW5kIGRldmVsb3BpbmcgbGlmZWxvbmcgbGVhcm5pbmcgc2tpbGxzLiIsImlkIjoiaHR0cHM6Ly9sZWFzdG9uLmJjZGlwbG9tYS5jb20vbGF3LWVjb25vbWljcy1tYW5hZ2VtZW50I0xlYXJuaW5nQWNoaWV2bWVudCIsInRpdGxlIjoiTWFzdGVyIGluIEluZm9ybWF0aW9uIGFuZCBDb21wdXRlciBTY2llbmNlcyJ9LCJsZWFybmluZ1NwZWNpZmljYXRpb24iOnsiZWN0c0NyZWRpdFBvaW50cyI6MTIwLCJlcWZMZXZlbCI6NywiaWQiOiJodHRwczovL2xlYXN0b24uYmNkaXBsb21hLmNvbS9sYXctZWNvbm9taWNzLW1hbmFnZW1lbnQjTGVhcm5pbmdTcGVjaWZpY2F0aW9uIiwiaXNjZWRmQ29kZSI6WyI3Il0sIm5xZkxldmVsIjpbIjciXX19LCJldmlkZW5jZSI6eyJkb2N1bWVudFByZXNlbmNlIjpbIlBoeXNpY2FsIl0sImV2aWRlbmNlRG9jdW1lbnQiOlsiUGFzc3BvcnQiXSwiaWQiOiJodHRwczovL2Vzc2lmLmV1cm9wYS5ldS90c3ItdmEvZXZpZGVuY2UvZjJhZWVjOTctZmMwZC00MmJmLThjYTctMDU0ODE5MmQ1Njc4Iiwic3ViamVjdFByZXNlbmNlIjoiUGh5c2ljYWwiLCJ0eXBlIjpbIkRvY3VtZW50VmVyaWZpY2F0aW9uIl0sInZlcmlmaWVyIjoiZGlkOmVic2k6Mjk2MmZiNzg0ZGY2MWJhYTI2N2M4MTMyNDk3NTM5ZjhjNjc0YjM3YzEyNDRhN2EifSwiaWQiOiJ1cm46dXVpZDo2YjFkODQxMS05ZWQ1LTQ1NjYtOWM3Zi00YzI0MTY1ZmYyMzYiLCJpc3N1YW5jZURhdGUiOiIyMDIzLTAyLTA5VDEyOjM0OjE4WiIsImlzc3VlZCI6IjIwMjMtMDItMDlUMTI6MzQ6MThaIiwiaXNzdWVyIjoiZGlkOmVic2k6emhTdzVyUFhrY0hqdnF1d25WY1R6ekIiLCJwcm9vZiI6eyJjcmVhdGVkIjoiMjAyMi0wNC0yN1QxMjoyNTowN1oiLCJjcmVhdG9yIjoiZGlkOmVic2k6emRSdnZLYlhoVlZCc1hoYXRqdWlCaHMiLCJkb21haW4iOiJodHRwczovL2FwaS5wcmVwcm9kLmVic2kuZXUiLCJqd3MiOiJleUppTmpRaU9tWmhiSE5sTENKamNtbDBJanBiSW1JMk5DSmRMQ0poYkdjaU9pSkZVekkxTmtzaWZRLi5tSUJuTThYRFFxU1lLUU5YX0x2YUpobXNieUNyNU9aNWNVMlprLVJlcUxwcjRkb0ZzZ21vb2JrTzUxMjh0WnktOEtpbVZqSmtHdzB3TDF1QlduTUxXUSIsIm5vbmNlIjoiM2VhNjhkYWUtZDA3YS00ZGFhLTkzMmItZmJiNThmNWMyMGM0IiwidHlwZSI6IkVjZHNhU2VjcDI1NmsxU2lnbmF0dXJlMjAxOSJ9LCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVmVyaWZpYWJsZUF0dGVzdGF0aW9uIiwiVmVyaWZpYWJsZURpcGxvbWEiXSwidmFsaWRGcm9tIjoiMjAyMy0wMi0wOVQxMjozNDoxOFoifX0.uQK9sK-VtqmKjLJIw_v5Ff5xAMDAosZCtl1LFYxZhUolReD6a7O-NI1f5lcswBCZPLfJ-HJyb5iShehHObzFDA","c_nonce":"128952c8-a876-11ed-bbc4-0a1628958560","c_nonce_expires_in":1000}'; // ignore: lines_longer_than_80_chars + dioAdapter ..onGet( issuer, @@ -286,10 +287,10 @@ void main() { jsonDecode(credentialRequestResponse), ), ); - final ebsi = Ebsi(client); test('When getCredentialType receive url it returns json response', () async { + final ebsi = Ebsi(client); final credential = await ebsi.getCredential( credentialRequest, mnemonic, @@ -298,7 +299,42 @@ void main() { expect(jsonEncode(credential), credentialRequestResponse); }); + test('throw Exception whren token is not verified', () { + const issuerDid = 'did:ebsi:zhSw5rPXkcHjvquwnVcTzzB'; + + const didDocumentUrl = + 'https://api-pilot.ebsi.eu/did-registry/v3/identifiers/$issuerDid'; + + const didDocumentResponse = + '{"assertionMethod":["did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53"],"authentication":["did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53"],"@context":"https://www.w3.org/ns/did/v1","id":"did:ebsi:zeFCExU2XAAshYkPCpjuahA","verificationMethod":[{"controller":"did:ebsi:zeFCExU2XAAshYkPCpjuahA","id":"did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53","publicKeyJwk":{"crv":"P-521","kty":"EC","x":"AekpBQ8ST8a8VcfVOTNl353vSrDCLLJXmPk06wTjxrrjcBpXp5EOnYG_NjFZ6OvLFV1jSfS9tsz4qUxcWceqwQGk","y":"ADSmRA43Z1DSNx_RvcLI87cdL07l6jQyyBXMoxVg_l2Th-x3S1WDhjDly79ajL4Kkd0AZMaZmh9ubmf63e3kyMj2"},"type":"Ed25519VerificationKey2019"}]}'; + + dioAdapter.onGet( + didDocumentUrl, + (request) => request.reply(200, jsonDecode(didDocumentResponse)), + ); + + final ebsi = Ebsi(client); + + expect( + () async { + await ebsi.getCredential( + credentialRequest, + mnemonic, + null, + ); + }, + throwsA( + isA().having( + (p0) => p0.toString(), + 'toString()', + 'Exception: VERIFICATION_ISSUE', + ), + ), + ); + }); + group('build token data', () { + final ebsi = Ebsi(client); test('get token data with credentialRequestUri', () async { const expectedTokenData = '{"code":"cb803d46-9c88-11ed-bdb3-0a1628958560","grant_type":"authorization_code"}'; // ignore: lines_longer_than_80_chars @@ -318,6 +354,7 @@ void main() { }); group('getIssuer', () { + final ebsi = Ebsi(client); test('get issuer with credentialRequestUri', () { const expectedIssuer = 'https://talao.co/sandbox/ebsi/issuer/vgvghylozl'; @@ -339,6 +376,8 @@ void main() { const openidConfigurationResponse = r'{"authorization_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/authorize","batch_credential_endpoint":null,"credential_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/credential","credential_issuer":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl","credential_manifests":[{"id":"VerifiableDiploma_1","issuer":{"id":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","name":"Test EBSILUX"},"output_descriptors":[{"display":{"description":{"fallback":"This card is a proof that you passed this diploma successfully. You can use this card when you need to prove this information to services that have adopted EU EBSI framework.","path":[],"schema":{"type":"string"}},"properties":[{"fallback":"Unknown","label":"First name","path":["$.credentialSubject.firstName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Last name","path":["$.credentialSubject.familyName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Birth date","path":["$.credentialSubject.dateOfBirth"],"schema":{"format":"date","type":"string"}},{"fallback":"Unknown","label":"Grading scheme","path":["$.credentialSubject.gradingScheme.title"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Title","path":["$.credentialSubject.learningAchievement.title"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Description","path":["$.credentialSubject.learningAchievement.description"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"ECTS Points","path":["$.credentialSubject.learningSpecification.ectsCreditPoints"],"schema":{"type":"number"}},{"fallback":"Unknown","label":"Issue date","path":["$.issuanceDate"],"schema":{"format":"date","type":"string"}},{"fallback":"Unknown","label":"Issued by","path":["$.credentialSubject.awardingOpportunity.awardingBody.preferredName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Registration","path":["$.credentialSubject.awardingOpportunity.awardingBody.registration"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Website","path":["$.credentialSubject.awardingOpportunity.awardingBody.homepage"],"schema":{"format":"uri","type":"string"}}],"subtitle":{"fallback":"EBSI Verifiable diploma","path":[],"schema":{"type":"string"}},"title":{"fallback":"Diploma","path":[],"schema":{"type":"string"}}},"id":"diploma_01","schema":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"}],"spec_version":"https://identity.foundation/credential-manifest/spec/v1.0.0/"}],"credential_supported":[{"cryptographic_binding_methods_supported":["did"],"cryptographic_suites_supported":["ES256K","ES256","ES384","ES512","RS256"],"display":[{"locale":"en-US","name":"Issuer Talao"}],"format":"jwt_vc","id":"VerifiableDiploma","types":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"}],"pre-authorized_grant_anonymous_access_supported":true,"subject_syntax_types_supported":["did:ebsi"],"token_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/token"}'; + final ebsi = Ebsi(client); + final issuer = ebsi.readTokenEndPoint( Response( requestOptions: RequestOptions(path: ''), @@ -351,6 +390,7 @@ void main() { test('get issuer did with openidConfigurationResponse', () { const openidConfigurationResponse = r'{"authorization_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/authorize","batch_credential_endpoint":null,"credential_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/credential","credential_issuer":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl","credential_manifests":[{"id":"VerifiableDiploma_1","issuer":{"id":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","name":"Test EBSILUX"},"output_descriptors":[{"display":{"description":{"fallback":"This card is a proof that you passed this diploma successfully. You can use this card when you need to prove this information to services that have adopted EU EBSI framework.","path":[],"schema":{"type":"string"}},"properties":[{"fallback":"Unknown","label":"First name","path":["$.credentialSubject.firstName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Last name","path":["$.credentialSubject.familyName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Birth date","path":["$.credentialSubject.dateOfBirth"],"schema":{"format":"date","type":"string"}},{"fallback":"Unknown","label":"Grading scheme","path":["$.credentialSubject.gradingScheme.title"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Title","path":["$.credentialSubject.learningAchievement.title"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Description","path":["$.credentialSubject.learningAchievement.description"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"ECTS Points","path":["$.credentialSubject.learningSpecification.ectsCreditPoints"],"schema":{"type":"number"}},{"fallback":"Unknown","label":"Issue date","path":["$.issuanceDate"],"schema":{"format":"date","type":"string"}},{"fallback":"Unknown","label":"Issued by","path":["$.credentialSubject.awardingOpportunity.awardingBody.preferredName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Registration","path":["$.credentialSubject.awardingOpportunity.awardingBody.registration"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Website","path":["$.credentialSubject.awardingOpportunity.awardingBody.homepage"],"schema":{"format":"uri","type":"string"}}],"subtitle":{"fallback":"EBSI Verifiable diploma","path":[],"schema":{"type":"string"}},"title":{"fallback":"Diploma","path":[],"schema":{"type":"string"}}},"id":"diploma_01","schema":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"}],"spec_version":"https://identity.foundation/credential-manifest/spec/v1.0.0/"}],"credential_supported":[{"cryptographic_binding_methods_supported":["did"],"cryptographic_suites_supported":["ES256K","ES256","ES384","ES512","RS256"],"display":[{"locale":"en-US","name":"Issuer Talao"}],"format":"jwt_vc","id":"VerifiableDiploma","types":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"},{"id":"VerifiableDiploma_1","issuer":{"id":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","name":"Test EBSILUX"},"output_descriptors":[{"display":{"description":{"fallback":"This card is a proof that you passed this diploma successfully. You can use this card when you need to prove this information to services that have adopted EU EBSI framework.","path":[],"schema":{"type":"string"}},"properties":[{"fallback":"Unknown","label":"First name","path":["$.credentialSubject.firstName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Last name","path":["$.credentialSubject.familyName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Birth date","path":["$.credentialSubject.dateOfBirth"],"schema":{"format":"date","type":"string"}},{"fallback":"Unknown","label":"Grading scheme","path":["$.credentialSubject.gradingScheme.title"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Title","path":["$.credentialSubject.learningAchievement.title"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Description","path":["$.credentialSubject.learningAchievement.description"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"ECTS Points","path":["$.credentialSubject.learningSpecification.ectsCreditPoints"],"schema":{"type":"number"}},{"fallback":"Unknown","label":"Issue date","path":["$.issuanceDate"],"schema":{"format":"date","type":"string"}},{"fallback":"Unknown","label":"Issued by","path":["$.credentialSubject.awardingOpportunity.awardingBody.preferredName"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Registration","path":["$.credentialSubject.awardingOpportunity.awardingBody.registration"],"schema":{"type":"string"}},{"fallback":"Unknown","label":"Website","path":["$.credentialSubject.awardingOpportunity.awardingBody.homepage"],"schema":{"format":"uri","type":"string"}}],"subtitle":{"fallback":"EBSI Verifiable diploma","path":[],"schema":{"type":"string"}},"title":{"fallback":"Diploma","path":[],"schema":{"type":"string"}}},"id":"diploma_01","schema":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"}],"spec_version":"https://identity.foundation/credential-manifest/spec/v1.0.0/"}],"credential_supported":[{"cryptographic_binding_methods_supported":["did"],"cryptographic_suites_supported":["ES256K","ES256","ES384","ES512","RS256"],"display":[{"locale":"en-US","name":"Issuer Talao"}],"format":"jwt_vc","id":"VerifiableDiploma","types":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd"}],"pre-authorized_grant_anonymous_access_supported":true,"subject_syntax_types_supported":["did:ebsi"],"token_endpoint":"https://talao.co/sandbox/ebsi/issuer/vgvghylozl/token"}'; + final ebsi = Ebsi(client); final issuer = ebsi.readIssuerDid( Response( @@ -369,6 +409,9 @@ void main() { const expectedPublicKey = '{"alg":"EdDSA","crv":"Ed25519","kid":"3623b877bbb24b08ba390f3585418f53","kty":"OKP","use":"sig","x":"pWgA8M3etXlLaqcRmgjEQkz7waseg3FKzMCzfm9Yeow"}'; + + final ebsi = Ebsi(client); + final publicKey = ebsi.readPublicKeyJwk( issuerDid, Response( @@ -380,6 +423,66 @@ void main() { }); }); + group('verify credential', () { + const issuerDid1 = 'did:ebsi:zeFCExU2XAAshYkPCpjuahA'; + const issuerDid2 = 'did:ebsi:zhSw5rPXkcHjvquwnVcTzzC'; + + const didDocumentUrl = + 'https://api-pilot.ebsi.eu/did-registry/v3/identifiers/$issuerDid1'; + + const didDocumentResponse = + '{"assertionMethod":["did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53"],"authentication":["did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53"],"@context":"https://www.w3.org/ns/did/v1","id":"did:ebsi:zeFCExU2XAAshYkPCpjuahA","verificationMethod":[{"controller":"did:ebsi:zeFCExU2XAAshYkPCpjuahA","id":"did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53","publicKeyJwk":{"crv":"P-521","kty":"EC","x":"AekpBQ8ST8a8VcfVOTNl353vSrDCLLJXmPk06wTjxrrjcBpXp5EOnYG_NjFZ6OvLFV1jSfS9tsz4qUxcWceqwQGk","y":"ADSmRA43Z1DSNx_RvcLI87cdL07l6jQyyBXMoxVg_l2Th-x3S1WDhjDly79ajL4Kkd0AZMaZmh9ubmf63e3kyMj2"},"type":"Ed25519VerificationKey2019"}]}'; + + const didDocumentUrl2 = + 'https://api-pilot.ebsi.eu/did-registry/v3/identifiers/$issuerDid2'; + + const didDocumentResponse2 = + '{"assertionMethod":["did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53"],"authentication":["did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53"],"@context":"https://www.w3.org/ns/did/v1","id":"did:ebsi:zeFCExU2XAAshYkPCpjuahA","verificationMethod":[{"controller":"did:ebsi:zeFCExU2XAAshYkPCpjuahA","id":"did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53","publicKeyJwk":{"crv":"Ed25519","kty":"OKP","x":"AekpBQ8ST8a8VcfVOTNl353vSrDCLLJXmPk06wTjxrrjcBpXp5EOnYG_NjFZ6OvLFV1jSfS9tsz4qUxcWceqwQGk","y":"ADSmRA43Z1DSNx_RvcLI87cdL07l6jQyyBXMoxVg_l2Th-x3S1WDhjDly79ajL4Kkd0AZMaZmh9ubmf63e3kyMj2"},"type":"Ed25519VerificationKey2019"}]}'; + + dioAdapter + ..onGet( + didDocumentUrl, + (request) => request.reply(200, jsonDecode(didDocumentResponse)), + ) + ..onGet( + didDocumentUrl2, + (request) => request.reply(200, jsonDecode(didDocumentResponse2)), + ); + + final ebsi = Ebsi(client); + test('returns VerificationType.verified', () async { + const vcJwt = 'eyJhbGciOiJFUzUxMiJ9.' + 'UGF5bG9hZA.' + 'AdwMgeerwtHoh-l192l60hp9wAHZFVJbLfD_UxMi70cwnZOYaRI1bKPWROc-mZZq' + 'wqT2SI-KGDKB34XO0aw_7XdtAG8GaSwFKdCAPZgoXD2YBJZCPEX3xKpRwcdOO8Kp' + 'EHwJjyqOgzDO7iKvU8vcnwNrmxYbSW9ERBXukOXolLzeO_Jn'; + + final isVerified = + await ebsi.verifyCredential(issuerDid: issuerDid1, vcJwt: vcJwt); + + expect(isVerified, VerificationType.verified); + }); + + test('returns VerificationType.notVerified', () async { + const vcJwt = + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksiLCJqd2siOnsiY3J2IjoiUC0yNTZLIiwia3R5IjoiRUMiLCJ4IjoiSjR2UXRMVXlyVlVpRklYUnJ0RXE0eHVybUJacDJlcTl3Sm1Ya0lBX3N0SSIsInkiOiJFVVU2dlhvRzNCR1gyenp3alhyR0RjcjRFeUREMFZmazNfNWZnNWtTZ0tFIn0sImtpZCI6ImRpZDplYnNpOnpvOUZSMVlmQUtGUDNRNmR2cWh4Y1h4bmZlRGlKRFA5N2ttbnFoeUFVU0FDaiNDZ2NnMXk5eGo5dVdGdzU2UE1jMjlYQmQ5RVJlaXh6dm5mdEJ6OEp3UUZpQiJ9.eyJpc3MiOiJkaWQ6ZWJzaTp6bzlGUjFZZkFLRlAzUTZkdnFoeGNYeG5mZURpSkRQOTdrbW5xaHlBVVNBQ2oiLCJub25jZSI6IjdhMDdkZTBmLWE4NzktMTFlZC04MjJiLTBhMTYyODk1ODU2MCIsImlhdCI6MTY3NzA1MDc0MDEyMzIzNSwiYXVkIjoiaHR0cHM6Ly90YWxhby5jby9zYW5kYm94L2Vic2kvaXNzdWVyL3ZndmdoeWxvemwifQ.htjRCpFWbRwanAyQcAq9XZ4vxCXyFbzaaN3yPbPxWIcKFFzDDcA4QCHTUl-L4vzWq0R3LSgQFXQ9bo5D9uCm4w'; + + final isVerified = + await ebsi.verifyCredential(issuerDid: issuerDid1, vcJwt: vcJwt); + + expect(isVerified, VerificationType.notVerified); + }); + + test('returns VerificationType.unKnown', () async { + const vcJwt = + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksiLCJqd2siOnsiY3J2IjoiUC0yNTZLIiwia3R5IjoiRUMiLCJ4IjoiSjR2UXRMVXlyVlVpRklYUnJ0RXE0eHVybUJacDJlcTl3Sm1Ya0lBX3N0SSIsInkiOiJFVVU2dlhvRzNCR1gyenp3alhyR0RjcjRFeUREMFZmazNfNWZnNWtTZ0tFIn0sImtpZCI6ImRpZDplYnNpOnpvOUZSMVlmQUtGUDNRNmR2cWh4Y1h4bmZlRGlKRFA5N2ttbnFoeUFVU0FDaiNDZ2NnMXk5eGo5dVdGdzU2UE1jMjlYQmQ5RVJlaXh6dm5mdEJ6OEp3UUZpQiJ9.eyJpc3MiOiJkaWQ6ZWJzaTp6bzlGUjFZZkFLRlAzUTZkdnFoeGNYeG5mZURpSkRQOTdrbW5xaHlBVVNBQ2oiLCJub25jZSI6IjdhMDdkZTBmLWE4NzktMTFlZC04MjJiLTBhMTYyODk1ODU2MCIsImlhdCI6MTY3NzA1MDc0MDEyMzIzNSwiYXVkIjoiaHR0cHM6Ly90YWxhby5jby9zYW5kYm94L2Vic2kvaXNzdWVyL3ZndmdoeWxvemwifQ.htjRCpFWbRwanAyQcAq9XZ4vxCXyFbzaaN3yPbPxWIcKFFzDDcA4QCHTUl-L4vzWq0R3LSgQFXQ9bo5D9uCm4w'; + + final isVerified = + await ebsi.verifyCredential(issuerDid: issuerDid2, vcJwt: vcJwt); + expect(isVerified, VerificationType.unKnown); + }); + }); + group('getCredentialRequest', () { final ebsi = Ebsi(client); diff --git a/packages/ebsi/test/src/verifier_token_parameters/verifier_token_parameters_test.dart b/packages/ebsi/test/src/verifier_token_parameters/verifier_token_parameters_test.dart index fd7921744..f9f0a1d6a 100644 --- a/packages/ebsi/test/src/verifier_token_parameters/verifier_token_parameters_test.dart +++ b/packages/ebsi/test/src/verifier_token_parameters/verifier_token_parameters_test.dart @@ -26,7 +26,8 @@ void main() { final uri = Uri.parse(url); const credentialsToBePresented = [ - r'{"id":"urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236","receivedId":"urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236","image":null,"data":{"@context":["https://www.w3.org/2018/credentials/v1"],"credentialSchema":{"id":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd","type":"JsonSchemaValidator2018"},"credentialStatus":{"id":"https://essif.europa.eu/status/education#higherEducation#392ac7f6-399a-437b-a268-4691ead8f176","type":"CredentialStatusList2020"},"credentialSubject":{"awardingOpportunity":{"awardingBody":{"eidasLegalIdentifier":"Unknown","homepage":"https://leaston.bcdiploma.com/","id":"did:ebsi:zdRvvKbXhVVBsXhatjuiBhs","preferredName":"Leaston University","registration":"0597065J"},"endedAtTime":"2020-06-26T00:00:00Z","id":"https://leaston.bcdiploma.com/law-economics-management#AwardingOpportunity","identifier":"https://certificate-demo.bcdiploma.com/check/87ED2F2270E6C41456E94B86B9D9115B4E35BCCAD200A49B846592C14F79C86BV1Fnbllta0NZTnJkR3lDWlRmTDlSRUJEVFZISmNmYzJhUU5sZUJ5Z2FJSHpWbmZZ","location":"FRANCE","startedAtTime":"2019-09-02T00:00:00Z"},"dateOfBirth":"1993-04-08","familyName":"DOE","givenNames":"Jane","gradingScheme":{"id":"https://leaston.bcdiploma.com/law-economics-management#GradingScheme","title":"2 year full-time programme / 4 semesters"},"id":"did:ebsi:zoxRGVZQndTfQk54B7tKdwwNdhai5gm9F8Nav8eceNABa","identifier":"0904008084H","learningAchievement":{"additionalNote":["DISTRIBUTION MANAGEMENT"],"description":"The Master in Information and Computer Sciences (MICS) at the University of Luxembourg enables students to acquire deeper knowledge in computer science by understanding its abstract and interdisciplinary foundations, focusing on problem solving and developing lifelong learning skills.","id":"https://leaston.bcdiploma.com/law-economics-management#LearningAchievment","title":"Master in Information and Computer Sciences"},"learningSpecification":{"ectsCreditPoints":120,"eqfLevel":7,"id":"https://leaston.bcdiploma.com/law-economics-management#LearningSpecification","iscedfCode":["7"],"nqfLevel":["7"]}},"evidence":{"documentPresence":["Physical"],"evidenceDocument":["Passport"],"id":"https://essif.europa.eu/tsr-va/evidence/f2aeec97-fc0d-42bf-8ca7-0548192d5678","subjectPresence":"Physical","type":["DocumentVerification"],"verifier":"did:ebsi:2962fb784df61baa267c8132497539f8c674b37c1244a7a"},"id":"urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236","issuanceDate":"2023-02-08T15:10:56Z","issued":"2023-02-08T15:10:56Z","issuer":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","proof":{"created":"2022-04-27T12:25:07Z","creator":"did:ebsi:zdRvvKbXhVVBsXhatjuiBhs","domain":"https://api.preprod.ebsi.eu","jws":"eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzI1NksifQ..mIBnM8XDQqSYKQNX_LvaJhmsbyCr5OZ5cU2Zk-ReqLpr4doFsgmoobkO5128tZy-8KimVjJkGw0wL1uBWnMLWQ","nonce":"3ea68dae-d07a-4daa-932b-fbb58f5c20c4","type":"EcdsaSecp256k1Signature2019"},"type":["VerifiableCredential","VerifiableAttestation","VerifiableDiploma"],"validFrom":"2023-02-08T15:10:56Z"},"shareLink":"","credentialPreview":{"id":"urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236","type":["VerifiableCredential","VerifiableAttestation","VerifiableDiploma"],"issuer":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","description":[],"name":[],"issuanceDate":"2023-02-08T15:10:56Z","proof":[{"type":"EcdsaSecp256k1Signature2019","proofPurpose":null,"verificationMethod":null,"created":"2022-04-27T12:25:07Z","jws":"eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzI1NksifQ..mIBnM8XDQqSYKQNX_LvaJhmsbyCr5OZ5cU2Zk-ReqLpr4doFsgmoobkO5128tZy-8KimVjJkGw0wL1uBWnMLWQ"}],"credentialSubject":{"id":"did:ebsi:zoxRGVZQndTfQk54B7tKdwwNdhai5gm9F8Nav8eceNABa","type":null,"issuedBy":{"name":""}},"evidence":[{"id":"https://essif.europa.eu/tsr-va/evidence/f2aeec97-fc0d-42bf-8ca7-0548192d5678","type":["DocumentVerification"]}],"credentialStatus":{"id":"https://essif.europa.eu/status/education#higherEducation#392ac7f6-399a-437b-a268-4691ead8f176","type":"CredentialStatusList2020","revocationListIndex":"","revocationListCredential":""}},"display":{"backgroundColor":"","icon":"","nameFallback":"","descriptionFallback":""},"expirationDate":null,"credential_manifest":{"id":"VerifiableDiploma_1","issuer":{"id":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","name":"Test EBSILUX"},"output_descriptors":[{"id":"diploma_01","schema":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd","name":null,"description":null,"styles":null,"display":{"title":{"path":[],"schema":{"type":"string","format":null},"fallback":"Diploma"},"subtitle":{"path":[],"schema":{"type":"string","format":null},"fallback":"EBSI Verifiable diploma"},"description":{"path":[],"schema":{"type":"string","format":null},"fallback":"This card is a proof that you passed this diploma successfully. You can use this card when you need to prove this information to services that have adopted EU EBSI framework."},"properties":[{"label":"First name","path":["$.credentialSubject.firstName"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Last name","path":["$.credentialSubject.familyName"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Birth date","path":["$.credentialSubject.dateOfBirth"],"schema":{"type":"string","format":"date"},"fallback":"Unknown"},{"label":"Grading scheme","path":["$.credentialSubject.gradingScheme.title"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Title","path":["$.credentialSubject.learningAchievement.title"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Description","path":["$.credentialSubject.learningAchievement.description"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"ECTS Points","path":["$.credentialSubject.learningSpecification.ectsCreditPoints"],"schema":{"type":"number","format":null},"fallback":"Unknown"},{"label":"Issue date","path":["$.issuanceDate"],"schema":{"type":"string","format":"date"},"fallback":"Unknown"},{"label":"Issued by","path":["$.credentialSubject.awardingOpportunity.awardingBody.preferredName"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Registration","path":["$.credentialSubject.awardingOpportunity.awardingBody.registration"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Website","path":["$.credentialSubject.awardingOpportunity.awardingBody.homepage"],"schema":{"type":"string","format":"uri"},"fallback":"Unknown"}]}}],"presentation_definition":null},"challenge":null,"domain":null,"activities":[{"acquisitionAt":"2023-02-08T20:41:02.024360","presentation":null}],"jwt":"eyJhbGciOiJFUzI1NksiLCJraWQiOiJkaWQ6ZWJzaTp6aFN3NXJQWGtjSGp2cXV3blZjVHp6QiM1MTVhOWM0MzZjMGYyYWQzYWI2NWQ2Y2VmYzVjMWYwNmMwNWI4YWRmY2Y1NGVlMDZkYzgwNTQzMjA0NzBmZmFmIiwidHlwIjoiSldUIn0.eyJleHAiOjE2NzU4NzAwNTYuMTcxMDgyLCJpYXQiOjE2NzU4NjkwNTYuMTcxMDc1LCJpc3MiOiJkaWQ6ZWJzaTp6aFN3NXJQWGtjSGp2cXV3blZjVHp6QiIsImp0aSI6InVybjp1dWlkOjZiMWQ4NDExLTllZDUtNDU2Ni05YzdmLTRjMjQxNjVmZjIzNiIsIm5iZiI6MTY3NTg2OTA1Ni4xNzEwOCwibm9uY2UiOiJjOGM3Nzg0Yi1hN2MyLTExZWQtOGUwMS0wYTE2Mjg5NTg1NjAiLCJzdWIiOiJkaWQ6ZWJzaTp6b3hSR1ZaUW5kVGZRazU0Qjd0S2R3d05kaGFpNWdtOUY4TmF2OGVjZU5BQmEiLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJjcmVkZW50aWFsU2NoZW1hIjp7ImlkIjoiaHR0cHM6Ly9hcGkucHJlcHJvZC5lYnNpLmV1L3RydXN0ZWQtc2NoZW1hcy1yZWdpc3RyeS92MS9zY2hlbWFzLzB4YmY3OGZjMDhhN2E5ZjI4ZjU0NzlmNThkZWEyNjlkMzY1N2Y1NGYxM2NhMzdkMzgwY2Q0ZTkyMjM3ZmI2OTFkZCIsInR5cGUiOiJKc29uU2NoZW1hVmFsaWRhdG9yMjAxOCJ9LCJjcmVkZW50aWFsU3RhdHVzIjp7ImlkIjoiaHR0cHM6Ly9lc3NpZi5ldXJvcGEuZXUvc3RhdHVzL2VkdWNhdGlvbiNoaWdoZXJFZHVjYXRpb24jMzkyYWM3ZjYtMzk5YS00MzdiLWEyNjgtNDY5MWVhZDhmMTc2IiwidHlwZSI6IkNyZWRlbnRpYWxTdGF0dXNMaXN0MjAyMCJ9LCJjcmVkZW50aWFsU3ViamVjdCI6eyJhd2FyZGluZ09wcG9ydHVuaXR5Ijp7ImF3YXJkaW5nQm9keSI6eyJlaWRhc0xlZ2FsSWRlbnRpZmllciI6IlVua25vd24iLCJob21lcGFnZSI6Imh0dHBzOi8vbGVhc3Rvbi5iY2RpcGxvbWEuY29tLyIsImlkIjoiZGlkOmVic2k6emRSdnZLYlhoVlZCc1hoYXRqdWlCaHMiLCJwcmVmZXJyZWROYW1lIjoiTGVhc3RvbiBVbml2ZXJzaXR5IiwicmVnaXN0cmF0aW9uIjoiMDU5NzA2NUoifSwiZW5kZWRBdFRpbWUiOiIyMDIwLTA2LTI2VDAwOjAwOjAwWiIsImlkIjoiaHR0cHM6Ly9sZWFzdG9uLmJjZGlwbG9tYS5jb20vbGF3LWVjb25vbWljcy1tYW5hZ2VtZW50I0F3YXJkaW5nT3Bwb3J0dW5pdHkiLCJpZGVudGlmaWVyIjoiaHR0cHM6Ly9jZXJ0aWZpY2F0ZS1kZW1vLmJjZGlwbG9tYS5jb20vY2hlY2svODdFRDJGMjI3MEU2QzQxNDU2RTk0Qjg2QjlEOTExNUI0RTM1QkNDQUQyMDBBNDlCODQ2NTkyQzE0Rjc5Qzg2QlYxRm5ibGx0YTBOWlRuSmtSM2xEV2xSbVREbFNSVUpFVkZaSVNtTm1ZekpoVVU1c1pVSjVaMkZKU0hwV2JtWloiLCJsb2NhdGlvbiI6IkZSQU5DRSIsInN0YXJ0ZWRBdFRpbWUiOiIyMDE5LTA5LTAyVDAwOjAwOjAwWiJ9LCJkYXRlT2ZCaXJ0aCI6IjE5OTMtMDQtMDgiLCJmYW1pbHlOYW1lIjoiRE9FIiwiZ2l2ZW5OYW1lcyI6IkphbmUiLCJncmFkaW5nU2NoZW1lIjp7ImlkIjoiaHR0cHM6Ly9sZWFzdG9uLmJjZGlwbG9tYS5jb20vbGF3LWVjb25vbWljcy1tYW5hZ2VtZW50I0dyYWRpbmdTY2hlbWUiLCJ0aXRsZSI6IjIgeWVhciBmdWxsLXRpbWUgcHJvZ3JhbW1lIC8gNCBzZW1lc3RlcnMifSwiaWQiOiJkaWQ6ZWJzaTp6b3hSR1ZaUW5kVGZRazU0Qjd0S2R3d05kaGFpNWdtOUY4TmF2OGVjZU5BQmEiLCJpZGVudGlmaWVyIjoiMDkwNDAwODA4NEgiLCJsZWFybmluZ0FjaGlldmVtZW50Ijp7ImFkZGl0aW9uYWxOb3RlIjpbIkRJU1RSSUJVVElPTiBNQU5BR0VNRU5UIl0sImRlc2NyaXB0aW9uIjoiVGhlIE1hc3RlciBpbiBJbmZvcm1hdGlvbiBhbmQgQ29tcHV0ZXIgU2NpZW5jZXMgKE1JQ1MpIGF0IHRoZSBVbml2ZXJzaXR5IG9mIEx1eGVtYm91cmcgZW5hYmxlcyBzdHVkZW50cyB0byBhY3F1aXJlIGRlZXBlciBrbm93bGVkZ2UgaW4gY29tcHV0ZXIgc2NpZW5jZSBieSB1bmRlcnN0YW5kaW5nIGl0cyBhYnN0cmFjdCBhbmQgaW50ZXJkaXNjaXBsaW5hcnkgZm91bmRhdGlvbnMsIGZvY3VzaW5nIG9uIHByb2JsZW0gc29sdmluZyBhbmQgZGV2ZWxvcGluZyBsaWZlbG9uZyBsZWFybmluZyBza2lsbHMuIiwiaWQiOiJodHRwczovL2xlYXN0b24uYmNkaXBsb21hLmNvbS9sYXctZWNvbm9taWNzLW1hbmFnZW1lbnQjTGVhcm5pbmdBY2hpZXZtZW50IiwidGl0bGUiOiJNYXN0ZXIgaW4gSW5mb3JtYXRpb24gYW5kIENvbXB1dGVyIFNjaWVuY2VzIn0sImxlYXJuaW5nU3BlY2lmaWNhdGlvbiI6eyJlY3RzQ3JlZGl0UG9pbnRzIjoxMjAsImVxZkxldmVsIjo3LCJpZCI6Imh0dHBzOi8vbGVhc3Rvbi5iY2RpcGxvbWEuY29tL2xhdy1lY29ub21pY3MtbWFuYWdlbWVudCNMZWFybmluZ1NwZWNpZmljYXRpb24iLCJpc2NlZGZDb2RlIjpbIjciXSwibnFmTGV2ZWwiOlsiNyJdfX0sImV2aWRlbmNlIjp7ImRvY3VtZW50UHJlc2VuY2UiOlsiUGh5c2ljYWwiXSwiZXZpZGVuY2VEb2N1bWVudCI6WyJQYXNzcG9ydCJdLCJpZCI6Imh0dHBzOi8vZXNzaWYuZXVyb3BhLmV1L3Rzci12YS9ldmlkZW5jZS9mMmFlZWM5Ny1mYzBkLTQyYmYtOGNhNy0wNTQ4MTkyZDU2NzgiLCJzdWJqZWN0UHJlc2VuY2UiOiJQaHlzaWNhbCIsInR5cGUiOlsiRG9jdW1lbnRWZXJpZmljYXRpb24iXSwidmVyaWZpZXIiOiJkaWQ6ZWJzaToyOTYyZmI3ODRkZjYxYmFhMjY3YzgxMzI0OTc1MzlmOGM2NzRiMzdjMTI0NGE3YSJ9LCJpZCI6InVybjp1dWlkOjZiMWQ4NDExLTllZDUtNDU2Ni05YzdmLTRjMjQxNjVmZjIzNiIsImlzc3VhbmNlRGF0ZSI6IjIwMjMtMDItMDhUMTU6MTA6NTZaIiwiaXNzdWVkIjoiMjAyMy0wMi0wOFQxNToxMDo1NloiLCJpc3N1ZXIiOiJkaWQ6ZWJzaTp6aFN3NXJQWGtjSGp2cXV3blZjVHp6QiIsInByb29mIjp7ImNyZWF0ZWQiOiIyMDIyLTA0LTI3VDEyOjI1OjA3WiIsImNyZWF0b3IiOiJkaWQ6ZWJzaTp6ZFJ2dktiWGhWVkJzWGhhdGp1aUJocyIsImRvbWFpbiI6Imh0dHBzOi8vYXBpLnByZXByb2QuZWJzaS5ldSIsImp3cyI6ImV5SmlOalFpT21aaGJITmxMQ0pqY21sMElqcGJJbUkyTkNKZExDSmhiR2NpT2lKRlV6STFOa3NpZlEuLm1JQm5NOFhEUXFTWUtRTlhfTHZhSmhtc2J5Q3I1T1o1Y1UyWmstUmVxTHByNGRvRnNnbW9vYmtPNTEyOHRaeS04S2ltVmpKa0d3MHdMMXVCV25NTFdRIiwibm9uY2UiOiIzZWE2OGRhZS1kMDdhLTRkYWEtOTMyYi1mYmI1OGY1YzIwYzQiLCJ0eXBlIjoiRWNkc2FTZWNwMjU2azFTaWduYXR1cmUyMDE5In0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJWZXJpZmlhYmxlQXR0ZXN0YXRpb24iLCJWZXJpZmlhYmxlRGlwbG9tYSJdLCJ2YWxpZEZyb20iOiIyMDIzLTAyLTA4VDE1OjEwOjU2WiJ9fQ.avhpj0UC7SJiOo8PWJV3zhJoxngdmrBnyTkQsnqe76JlOmatLkgAY5Mp_sUJQnN1uENiT-_KBf0KY3izx4X_SA"}' + r'{"id":"urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236","receivedId":"urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236","image":null,"data":{"@context":["https://www.w3.org/2018/credentials/v1"],"credentialSchema":{"id":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd","type":"JsonSchemaValidator2018"},"credentialStatus":{"id":"https://essif.europa.eu/status/education#higherEducation#392ac7f6-399a-437b-a268-4691ead8f176","type":"CredentialStatusList2020"},"credentialSubject":{"awardingOpportunity":{"awardingBody":{"eidasLegalIdentifier":"Unknown","homepage":"https://leaston.bcdiploma.com/","id":"did:ebsi:zdRvvKbXhVVBsXhatjuiBhs","preferredName":"Leaston University","registration":"0597065J"},"endedAtTime":"2020-06-26T00:00:00Z","id":"https://leaston.bcdiploma.com/law-economics-management#AwardingOpportunity","identifier":"https://certificate-demo.bcdiploma.com/check/87ED2F2270E6C41456E94B86B9D9115B4E35BCCAD200A49B846592C14F79C86BV1Fnbllta0NZTnJkR3lDWlRmTDlSRUJEVFZISmNmYzJhUU5sZUJ5Z2FJSHpWbmZZ","location":"FRANCE","startedAtTime":"2019-09-02T00:00:00Z"},"dateOfBirth":"1993-04-08","familyName":"DOE","givenNames":"Jane","gradingScheme":{"id":"https://leaston.bcdiploma.com/law-economics-management#GradingScheme","title":"2 year full-time programme / 4 semesters"},"id":"did:ebsi:zoxRGVZQndTfQk54B7tKdwwNdhai5gm9F8Nav8eceNABa","identifier":"0904008084H","learningAchievement":{"additionalNote":["DISTRIBUTION MANAGEMENT"],"description":"The Master in Information and Computer Sciences (MICS) at the University of Luxembourg enables students to acquire deeper knowledge in computer science by understanding its abstract and interdisciplinary foundations, focusing on problem solving and developing lifelong learning skills.","id":"https://leaston.bcdiploma.com/law-economics-management#LearningAchievment","title":"Master in Information and Computer Sciences"},"learningSpecification":{"ectsCreditPoints":120,"eqfLevel":7,"id":"https://leaston.bcdiploma.com/law-economics-management#LearningSpecification","iscedfCode":["7"],"nqfLevel":["7"]}},"evidence":{"documentPresence":["Physical"],"evidenceDocument":["Passport"],"id":"https://essif.europa.eu/tsr-va/evidence/f2aeec97-fc0d-42bf-8ca7-0548192d5678","subjectPresence":"Physical","type":["DocumentVerification"],"verifier":"did:ebsi:2962fb784df61baa267c8132497539f8c674b37c1244a7a"},"id":"urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236","issuanceDate":"2023-02-08T15:10:56Z","issued":"2023-02-08T15:10:56Z","issuer":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","proof":{"created":"2022-04-27T12:25:07Z","creator":"did:ebsi:zdRvvKbXhVVBsXhatjuiBhs","domain":"https://api.preprod.ebsi.eu","jws":"eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzI1NksifQ..mIBnM8XDQqSYKQNX_LvaJhmsbyCr5OZ5cU2Zk-ReqLpr4doFsgmoobkO5128tZy-8KimVjJkGw0wL1uBWnMLWQ","nonce":"3ea68dae-d07a-4daa-932b-fbb58f5c20c4","type":"EcdsaSecp256k1Signature2019"},"type":["VerifiableCredential","VerifiableAttestation","VerifiableDiploma"],"validFrom":"2023-02-08T15:10:56Z"},"shareLink":"","credentialPreview":{"id":"urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236","type":["VerifiableCredential","VerifiableAttestation","VerifiableDiploma"],"issuer":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","description":[],"name":[],"issuanceDate":"2023-02-08T15:10:56Z","proof":[{"type":"EcdsaSecp256k1Signature2019","proofPurpose":null,"verificationMethod":null,"created":"2022-04-27T12:25:07Z","jws":"eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzI1NksifQ..mIBnM8XDQqSYKQNX_LvaJhmsbyCr5OZ5cU2Zk-ReqLpr4doFsgmoobkO5128tZy-8KimVjJkGw0wL1uBWnMLWQ"}],"credentialSubject":{"id":"did:ebsi:zoxRGVZQndTfQk54B7tKdwwNdhai5gm9F8Nav8eceNABa","type":null,"issuedBy":{"name":""}},"evidence":[{"id":"https://essif.europa.eu/tsr-va/evidence/f2aeec97-fc0d-42bf-8ca7-0548192d5678","type":["DocumentVerification"]}],"credentialStatus":{"id":"https://essif.europa.eu/status/education#higherEducation#392ac7f6-399a-437b-a268-4691ead8f176","type":"CredentialStatusList2020","revocationListIndex":"","revocationListCredential":""}},"display":{"backgroundColor":"","icon":"","nameFallback":"","descriptionFallback":""},"expirationDate":null,"credential_manifest":{"id":"VerifiableDiploma_1","issuer":{"id":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","name":"Test EBSILUX"},"output_descriptors":[{"id":"diploma_01","schema":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd","name":null,"description":null,"styles":null,"display":{"title":{"path":[],"schema":{"type":"string","format":null},"fallback":"Diploma"},"subtitle":{"path":[],"schema":{"type":"string","format":null},"fallback":"EBSI Verifiable diploma"},"description":{"path":[],"schema":{"type":"string","format":null},"fallback":"This card is a proof that you passed this diploma successfully. You can use this card when you need to prove this information to services that have adopted EU EBSI framework."},"properties":[{"label":"First name","path":["$.credentialSubject.firstName"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Last name","path":["$.credentialSubject.familyName"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Birth date","path":["$.credentialSubject.dateOfBirth"],"schema":{"type":"string","format":"date"},"fallback":"Unknown"},{"label":"Grading scheme","path":["$.credentialSubject.gradingScheme.title"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Title","path":["$.credentialSubject.learningAchievement.title"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Description","path":["$.credentialSubject.learningAchievement.description"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"ECTS Points","path":["$.credentialSubject.learningSpecification.ectsCreditPoints"],"schema":{"type":"number","format":null},"fallback":"Unknown"},{"label":"Issue date","path":["$.issuanceDate"],"schema":{"type":"string","format":"date"},"fallback":"Unknown"},{"label":"Issued by","path":["$.credentialSubject.awardingOpportunity.awardingBody.preferredName"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Registration","path":["$.credentialSubject.awardingOpportunity.awardingBody.registration"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Website","path":["$.credentialSubject.awardingOpportunity.awardingBody.homepage"],"schema":{"type":"string","format":"uri"},"fallback":"Unknown"}]}}],"presentation_definition":null},"challenge":null,"domain":null,"activities":[{"acquisitionAt":"2023-02-08T20:41:02.024360","presentation":null}],"jwt":"eyJhbGciOiJFUzI1NksiLCJraWQiOiJkaWQ6ZWJzaTp6aFN3NXJQWGtjSGp2cXV3blZjVHp6QiM1MTVhOWM0MzZjMGYyYWQzYWI2NWQ2Y2VmYzVjMWYwNmMwNWI4YWRmY2Y1NGVlMDZkYzgwNTQzMjA0NzBmZmFmIiwidHlwIjoiSldUIn0.eyJleHAiOjE2NzU4NzAwNTYuMTcxMDgyLCJpYXQiOjE2NzU4NjkwNTYuMTcxMDc1LCJpc3MiOiJkaWQ6ZWJzaTp6aFN3NXJQWGtjSGp2cXV3blZjVHp6QiIsImp0aSI6InVybjp1dWlkOjZiMWQ4NDExLTllZDUtNDU2Ni05YzdmLTRjMjQxNjVmZjIzNiIsIm5iZiI6MTY3NTg2OTA1Ni4xNzEwOCwibm9uY2UiOiJjOGM3Nzg0Yi1hN2MyLTExZWQtOGUwMS0wYTE2Mjg5NTg1NjAiLCJzdWIiOiJkaWQ6ZWJzaTp6b3hSR1ZaUW5kVGZRazU0Qjd0S2R3d05kaGFpNWdtOUY4TmF2OGVjZU5BQmEiLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJjcmVkZW50aWFsU2NoZW1hIjp7ImlkIjoiaHR0cHM6Ly9hcGkucHJlcHJvZC5lYnNpLmV1L3RydXN0ZWQtc2NoZW1hcy1yZWdpc3RyeS92MS9zY2hlbWFzLzB4YmY3OGZjMDhhN2E5ZjI4ZjU0NzlmNThkZWEyNjlkMzY1N2Y1NGYxM2NhMzdkMzgwY2Q0ZTkyMjM3ZmI2OTFkZCIsInR5cGUiOiJKc29uU2NoZW1hVmFsaWRhdG9yMjAxOCJ9LCJjcmVkZW50aWFsU3RhdHVzIjp7ImlkIjoiaHR0cHM6Ly9lc3NpZi5ldXJvcGEuZXUvc3RhdHVzL2VkdWNhdGlvbiNoaWdoZXJFZHVjYXRpb24jMzkyYWM3ZjYtMzk5YS00MzdiLWEyNjgtNDY5MWVhZDhmMTc2IiwidHlwZSI6IkNyZWRlbnRpYWxTdGF0dXNMaXN0MjAyMCJ9LCJjcmVkZW50aWFsU3ViamVjdCI6eyJhd2FyZGluZ09wcG9ydHVuaXR5Ijp7ImF3YXJkaW5nQm9keSI6eyJlaWRhc0xlZ2FsSWRlbnRpZmllciI6IlVua25vd24iLCJob21lcGFnZSI6Imh0dHBzOi8vbGVhc3Rvbi5iY2RpcGxvbWEuY29tLyIsImlkIjoiZGlkOmVic2k6emRSdnZLYlhoVlZCc1hoYXRqdWlCaHMiLCJwcmVmZXJyZWROYW1lIjoiTGVhc3RvbiBVbml2ZXJzaXR5IiwicmVnaXN0cmF0aW9uIjoiMDU5NzA2NUoifSwiZW5kZWRBdFRpbWUiOiIyMDIwLTA2LTI2VDAwOjAwOjAwWiIsImlkIjoiaHR0cHM6Ly9sZWFzdG9uLmJjZGlwbG9tYS5jb20vbGF3LWVjb25vbWljcy1tYW5hZ2VtZW50I0F3YXJkaW5nT3Bwb3J0dW5pdHkiLCJpZGVudGlmaWVyIjoiaHR0cHM6Ly9jZXJ0aWZpY2F0ZS1kZW1vLmJjZGlwbG9tYS5jb20vY2hlY2svODdFRDJGMjI3MEU2QzQxNDU2RTk0Qjg2QjlEOTExNUI0RTM1QkNDQUQyMDBBNDlCODQ2NTkyQzE0Rjc5Qzg2QlYxRm5ibGx0YTBOWlRuSmtSM2xEV2xSbVREbFNSVUpFVkZaSVNtTm1ZekpoVVU1c1pVSjVaMkZKU0hwV2JtWloiLCJsb2NhdGlvbiI6IkZSQU5DRSIsInN0YXJ0ZWRBdFRpbWUiOiIyMDE5LTA5LTAyVDAwOjAwOjAwWiJ9LCJkYXRlT2ZCaXJ0aCI6IjE5OTMtMDQtMDgiLCJmYW1pbHlOYW1lIjoiRE9FIiwiZ2l2ZW5OYW1lcyI6IkphbmUiLCJncmFkaW5nU2NoZW1lIjp7ImlkIjoiaHR0cHM6Ly9sZWFzdG9uLmJjZGlwbG9tYS5jb20vbGF3LWVjb25vbWljcy1tYW5hZ2VtZW50I0dyYWRpbmdTY2hlbWUiLCJ0aXRsZSI6IjIgeWVhciBmdWxsLXRpbWUgcHJvZ3JhbW1lIC8gNCBzZW1lc3RlcnMifSwiaWQiOiJkaWQ6ZWJzaTp6b3hSR1ZaUW5kVGZRazU0Qjd0S2R3d05kaGFpNWdtOUY4TmF2OGVjZU5BQmEiLCJpZGVudGlmaWVyIjoiMDkwNDAwODA4NEgiLCJsZWFybmluZ0FjaGlldmVtZW50Ijp7ImFkZGl0aW9uYWxOb3RlIjpbIkRJU1RSSUJVVElPTiBNQU5BR0VNRU5UIl0sImRlc2NyaXB0aW9uIjoiVGhlIE1hc3RlciBpbiBJbmZvcm1hdGlvbiBhbmQgQ29tcHV0ZXIgU2NpZW5jZXMgKE1JQ1MpIGF0IHRoZSBVbml2ZXJzaXR5IG9mIEx1eGVtYm91cmcgZW5hYmxlcyBzdHVkZW50cyB0byBhY3F1aXJlIGRlZXBlciBrbm93bGVkZ2UgaW4gY29tcHV0ZXIgc2NpZW5jZSBieSB1bmRlcnN0YW5kaW5nIGl0cyBhYnN0cmFjdCBhbmQgaW50ZXJkaXNjaXBsaW5hcnkgZm91bmRhdGlvbnMsIGZvY3VzaW5nIG9uIHByb2JsZW0gc29sdmluZyBhbmQgZGV2ZWxvcGluZyBsaWZlbG9uZyBsZWFybmluZyBza2lsbHMuIiwiaWQiOiJodHRwczovL2xlYXN0b24uYmNkaXBsb21hLmNvbS9sYXctZWNvbm9taWNzLW1hbmFnZW1lbnQjTGVhcm5pbmdBY2hpZXZtZW50IiwidGl0bGUiOiJNYXN0ZXIgaW4gSW5mb3JtYXRpb24gYW5kIENvbXB1dGVyIFNjaWVuY2VzIn0sImxlYXJuaW5nU3BlY2lmaWNhdGlvbiI6eyJlY3RzQ3JlZGl0UG9pbnRzIjoxMjAsImVxZkxldmVsIjo3LCJpZCI6Imh0dHBzOi8vbGVhc3Rvbi5iY2RpcGxvbWEuY29tL2xhdy1lY29ub21pY3MtbWFuYWdlbWVudCNMZWFybmluZ1NwZWNpZmljYXRpb24iLCJpc2NlZGZDb2RlIjpbIjciXSwibnFmTGV2ZWwiOlsiNyJdfX0sImV2aWRlbmNlIjp7ImRvY3VtZW50UHJlc2VuY2UiOlsiUGh5c2ljYWwiXSwiZXZpZGVuY2VEb2N1bWVudCI6WyJQYXNzcG9ydCJdLCJpZCI6Imh0dHBzOi8vZXNzaWYuZXVyb3BhLmV1L3Rzci12YS9ldmlkZW5jZS9mMmFlZWM5Ny1mYzBkLTQyYmYtOGNhNy0wNTQ4MTkyZDU2NzgiLCJzdWJqZWN0UHJlc2VuY2UiOiJQaHlzaWNhbCIsInR5cGUiOlsiRG9jdW1lbnRWZXJpZmljYXRpb24iXSwidmVyaWZpZXIiOiJkaWQ6ZWJzaToyOTYyZmI3ODRkZjYxYmFhMjY3YzgxMzI0OTc1MzlmOGM2NzRiMzdjMTI0NGE3YSJ9LCJpZCI6InVybjp1dWlkOjZiMWQ4NDExLTllZDUtNDU2Ni05YzdmLTRjMjQxNjVmZjIzNiIsImlzc3VhbmNlRGF0ZSI6IjIwMjMtMDItMDhUMTU6MTA6NTZaIiwiaXNzdWVkIjoiMjAyMy0wMi0wOFQxNToxMDo1NloiLCJpc3N1ZXIiOiJkaWQ6ZWJzaTp6aFN3NXJQWGtjSGp2cXV3blZjVHp6QiIsInByb29mIjp7ImNyZWF0ZWQiOiIyMDIyLTA0LTI3VDEyOjI1OjA3WiIsImNyZWF0b3IiOiJkaWQ6ZWJzaTp6ZFJ2dktiWGhWVkJzWGhhdGp1aUJocyIsImRvbWFpbiI6Imh0dHBzOi8vYXBpLnByZXByb2QuZWJzaS5ldSIsImp3cyI6ImV5SmlOalFpT21aaGJITmxMQ0pqY21sMElqcGJJbUkyTkNKZExDSmhiR2NpT2lKRlV6STFOa3NpZlEuLm1JQm5NOFhEUXFTWUtRTlhfTHZhSmhtc2J5Q3I1T1o1Y1UyWmstUmVxTHByNGRvRnNnbW9vYmtPNTEyOHRaeS04S2ltVmpKa0d3MHdMMXVCV25NTFdRIiwibm9uY2UiOiIzZWE2OGRhZS1kMDdhLTRkYWEtOTMyYi1mYmI1OGY1YzIwYzQiLCJ0eXBlIjoiRWNkc2FTZWNwMjU2azFTaWduYXR1cmUyMDE5In0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJWZXJpZmlhYmxlQXR0ZXN0YXRpb24iLCJWZXJpZmlhYmxlRGlwbG9tYSJdLCJ2YWxpZEZyb20iOiIyMDIzLTAyLTA4VDE1OjEwOjU2WiJ9fQ.avhpj0UC7SJiOo8PWJV3zhJoxngdmrBnyTkQsnqe76JlOmatLkgAY5Mp_sUJQnN1uENiT-_KBf0KY3izx4X_SA"}', + r'{"id":"urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236","receivedId":"urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236","image":null,"data":{"@context":["https://www.w3.org/2018/credentials/v1"],"credentialSchema":{"id":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd","type":"JsonSchemaValidator2018"},"credentialStatus":{"id":"https://essif.europa.eu/status/education#higherEducation#392ac7f6-399a-437b-a268-4691ead8f176","type":"CredentialStatusList2020"},"credentialSubject":{"awardingOpportunity":{"awardingBody":{"eidasLegalIdentifier":"Unknown","homepage":"https://leaston.bcdiploma.com/","id":"did:ebsi:zdRvvKbXhVVBsXhatjuiBhs","preferredName":"Leaston University","registration":"0597065J"},"endedAtTime":"2020-06-26T00:00:00Z","id":"https://leaston.bcdiploma.com/law-economics-management#AwardingOpportunity","identifier":"https://certificate-demo.bcdiploma.com/check/87ED2F2270E6C41456E94B86B9D9115B4E35BCCAD200A49B846592C14F79C86BV1Fnbllta0NZTnJkR3lDWlRmTDlSRUJEVFZISmNmYzJhUU5sZUJ5Z2FJSHpWbmZZ","location":"FRANCE","startedAtTime":"2019-09-02T00:00:00Z"},"dateOfBirth":"1993-04-08","familyName":"DOE","givenNames":"Jane","gradingScheme":{"id":"https://leaston.bcdiploma.com/law-economics-management#GradingScheme","title":"2 year full-time programme / 4 semesters"},"id":"did:ebsi:zoxRGVZQndTfQk54B7tKdwwNdhai5gm9F8Nav8eceNABa","identifier":"0904008084H","learningAchievement":{"additionalNote":["DISTRIBUTION MANAGEMENT"],"description":"The Master in Information and Computer Sciences (MICS) at the University of Luxembourg enables students to acquire deeper knowledge in computer science by understanding its abstract and interdisciplinary foundations, focusing on problem solving and developing lifelong learning skills.","id":"https://leaston.bcdiploma.com/law-economics-management#LearningAchievment","title":"Master in Information and Computer Sciences"},"learningSpecification":{"ectsCreditPoints":120,"eqfLevel":7,"id":"https://leaston.bcdiploma.com/law-economics-management#LearningSpecification","iscedfCode":["7"],"nqfLevel":["7"]}},"evidence":{"documentPresence":["Physical"],"evidenceDocument":["Passport"],"id":"https://essif.europa.eu/tsr-va/evidence/f2aeec97-fc0d-42bf-8ca7-0548192d5678","subjectPresence":"Physical","type":["DocumentVerification"],"verifier":"did:ebsi:2962fb784df61baa267c8132497539f8c674b37c1244a7a"},"id":"urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236","issuanceDate":"2023-02-08T15:10:56Z","issued":"2023-02-08T15:10:56Z","issuer":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","proof":{"created":"2022-04-27T12:25:07Z","creator":"did:ebsi:zdRvvKbXhVVBsXhatjuiBhs","domain":"https://api.preprod.ebsi.eu","jws":"eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzI1NksifQ..mIBnM8XDQqSYKQNX_LvaJhmsbyCr5OZ5cU2Zk-ReqLpr4doFsgmoobkO5128tZy-8KimVjJkGw0wL1uBWnMLWQ","nonce":"3ea68dae-d07a-4daa-932b-fbb58f5c20c4","type":"EcdsaSecp256k1Signature2019"},"type":["VerifiableCredential","VerifiableAttestation","VerifiableDiploma"],"validFrom":"2023-02-08T15:10:56Z"},"shareLink":"","credentialPreview":{"id":"urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236","type":["VerifiableCredential","VerifiableAttestation","VerifiableDiploma"],"issuer":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","description":[],"name":[],"issuanceDate":"2023-02-08T15:10:56Z","proof":[{"type":"EcdsaSecp256k1Signature2019","proofPurpose":null,"verificationMethod":null,"created":"2022-04-27T12:25:07Z","jws":"eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzI1NksifQ..mIBnM8XDQqSYKQNX_LvaJhmsbyCr5OZ5cU2Zk-ReqLpr4doFsgmoobkO5128tZy-8KimVjJkGw0wL1uBWnMLWQ"}],"credentialSubject":{"id":"did:ebsi:zoxRGVZQndTfQk54B7tKdwwNdhai5gm9F8Nav8eceNABa","type":null,"issuedBy":{"name":""}},"evidence":[{"id":"https://essif.europa.eu/tsr-va/evidence/f2aeec97-fc0d-42bf-8ca7-0548192d5678","type":["DocumentVerification"]}],"credentialStatus":{"id":"https://essif.europa.eu/status/education#higherEducation#392ac7f6-399a-437b-a268-4691ead8f176","type":"CredentialStatusList2020","revocationListIndex":"","revocationListCredential":""}},"display":{"backgroundColor":"","icon":"","nameFallback":"","descriptionFallback":""},"expirationDate":null,"credential_manifest":{"id":"VerifiableDiploma_1","issuer":{"id":"did:ebsi:zhSw5rPXkcHjvquwnVcTzzB","name":"Test EBSILUX"},"output_descriptors":[{"id":"diploma_01","schema":"https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd","name":null,"description":null,"styles":null,"display":{"title":{"path":[],"schema":{"type":"string","format":null},"fallback":"Diploma"},"subtitle":{"path":[],"schema":{"type":"string","format":null},"fallback":"EBSI Verifiable diploma"},"description":{"path":[],"schema":{"type":"string","format":null},"fallback":"This card is a proof that you passed this diploma successfully. You can use this card when you need to prove this information to services that have adopted EU EBSI framework."},"properties":[{"label":"First name","path":["$.credentialSubject.firstName"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Last name","path":["$.credentialSubject.familyName"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Birth date","path":["$.credentialSubject.dateOfBirth"],"schema":{"type":"string","format":"date"},"fallback":"Unknown"},{"label":"Grading scheme","path":["$.credentialSubject.gradingScheme.title"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Title","path":["$.credentialSubject.learningAchievement.title"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Description","path":["$.credentialSubject.learningAchievement.description"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"ECTS Points","path":["$.credentialSubject.learningSpecification.ectsCreditPoints"],"schema":{"type":"number","format":null},"fallback":"Unknown"},{"label":"Issue date","path":["$.issuanceDate"],"schema":{"type":"string","format":"date"},"fallback":"Unknown"},{"label":"Issued by","path":["$.credentialSubject.awardingOpportunity.awardingBody.preferredName"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Registration","path":["$.credentialSubject.awardingOpportunity.awardingBody.registration"],"schema":{"type":"string","format":null},"fallback":"Unknown"},{"label":"Website","path":["$.credentialSubject.awardingOpportunity.awardingBody.homepage"],"schema":{"type":"string","format":"uri"},"fallback":"Unknown"}]}}],"presentation_definition":null},"challenge":null,"domain":null,"activities":[{"acquisitionAt":"2023-02-08T20:41:02.024360","presentation":null}]}' ]; final verifierTokenParameters = @@ -38,7 +39,93 @@ void main() { const jwtsOfCredentials = [ // ignore: lines_longer_than_80_chars - 'eyJhbGciOiJFUzI1NksiLCJraWQiOiJkaWQ6ZWJzaTp6aFN3NXJQWGtjSGp2cXV3blZjVHp6QiM1MTVhOWM0MzZjMGYyYWQzYWI2NWQ2Y2VmYzVjMWYwNmMwNWI4YWRmY2Y1NGVlMDZkYzgwNTQzMjA0NzBmZmFmIiwidHlwIjoiSldUIn0.eyJleHAiOjE2NzU4NzAwNTYuMTcxMDgyLCJpYXQiOjE2NzU4NjkwNTYuMTcxMDc1LCJpc3MiOiJkaWQ6ZWJzaTp6aFN3NXJQWGtjSGp2cXV3blZjVHp6QiIsImp0aSI6InVybjp1dWlkOjZiMWQ4NDExLTllZDUtNDU2Ni05YzdmLTRjMjQxNjVmZjIzNiIsIm5iZiI6MTY3NTg2OTA1Ni4xNzEwOCwibm9uY2UiOiJjOGM3Nzg0Yi1hN2MyLTExZWQtOGUwMS0wYTE2Mjg5NTg1NjAiLCJzdWIiOiJkaWQ6ZWJzaTp6b3hSR1ZaUW5kVGZRazU0Qjd0S2R3d05kaGFpNWdtOUY4TmF2OGVjZU5BQmEiLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJjcmVkZW50aWFsU2NoZW1hIjp7ImlkIjoiaHR0cHM6Ly9hcGkucHJlcHJvZC5lYnNpLmV1L3RydXN0ZWQtc2NoZW1hcy1yZWdpc3RyeS92MS9zY2hlbWFzLzB4YmY3OGZjMDhhN2E5ZjI4ZjU0NzlmNThkZWEyNjlkMzY1N2Y1NGYxM2NhMzdkMzgwY2Q0ZTkyMjM3ZmI2OTFkZCIsInR5cGUiOiJKc29uU2NoZW1hVmFsaWRhdG9yMjAxOCJ9LCJjcmVkZW50aWFsU3RhdHVzIjp7ImlkIjoiaHR0cHM6Ly9lc3NpZi5ldXJvcGEuZXUvc3RhdHVzL2VkdWNhdGlvbiNoaWdoZXJFZHVjYXRpb24jMzkyYWM3ZjYtMzk5YS00MzdiLWEyNjgtNDY5MWVhZDhmMTc2IiwidHlwZSI6IkNyZWRlbnRpYWxTdGF0dXNMaXN0MjAyMCJ9LCJjcmVkZW50aWFsU3ViamVjdCI6eyJhd2FyZGluZ09wcG9ydHVuaXR5Ijp7ImF3YXJkaW5nQm9keSI6eyJlaWRhc0xlZ2FsSWRlbnRpZmllciI6IlVua25vd24iLCJob21lcGFnZSI6Imh0dHBzOi8vbGVhc3Rvbi5iY2RpcGxvbWEuY29tLyIsImlkIjoiZGlkOmVic2k6emRSdnZLYlhoVlZCc1hoYXRqdWlCaHMiLCJwcmVmZXJyZWROYW1lIjoiTGVhc3RvbiBVbml2ZXJzaXR5IiwicmVnaXN0cmF0aW9uIjoiMDU5NzA2NUoifSwiZW5kZWRBdFRpbWUiOiIyMDIwLTA2LTI2VDAwOjAwOjAwWiIsImlkIjoiaHR0cHM6Ly9sZWFzdG9uLmJjZGlwbG9tYS5jb20vbGF3LWVjb25vbWljcy1tYW5hZ2VtZW50I0F3YXJkaW5nT3Bwb3J0dW5pdHkiLCJpZGVudGlmaWVyIjoiaHR0cHM6Ly9jZXJ0aWZpY2F0ZS1kZW1vLmJjZGlwbG9tYS5jb20vY2hlY2svODdFRDJGMjI3MEU2QzQxNDU2RTk0Qjg2QjlEOTExNUI0RTM1QkNDQUQyMDBBNDlCODQ2NTkyQzE0Rjc5Qzg2QlYxRm5ibGx0YTBOWlRuSmtSM2xEV2xSbVREbFNSVUpFVkZaSVNtTm1ZekpoVVU1c1pVSjVaMkZKU0hwV2JtWloiLCJsb2NhdGlvbiI6IkZSQU5DRSIsInN0YXJ0ZWRBdFRpbWUiOiIyMDE5LTA5LTAyVDAwOjAwOjAwWiJ9LCJkYXRlT2ZCaXJ0aCI6IjE5OTMtMDQtMDgiLCJmYW1pbHlOYW1lIjoiRE9FIiwiZ2l2ZW5OYW1lcyI6IkphbmUiLCJncmFkaW5nU2NoZW1lIjp7ImlkIjoiaHR0cHM6Ly9sZWFzdG9uLmJjZGlwbG9tYS5jb20vbGF3LWVjb25vbWljcy1tYW5hZ2VtZW50I0dyYWRpbmdTY2hlbWUiLCJ0aXRsZSI6IjIgeWVhciBmdWxsLXRpbWUgcHJvZ3JhbW1lIC8gNCBzZW1lc3RlcnMifSwiaWQiOiJkaWQ6ZWJzaTp6b3hSR1ZaUW5kVGZRazU0Qjd0S2R3d05kaGFpNWdtOUY4TmF2OGVjZU5BQmEiLCJpZGVudGlmaWVyIjoiMDkwNDAwODA4NEgiLCJsZWFybmluZ0FjaGlldmVtZW50Ijp7ImFkZGl0aW9uYWxOb3RlIjpbIkRJU1RSSUJVVElPTiBNQU5BR0VNRU5UIl0sImRlc2NyaXB0aW9uIjoiVGhlIE1hc3RlciBpbiBJbmZvcm1hdGlvbiBhbmQgQ29tcHV0ZXIgU2NpZW5jZXMgKE1JQ1MpIGF0IHRoZSBVbml2ZXJzaXR5IG9mIEx1eGVtYm91cmcgZW5hYmxlcyBzdHVkZW50cyB0byBhY3F1aXJlIGRlZXBlciBrbm93bGVkZ2UgaW4gY29tcHV0ZXIgc2NpZW5jZSBieSB1bmRlcnN0YW5kaW5nIGl0cyBhYnN0cmFjdCBhbmQgaW50ZXJkaXNjaXBsaW5hcnkgZm91bmRhdGlvbnMsIGZvY3VzaW5nIG9uIHByb2JsZW0gc29sdmluZyBhbmQgZGV2ZWxvcGluZyBsaWZlbG9uZyBsZWFybmluZyBza2lsbHMuIiwiaWQiOiJodHRwczovL2xlYXN0b24uYmNkaXBsb21hLmNvbS9sYXctZWNvbm9taWNzLW1hbmFnZW1lbnQjTGVhcm5pbmdBY2hpZXZtZW50IiwidGl0bGUiOiJNYXN0ZXIgaW4gSW5mb3JtYXRpb24gYW5kIENvbXB1dGVyIFNjaWVuY2VzIn0sImxlYXJuaW5nU3BlY2lmaWNhdGlvbiI6eyJlY3RzQ3JlZGl0UG9pbnRzIjoxMjAsImVxZkxldmVsIjo3LCJpZCI6Imh0dHBzOi8vbGVhc3Rvbi5iY2RpcGxvbWEuY29tL2xhdy1lY29ub21pY3MtbWFuYWdlbWVudCNMZWFybmluZ1NwZWNpZmljYXRpb24iLCJpc2NlZGZDb2RlIjpbIjciXSwibnFmTGV2ZWwiOlsiNyJdfX0sImV2aWRlbmNlIjp7ImRvY3VtZW50UHJlc2VuY2UiOlsiUGh5c2ljYWwiXSwiZXZpZGVuY2VEb2N1bWVudCI6WyJQYXNzcG9ydCJdLCJpZCI6Imh0dHBzOi8vZXNzaWYuZXVyb3BhLmV1L3Rzci12YS9ldmlkZW5jZS9mMmFlZWM5Ny1mYzBkLTQyYmYtOGNhNy0wNTQ4MTkyZDU2NzgiLCJzdWJqZWN0UHJlc2VuY2UiOiJQaHlzaWNhbCIsInR5cGUiOlsiRG9jdW1lbnRWZXJpZmljYXRpb24iXSwidmVyaWZpZXIiOiJkaWQ6ZWJzaToyOTYyZmI3ODRkZjYxYmFhMjY3YzgxMzI0OTc1MzlmOGM2NzRiMzdjMTI0NGE3YSJ9LCJpZCI6InVybjp1dWlkOjZiMWQ4NDExLTllZDUtNDU2Ni05YzdmLTRjMjQxNjVmZjIzNiIsImlzc3VhbmNlRGF0ZSI6IjIwMjMtMDItMDhUMTU6MTA6NTZaIiwiaXNzdWVkIjoiMjAyMy0wMi0wOFQxNToxMDo1NloiLCJpc3N1ZXIiOiJkaWQ6ZWJzaTp6aFN3NXJQWGtjSGp2cXV3blZjVHp6QiIsInByb29mIjp7ImNyZWF0ZWQiOiIyMDIyLTA0LTI3VDEyOjI1OjA3WiIsImNyZWF0b3IiOiJkaWQ6ZWJzaTp6ZFJ2dktiWGhWVkJzWGhhdGp1aUJocyIsImRvbWFpbiI6Imh0dHBzOi8vYXBpLnByZXByb2QuZWJzaS5ldSIsImp3cyI6ImV5SmlOalFpT21aaGJITmxMQ0pqY21sMElqcGJJbUkyTkNKZExDSmhiR2NpT2lKRlV6STFOa3NpZlEuLm1JQm5NOFhEUXFTWUtRTlhfTHZhSmhtc2J5Q3I1T1o1Y1UyWmstUmVxTHByNGRvRnNnbW9vYmtPNTEyOHRaeS04S2ltVmpKa0d3MHdMMXVCV25NTFdRIiwibm9uY2UiOiIzZWE2OGRhZS1kMDdhLTRkYWEtOTMyYi1mYmI1OGY1YzIwYzQiLCJ0eXBlIjoiRWNkc2FTZWNwMjU2azFTaWduYXR1cmUyMDE5In0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJWZXJpZmlhYmxlQXR0ZXN0YXRpb24iLCJWZXJpZmlhYmxlRGlwbG9tYSJdLCJ2YWxpZEZyb20iOiIyMDIzLTAyLTA4VDE1OjEwOjU2WiJ9fQ.avhpj0UC7SJiOo8PWJV3zhJoxngdmrBnyTkQsnqe76JlOmatLkgAY5Mp_sUJQnN1uENiT-_KBf0KY3izx4X_SA' + 'eyJhbGciOiJFUzI1NksiLCJraWQiOiJkaWQ6ZWJzaTp6aFN3NXJQWGtjSGp2cXV3blZjVHp6QiM1MTVhOWM0MzZjMGYyYWQzYWI2NWQ2Y2VmYzVjMWYwNmMwNWI4YWRmY2Y1NGVlMDZkYzgwNTQzMjA0NzBmZmFmIiwidHlwIjoiSldUIn0.eyJleHAiOjE2NzU4NzAwNTYuMTcxMDgyLCJpYXQiOjE2NzU4NjkwNTYuMTcxMDc1LCJpc3MiOiJkaWQ6ZWJzaTp6aFN3NXJQWGtjSGp2cXV3blZjVHp6QiIsImp0aSI6InVybjp1dWlkOjZiMWQ4NDExLTllZDUtNDU2Ni05YzdmLTRjMjQxNjVmZjIzNiIsIm5iZiI6MTY3NTg2OTA1Ni4xNzEwOCwibm9uY2UiOiJjOGM3Nzg0Yi1hN2MyLTExZWQtOGUwMS0wYTE2Mjg5NTg1NjAiLCJzdWIiOiJkaWQ6ZWJzaTp6b3hSR1ZaUW5kVGZRazU0Qjd0S2R3d05kaGFpNWdtOUY4TmF2OGVjZU5BQmEiLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJjcmVkZW50aWFsU2NoZW1hIjp7ImlkIjoiaHR0cHM6Ly9hcGkucHJlcHJvZC5lYnNpLmV1L3RydXN0ZWQtc2NoZW1hcy1yZWdpc3RyeS92MS9zY2hlbWFzLzB4YmY3OGZjMDhhN2E5ZjI4ZjU0NzlmNThkZWEyNjlkMzY1N2Y1NGYxM2NhMzdkMzgwY2Q0ZTkyMjM3ZmI2OTFkZCIsInR5cGUiOiJKc29uU2NoZW1hVmFsaWRhdG9yMjAxOCJ9LCJjcmVkZW50aWFsU3RhdHVzIjp7ImlkIjoiaHR0cHM6Ly9lc3NpZi5ldXJvcGEuZXUvc3RhdHVzL2VkdWNhdGlvbiNoaWdoZXJFZHVjYXRpb24jMzkyYWM3ZjYtMzk5YS00MzdiLWEyNjgtNDY5MWVhZDhmMTc2IiwidHlwZSI6IkNyZWRlbnRpYWxTdGF0dXNMaXN0MjAyMCJ9LCJjcmVkZW50aWFsU3ViamVjdCI6eyJhd2FyZGluZ09wcG9ydHVuaXR5Ijp7ImF3YXJkaW5nQm9keSI6eyJlaWRhc0xlZ2FsSWRlbnRpZmllciI6IlVua25vd24iLCJob21lcGFnZSI6Imh0dHBzOi8vbGVhc3Rvbi5iY2RpcGxvbWEuY29tLyIsImlkIjoiZGlkOmVic2k6emRSdnZLYlhoVlZCc1hoYXRqdWlCaHMiLCJwcmVmZXJyZWROYW1lIjoiTGVhc3RvbiBVbml2ZXJzaXR5IiwicmVnaXN0cmF0aW9uIjoiMDU5NzA2NUoifSwiZW5kZWRBdFRpbWUiOiIyMDIwLTA2LTI2VDAwOjAwOjAwWiIsImlkIjoiaHR0cHM6Ly9sZWFzdG9uLmJjZGlwbG9tYS5jb20vbGF3LWVjb25vbWljcy1tYW5hZ2VtZW50I0F3YXJkaW5nT3Bwb3J0dW5pdHkiLCJpZGVudGlmaWVyIjoiaHR0cHM6Ly9jZXJ0aWZpY2F0ZS1kZW1vLmJjZGlwbG9tYS5jb20vY2hlY2svODdFRDJGMjI3MEU2QzQxNDU2RTk0Qjg2QjlEOTExNUI0RTM1QkNDQUQyMDBBNDlCODQ2NTkyQzE0Rjc5Qzg2QlYxRm5ibGx0YTBOWlRuSmtSM2xEV2xSbVREbFNSVUpFVkZaSVNtTm1ZekpoVVU1c1pVSjVaMkZKU0hwV2JtWloiLCJsb2NhdGlvbiI6IkZSQU5DRSIsInN0YXJ0ZWRBdFRpbWUiOiIyMDE5LTA5LTAyVDAwOjAwOjAwWiJ9LCJkYXRlT2ZCaXJ0aCI6IjE5OTMtMDQtMDgiLCJmYW1pbHlOYW1lIjoiRE9FIiwiZ2l2ZW5OYW1lcyI6IkphbmUiLCJncmFkaW5nU2NoZW1lIjp7ImlkIjoiaHR0cHM6Ly9sZWFzdG9uLmJjZGlwbG9tYS5jb20vbGF3LWVjb25vbWljcy1tYW5hZ2VtZW50I0dyYWRpbmdTY2hlbWUiLCJ0aXRsZSI6IjIgeWVhciBmdWxsLXRpbWUgcHJvZ3JhbW1lIC8gNCBzZW1lc3RlcnMifSwiaWQiOiJkaWQ6ZWJzaTp6b3hSR1ZaUW5kVGZRazU0Qjd0S2R3d05kaGFpNWdtOUY4TmF2OGVjZU5BQmEiLCJpZGVudGlmaWVyIjoiMDkwNDAwODA4NEgiLCJsZWFybmluZ0FjaGlldmVtZW50Ijp7ImFkZGl0aW9uYWxOb3RlIjpbIkRJU1RSSUJVVElPTiBNQU5BR0VNRU5UIl0sImRlc2NyaXB0aW9uIjoiVGhlIE1hc3RlciBpbiBJbmZvcm1hdGlvbiBhbmQgQ29tcHV0ZXIgU2NpZW5jZXMgKE1JQ1MpIGF0IHRoZSBVbml2ZXJzaXR5IG9mIEx1eGVtYm91cmcgZW5hYmxlcyBzdHVkZW50cyB0byBhY3F1aXJlIGRlZXBlciBrbm93bGVkZ2UgaW4gY29tcHV0ZXIgc2NpZW5jZSBieSB1bmRlcnN0YW5kaW5nIGl0cyBhYnN0cmFjdCBhbmQgaW50ZXJkaXNjaXBsaW5hcnkgZm91bmRhdGlvbnMsIGZvY3VzaW5nIG9uIHByb2JsZW0gc29sdmluZyBhbmQgZGV2ZWxvcGluZyBsaWZlbG9uZyBsZWFybmluZyBza2lsbHMuIiwiaWQiOiJodHRwczovL2xlYXN0b24uYmNkaXBsb21hLmNvbS9sYXctZWNvbm9taWNzLW1hbmFnZW1lbnQjTGVhcm5pbmdBY2hpZXZtZW50IiwidGl0bGUiOiJNYXN0ZXIgaW4gSW5mb3JtYXRpb24gYW5kIENvbXB1dGVyIFNjaWVuY2VzIn0sImxlYXJuaW5nU3BlY2lmaWNhdGlvbiI6eyJlY3RzQ3JlZGl0UG9pbnRzIjoxMjAsImVxZkxldmVsIjo3LCJpZCI6Imh0dHBzOi8vbGVhc3Rvbi5iY2RpcGxvbWEuY29tL2xhdy1lY29ub21pY3MtbWFuYWdlbWVudCNMZWFybmluZ1NwZWNpZmljYXRpb24iLCJpc2NlZGZDb2RlIjpbIjciXSwibnFmTGV2ZWwiOlsiNyJdfX0sImV2aWRlbmNlIjp7ImRvY3VtZW50UHJlc2VuY2UiOlsiUGh5c2ljYWwiXSwiZXZpZGVuY2VEb2N1bWVudCI6WyJQYXNzcG9ydCJdLCJpZCI6Imh0dHBzOi8vZXNzaWYuZXVyb3BhLmV1L3Rzci12YS9ldmlkZW5jZS9mMmFlZWM5Ny1mYzBkLTQyYmYtOGNhNy0wNTQ4MTkyZDU2NzgiLCJzdWJqZWN0UHJlc2VuY2UiOiJQaHlzaWNhbCIsInR5cGUiOlsiRG9jdW1lbnRWZXJpZmljYXRpb24iXSwidmVyaWZpZXIiOiJkaWQ6ZWJzaToyOTYyZmI3ODRkZjYxYmFhMjY3YzgxMzI0OTc1MzlmOGM2NzRiMzdjMTI0NGE3YSJ9LCJpZCI6InVybjp1dWlkOjZiMWQ4NDExLTllZDUtNDU2Ni05YzdmLTRjMjQxNjVmZjIzNiIsImlzc3VhbmNlRGF0ZSI6IjIwMjMtMDItMDhUMTU6MTA6NTZaIiwiaXNzdWVkIjoiMjAyMy0wMi0wOFQxNToxMDo1NloiLCJpc3N1ZXIiOiJkaWQ6ZWJzaTp6aFN3NXJQWGtjSGp2cXV3blZjVHp6QiIsInByb29mIjp7ImNyZWF0ZWQiOiIyMDIyLTA0LTI3VDEyOjI1OjA3WiIsImNyZWF0b3IiOiJkaWQ6ZWJzaTp6ZFJ2dktiWGhWVkJzWGhhdGp1aUJocyIsImRvbWFpbiI6Imh0dHBzOi8vYXBpLnByZXByb2QuZWJzaS5ldSIsImp3cyI6ImV5SmlOalFpT21aaGJITmxMQ0pqY21sMElqcGJJbUkyTkNKZExDSmhiR2NpT2lKRlV6STFOa3NpZlEuLm1JQm5NOFhEUXFTWUtRTlhfTHZhSmhtc2J5Q3I1T1o1Y1UyWmstUmVxTHByNGRvRnNnbW9vYmtPNTEyOHRaeS04S2ltVmpKa0d3MHdMMXVCV25NTFdRIiwibm9uY2UiOiIzZWE2OGRhZS1kMDdhLTRkYWEtOTMyYi1mYmI1OGY1YzIwYzQiLCJ0eXBlIjoiRWNkc2FTZWNwMjU2azFTaWduYXR1cmUyMDE5In0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJWZXJpZmlhYmxlQXR0ZXN0YXRpb24iLCJWZXJpZmlhYmxlRGlwbG9tYSJdLCJ2YWxpZEZyb20iOiIyMDIzLTAyLTA4VDE1OjEwOjU2WiJ9fQ.avhpj0UC7SJiOo8PWJV3zhJoxngdmrBnyTkQsnqe76JlOmatLkgAY5Mp_sUJQnN1uENiT-_KBf0KY3izx4X_SA', + + { + '@context': ['https://www.w3.org/2018/credentials/v1'], + 'credentialSchema': { + 'id': + 'https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd', + 'type': 'JsonSchemaValidator2018' + }, + 'credentialStatus': { + 'id': + 'https://essif.europa.eu/status/education#higherEducation#392ac7f6-399a-437b-a268-4691ead8f176', + 'type': 'CredentialStatusList2020' + }, + 'credentialSubject': { + 'awardingOpportunity': { + 'awardingBody': { + 'eidasLegalIdentifier': 'Unknown', + 'homepage': 'https://leaston.bcdiploma.com/', + 'id': 'did:ebsi:zdRvvKbXhVVBsXhatjuiBhs', + 'preferredName': 'Leaston University', + 'registration': '0597065J' + }, + 'endedAtTime': '2020-06-26T00:00:00Z', + 'id': + 'https://leaston.bcdiploma.com/law-economics-management#AwardingOpportunity', + 'identifier': + 'https://certificate-demo.bcdiploma.com/check/87ED2F2270E6C41456E94B86B9D9115B4E35BCCAD200A49B846592C14F79C86BV1Fnbllta0NZTnJkR3lDWlRmTDlSRUJEVFZISmNmYzJhUU5sZUJ5Z2FJSHpWbmZZ', + 'location': 'FRANCE', + 'startedAtTime': '2019-09-02T00:00:00Z' + }, + 'dateOfBirth': '1993-04-08', + 'familyName': 'DOE', + 'givenNames': 'Jane', + 'gradingScheme': { + 'id': + 'https://leaston.bcdiploma.com/law-economics-management#GradingScheme', + 'title': '2 year full-time programme / 4 semesters' + }, + 'id': 'did:ebsi:zoxRGVZQndTfQk54B7tKdwwNdhai5gm9F8Nav8eceNABa', + 'identifier': '0904008084H', + 'learningAchievement': { + 'additionalNote': ['DISTRIBUTION MANAGEMENT'], + 'description': + 'The Master in Information and Computer Sciences (MICS) at the University of Luxembourg enables students to acquire deeper knowledge in computer science by understanding its abstract and interdisciplinary foundations, focusing on problem solving and developing lifelong learning skills.', + 'id': + 'https://leaston.bcdiploma.com/law-economics-management#LearningAchievment', + 'title': 'Master in Information and Computer Sciences' + }, + 'learningSpecification': { + 'ectsCreditPoints': 120, + 'eqfLevel': 7, + 'id': + 'https://leaston.bcdiploma.com/law-economics-management#LearningSpecification', + 'iscedfCode': ['7'], + 'nqfLevel': ['7'] + } + }, + 'evidence': { + 'documentPresence': ['Physical'], + 'evidenceDocument': ['Passport'], + 'id': + 'https://essif.europa.eu/tsr-va/evidence/f2aeec97-fc0d-42bf-8ca7-0548192d5678', + 'subjectPresence': 'Physical', + 'type': ['DocumentVerification'], + 'verifier': 'did:ebsi:2962fb784df61baa267c8132497539f8c674b37c1244a7a' + }, + 'id': 'urn:uuid:6b1d8411-9ed5-4566-9c7f-4c24165ff236', + 'issuanceDate': '2023-02-08T15:10:56Z', + 'issued': '2023-02-08T15:10:56Z', + 'issuer': 'did:ebsi:zhSw5rPXkcHjvquwnVcTzzB', + 'proof': { + 'created': '2022-04-27T12:25:07Z', + 'creator': 'did:ebsi:zdRvvKbXhVVBsXhatjuiBhs', + 'domain': 'https://api.preprod.ebsi.eu', + 'jws': + 'eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzI1NksifQ..mIBnM8XDQqSYKQNX_LvaJhmsbyCr5OZ5cU2Zk-ReqLpr4doFsgmoobkO5128tZy-8KimVjJkGw0wL1uBWnMLWQ', + 'nonce': '3ea68dae-d07a-4daa-932b-fbb58f5c20c4', + 'type': 'EcdsaSecp256k1Signature2019' + }, + 'type': [ + 'VerifiableCredential', + 'VerifiableAttestation', + 'VerifiableDiploma' + ], + 'validFrom': '2023-02-08T15:10:56Z' + } ]; test('nonce', () { From c2e826019698a38a5e684086dca37a1bb9a26037 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Wed, 22 Feb 2023 15:00:11 +0530 Subject: [PATCH 061/190] linter update --- packages/ebsi/test/src/ebsi_test.dart | 6 +++--- .../verifier_token_parameters_test.dart | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/ebsi/test/src/ebsi_test.dart b/packages/ebsi/test/src/ebsi_test.dart index f2b65de34..05bcacaba 100644 --- a/packages/ebsi/test/src/ebsi_test.dart +++ b/packages/ebsi/test/src/ebsi_test.dart @@ -408,7 +408,7 @@ void main() { const issuerDid = 'did:ebsi:zeFCExU2XAAshYkPCpjuahA'; const expectedPublicKey = - '{"alg":"EdDSA","crv":"Ed25519","kid":"3623b877bbb24b08ba390f3585418f53","kty":"OKP","use":"sig","x":"pWgA8M3etXlLaqcRmgjEQkz7waseg3FKzMCzfm9Yeow"}'; + '{"alg":"EdDSA","crv":"Ed25519","kid":"3623b877bbb24b08ba390f3585418f53","kty":"OKP","use":"sig","x":"pWgA8M3etXlLaqcRmgjEQkz7waseg3FKzMCzfm9Yeow"}'; // ignore: lines_longer_than_80_chars final ebsi = Ebsi(client); @@ -465,7 +465,7 @@ void main() { test('returns VerificationType.notVerified', () async { const vcJwt = - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksiLCJqd2siOnsiY3J2IjoiUC0yNTZLIiwia3R5IjoiRUMiLCJ4IjoiSjR2UXRMVXlyVlVpRklYUnJ0RXE0eHVybUJacDJlcTl3Sm1Ya0lBX3N0SSIsInkiOiJFVVU2dlhvRzNCR1gyenp3alhyR0RjcjRFeUREMFZmazNfNWZnNWtTZ0tFIn0sImtpZCI6ImRpZDplYnNpOnpvOUZSMVlmQUtGUDNRNmR2cWh4Y1h4bmZlRGlKRFA5N2ttbnFoeUFVU0FDaiNDZ2NnMXk5eGo5dVdGdzU2UE1jMjlYQmQ5RVJlaXh6dm5mdEJ6OEp3UUZpQiJ9.eyJpc3MiOiJkaWQ6ZWJzaTp6bzlGUjFZZkFLRlAzUTZkdnFoeGNYeG5mZURpSkRQOTdrbW5xaHlBVVNBQ2oiLCJub25jZSI6IjdhMDdkZTBmLWE4NzktMTFlZC04MjJiLTBhMTYyODk1ODU2MCIsImlhdCI6MTY3NzA1MDc0MDEyMzIzNSwiYXVkIjoiaHR0cHM6Ly90YWxhby5jby9zYW5kYm94L2Vic2kvaXNzdWVyL3ZndmdoeWxvemwifQ.htjRCpFWbRwanAyQcAq9XZ4vxCXyFbzaaN3yPbPxWIcKFFzDDcA4QCHTUl-L4vzWq0R3LSgQFXQ9bo5D9uCm4w'; + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksiLCJqd2siOnsiY3J2IjoiUC0yNTZLIiwia3R5IjoiRUMiLCJ4IjoiSjR2UXRMVXlyVlVpRklYUnJ0RXE0eHVybUJacDJlcTl3Sm1Ya0lBX3N0SSIsInkiOiJFVVU2dlhvRzNCR1gyenp3alhyR0RjcjRFeUREMFZmazNfNWZnNWtTZ0tFIn0sImtpZCI6ImRpZDplYnNpOnpvOUZSMVlmQUtGUDNRNmR2cWh4Y1h4bmZlRGlKRFA5N2ttbnFoeUFVU0FDaiNDZ2NnMXk5eGo5dVdGdzU2UE1jMjlYQmQ5RVJlaXh6dm5mdEJ6OEp3UUZpQiJ9.eyJpc3MiOiJkaWQ6ZWJzaTp6bzlGUjFZZkFLRlAzUTZkdnFoeGNYeG5mZURpSkRQOTdrbW5xaHlBVVNBQ2oiLCJub25jZSI6IjdhMDdkZTBmLWE4NzktMTFlZC04MjJiLTBhMTYyODk1ODU2MCIsImlhdCI6MTY3NzA1MDc0MDEyMzIzNSwiYXVkIjoiaHR0cHM6Ly90YWxhby5jby9zYW5kYm94L2Vic2kvaXNzdWVyL3ZndmdoeWxvemwifQ.htjRCpFWbRwanAyQcAq9XZ4vxCXyFbzaaN3yPbPxWIcKFFzDDcA4QCHTUl-L4vzWq0R3LSgQFXQ9bo5D9uCm4w'; // ignore: lines_longer_than_80_chars final isVerified = await ebsi.verifyCredential(issuerDid: issuerDid1, vcJwt: vcJwt); @@ -475,7 +475,7 @@ void main() { test('returns VerificationType.unKnown', () async { const vcJwt = - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksiLCJqd2siOnsiY3J2IjoiUC0yNTZLIiwia3R5IjoiRUMiLCJ4IjoiSjR2UXRMVXlyVlVpRklYUnJ0RXE0eHVybUJacDJlcTl3Sm1Ya0lBX3N0SSIsInkiOiJFVVU2dlhvRzNCR1gyenp3alhyR0RjcjRFeUREMFZmazNfNWZnNWtTZ0tFIn0sImtpZCI6ImRpZDplYnNpOnpvOUZSMVlmQUtGUDNRNmR2cWh4Y1h4bmZlRGlKRFA5N2ttbnFoeUFVU0FDaiNDZ2NnMXk5eGo5dVdGdzU2UE1jMjlYQmQ5RVJlaXh6dm5mdEJ6OEp3UUZpQiJ9.eyJpc3MiOiJkaWQ6ZWJzaTp6bzlGUjFZZkFLRlAzUTZkdnFoeGNYeG5mZURpSkRQOTdrbW5xaHlBVVNBQ2oiLCJub25jZSI6IjdhMDdkZTBmLWE4NzktMTFlZC04MjJiLTBhMTYyODk1ODU2MCIsImlhdCI6MTY3NzA1MDc0MDEyMzIzNSwiYXVkIjoiaHR0cHM6Ly90YWxhby5jby9zYW5kYm94L2Vic2kvaXNzdWVyL3ZndmdoeWxvemwifQ.htjRCpFWbRwanAyQcAq9XZ4vxCXyFbzaaN3yPbPxWIcKFFzDDcA4QCHTUl-L4vzWq0R3LSgQFXQ9bo5D9uCm4w'; + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksiLCJqd2siOnsiY3J2IjoiUC0yNTZLIiwia3R5IjoiRUMiLCJ4IjoiSjR2UXRMVXlyVlVpRklYUnJ0RXE0eHVybUJacDJlcTl3Sm1Ya0lBX3N0SSIsInkiOiJFVVU2dlhvRzNCR1gyenp3alhyR0RjcjRFeUREMFZmazNfNWZnNWtTZ0tFIn0sImtpZCI6ImRpZDplYnNpOnpvOUZSMVlmQUtGUDNRNmR2cWh4Y1h4bmZlRGlKRFA5N2ttbnFoeUFVU0FDaiNDZ2NnMXk5eGo5dVdGdzU2UE1jMjlYQmQ5RVJlaXh6dm5mdEJ6OEp3UUZpQiJ9.eyJpc3MiOiJkaWQ6ZWJzaTp6bzlGUjFZZkFLRlAzUTZkdnFoeGNYeG5mZURpSkRQOTdrbW5xaHlBVVNBQ2oiLCJub25jZSI6IjdhMDdkZTBmLWE4NzktMTFlZC04MjJiLTBhMTYyODk1ODU2MCIsImlhdCI6MTY3NzA1MDc0MDEyMzIzNSwiYXVkIjoiaHR0cHM6Ly90YWxhby5jby9zYW5kYm94L2Vic2kvaXNzdWVyL3ZndmdoeWxvemwifQ.htjRCpFWbRwanAyQcAq9XZ4vxCXyFbzaaN3yPbPxWIcKFFzDDcA4QCHTUl-L4vzWq0R3LSgQFXQ9bo5D9uCm4w'; // ignore: lines_longer_than_80_chars final isVerified = await ebsi.verifyCredential(issuerDid: issuerDid2, vcJwt: vcJwt); diff --git a/packages/ebsi/test/src/verifier_token_parameters/verifier_token_parameters_test.dart b/packages/ebsi/test/src/verifier_token_parameters/verifier_token_parameters_test.dart index f9f0a1d6a..e0e6f3a7e 100644 --- a/packages/ebsi/test/src/verifier_token_parameters/verifier_token_parameters_test.dart +++ b/packages/ebsi/test/src/verifier_token_parameters/verifier_token_parameters_test.dart @@ -83,7 +83,7 @@ void main() { 'learningAchievement': { 'additionalNote': ['DISTRIBUTION MANAGEMENT'], 'description': - 'The Master in Information and Computer Sciences (MICS) at the University of Luxembourg enables students to acquire deeper knowledge in computer science by understanding its abstract and interdisciplinary foundations, focusing on problem solving and developing lifelong learning skills.', + 'The Master in Information and Computer Sciences (MICS) at the University of Luxembourg enables students to acquire deeper knowledge in computer science by understanding its abstract and interdisciplinary foundations, focusing on problem solving and developing lifelong learning skills.', // ignore: lines_longer_than_80_chars 'id': 'https://leaston.bcdiploma.com/law-economics-management#LearningAchievment', 'title': 'Master in Information and Computer Sciences' @@ -115,7 +115,7 @@ void main() { 'creator': 'did:ebsi:zdRvvKbXhVVBsXhatjuiBhs', 'domain': 'https://api.preprod.ebsi.eu', 'jws': - 'eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzI1NksifQ..mIBnM8XDQqSYKQNX_LvaJhmsbyCr5OZ5cU2Zk-ReqLpr4doFsgmoobkO5128tZy-8KimVjJkGw0wL1uBWnMLWQ', + 'eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzI1NksifQ..mIBnM8XDQqSYKQNX_LvaJhmsbyCr5OZ5cU2Zk-ReqLpr4doFsgmoobkO5128tZy-8KimVjJkGw0wL1uBWnMLWQ', // ignore: lines_longer_than_80_chars 'nonce': '3ea68dae-d07a-4daa-932b-fbb58f5c20c4', 'type': 'EcdsaSecp256k1Signature2019' }, From 76b147e941dc596d92a19a3c192bcd9094590311 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Wed, 22 Feb 2023 17:07:22 +0330 Subject: [PATCH 062/190] show unread message badge --- .../dashboard/widgets/bottom_bar_item.dart | 2 +- .../drawer/drawer/view/reset_wallet_menu.dart | 2 - .../live_chat/cubit/live_chat_cubit.dart | 51 +++++++++++++++---- .../drawer/live_chat/view/live_chat_page.dart | 10 ++-- 4 files changed, 47 insertions(+), 18 deletions(-) diff --git a/lib/dashboard/dashboard/widgets/bottom_bar_item.dart b/lib/dashboard/dashboard/widgets/bottom_bar_item.dart index 3abb2a591..f833bff30 100644 --- a/lib/dashboard/dashboard/widgets/bottom_bar_item.dart +++ b/lib/dashboard/dashboard/widgets/bottom_bar_item.dart @@ -1,6 +1,6 @@ import 'package:altme/theme/theme.dart'; -import 'package:flutter/material.dart'; import 'package:badges/badges.dart' as badges; +import 'package:flutter/material.dart'; class BottomBarItem extends StatelessWidget { const BottomBarItem({ diff --git a/lib/dashboard/drawer/drawer/view/reset_wallet_menu.dart b/lib/dashboard/drawer/drawer/view/reset_wallet_menu.dart index 8fe5b907b..049a7c21a 100644 --- a/lib/dashboard/drawer/drawer/view/reset_wallet_menu.dart +++ b/lib/dashboard/drawer/drawer/view/reset_wallet_menu.dart @@ -110,14 +110,12 @@ class ResetWalletView extends StatelessWidget { if (pinCode?.isEmpty ?? true) { await context.read().resetWallet(); await context.read().dispose(); - await context.read().init(); } else { await Navigator.of(context).push( PinCodePage.route( isValidCallback: () { context.read().resetWallet(); context.read().dispose(); - context.read().init(); }, restrictToBack: false, ), diff --git a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart index e28c21638..655cf2fd1 100644 --- a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart +++ b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart @@ -42,10 +42,12 @@ class LiveChatCubit extends Cubit { String _roomId = ''; StreamSubscription? _onEventSubscription; final DIDCubit didCubit; - final _notificationStreamController = StreamController(); + StreamController? _notificationStreamController; - Stream get unreadMessageCountStream => - _notificationStreamController.stream; + Stream get unreadMessageCountStream { + _notificationStreamController ??= StreamController(); + return _notificationStreamController!.stream; + } Future onSendPressed(PartialText partialText) async { try { @@ -249,13 +251,13 @@ class LiveChatCubit extends Cubit { emit(state.copyWith(messages: newMessages)); } else { final Message message = _mapEventToMessage(event); + _getUnreadMessageCount(); emit( state.copyWith( messages: [message, ...state.messages], ), ); } - _getUnreadMessageCount(); } }); } @@ -330,23 +332,50 @@ class LiveChatCubit extends Cubit { return message; } + int get unreadMessageCount => + client.getRoomById(_roomId)?.notificationCount ?? 0; + void _getUnreadMessageCount() { - final unreadCount = client.getRoomById(_roomId)?.notificationCount ?? 0; - _notificationStreamController.sink.add(unreadCount); + final unreadCount = unreadMessageCount; + logger.i('unread message count: $unreadCount'); + _notificationStreamController?.sink.add(unreadCount); } Future markMessageAsRead(List? eventIds) async { - if (eventIds == null) return; + if (eventIds == null || eventIds.isEmpty) return; + + final room = client.getRoomById(_roomId); + if (room == null) return; try { for (final eventId in eventIds) { if (eventId != null) { - await client.getRoomById(_roomId)?.setReadMarker(eventId); + await room.postReceipt(eventId); } } - _getUnreadMessageCount(); } catch (e, s) { logger.e('e: $e , s: $s'); } + _getUnreadMessageCount(); + } + + // this function called when state emited in UI and needs + // to set messages as read + void setMessagesAsRead() { + try { + logger.i('setMessagesAsRead'); + if (unreadMessageCount > 0) { + final unreadMessageEventIds = state.messages + .take(unreadMessageCount) + .map((e) => e.remoteId!) + .toList(); + logger.i( + 'unread message event ids lenght: ${unreadMessageEventIds.length}', + ); + markMessageAsRead(unreadMessageEventIds); + } + } catch (e, s) { + logger.e('e: $e, s: $s'); + } } Future> _retriveMessagesFromDB(String roomId) async { @@ -401,6 +430,7 @@ class LiveChatCubit extends Cubit { await client.init( newHomeserver: Uri.parse(Urls.matrixHomeServer), ); + _notificationStreamController = StreamController(); } Future _getDidAuth(String did, String nonce) async { @@ -493,7 +523,8 @@ class LiveChatCubit extends Cubit { Future dispose() async { await client.logout(); await client.dispose(); - await _notificationStreamController.close(); + await _notificationStreamController?.close(); + _notificationStreamController = null; await _onEventSubscription?.cancel(); _onEventSubscription = null; } diff --git a/lib/dashboard/drawer/live_chat/view/live_chat_page.dart b/lib/dashboard/drawer/live_chat/view/live_chat_page.dart index a5488ec42..5dfbc8152 100644 --- a/lib/dashboard/drawer/live_chat/view/live_chat_page.dart +++ b/lib/dashboard/drawer/live_chat/view/live_chat_page.dart @@ -45,6 +45,7 @@ class LiveChatView extends StatefulWidget { class _ContactUsViewState extends State { late final LiveChatCubit liveChatCubit; + @override void initState() { liveChatCubit = context.read(); @@ -65,11 +66,7 @@ class _ContactUsViewState extends State { titleLeading: widget.hideAppBar ? null : const BackLeadingButton(), titleAlignment: Alignment.topCenter, padding: const EdgeInsets.all(Sizes.spaceSmall), - body: BlocConsumer( - listener: (context, state) { - final eventIds = state.messages.map((e) => e.remoteId).toList(); - liveChatCubit.markMessageAsRead(eventIds); - }, + body: BlocBuilder( builder: (context, state) { if (state.status == AppStatus.loading) { return const Center( @@ -80,6 +77,9 @@ class _ContactUsViewState extends State { child: Text(l10n.somethingsWentWrongTryAgainLater), ); } else { + if (context.read().state.selectedIndex == 3) { + liveChatCubit.setMessagesAsRead(); + } return Stack( alignment: Alignment.topCenter, children: [ From 1ce6933f64e1a2217e912f30637d5612fbe628ca Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Wed, 22 Feb 2023 19:55:15 +0530 Subject: [PATCH 063/190] url update and version update --- lib/app/shared/constants/parameters.dart | 3 +-- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/app/shared/constants/parameters.dart b/lib/app/shared/constants/parameters.dart index 8534adbec..0723a3a2b 100644 --- a/lib/app/shared/constants/parameters.dart +++ b/lib/app/shared/constants/parameters.dart @@ -27,8 +27,7 @@ class Parameters { isPassEnabled: true, ); - static const ebsiUniversalLink = - 'https://app.altme.io/app/download/credential'; + static const ebsiUniversalLink = 'https://app.altme.io/app/download'; static const web3RpcMainnetUrl = 'https://mainnet.infura.io/v3/'; } diff --git a/pubspec.yaml b/pubspec.yaml index 9ef0b98dc..e05ba8c0e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: altme description: AltMe Flutter App -version: 1.10.1+152 +version: 1.10.2+153 publish_to: none environment: From 13148667440f34446b1b977a3f2aae6d43ff510a Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Wed, 22 Feb 2023 17:56:22 +0330 Subject: [PATCH 064/190] fix minor bugs --- .../dashboard/view/dashboard_page.dart | 3 + .../live_chat/cubit/live_chat_cubit.dart | 2 + .../drawer/live_chat/view/live_chat_page.dart | 109 ++++++++++-------- .../view/onboarding_gen_phrase.dart | 3 +- pubspec.lock | 4 +- pubspec.yaml | 1 + 6 files changed, 72 insertions(+), 50 deletions(-) diff --git a/lib/dashboard/dashboard/view/dashboard_page.dart b/lib/dashboard/dashboard/view/dashboard_page.dart index a89ec6e41..8ac6ceff2 100644 --- a/lib/dashboard/dashboard/view/dashboard_page.dart +++ b/lib/dashboard/dashboard/view/dashboard_page.dart @@ -165,6 +165,9 @@ class _DashboardViewState extends State { ], child: BlocBuilder( builder: (context, state) { + if (state.selectedIndex == 3) { + context.read().setMessagesAsRead(); + } return WillPopScope( onWillPop: () async { if (scaffoldKey.currentState!.isDrawerOpen) { diff --git a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart index 01cb27554..de304beb1 100644 --- a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart +++ b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart @@ -190,6 +190,8 @@ class LiveChatCubit extends Cubit { Future init() async { try { + final ssiKey = await secureStorageProvider.get(SecureStorageKeys.ssiKey); + if (ssiKey == null) return; emit(state.copyWith(status: AppStatus.loading)); await _initClient(); final username = didCubit.state.did!.replaceAll(':', '-'); diff --git a/lib/dashboard/drawer/live_chat/view/live_chat_page.dart b/lib/dashboard/drawer/live_chat/view/live_chat_page.dart index 5dfbc8152..bc97ec73c 100644 --- a/lib/dashboard/drawer/live_chat/view/live_chat_page.dart +++ b/lib/dashboard/drawer/live_chat/view/live_chat_page.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_chat_types/flutter_chat_types.dart'; import 'package:flutter_chat_ui/flutter_chat_ui.dart' hide FileMessage, Message; +import 'package:visibility_detector/visibility_detector.dart'; class LiveChatPage extends StatelessWidget { const LiveChatPage({ @@ -46,6 +47,8 @@ class LiveChatView extends StatefulWidget { class _ContactUsViewState extends State { late final LiveChatCubit liveChatCubit; + bool pageIsVisible = false; + @override void initState() { liveChatCubit = context.read(); @@ -77,56 +80,68 @@ class _ContactUsViewState extends State { child: Text(l10n.somethingsWentWrongTryAgainLater), ); } else { - if (context.read().state.selectedIndex == 3) { + if (pageIsVisible) { liveChatCubit.setMessagesAsRead(); } - return Stack( - alignment: Alignment.topCenter, - children: [ - Chat( - theme: const DarkChatTheme(), - messages: state.messages, - onSendPressed: liveChatCubit.onSendPressed, - onAttachmentPressed: _handleAttachmentPressed, - onMessageTap: _handleMessageTap, - onPreviewDataFetched: liveChatCubit.handlePreviewDataFetched, - user: state.user ?? const User(id: ''), - ), - if (state.messages.isEmpty) - BackgroundCard( - padding: const EdgeInsets.all(Sizes.spaceSmall), - margin: const EdgeInsets.all(Sizes.spaceNormal), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - l10n.supportChatWelcomeMessage, - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.bodyMedium, - ), - const SizedBox( - height: Sizes.spaceSmall, - ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon( - Icons.lock, - size: Sizes.icon, - ), - const SizedBox( - width: Sizes.space2XSmall, - ), - Text( - l10n.e2eEncyptedChat, - style: Theme.of(context).textTheme.subtitle4, - ), - ], - ) - ], - ), + return VisibilityDetector( + key: const Key('chat-widget'), + onVisibilityChanged: (visibilityInfo) { + if (visibilityInfo.visibleFraction == 1.0) { + liveChatCubit.setMessagesAsRead(); + pageIsVisible = true; + } else { + pageIsVisible = false; + } + }, + child: Stack( + alignment: Alignment.topCenter, + children: [ + Chat( + theme: const DarkChatTheme(), + messages: state.messages, + onSendPressed: liveChatCubit.onSendPressed, + onAttachmentPressed: _handleAttachmentPressed, + onMessageTap: _handleMessageTap, + onPreviewDataFetched: + liveChatCubit.handlePreviewDataFetched, + user: state.user ?? const User(id: ''), ), - ], + if (state.messages.isEmpty) + BackgroundCard( + padding: const EdgeInsets.all(Sizes.spaceSmall), + margin: const EdgeInsets.all(Sizes.spaceNormal), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + l10n.supportChatWelcomeMessage, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodyMedium, + ), + const SizedBox( + height: Sizes.spaceSmall, + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.lock, + size: Sizes.icon, + ), + const SizedBox( + width: Sizes.space2XSmall, + ), + Text( + l10n.e2eEncyptedChat, + style: Theme.of(context).textTheme.subtitle4, + ), + ], + ) + ], + ), + ), + ], + ), ); } }, diff --git a/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart b/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart index a0a51ed45..57728e926 100644 --- a/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart +++ b/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart @@ -51,7 +51,8 @@ class _OnBoardingGenPhraseViewState extends State { void initState() { super.initState(); mnemonic = bip39.generateMnemonic().split(' '); - Future.microtask(() => context.read().initialize()); + Future.microtask(() => context.read().initialize()) + .catchError((_) => null); } @override diff --git a/pubspec.lock b/pubspec.lock index f54b64a6f..8f4f97398 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2278,7 +2278,7 @@ packages: source: hosted version: "4.0.0+1" visibility_detector: - dependency: transitive + dependency: "direct main" description: name: visibility_detector sha256: "15c54a459ec2c17b4705450483f3d5a2858e733aee893dcee9d75fd04814940d" @@ -2431,5 +2431,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.19.0 <3.0.0" + dart: ">=2.19.0 <3.7.0" flutter: ">=3.7.0-0" diff --git a/pubspec.yaml b/pubspec.yaml index 42b88c430..790dc879c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -105,6 +105,7 @@ dependencies: url_launcher: ^6.1.9 uuid: ^3.0.7 #wallet_connect: ^1.0.4 + visibility_detector: ^0.3.3 wallet_connect: git: url: https://github.com/bibash28/wallet-connect-dart.git From 17800858d393fc597f7cd5aaa2dde0ea8945b8ce Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Thu, 23 Feb 2023 11:45:15 +0530 Subject: [PATCH 065/190] ebsi exporting jwt #1345 --- .../detail/view/credentials_details_page.dart | 163 ++++++++++-------- pubspec.lock | 2 +- 2 files changed, 93 insertions(+), 72 deletions(-) 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 f3b06b883..9e48068ca 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 @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:math'; import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; @@ -159,77 +160,6 @@ class _CredentialsDetailsViewState extends State { // ), // ), padding: const EdgeInsets.symmetric(horizontal: 10), - navigation: widget.readOnly - ? null - : SafeArea( - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 24, - vertical: 5, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - MyOutlinedButton( - onPressed: disAllowDelete ? null : delete, - text: l10n.credentialDetailDeleteCard, - ), - const SizedBox(height: 8), - MyOutlinedButton( - text: isLinkeInCard - ? l10n.exportToLinkedIn - : l10n.share, - onPressed: () { - if (isLinkeInCard) { - Navigator.of(context).push( - GetLinkedinInfoPage.route( - credentialModel: widget.credentialModel, - ), - ); - } else { - if (isEUDiplomaCard) { - /// removing type that was added in add_ebsi_credential.dart // ignore: lines_longer_than_80_chars - widget.credentialModel.data['credentialSubject'] - .remove('type'); - } - - final box = - context.findRenderObject() as RenderBox?; - final subject = l10n.shareWith; - - Share.share( - jsonEncode(widget.credentialModel.data), - subject: subject, - sharePositionOrigin: - box!.localToGlobal(Offset.zero) & box.size, - ); - } - }, - ), - if (widget.credentialModel.shareLink != '') - MyOutlinedButton.icon( - icon: SvgPicture.asset( - IconStrings.qrCode, - width: 24, - height: 24, - color: Theme.of(context).colorScheme.onPrimary, - ), - onPressed: () { - Navigator.of(context).push( - QrCodeDisplayPage.route( - title: '', - data: widget.credentialModel.shareLink, - ), - ); - }, - text: l10n.credentialDetailShare, - ) - else - Container(), - ], - ), - ), - ), scrollView: false, body: Column( children: [ @@ -322,6 +252,97 @@ class _CredentialsDetailsViewState extends State { ), ], ), + + navigation: widget.readOnly + ? null + : SafeArea( + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 24, + vertical: 5, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + MyOutlinedButton( + onPressed: disAllowDelete ? null : delete, + text: l10n.credentialDetailDeleteCard, + ), + const SizedBox(height: 8), + MyOutlinedButton( + text: isLinkeInCard + ? l10n.exportToLinkedIn + : l10n.share, + onPressed: () { + if (isLinkeInCard) { + Navigator.of(context).push( + GetLinkedinInfoPage.route( + credentialModel: widget.credentialModel, + ), + ); + } else { + if (isEUDiplomaCard) { + /// removing type that was added in add_ebsi_credential.dart // ignore: lines_longer_than_80_chars + widget.credentialModel.data['credentialSubject'] + .remove('type'); + } + + late String data; + + if (isVerifiableDiplomaType( + widget.credentialModel, + )) { + final String? jwt = widget.credentialModel.jwt; + if (jwt != null) { + data = jwt; + } else { + data = + jsonEncode(widget.credentialModel.data); + } + } else { + data = jsonEncode(widget.credentialModel.data); + } + + getLogger('CredentialDetailsPage - shared date') + .i(data); + + final box = + context.findRenderObject() as RenderBox?; + final subject = l10n.shareWith; + + Share.share( + data!, + subject: subject, + sharePositionOrigin: + box!.localToGlobal(Offset.zero) & box.size, + ); + } + }, + ), + if (widget.credentialModel.shareLink != '') + MyOutlinedButton.icon( + icon: SvgPicture.asset( + IconStrings.qrCode, + width: 24, + height: 24, + color: Theme.of(context).colorScheme.onPrimary, + ), + onPressed: () { + Navigator.of(context).push( + QrCodeDisplayPage.route( + title: '', + data: widget.credentialModel.shareLink, + ), + ); + }, + text: l10n.credentialDetailShare, + ) + else + Container(), + ], + ), + ), + ), ); }, ); diff --git a/pubspec.lock b/pubspec.lock index 8f4f97398..5e2029553 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2431,5 +2431,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.19.0 <3.7.0" + dart: ">=2.19.0 <3.0.0" flutter: ">=3.7.0-0" From 1382337b2f9b1052f83bcf8af030c95ec25cbfe0 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Thu, 23 Feb 2023 11:49:06 +0530 Subject: [PATCH 066/190] linter update --- .../credentials/detail/view/credentials_details_page.dart | 4 +--- .../view/credential_manifest_credential_offer_pick_page.dart | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) 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 9e48068ca..4eb97bd25 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 @@ -1,6 +1,4 @@ import 'dart:convert'; -import 'dart:math'; - import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/dashboard/home/tab_bar/credentials/models/activity/activity.dart'; @@ -311,7 +309,7 @@ class _CredentialsDetailsViewState extends State { final subject = l10n.shareWith; Share.share( - data!, + data, subject: subject, sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size, 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 4c542666d..28b0931a7 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 @@ -170,9 +170,7 @@ class CredentialManifestOfferPickView extends StatelessWidget { presentationDefinition .inputDescriptors[ inputDescriptorIndex]; - // TODO(all): no sure if I'm correct to - // take first field - //to check optional + final isOptional = inputDescriptor .constraints ?.fields From 5cf33b45c7718dd71197abbe0aa35fe0f0f02128 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Thu, 23 Feb 2023 10:57:45 +0330 Subject: [PATCH 067/190] fix matrix initialization and fix welcom text in help --- .../drawer/live_chat/cubit/live_chat_cubit.dart | 5 ++--- .../drawer/live_chat/view/live_chat_page.dart | 10 +++++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart index de304beb1..df4ac0800 100644 --- a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart +++ b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart @@ -460,9 +460,8 @@ class LiveChatCubit extends Cubit { return db; }, ); - await client.init( - newHomeserver: Uri.parse(Urls.matrixHomeServer), - ); + client.homeserver = Uri.parse(Urls.matrixHomeServer); + await client.init(); _notificationStreamController = StreamController(); } diff --git a/lib/dashboard/drawer/live_chat/view/live_chat_page.dart b/lib/dashboard/drawer/live_chat/view/live_chat_page.dart index bc97ec73c..70708172e 100644 --- a/lib/dashboard/drawer/live_chat/view/live_chat_page.dart +++ b/lib/dashboard/drawer/live_chat/view/live_chat_page.dart @@ -131,9 +131,13 @@ class _ContactUsViewState extends State { const SizedBox( width: Sizes.space2XSmall, ), - Text( - l10n.e2eEncyptedChat, - style: Theme.of(context).textTheme.subtitle4, + Flexible( + child: MyText( + l10n.e2eEncyptedChat, + maxLines: 1, + minFontSize: 8, + style: Theme.of(context).textTheme.subtitle4, + ), ), ], ) From 1ea1741478cea791841c94b0e65aa2c941813cac Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Thu, 23 Feb 2023 12:38:39 +0330 Subject: [PATCH 068/190] improve LiveChat --- lib/app/view/app.dart | 2 +- lib/bootstrap.dart | 2 +- .../dashboard/view/dashboard_page.dart | 25 ++++-- .../live_chat/cubit/live_chat_cubit.dart | 84 +++++++++++++++---- .../live_chat/cubit/live_chat_state.dart | 5 ++ .../drawer/live_chat/view/live_chat_page.dart | 15 +++- .../all_tokens/cubit/all_tokens_cubit.dart | 5 +- 7 files changed, 109 insertions(+), 29 deletions(-) diff --git a/lib/app/view/app.dart b/lib/app/view/app.dart index 159a5ad0d..75002a3bc 100644 --- a/lib/app/view/app.dart +++ b/lib/app/view/app.dart @@ -175,7 +175,7 @@ class App extends StatelessWidget { lazy: false, create: (context) => LiveChatCubit( dioClient: DioClient('', Dio()), - didCubit: context.read(), + didKit: DIDKitProvider(), secureStorageProvider: getSecureStorage, ), ), diff --git a/lib/bootstrap.dart b/lib/bootstrap.dart index 231fe63d4..116605d17 100644 --- a/lib/bootstrap.dart +++ b/lib/bootstrap.dart @@ -91,7 +91,7 @@ class AppBlocObserver extends BlocObserver { @override void onChange(BlocBase bloc, Change change) { super.onChange(bloc, change); - log('onChange(${bloc.runtimeType}, $change)'); + log('onChange(${bloc.runtimeType}, ${change.runtimeType})'); } @override diff --git a/lib/dashboard/dashboard/view/dashboard_page.dart b/lib/dashboard/dashboard/view/dashboard_page.dart index 8ac6ceff2..e16938dd9 100644 --- a/lib/dashboard/dashboard/view/dashboard_page.dart +++ b/lib/dashboard/dashboard/view/dashboard_page.dart @@ -90,6 +90,21 @@ class _DashboardViewState extends State { } } + String _getTitle(int selectedIndex, AppLocalizations l10n) { + switch (selectedIndex) { + case 0: + return l10n.myWallet; + case 1: + return l10n.discover; + case 2: + return Parameters.hasCryptoCallToAction ? l10n.buy : l10n.search; + case 3: + return l10n.help; + default: + return ''; + } + } + @override Widget build(BuildContext context) { final l10n = context.l10n; @@ -177,15 +192,7 @@ class _DashboardViewState extends State { }, child: BasePage( scrollView: false, - title: state.selectedIndex == 0 - ? l10n.myWallet - : state.selectedIndex == 1 - ? l10n.discover - : state.selectedIndex == 2 - ? Parameters.hasCryptoCallToAction - ? l10n.buy - : l10n.search - : '', + title: _getTitle(state.selectedIndex, l10n), scaffoldKey: scaffoldKey, padding: EdgeInsets.zero, drawer: const DrawerPage(), diff --git a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart index df4ac0800..7dcec5667 100644 --- a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart +++ b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart @@ -3,9 +3,9 @@ import 'dart:convert'; import 'dart:io'; import 'package:altme/app/app.dart'; -import 'package:altme/did/cubit/did_cubit.dart'; import 'package:bloc/bloc.dart'; import 'package:crypto/crypto.dart'; +import 'package:did_kit/did_kit.dart'; import 'package:equatable/equatable.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; @@ -27,7 +27,7 @@ part 'live_chat_state.dart'; class LiveChatCubit extends Cubit { LiveChatCubit({ - required this.didCubit, + required this.didKit, required this.secureStorageProvider, required this.dioClient, }) : super( @@ -41,8 +41,8 @@ class LiveChatCubit extends Cubit { final DioClient dioClient; final logger = getLogger('LiveChatCubit'); String _roomId = ''; + final DIDKitProvider didKit; StreamSubscription? _onEventSubscription; - final DIDCubit didCubit; StreamController? _notificationStreamController; Stream get unreadMessageCountStream { @@ -191,15 +191,43 @@ class LiveChatCubit extends Cubit { Future init() async { try { final ssiKey = await secureStorageProvider.get(SecureStorageKeys.ssiKey); - if (ssiKey == null) return; + if (ssiKey == null || ssiKey.isEmpty) { + emit( + state.copyWith( + status: AppStatus.error, + message: StateMessage.error( + messageHandler: ResponseMessage( + ResponseString + .RESPONSE_STRING_SOMETHING_WENT_WRONG_TRY_AGAIN_LATER, + ), + ), + ), + ); + return; + } + final did = await secureStorageProvider.get(SecureStorageKeys.did) ?? ''; + final username = did.replaceAll(':', '-'); + if (username.isEmpty) { + emit( + state.copyWith( + status: AppStatus.error, + message: StateMessage.error( + messageHandler: ResponseMessage( + ResponseString + .RESPONSE_STRING_SOMETHING_WENT_WRONG_TRY_AGAIN_LATER, + ), + ), + ), + ); + return; + } emit(state.copyWith(status: AppStatus.loading)); await _initClient(); - final username = didCubit.state.did!.replaceAll(':', '-'); final isUserRegisteredMatrix = await secureStorageProvider .get(SecureStorageKeys.isUserRegisteredMatrix); late String userId; if (isUserRegisteredMatrix != 'true') { - await _register(username: username); + await _register(did: did); await secureStorageProvider.set( SecureStorageKeys.isUserRegisteredMatrix, true.toString(), @@ -230,7 +258,28 @@ class LiveChatCubit extends Cubit { ); } catch (e, s) { logger.e('error: $e, stack: $s'); - emit(state.copyWith(status: AppStatus.error)); + if (e is MatrixException) { + emit( + state.copyWith( + status: AppStatus.error, + message: StateMessage.error( + stringMessage: e.errorMessage, + ), + ), + ); + } else { + emit( + state.copyWith( + status: AppStatus.error, + message: StateMessage( + messageHandler: ResponseMessage( + ResponseString + .RESPONSE_STRING_SOMETHING_WENT_WRONG_TRY_AGAIN_LATER, + ), + ), + ), + ); + } } } @@ -478,7 +527,7 @@ class LiveChatCubit extends Cubit { final key = (await secureStorageProvider.get(SecureStorageKeys.ssiKey))!; - final String didAuth = await didCubit.didKitProvider.didAuth( + final String didAuth = await didKit.didAuth( did, jsonEncode(options), key, @@ -511,9 +560,8 @@ class LiveChatCubit extends Cubit { } Future _register({ - required String username, + required String did, }) async { - final did = didCubit.state.did!; final nonce = await _getNonce(did); final didAuth = await _getDidAuth(did, nonce); await dotenv.load(); @@ -555,12 +603,16 @@ class LiveChatCubit extends Cubit { } Future dispose() async { - await client.logout(); - await client.dispose(); - await _notificationStreamController?.close(); - _notificationStreamController = null; - await _onEventSubscription?.cancel(); - _onEventSubscription = null; + try { + await client.logout(); + await client.dispose(); + await _notificationStreamController?.close(); + _notificationStreamController = null; + await _onEventSubscription?.cancel(); + _onEventSubscription = null; + } catch (e) { + logger.e('e: $e'); + } } String _getUrlFromUri(String uri) { diff --git a/lib/dashboard/drawer/live_chat/cubit/live_chat_state.dart b/lib/dashboard/drawer/live_chat/cubit/live_chat_state.dart index f4cbed177..2f2741959 100644 --- a/lib/dashboard/drawer/live_chat/cubit/live_chat_state.dart +++ b/lib/dashboard/drawer/live_chat/cubit/live_chat_state.dart @@ -6,6 +6,7 @@ class LiveChatState extends Equatable { this.status = AppStatus.idle, this.messages = const [], this.user, + this.message, }); factory LiveChatState.fromJson(Map json) => @@ -13,6 +14,7 @@ class LiveChatState extends Equatable { final AppStatus status; final List messages; + final StateMessage? message; final User? user; Map toJson() => _$LiveChatStateToJson(this); @@ -21,11 +23,13 @@ class LiveChatState extends Equatable { AppStatus? status, List? messages, User? user, + StateMessage? message, }) { return LiveChatState( status: status ?? this.status, messages: messages ?? this.messages, user: user ?? this.user, + message: message ?? this.message, ); } @@ -34,5 +38,6 @@ class LiveChatState extends Equatable { status, messages, user, + message, ]; } diff --git a/lib/dashboard/drawer/live_chat/view/live_chat_page.dart b/lib/dashboard/drawer/live_chat/view/live_chat_page.dart index 70708172e..230128ee3 100644 --- a/lib/dashboard/drawer/live_chat/view/live_chat_page.dart +++ b/lib/dashboard/drawer/live_chat/view/live_chat_page.dart @@ -76,8 +76,21 @@ class _ContactUsViewState extends State { child: CircularProgressIndicator(), ); } else if (state.status == AppStatus.error) { + String message = ''; + if (state.message != null) { + if (state.message!.stringMessage != null) { + message = state.message!.stringMessage!; + } else if (state.message!.messageHandler != null) { + final MessageHandler messageHandler = + state.message!.messageHandler!; + message = messageHandler.getMessage(context, messageHandler); + } + } return Center( - child: Text(l10n.somethingsWentWrongTryAgainLater), + child: ErrorView( + message: message, + onTap: liveChatCubit.init, + ), ); } else { if (pageIsVisible) { diff --git a/lib/dashboard/home/tab_bar/tokens/all_tokens/cubit/all_tokens_cubit.dart b/lib/dashboard/home/tab_bar/tokens/all_tokens/cubit/all_tokens_cubit.dart index 01d72a79b..d4d957450 100644 --- a/lib/dashboard/home/tab_bar/tokens/all_tokens/cubit/all_tokens_cubit.dart +++ b/lib/dashboard/home/tab_bar/tokens/all_tokens/cubit/all_tokens_cubit.dart @@ -90,7 +90,10 @@ class AllTokensCubit extends Cubit { emit(state.copyWith(selectedContracts: data)); getLogger( 'Tokens cubit', - ).i('returned selectedContracts from storage: $selectedContracts'); + ).i( + 'returned selectedContracts from storage' + ' lenght: ${selectedContracts.length}', + ); return data; } } catch (e, s) { From 0560913e7430cabb7235205f9231a5c824f4eb38 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Thu, 23 Feb 2023 12:44:41 +0330 Subject: [PATCH 069/190] fix minor ui bug --- lib/dashboard/drawer/drawer/view/help_center_menu.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dashboard/drawer/drawer/view/help_center_menu.dart b/lib/dashboard/drawer/drawer/view/help_center_menu.dart index a81aa2fb6..d428c4d04 100644 --- a/lib/dashboard/drawer/drawer/view/help_center_menu.dart +++ b/lib/dashboard/drawer/drawer/view/help_center_menu.dart @@ -73,7 +73,7 @@ class HelpCenterView extends StatelessWidget { textAlign: TextAlign.left, style: Theme.of(context).textTheme.drawerItem, ), - const SizedBox(width: 16), + const Spacer(), Icon( Icons.chevron_right, size: 26, From b029a4ed2b3b1e8a2fdc2f67dca878eeb2536b37 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Thu, 23 Feb 2023 16:27:41 +0330 Subject: [PATCH 070/190] fix bug of app crash when creating or importing new account #1401 --- lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart index 7dcec5667..40abb4519 100644 --- a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart +++ b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart @@ -46,7 +46,7 @@ class LiveChatCubit extends Cubit { StreamController? _notificationStreamController; Stream get unreadMessageCountStream { - _notificationStreamController ??= StreamController(); + _notificationStreamController ??= StreamController.broadcast(); return _notificationStreamController!.stream; } @@ -189,6 +189,7 @@ class LiveChatCubit extends Cubit { } Future init() async { + logger.i('init()'); try { final ssiKey = await secureStorageProvider.get(SecureStorageKeys.ssiKey); if (ssiKey == null || ssiKey.isEmpty) { @@ -511,7 +512,7 @@ class LiveChatCubit extends Cubit { ); client.homeserver = Uri.parse(Urls.matrixHomeServer); await client.init(); - _notificationStreamController = StreamController(); + _notificationStreamController ??= StreamController.broadcast(); } Future _getDidAuth(String did, String nonce) async { From cd990e73c0805f3fe03e62b9556cc88fa5a7244d Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Thu, 23 Feb 2023 16:46:20 +0330 Subject: [PATCH 071/190] check if room encrypted before then don't try to call encryption again --- lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart index 40abb4519..8986dc3c8 100644 --- a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart +++ b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart @@ -487,6 +487,10 @@ class LiveChatCubit extends Cubit { try { final room = client.getRoomById(roomId); if (room == null) return; + if (room.encrypted) { + logger.i('the room with id: ${room.id} encyrpted before!'); + return; + } final verificationResponse = await DeviceKeysList(client.userID!, client).startVerification(); logger.i('verification response: $verificationResponse'); From e26101fead0389569377176c4baa488034f80470 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Fri, 24 Feb 2023 10:38:25 +0530 Subject: [PATCH 072/190] portal issue fixed #1400 --- .../cubit/credential_details_cubit.dart | 3 ++- .../cubit/credential_details_state.dart | 4 ++-- .../widgets/credential_active_status.dart | 20 +++++++++++-------- .../eu_diploma_card_model.dart | 8 ++++---- lib/ebsi/add_ebsi_credential.dart | 6 +++--- 5 files changed, 23 insertions(+), 18 deletions(-) diff --git a/lib/dashboard/home/tab_bar/credentials/detail/cubit/credential_details_cubit.dart b/lib/dashboard/home/tab_bar/credentials/detail/cubit/credential_details_cubit.dart index c24a0b7e8..9455452f9 100644 --- a/lib/dashboard/home/tab_bar/credentials/detail/cubit/credential_details_cubit.dart +++ b/lib/dashboard/home/tab_bar/credentials/detail/cubit/credential_details_cubit.dart @@ -40,6 +40,7 @@ class CredentialDetailsCubit extends Cubit { status: AppStatus.idle, ), ); + return; } } @@ -53,7 +54,7 @@ class CredentialDetailsCubit extends Cubit { item.jwt!, ); - var credentialStatus = CredentialStatus.unknown; + late CredentialStatus credentialStatus; switch (isVerified) { case VerificationType.verified: diff --git a/lib/dashboard/home/tab_bar/credentials/detail/cubit/credential_details_state.dart b/lib/dashboard/home/tab_bar/credentials/detail/cubit/credential_details_state.dart index a193196a0..c8b130cba 100644 --- a/lib/dashboard/home/tab_bar/credentials/detail/cubit/credential_details_state.dart +++ b/lib/dashboard/home/tab_bar/credentials/detail/cubit/credential_details_state.dart @@ -5,7 +5,7 @@ class CredentialDetailsState extends Equatable { const CredentialDetailsState({ this.status = AppStatus.init, this.message, - this.credentialStatus = CredentialStatus.pending, + this.credentialStatus, this.credentialDetailTabStatus = CredentialDetailTabStatus.informations, }); @@ -14,7 +14,7 @@ class CredentialDetailsState extends Equatable { final AppStatus status; final StateMessage? message; - final CredentialStatus credentialStatus; + final CredentialStatus? credentialStatus; final CredentialDetailTabStatus credentialDetailTabStatus; CredentialDetailsState loading() { diff --git a/lib/dashboard/home/tab_bar/credentials/detail/widgets/credential_active_status.dart b/lib/dashboard/home/tab_bar/credentials/detail/widgets/credential_active_status.dart index ff82dfa10..4208e7ac7 100644 --- a/lib/dashboard/home/tab_bar/credentials/detail/widgets/credential_active_status.dart +++ b/lib/dashboard/home/tab_bar/credentials/detail/widgets/credential_active_status.dart @@ -4,9 +4,12 @@ import 'package:altme/theme/theme.dart'; import 'package:flutter/material.dart'; class CredentialActiveStatus extends StatelessWidget { - const CredentialActiveStatus({super.key, required this.credentialStatus}); + const CredentialActiveStatus({ + super.key, + required this.credentialStatus, + }); - final CredentialStatus credentialStatus; + final CredentialStatus? credentialStatus; @override Widget build(BuildContext context) { @@ -26,7 +29,7 @@ class CredentialActiveStatus extends StatelessWidget { .copyWith(color: Theme.of(context).colorScheme.titleColor), ), TextSpan( - text: credentialStatus.message(context), + text: credentialStatus?.message(context) ?? '', style: Theme.of(context) .textTheme .credentialFieldDescription @@ -36,11 +39,12 @@ class CredentialActiveStatus extends StatelessWidget { ), ), const SizedBox(width: 5), - Icon( - credentialStatus.icon, - size: 18, - color: credentialStatus.color(context), - ), + if (credentialStatus != null) + Icon( + credentialStatus!.icon, + size: 18, + color: credentialStatus!.color(context), + ), ], ); } diff --git a/lib/dashboard/home/tab_bar/credentials/models/eu_diploma_card/eu_diploma_card_model.dart b/lib/dashboard/home/tab_bar/credentials/models/eu_diploma_card/eu_diploma_card_model.dart index 7319f1b31..384221d05 100644 --- a/lib/dashboard/home/tab_bar/credentials/models/eu_diploma_card/eu_diploma_card_model.dart +++ b/lib/dashboard/home/tab_bar/credentials/models/eu_diploma_card/eu_diploma_card_model.dart @@ -136,11 +136,11 @@ class LearningSpecification { factory LearningSpecification.fromJson(Map json) => _$LearningSpecificationFromJson(json); - int? ectsCreditPoints; - int? eqfLevel; + dynamic ectsCreditPoints; + dynamic eqfLevel; String id; - List? iscedfCode; - List? nqfLevel; + List? iscedfCode; + List? nqfLevel; Map toJson() => _$LearningSpecificationToJson(this); } diff --git a/lib/ebsi/add_ebsi_credential.dart b/lib/ebsi/add_ebsi_credential.dart index c54b402f7..4cc821ab4 100644 --- a/lib/ebsi/add_ebsi_credential.dart +++ b/lib/ebsi/add_ebsi_credential.dart @@ -45,10 +45,10 @@ Future addEbsiCredential( ).toJson(); } + final newCredentialModel = CredentialModel.fromJson(newCredential); + final credentialModel = CredentialModel.copyWithData( - oldCredentialModel: CredentialModel.fromJson( - newCredential, - ), + oldCredentialModel: newCredentialModel, newData: credentialFromEbsi, activities: [Activity(acquisitionAt: DateTime.now())], ); From dcb1878f0ad55ccc57c1ca53c055fa002c6556f5 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Fri, 24 Feb 2023 14:16:02 +0330 Subject: [PATCH 073/190] support other ethereum blockchain type for sign transaction --- .../repository/connected_dapp_repository.dart | 24 +++++- .../cubit/wallet_connect_cubit.dart | 7 +- .../cubit/confirm_connection_cubit.dart | 6 +- .../connection/rights/cubit/rights_cubit.dart | 77 ++++++++++++++++++- .../cubit/sign_payload_cubit.dart | 4 +- 5 files changed, 107 insertions(+), 11 deletions(-) diff --git a/lib/connection_bridge/repository/connected_dapp_repository.dart b/lib/connection_bridge/repository/connected_dapp_repository.dart index f02ffb19b..0cdd4d1fc 100644 --- a/lib/connection_bridge/repository/connected_dapp_repository.dart +++ b/lib/connection_bridge/repository/connected_dapp_repository.dart @@ -72,9 +72,14 @@ class ConnectedDappRepository { id = savedDappData.peer!.publicKey; break; case BlockchainType.fantom: + id = savedDappData.wcSessionStore!.session.topic; + break; case BlockchainType.polygon: + id = savedDappData.wcSessionStore!.session.topic; + break; case BlockchainType.binance: - throw Exception(); + id = savedDappData.wcSessionStore!.session.topic; + break; } log.i('deleteing dapp data - ${SecureStorageKeys.savedDaaps}/$id'); await _secureStorageProvider.delete('${SecureStorageKeys.savedDaaps}/$id'); @@ -95,9 +100,17 @@ class ConnectedDappRepository { return savedData.walletAddress == savedDappData.walletAddress && savedData.peer!.name == savedDappData.peer!.name; case BlockchainType.fantom: + return savedData.walletAddress == savedDappData.walletAddress && + savedData.wcSessionStore!.remotePeerMeta.name == + savedDappData.wcSessionStore!.remotePeerMeta.name; case BlockchainType.polygon: + return savedData.walletAddress == savedDappData.walletAddress && + savedData.wcSessionStore!.remotePeerMeta.name == + savedDappData.wcSessionStore!.remotePeerMeta.name; case BlockchainType.binance: - throw Exception(); + return savedData.walletAddress == savedDappData.walletAddress && + savedData.wcSessionStore!.remotePeerMeta.name == + savedDappData.wcSessionStore!.remotePeerMeta.name; } }, @@ -118,9 +131,14 @@ class ConnectedDappRepository { id = savedDappData.peer!.publicKey; break; case BlockchainType.fantom: + id = savedDappData.wcSessionStore!.session.topic; + break; case BlockchainType.polygon: + id = savedDappData.wcSessionStore!.session.topic; + break; case BlockchainType.binance: - throw Exception(); + id = savedDappData.wcSessionStore!.session.topic; + break; } await _secureStorageProvider.set( diff --git a/lib/connection_bridge/wallet_connect/cubit/wallet_connect_cubit.dart b/lib/connection_bridge/wallet_connect/cubit/wallet_connect_cubit.dart index 2722610ee..bb97baf35 100644 --- a/lib/connection_bridge/wallet_connect/cubit/wallet_connect_cubit.dart +++ b/lib/connection_bridge/wallet_connect/cubit/wallet_connect_cubit.dart @@ -26,8 +26,13 @@ class WalletConnectCubit extends Cubit { log.i('initialise'); final List savedDapps = await connectedDappRepository.findAll(); + final ethereumConnectedDapps = List.of(savedDapps).where( - (element) => element.blockchainType == BlockchainType.ethereum, + (element) => + element.blockchainType == BlockchainType.ethereum || + element.blockchainType == BlockchainType.binance || + element.blockchainType == BlockchainType.fantom || + element.blockchainType == BlockchainType.polygon, ); final List wcClients = List.empty(growable: true); diff --git a/lib/dashboard/connection/confirm_connection/cubit/confirm_connection_cubit.dart b/lib/dashboard/connection/confirm_connection/cubit/confirm_connection_cubit.dart index 55e4a826f..4884a3106 100644 --- a/lib/dashboard/connection/confirm_connection/cubit/confirm_connection_cubit.dart +++ b/lib/dashboard/connection/confirm_connection/cubit/confirm_connection_cubit.dart @@ -104,7 +104,7 @@ class ConfirmConnectionCubit extends Cubit { final savedDappData = SavedDappData( walletAddress: currentAccount.walletAddress, - blockchainType: BlockchainType.ethereum, + blockchainType: currentAccount.blockchainType, wcSessionStore: WCSessionStore( session: wcClient.session!, peerMeta: wcClient.peerMeta!, @@ -127,8 +127,8 @@ class ConfirmConnectionCubit extends Cubit { ), ), ); - } catch (e) { - log.e('error connecting to $connectionBridgeType , e: $e'); + } catch (e,s) { + log.e('error connecting to $connectionBridgeType , e: $e , s: $s'); if (e is MessageHandler) { emit(state.error(messageHandler: e)); } else { diff --git a/lib/dashboard/connection/rights/cubit/rights_cubit.dart b/lib/dashboard/connection/rights/cubit/rights_cubit.dart index 0c1753d32..c30cdda16 100644 --- a/lib/dashboard/connection/rights/cubit/rights_cubit.dart +++ b/lib/dashboard/connection/rights/cubit/rights_cubit.dart @@ -90,12 +90,83 @@ class RightsCubit extends Cubit { } break; case BlockchainType.fantom: + final walletConnectState = walletConnectCubit.state; + final wcClient = walletConnectState.wcClients.firstWhereOrNull( + (element) => + element.remotePeerId == + walletConnectCubit.state.currentDappPeerId, + ); + if (wcClient != null) { + log.i( + '''disconnected - ${savedDappData.wcSessionStore!.remotePeerMeta}''', + ); + wcClient.disconnect(); + //remove from collection + } + + await connectedDappRepository.delete(savedDappData); + emit( + state.copyWith( + appStatus: AppStatus.success, + messageHandler: ResponseMessage( + ResponseString.RESPONSE_STRING_DISCONNECTED_FROM_DAPP, + ), + ), + ); + break; case BlockchainType.polygon: + final walletConnectState = walletConnectCubit.state; + final wcClient = walletConnectState.wcClients.firstWhereOrNull( + (element) => + element.remotePeerId == + walletConnectCubit.state.currentDappPeerId, + ); + if (wcClient != null) { + log.i( + '''disconnected - ${savedDappData.wcSessionStore!.remotePeerMeta}''', + ); + wcClient.disconnect(); + //remove from collection + } + + await connectedDappRepository.delete(savedDappData); + emit( + state.copyWith( + appStatus: AppStatus.success, + messageHandler: ResponseMessage( + ResponseString.RESPONSE_STRING_DISCONNECTED_FROM_DAPP, + ), + ), + ); + break; case BlockchainType.binance: - throw Exception(); + final walletConnectState = walletConnectCubit.state; + final wcClient = walletConnectState.wcClients.firstWhereOrNull( + (element) => + element.remotePeerId == + walletConnectCubit.state.currentDappPeerId, + ); + if (wcClient != null) { + log.i( + '''disconnected - ${savedDappData.wcSessionStore!.remotePeerMeta}''', + ); + wcClient.disconnect(); + //remove from collection + } + + await connectedDappRepository.delete(savedDappData); + emit( + state.copyWith( + appStatus: AppStatus.success, + messageHandler: ResponseMessage( + ResponseString.RESPONSE_STRING_DISCONNECTED_FROM_DAPP, + ), + ), + ); + break; } - } catch (e) { - log.e('disconnect failure , e: $e'); + } catch (e, s) { + log.e('disconnect failure , e: $e, s: $s'); if (e is MessageHandler) { emit(state.error(messageHandler: e)); } else { diff --git a/lib/dashboard/connection/sign_payload/cubit/sign_payload_cubit.dart b/lib/dashboard/connection/sign_payload/cubit/sign_payload_cubit.dart index 8ea734fd7..fcef9b430 100644 --- a/lib/dashboard/connection/sign_payload/cubit/sign_payload_cubit.dart +++ b/lib/dashboard/connection/sign_payload/cubit/sign_payload_cubit.dart @@ -187,7 +187,9 @@ class SignPayloadCubit extends Cubit { final CryptoAccountData? currentAccount = walletCubit.state.cryptoAccount.data.firstWhereOrNull( - (element) => element.walletAddress == dappData.walletAddress, + (element) => + element.walletAddress == dappData.walletAddress && + element.blockchainType == dappData.blockchainType, ); log.i('currentAccount -$currentAccount'); From 6457138b58763bccfdc7d87154a42ca0792d0a0b Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Fri, 24 Feb 2023 18:16:17 +0530 Subject: [PATCH 074/190] version update --- pubspec.lock | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 5e2029553..8f4f97398 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2431,5 +2431,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.19.0 <3.0.0" + dart: ">=2.19.0 <3.7.0" flutter: ">=3.7.0-0" diff --git a/pubspec.yaml b/pubspec.yaml index 543d747ca..65000395d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: altme description: AltMe Flutter App -version: 1.10.2+153 +version: 1.10.3+154 publish_to: none environment: From f5ab31d06b0e364ef92f36f346656751292201a2 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Mon, 27 Feb 2023 13:35:03 +0330 Subject: [PATCH 075/190] check if room is already in use then update room alias for specific cases of live chat --- .../drawer/live_chat/cubit/live_chat_cubit.dart | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart index 8986dc3c8..03c6bd785 100644 --- a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart +++ b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart @@ -473,9 +473,16 @@ class LiveChatCubit extends Cubit { return roomId; } catch (e, s) { logger.e('e: $e, s: $s'); - final roomId = await client.joinRoom(name); - await _enableRoomEncyption(roomId); - return roomId; + if (e is MatrixException && e.errcode == 'M_ROOM_IN_USE') { + final millisecondsSinceEpoch = DateTime.now().millisecondsSinceEpoch; + return _createRoomAndInviteSupport( + '$name-updated-$millisecondsSinceEpoch', + ); + } else { + final roomId = await client.joinRoom(name); + await _enableRoomEncyption(roomId); + return roomId; + } } } else { await _enableRoomEncyption(mRoomId); From 1a001229f956ef3d5bbe1b9f82cb84b4501ac500 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Mon, 27 Feb 2023 14:13:22 +0330 Subject: [PATCH 076/190] update nft details page minor ux --- .../tab_bar/nft/view/nft_details_page.dart | 59 +++++++++---------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/lib/dashboard/home/tab_bar/nft/view/nft_details_page.dart b/lib/dashboard/home/tab_bar/nft/view/nft_details_page.dart index 3925c7150..73614507a 100644 --- a/lib/dashboard/home/tab_bar/nft/view/nft_details_page.dart +++ b/lib/dashboard/home/tab_bar/nft/view/nft_details_page.dart @@ -136,20 +136,22 @@ class _NftDetailsViewState extends State { final nftModel = widget.nftModel as TezosNftModel; return [ const SizedBox(height: Sizes.spaceNormal), - Row( + Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '${l10n.contractAddress} : ', style: Theme.of(context).textTheme.titleMedium, ), - Flexible( - child: Text( - nftModel.contractAddress, - style: Theme.of(context).textTheme.bodySmall3, - ), - ), - IconButton( + Row( + children: [ + Flexible( + child: Text( + nftModel.contractAddress, + style: Theme.of(context).textTheme.bodySmall3, + ), + ), + IconButton( icon: const Icon( Icons.open_in_new, size: Sizes.icon, @@ -161,11 +163,13 @@ class _NftDetailsViewState extends State { ); }, ), + ], + ), ], ), if (nftModel.identifier != null) ...[ const SizedBox(height: Sizes.spaceNormal), - Row( + Column( children: [ Text( '${l10n.identifier} : ', @@ -182,14 +186,12 @@ class _NftDetailsViewState extends State { const SizedBox( height: Sizes.spaceXSmall, ), + Text( + '${l10n.creators} : ', + style: Theme.of(context).textTheme.titleMedium, + ), Row( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - '${l10n.creators} : ', - style: Theme.of(context).textTheme.titleMedium, - ), Flexible( child: Text( nftModel.creators?.join(', ') ?? '?', @@ -215,12 +217,12 @@ class _NftDetailsViewState extends State { const SizedBox( height: Sizes.spaceXSmall, ), + Text( + '${l10n.publishers} : ', + style: Theme.of(context).textTheme.titleMedium, + ), Row( children: [ - Text( - '${l10n.publishers} : ', - style: Theme.of(context).textTheme.titleMedium, - ), Flexible( child: Text( nftModel.publishers?.join(', ') ?? '?', @@ -266,13 +268,12 @@ class _NftDetailsViewState extends State { final nftModel = widget.nftModel as EthereumNftModel; return [ const SizedBox(height: Sizes.spaceNormal), + Text( + '${l10n.contractAddress} : ', + style: Theme.of(context).textTheme.titleMedium, + ), Row( - crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - '${l10n.contractAddress} : ', - style: Theme.of(context).textTheme.titleMedium, - ), Flexible( child: Text( nftModel.contractAddress, @@ -297,14 +298,12 @@ class _NftDetailsViewState extends State { const SizedBox( height: Sizes.spaceXSmall, ), + Text( + '${l10n.creator} : ', + style: Theme.of(context).textTheme.titleMedium, + ), Row( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - '${l10n.creator} : ', - style: Theme.of(context).textTheme.titleMedium, - ), Flexible( child: Text( nftModel.minterAddress ?? '?', From cfebd3fc02ee8741d63ed1af348869f3568557b0 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Mon, 27 Feb 2023 16:31:44 +0530 Subject: [PATCH 077/190] support for evms --- lib/app/shared/constants/urls.dart | 6 +- .../blockchain_network/binance_network.dart | 2 +- .../blockchain_network/fantom_network.dart | 2 +- .../repository/connected_dapp_repository.dart | 27 +-- .../cubit/wallet_connect_cubit.dart | 11 +- .../operation/cubit/operation_cubit.dart | 166 ++++++++++++++---- .../operation/cubit/operation_state.dart | 7 + .../operation/view/operation_page.dart | 77 ++++---- .../connection/rights/cubit/rights_cubit.dart | 78 +------- 9 files changed, 192 insertions(+), 184 deletions(-) diff --git a/lib/app/shared/constants/urls.dart b/lib/app/shared/constants/urls.dart index 2bd1236be..0fe3298a9 100644 --- a/lib/app/shared/constants/urls.dart +++ b/lib/app/shared/constants/urls.dart @@ -49,7 +49,8 @@ class Urls { static const xtzPrice = 'https://api.teztools.io/v1/xtz-price'; static const cryptoCompareBaseUrl = 'https://min-api.cryptocompare.com'; - static const ethPrice = '$cryptoCompareBaseUrl/data/price?fsym=ETH&tsyms=USD'; + static String ethPrice(String symbol) => + '$cryptoCompareBaseUrl/data/price?fsym=$symbol&tsyms=USD'; // TZKT static const tzktMainnetUrl = 'https://api.tzkt.io'; @@ -59,8 +60,7 @@ class Urls { static const moralisBaseUrl = 'https://deep-index.moralis.io/api/v2'; //Infura - static const infuraBaseUrl = - 'https://mainnet.infura.io/v3/'; + static const infuraBaseUrl = 'https://mainnet.infura.io/v3/'; static const objktUrl = 'https://objkt.com/'; static const raribleUrl = 'https://rarible.com/'; diff --git a/lib/app/shared/models/blockchain_network/binance_network.dart b/lib/app/shared/models/blockchain_network/binance_network.dart index a475ba614..8481d3b5d 100644 --- a/lib/app/shared/models/blockchain_network/binance_network.dart +++ b/lib/app/shared/models/blockchain_network/binance_network.dart @@ -28,7 +28,7 @@ class BinanceNetwork extends EthereumNetwork { apiUrl: Urls.moralisBaseUrl, chainId: 56, chain: 'bsc', - rpcNodeUrl: 'https://ethereum.publicnode.com', + rpcNodeUrl: 'https://bsc-dataseed.binance.org/', title: 'Binance Mainnet', subTitle: 'This network is the official Binance blockchain running Network.' diff --git a/lib/app/shared/models/blockchain_network/fantom_network.dart b/lib/app/shared/models/blockchain_network/fantom_network.dart index 81e3117a3..254bf4a73 100644 --- a/lib/app/shared/models/blockchain_network/fantom_network.dart +++ b/lib/app/shared/models/blockchain_network/fantom_network.dart @@ -28,7 +28,7 @@ class FantomNetwork extends EthereumNetwork { apiUrl: Urls.moralisBaseUrl, chainId: 250, chain: 'fantom', - rpcNodeUrl: 'https://ethereum.publicnode.com', + rpcNodeUrl: 'https://rpcapi.fantom.network/', title: 'Fantom Mainnet', subTitle: 'This network is the official Fantom blockchain running Network.' diff --git a/lib/connection_bridge/repository/connected_dapp_repository.dart b/lib/connection_bridge/repository/connected_dapp_repository.dart index 0cdd4d1fc..f4c14dd04 100644 --- a/lib/connection_bridge/repository/connected_dapp_repository.dart +++ b/lib/connection_bridge/repository/connected_dapp_repository.dart @@ -93,24 +93,15 @@ class ConnectedDappRepository { (SavedDappData savedData) { switch (savedDappData.blockchainType) { case BlockchainType.ethereum: - return savedData.walletAddress == savedDappData.walletAddress && - savedData.wcSessionStore!.remotePeerMeta.name == - savedDappData.wcSessionStore!.remotePeerMeta.name; - case BlockchainType.tezos: - return savedData.walletAddress == savedDappData.walletAddress && - savedData.peer!.name == savedDappData.peer!.name; case BlockchainType.fantom: - return savedData.walletAddress == savedDappData.walletAddress && - savedData.wcSessionStore!.remotePeerMeta.name == - savedDappData.wcSessionStore!.remotePeerMeta.name; case BlockchainType.polygon: - return savedData.walletAddress == savedDappData.walletAddress && - savedData.wcSessionStore!.remotePeerMeta.name == - savedDappData.wcSessionStore!.remotePeerMeta.name; case BlockchainType.binance: return savedData.walletAddress == savedDappData.walletAddress && savedData.wcSessionStore!.remotePeerMeta.name == savedDappData.wcSessionStore!.remotePeerMeta.name; + case BlockchainType.tezos: + return savedData.walletAddress == savedDappData.walletAddress && + savedData.peer!.name == savedDappData.peer!.name; } }, @@ -125,20 +116,14 @@ class ConnectedDappRepository { late String id; switch (savedDappData.blockchainType) { case BlockchainType.ethereum: - id = savedDappData.wcSessionStore!.session.topic; - break; - case BlockchainType.tezos: - id = savedDappData.peer!.publicKey; - break; case BlockchainType.fantom: - id = savedDappData.wcSessionStore!.session.topic; - break; case BlockchainType.polygon: - id = savedDappData.wcSessionStore!.session.topic; - break; case BlockchainType.binance: id = savedDappData.wcSessionStore!.session.topic; break; + case BlockchainType.tezos: + id = savedDappData.peer!.publicKey; + break; } await _secureStorageProvider.set( diff --git a/lib/connection_bridge/wallet_connect/cubit/wallet_connect_cubit.dart b/lib/connection_bridge/wallet_connect/cubit/wallet_connect_cubit.dart index bb97baf35..881ae6261 100644 --- a/lib/connection_bridge/wallet_connect/cubit/wallet_connect_cubit.dart +++ b/lib/connection_bridge/wallet_connect/cubit/wallet_connect_cubit.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:altme/app/app.dart'; import 'package:altme/connection_bridge/connection_bridge.dart'; +import 'package:altme/wallet/wallet.dart'; import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; @@ -27,16 +28,12 @@ class WalletConnectCubit extends Cubit { final List savedDapps = await connectedDappRepository.findAll(); - final ethereumConnectedDapps = List.of(savedDapps).where( - (element) => - element.blockchainType == BlockchainType.ethereum || - element.blockchainType == BlockchainType.binance || - element.blockchainType == BlockchainType.fantom || - element.blockchainType == BlockchainType.polygon, + final connectedDapps = List.of(savedDapps).where( + (element) => element.blockchainType != BlockchainType.tezos, ); final List wcClients = List.empty(growable: true); - for (final element in ethereumConnectedDapps) { + for (final element in connectedDapps) { final sessionStore = element.wcSessionStore; final WCClient? wcClient = createWCClient(element.wcSessionStore); diff --git a/lib/dashboard/connection/operation/cubit/operation_cubit.dart b/lib/dashboard/connection/operation/cubit/operation_cubit.dart index 287fe1936..8bfe06049 100644 --- a/lib/dashboard/connection/operation/cubit/operation_cubit.dart +++ b/lib/dashboard/connection/operation/cubit/operation_cubit.dart @@ -27,6 +27,7 @@ class OperationCubit extends Cubit { required this.nftCubit, required this.tokensCubit, required this.walletConnectCubit, + required this.connectedDappRepository, }) : super(const OperationState()); final WalletCubit walletCubit; @@ -37,9 +38,99 @@ class OperationCubit extends Cubit { final NftCubit nftCubit; final TokensCubit tokensCubit; final WalletConnectCubit walletConnectCubit; + final ConnectedDappRepository connectedDappRepository; final log = getLogger('OperationCubit'); + late WCClient? wcClient; + + Future initialise(ConnectionBridgeType connectionBridgeType) async { + if (isClosed) return; + + try { + emit(state.loading()); + switch (connectionBridgeType) { + case ConnectionBridgeType.beacon: + await getUsdPrice(connectionBridgeType); + break; + case ConnectionBridgeType.walletconnect: + final walletConnectState = walletConnectCubit.state; + wcClient = walletConnectState.wcClients.firstWhereOrNull( + (element) => + element.remotePeerId == + walletConnectCubit.state.currentDappPeerId, + ); + + log.i('wcClient -$wcClient'); + if (wcClient == null) { + throw ResponseMessage( + ResponseString + .RESPONSE_STRING_SOMETHING_WENT_WRONG_TRY_AGAIN_LATER, + ); + } + + final List savedDapps = + await connectedDappRepository.findAll(); + + final SavedDappData? dappData = savedDapps.firstWhereOrNull( + (element) { + return element.wcSessionStore != null && + element.wcSessionStore!.session.key == + wcClient!.sessionStore.session.key; + }, + ); + + log.i('dappData -$dappData'); + if (dappData == null) { + throw ResponseMessage( + ResponseString + .RESPONSE_STRING_SOMETHING_WENT_WRONG_TRY_AGAIN_LATER, + ); + } + + if (walletConnectState.transaction!.from != dappData.walletAddress) { + throw ResponseMessage( + ResponseString + .RESPONSE_STRING_SOMETHING_WENT_WRONG_TRY_AGAIN_LATER, + ); + } + + final CryptoAccountData? cryptoAccountData = + walletCubit.state.cryptoAccount.data.firstWhereOrNull( + (element) => + element.walletAddress == dappData.walletAddress && + element.blockchainType == dappData.blockchainType, + ); + + log.i('cryptoAccountData -$cryptoAccountData'); + if (cryptoAccountData == null) { + throw ResponseMessage( + ResponseString + .RESPONSE_STRING_SOMETHING_WENT_WRONG_TRY_AGAIN_LATER, + ); + } else { + emit(state.copyWith(cryptoAccountData: cryptoAccountData)); + await getUsdPrice(connectionBridgeType); + } + break; + } + } catch (e) { + log.e('intialisation , e: $e'); + if (e is MessageHandler) { + emit(state.error(messageHandler: e)); + } else { + emit( + state.error( + messageHandler: ResponseMessage( + ResponseString + .RESPONSE_STRING_SOMETHING_WENT_WRONG_TRY_AGAIN_LATER, + ), + ), + ); + } + } + } + Future getUsdPrice(ConnectionBridgeType connectionBridgeType) async { if (isClosed) return; try { @@ -53,9 +144,12 @@ class OperationCubit extends Cubit { emit(state.copyWith(usdRate: xtzData.price)); break; case ConnectionBridgeType.walletconnect: - log.i('fetching eth USDprice'); - final response = - await dioClient.get(Urls.ethPrice) as Map; + log.i('fetching evm USDprice'); + + final symbol = state.cryptoAccountData!.blockchainType.symbol; + + final response = await dioClient.get(Urls.ethPrice(symbol)) + as Map; log.i('response - $response'); final double usdRate = response['USD'] as double; emit(state.copyWith(usdRate: usdRate)); @@ -63,7 +157,27 @@ class OperationCubit extends Cubit { } await getOtherPrices(connectionBridgeType); } catch (e) { - log.e(e); + log.e('getUsdPrice failure , e: $e'); + if (e is MessageHandler) { + emit( + state.copyWith( + status: AppStatus.errorWhileFetching, + message: StateMessage.error(messageHandler: e), + ), + ); + } else { + emit( + state.copyWith( + status: AppStatus.errorWhileFetching, + message: StateMessage.error( + messageHandler: ResponseMessage( + ResponseString + .RESPONSE_STRING_SOMETHING_WENT_WRONG_TRY_AGAIN_LATER, + ), + ), + ), + ); + } } } @@ -241,36 +355,8 @@ class OperationCubit extends Cubit { success = json.decode(response['success'].toString()) as bool; break; case ConnectionBridgeType.walletconnect: - final walletConnectState = walletConnectCubit.state; - final wcClient = walletConnectState.wcClients.firstWhereOrNull( - (element) => - element.remotePeerId == - walletConnectCubit.state.currentDappPeerId, - ); - - log.i('wcClient -$wcClient'); - if (wcClient == null) { - throw ResponseMessage( - ResponseString - .RESPONSE_STRING_SOMETHING_WENT_WRONG_TRY_AGAIN_LATER, - ); - } - - final CryptoAccountData? currentAccount = - walletCubit.state.cryptoAccount.data.firstWhereOrNull( - (element) => - element.walletAddress == walletConnectState.transaction!.from && - element.blockchainType == - walletCubit.state.currentAccount!.blockchainType, - ); - - log.i('currentAccount -$currentAccount'); - if (currentAccount == null) { - throw ResponseMessage( - ResponseString - .RESPONSE_STRING_SOMETHING_WENT_WRONG_TRY_AGAIN_LATER, - ); - } + final CryptoAccountData transactionAccountData = + state.cryptoAccountData!; final WCEthereumTransaction transaction = walletConnectCubit.state.transaction!; @@ -288,7 +374,7 @@ class OperationCubit extends Cubit { late String rpcUrl; - switch (currentAccount.blockchainType) { + switch (transactionAccountData.blockchainType) { case BlockchainType.tezos: throw Exception(); case BlockchainType.ethereum: @@ -305,19 +391,21 @@ class OperationCubit extends Cubit { break; } + log.i('rpcUrl - $rpcUrl'); + final String transactionHash = await MWeb3Client.sendEthereumTransaction( - chainId: currentAccount.blockchainType.chainId, + chainId: transactionAccountData.blockchainType.chainId, web3RpcURL: rpcUrl, - privateKey: currentAccount.secretKey, + privateKey: transactionAccountData.secretKey, sender: EthereumAddress.fromHex(transaction.from), reciever: EthereumAddress.fromHex(transaction.to!), amount: ethAmount, data: transaction.data, ); - wcClient.approveRequest( - id: walletConnectState.transactionId!, + wcClient!.approveRequest( + id: walletConnectCubit.state.transactionId!, result: transactionHash, ); diff --git a/lib/dashboard/connection/operation/cubit/operation_state.dart b/lib/dashboard/connection/operation/cubit/operation_state.dart index f8ed88004..402fe3c6c 100644 --- a/lib/dashboard/connection/operation/cubit/operation_state.dart +++ b/lib/dashboard/connection/operation/cubit/operation_state.dart @@ -8,6 +8,7 @@ class OperationState extends Equatable { this.amount = 0, this.fee = 0, this.usdRate = 0, + this.cryptoAccountData, }); factory OperationState.fromJson(Map json) => @@ -18,6 +19,7 @@ class OperationState extends Equatable { final double amount; final double fee; final double usdRate; + final CryptoAccountData? cryptoAccountData; OperationState loading() { return OperationState( @@ -25,6 +27,7 @@ class OperationState extends Equatable { amount: amount, fee: fee, usdRate: usdRate, + cryptoAccountData: cryptoAccountData, ); } @@ -37,6 +40,7 @@ class OperationState extends Equatable { amount: amount, fee: fee, usdRate: usdRate, + cryptoAccountData: cryptoAccountData, ); } @@ -47,6 +51,7 @@ class OperationState extends Equatable { double? fee, double? usdRate, int? selectedIndex, + CryptoAccountData? cryptoAccountData, }) { return OperationState( status: status ?? this.status, @@ -54,6 +59,7 @@ class OperationState extends Equatable { amount: amount ?? this.usdRate, fee: fee ?? this.usdRate, usdRate: usdRate ?? this.usdRate, + cryptoAccountData: cryptoAccountData ?? this.cryptoAccountData, ); } @@ -66,5 +72,6 @@ class OperationState extends Equatable { amount, fee, usdRate, + cryptoAccountData, ]; } diff --git a/lib/dashboard/connection/operation/view/operation_page.dart b/lib/dashboard/connection/operation/view/operation_page.dart index 323bd31f7..670dc6e2d 100644 --- a/lib/dashboard/connection/operation/view/operation_page.dart +++ b/lib/dashboard/connection/operation/view/operation_page.dart @@ -9,6 +9,7 @@ import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:key_generator/key_generator.dart'; +import 'package:secure_storage/secure_storage.dart'; class OperationPage extends StatelessWidget { const OperationPage({ @@ -41,6 +42,7 @@ class OperationPage extends StatelessWidget { nftCubit: context.read(), tokensCubit: context.read(), walletConnectCubit: context.read(), + connectedDappRepository: ConnectedDappRepository(getSecureStorage), ), child: OperationView(connectionBridgeType: connectionBridgeType), ); @@ -67,7 +69,7 @@ class _OperationViewState extends State { (_) async { await context .read() - .getUsdPrice(widget.connectionBridgeType); + .initialise(widget.connectionBridgeType); }, ); } @@ -76,34 +78,6 @@ class _OperationViewState extends State { Widget build(BuildContext context) { final l10n = context.l10n; - final BeaconRequest? beaconRequest = - context.read().state.beaconRequest; - - final WalletConnectState walletConnectState = - context.read().state; - - late String dAppName; - late String sender; - late String reciever; - - late String symbol; - - switch (widget.connectionBridgeType) { - case ConnectionBridgeType.beacon: - dAppName = beaconRequest!.request!.appMetadata!.name!; - symbol = 'XTZ'; - sender = beaconRequest.request!.sourceAddress!; - reciever = beaconRequest.operationDetails!.first.destination!; - break; - - case ConnectionBridgeType.walletconnect: - dAppName = walletConnectState.currentDAppPeerMeta!.name; - symbol = 'ETH'; - sender = walletConnectState.transaction!.from; - reciever = walletConnectState.transaction!.to ?? ''; - break; - } - return BlocConsumer( listener: (context, state) { if (state.status == AppStatus.loading) { @@ -130,6 +104,34 @@ class _OperationViewState extends State { } }, builder: (context, state) { + final BeaconRequest? beaconRequest = + context.read().state.beaconRequest; + + final WalletConnectState walletConnectState = + context.read().state; + + late String dAppName; + late String sender; + late String reciever; + + late String? symbol; + + switch (widget.connectionBridgeType) { + case ConnectionBridgeType.beacon: + dAppName = beaconRequest!.request!.appMetadata!.name!; + symbol = 'XTZ'; + sender = beaconRequest.request!.sourceAddress!; + reciever = beaconRequest.operationDetails!.first.destination!; + break; + + case ConnectionBridgeType.walletconnect: + dAppName = walletConnectState.currentDAppPeerMeta!.name; + symbol = state.cryptoAccountData?.blockchainType.symbol; + sender = walletConnectState.transaction!.from; + reciever = walletConnectState.transaction!.to ?? ''; + break; + } + String message = ''; if (state.message != null) { final MessageHandler messageHandler = state.message!.messageHandler!; @@ -161,7 +163,7 @@ class _OperationViewState extends State { onTap: () { context .read() - .getOtherPrices(widget.connectionBridgeType); + .initialise(widget.connectionBridgeType); }, ) : SingleChildScrollView( @@ -179,7 +181,7 @@ class _OperationViewState extends State { ), const SizedBox(height: Sizes.spaceSmall), MyText( - '''${state.amount.toStringAsFixed(6).formatNumber()} $symbol''', + '''${state.amount.toStringAsFixed(6).formatNumber()} ${symbol ?? ''}''', textAlign: TextAlign.center, style: Theme.of(context) .textTheme @@ -207,12 +209,13 @@ class _OperationViewState extends State { height: Sizes.icon3x, ), const SizedBox(height: Sizes.spaceNormal), - FeeDetails( - amount: state.amount, - symbol: symbol, - tokenUSDRate: state.usdRate, - fee: state.fee, - ), + if (symbol != null) + FeeDetails( + amount: state.amount, + symbol: symbol, + tokenUSDRate: state.usdRate, + fee: state.fee, + ), const SizedBox(height: Sizes.spaceNormal), ], ), diff --git a/lib/dashboard/connection/rights/cubit/rights_cubit.dart b/lib/dashboard/connection/rights/cubit/rights_cubit.dart index c30cdda16..d40430bc8 100644 --- a/lib/dashboard/connection/rights/cubit/rights_cubit.dart +++ b/lib/dashboard/connection/rights/cubit/rights_cubit.dart @@ -38,6 +38,9 @@ class RightsCubit extends Cubit { switch (savedDappData.blockchainType) { case BlockchainType.ethereum: + case BlockchainType.fantom: + case BlockchainType.polygon: + case BlockchainType.binance: final walletConnectState = walletConnectCubit.state; final wcClient = walletConnectState.wcClients.firstWhereOrNull( (element) => @@ -89,81 +92,6 @@ class RightsCubit extends Cubit { ); } break; - case BlockchainType.fantom: - final walletConnectState = walletConnectCubit.state; - final wcClient = walletConnectState.wcClients.firstWhereOrNull( - (element) => - element.remotePeerId == - walletConnectCubit.state.currentDappPeerId, - ); - if (wcClient != null) { - log.i( - '''disconnected - ${savedDappData.wcSessionStore!.remotePeerMeta}''', - ); - wcClient.disconnect(); - //remove from collection - } - - await connectedDappRepository.delete(savedDappData); - emit( - state.copyWith( - appStatus: AppStatus.success, - messageHandler: ResponseMessage( - ResponseString.RESPONSE_STRING_DISCONNECTED_FROM_DAPP, - ), - ), - ); - break; - case BlockchainType.polygon: - final walletConnectState = walletConnectCubit.state; - final wcClient = walletConnectState.wcClients.firstWhereOrNull( - (element) => - element.remotePeerId == - walletConnectCubit.state.currentDappPeerId, - ); - if (wcClient != null) { - log.i( - '''disconnected - ${savedDappData.wcSessionStore!.remotePeerMeta}''', - ); - wcClient.disconnect(); - //remove from collection - } - - await connectedDappRepository.delete(savedDappData); - emit( - state.copyWith( - appStatus: AppStatus.success, - messageHandler: ResponseMessage( - ResponseString.RESPONSE_STRING_DISCONNECTED_FROM_DAPP, - ), - ), - ); - break; - case BlockchainType.binance: - final walletConnectState = walletConnectCubit.state; - final wcClient = walletConnectState.wcClients.firstWhereOrNull( - (element) => - element.remotePeerId == - walletConnectCubit.state.currentDappPeerId, - ); - if (wcClient != null) { - log.i( - '''disconnected - ${savedDappData.wcSessionStore!.remotePeerMeta}''', - ); - wcClient.disconnect(); - //remove from collection - } - - await connectedDappRepository.delete(savedDappData); - emit( - state.copyWith( - appStatus: AppStatus.success, - messageHandler: ResponseMessage( - ResponseString.RESPONSE_STRING_DISCONNECTED_FROM_DAPP, - ), - ), - ); - break; } } catch (e, s) { log.e('disconnect failure , e: $e, s: $s'); From 15a9fe733a96ce875926e4d703b5d676a9125283 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Mon, 27 Feb 2023 15:39:40 +0330 Subject: [PATCH 078/190] update erc721 abi file --- assets/abi/erc721.abi.json | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/assets/abi/erc721.abi.json b/assets/abi/erc721.abi.json index ac1eae7d5..ded98e0fe 100644 --- a/assets/abi/erc721.abi.json +++ b/assets/abi/erc721.abi.json @@ -287,34 +287,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "_data", - "type": "bytes" - } - ], - "name": "safeTransferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [ { From 5cbf937d47bd497cd7f0406e8b8e249adaed88f7 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Mon, 27 Feb 2023 19:34:32 +0530 Subject: [PATCH 079/190] code optimisation --- .../verify_age/view/camera_page.dart | 5 +- lib/dashboard/home/home/cubit/home_cubit.dart | 68 ++----------------- pubspec.lock | 2 +- 3 files changed, 6 insertions(+), 69 deletions(-) diff --git a/lib/dashboard/ai_age_verification/verify_age/view/camera_page.dart b/lib/dashboard/ai_age_verification/verify_age/view/camera_page.dart index d28066ac5..32e6adef6 100644 --- a/lib/dashboard/ai_age_verification/verify_age/view/camera_page.dart +++ b/lib/dashboard/ai_age_verification/verify_age/view/camera_page.dart @@ -130,10 +130,7 @@ class _CameraViewState extends State { cameraCubit: context.read(), ); LoadingView().hide(); - await Navigator.push( - context, - AiAgeResultPage.route(context), - ); + await Navigator.push(context, AiAgeResultPage.route(context)); } }, ), diff --git a/lib/dashboard/home/home/cubit/home_cubit.dart b/lib/dashboard/home/home/cubit/home_cubit.dart index 40f9b5963..b0ab00fc4 100644 --- a/lib/dashboard/home/home/cubit/home_cubit.dart +++ b/lib/dashboard/home/home/cubit/home_cubit.dart @@ -159,62 +159,7 @@ class HomeCubit extends Cubit { }, data: data, ); - } catch (e) { - if (e is NetworkException) { - String? message; - if (e.data != null) { - if (e.data['error_description'] is String) { - try { - final dynamic errorDescriptionJson = - jsonDecode(e.data['error_description'] as String); - message = errorDescriptionJson['error_message'] as String; - } catch (_, __) { - message = e.data['error_description'] as String; - } - } else if (e.data['error_description'] is Map) { - message = e.data['error_description']['error_message'] as String; - } - } - emit( - state.copyWith( - status: AppStatus.error, - message: StateMessage( - messageHandler: ResponseMessage( - ResponseString - .RESPONSE_STRING_SOMETHING_WENT_WRONG_TRY_AGAIN_LATER, - ), - ), - ), - ); - emit( - state.copyWith( - status: AppStatus.error, - message: StateMessage( - stringMessage: message, - messageHandler: message == null - ? null - : ResponseMessage( - ResponseString - .RESPONSE_STRING_SOMETHING_WENT_WRONG_TRY_AGAIN_LATER, // ignore: lines_longer_than_80_chars - ), - ), - ), - ); - } else { - emit( - state.copyWith( - status: AppStatus.error, - message: StateMessage( - messageHandler: ResponseMessage( - ResponseString - .RESPONSE_STRING_SOMETHING_WENT_WRONG_TRY_AGAIN_LATER, - ), - ), - ), - ); - } - } - try { + if (response != null) { final credential = jsonDecode(response as String) as Map; @@ -248,11 +193,7 @@ class HomeCubit extends Cubit { showMessage: true, ); await cameraCubit.incrementAcquiredCredentialsQuantity(); - emit( - state.copyWith( - status: AppStatus.success, - ), - ); + emit(state.copyWith(status: AppStatus.success)); } else { await cameraCubit.updateAgeEstimate( credentialModel.data['credentialSubject']['ageEstimate'] @@ -260,8 +201,8 @@ class HomeCubit extends Cubit { ); } } - logger.i('response : $response'); - } catch (e, s) { + } catch (e) { + logger.e(e); if (e is NetworkException) { String? message; if (e.data != null) { @@ -304,7 +245,6 @@ class HomeCubit extends Cubit { ), ); } - logger.e('error: $e , stack: $s'); } } } diff --git a/pubspec.lock b/pubspec.lock index 8f4f97398..5e2029553 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2431,5 +2431,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.19.0 <3.7.0" + dart: ">=2.19.0 <3.0.0" flutter: ">=3.7.0-0" From 52ee12152fdd9ba6046a05bd1f177484c5d07e6c Mon Sep 17 00:00:00 2001 From: hawkbee1 Date: Mon, 27 Feb 2023 16:41:15 +0100 Subject: [PATCH 080/190] version: 1.10.4+155 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 65000395d..5a22c559d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: altme description: AltMe Flutter App -version: 1.10.3+154 +version: 1.10.4+155 publish_to: none environment: From d57328c66164c0b22a62d352948ec7e4498900a5 Mon Sep 17 00:00:00 2001 From: hawkbee1 Date: Mon, 27 Feb 2023 17:33:55 +0100 Subject: [PATCH 081/190] change ebsi redirect_uri into app/download/ebsi --- lib/app/shared/constants/parameters.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/app/shared/constants/parameters.dart b/lib/app/shared/constants/parameters.dart index 0723a3a2b..49e594332 100644 --- a/lib/app/shared/constants/parameters.dart +++ b/lib/app/shared/constants/parameters.dart @@ -27,7 +27,7 @@ class Parameters { isPassEnabled: true, ); - static const ebsiUniversalLink = 'https://app.altme.io/app/download'; + static const ebsiUniversalLink = 'https://app.altme.io/app/download/ebsi'; static const web3RpcMainnetUrl = 'https://mainnet.infura.io/v3/'; } From 5292ba190748cc0fdda4561c14777080c45a4f3e Mon Sep 17 00:00:00 2001 From: hawkbee1 Date: Mon, 27 Feb 2023 19:24:52 +0100 Subject: [PATCH 082/190] version: 1.9.5+146 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 4994684d0..ff4db8d1d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: altme description: AltMe Flutter App -version: 1.9.3+144 +version: 1.9.5+146 publish_to: none environment: From 43630fa648d44095866157c0e70499008d1ea638 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Tue, 28 Feb 2023 13:40:25 +0530 Subject: [PATCH 083/190] add blommeta pass card in discover #1391 --- assets/image/bloometa-pass.png | Bin 12007 -> 8991 bytes lib/app/shared/constants/constant_list.dart | 4 ++++ .../credential_subject_type_extension.dart | 3 ++- .../home_credential/home_credential.dart | 3 +++ 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/assets/image/bloometa-pass.png b/assets/image/bloometa-pass.png index fe58e3eeb91375729f11c6449bfb219e339206e4..4a5cc141592e1b06ec25ba7d13d5722ab045c7ce 100644 GIT binary patch literal 8991 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 literal 12007 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 R;Y$bTJT-h$t6~4 communityCategories = [ // CredentialSubjectType.talaoCommunityCard ]; + static final List identityCategories = [ CredentialSubjectType.emailPass, //CredentialSubjectType.gender, @@ -25,5 +28,6 @@ class DiscoverList { CredentialSubjectType.phonePass, CredentialSubjectType.twitterCard, ]; + static final List myProfessionalCategories = []; } 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 a0136cd4a..0da280448 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 @@ -154,7 +154,8 @@ extension CredentialSubjectTypeExtension on CredentialSubjectType { this == CredentialSubjectType.pigsPass || this == CredentialSubjectType.bunnyPass || this == CredentialSubjectType.troopezPass || - this == CredentialSubjectType.matterlightPass) { + this == CredentialSubjectType.matterlightPass || + this == CredentialSubjectType.bloometaPass) { return true; } return false; diff --git a/lib/dashboard/home/tab_bar/credentials/models/home_credential/home_credential.dart b/lib/dashboard/home/tab_bar/credentials/models/home_credential/home_credential.dart index e0fead4ce..07203d2fb 100644 --- a/lib/dashboard/home/tab_bar/credentials/models/home_credential/home_credential.dart +++ b/lib/dashboard/home/tab_bar/credentials/models/home_credential/home_credential.dart @@ -247,6 +247,9 @@ class HomeCredential extends Equatable { break; case CredentialSubjectType.bloometaPass: + image = ImageStrings.bloometaPass; + break; + case CredentialSubjectType.voucher: case CredentialSubjectType.selfIssued: case CredentialSubjectType.defaultCredential: From 46264913c78c6230a396d6352f435e69052e3a8a Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Tue, 28 Feb 2023 15:24:41 +0530 Subject: [PATCH 084/190] add consent after scanning #1395 --- .../cubit/qr_code_scan_cubit.dart | 22 ++++++++------- lib/splash/bloclisteners/blocklisteners.dart | 27 ++++++++++++++----- 2 files changed, 33 insertions(+), 16 deletions(-) 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 f0f856bae..73fca70d2 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 @@ -80,16 +80,6 @@ class QRCodeScanCubit extends Cubit { } else if (scannedResponse.startsWith('wc:')) { await walletConnectCubit.connect(scannedResponse); - emit(state.copyWith(qrScanStatus: QrScanStatus.goBack)); - } else if (scannedResponse.startsWith('openid://initiate_issuance?')) { - // convert String from QR code into Uri - await initiateEbsiCredentialIssuance( - scannedResponse, - client, - walletCubit, - getSecureStorage, - ); - emit(state.copyWith(qrScanStatus: QrScanStatus.goBack)); } else { await host(url: scannedResponse); @@ -337,6 +327,18 @@ class QRCodeScanCubit extends Cubit { late final dynamic data; try { + if (state.uri.toString().startsWith('openid://initiate_issuance?')) { + await initiateEbsiCredentialIssuance( + state.uri.toString(), + client, + walletCubit, + getSecureStorage, + ); + + emit(state.copyWith(qrScanStatus: QrScanStatus.goBack)); + return; + } + final dynamic response = await client.get(uri.toString()); data = response is String ? jsonDecode(response) : response; diff --git a/lib/splash/bloclisteners/blocklisteners.dart b/lib/splash/bloclisteners/blocklisteners.dart index 43ef74d7d..f954cefa7 100644 --- a/lib/splash/bloclisteners/blocklisteners.dart +++ b/lib/splash/bloclisteners/blocklisteners.dart @@ -164,9 +164,18 @@ final qrCodeBlocListener = BlocListener( if (state.uri != null) { final profileCubit = context.read(); var approvedIssuer = Issuer.emptyIssuer(state.uri!.host); - final isIssuerVerificationSettingTrue = + + late bool isIssuerVerificationSettingTrue; + + isIssuerVerificationSettingTrue = profileCubit.state.model.issuerVerificationUrl != ''; + + if (state.uri!.toString().startsWith('openid://initiate_issuance?')) { + isIssuerVerificationSettingTrue = true; + } + log.i('checking issuer - $isIssuerVerificationSettingTrue'); + if (isIssuerVerificationSettingTrue) { try { approvedIssuer = await CheckIssuer( @@ -175,6 +184,7 @@ final qrCodeBlocListener = BlocListener( state.uri!, ).isIssuerInApprovedList(); } catch (e) { + log.e(e); if (e is MessageHandler) { await context.read().emitError(e); } else { @@ -191,16 +201,21 @@ final qrCodeBlocListener = BlocListener( var acceptHost = true; - if (approvedIssuer.did.isEmpty && - profileCubit.state.model.issuerVerificationUrl.isNotEmpty) { + if (approvedIssuer.did.isEmpty && isIssuerVerificationSettingTrue) { + String subtitle = (approvedIssuer.did.isEmpty) + ? state.uri!.host + : '''${approvedIssuer.organizationInfo.legalName}\n${approvedIssuer.organizationInfo.currentAddress}'''; + + if (state.uri!.toString().startsWith('openid://initiate_issuance?')) { + subtitle = state.uri!.queryParameters['issuer'].toString(); + } + acceptHost = await showDialog( context: context, builder: (BuildContext context) { return ConfirmDialog( title: l10n.scanPromptHost, - subtitle: (approvedIssuer.did.isEmpty) - ? state.uri!.host - : '''${approvedIssuer.organizationInfo.legalName}\n${approvedIssuer.organizationInfo.currentAddress}''', + subtitle: subtitle, yes: l10n.communicationHostAllow, no: l10n.communicationHostDeny, //lock: state.uri!.scheme == 'http', From 31c1f36274cb3a6e1af5cebc257d5254c5a6de13 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Tue, 28 Feb 2023 14:00:31 +0330 Subject: [PATCH 085/190] prevent room creation before user sending the first message --- .../live_chat/cubit/live_chat_cubit.dart | 137 ++++++++++-------- pubspec.lock | 2 +- 2 files changed, 79 insertions(+), 60 deletions(-) diff --git a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart index 03c6bd785..01f9805a0 100644 --- a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart +++ b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart @@ -40,7 +40,7 @@ class LiveChatCubit extends Cubit { late Client client; final DioClient dioClient; final logger = getLogger('LiveChatCubit'); - String _roomId = ''; + String? _roomId; final DIDKitProvider didKit; StreamSubscription? _onEventSubscription; StreamController? _notificationStreamController; @@ -52,11 +52,12 @@ class LiveChatCubit extends Cubit { Future onSendPressed(PartialText partialText) async { try { - final room = client.getRoomById(_roomId); + await _checkIfRoomNotExistThenCreateIt(); + final room = client.getRoomById(_roomId!); if (room == null) { - await client.joinRoomById(_roomId); + await client.joinRoomById(_roomId!); } - final eventId = await client.getRoomById(_roomId)?.sendTextEvent( + final eventId = await client.getRoomById(_roomId!)?.sendTextEvent( partialText.text, txid: const Uuid().v4(), ); @@ -141,8 +142,8 @@ class LiveChatCubit extends Cubit { status: Status.sending, ); emit(state.copyWith(messages: [message, ...state.messages])); - - await client.getRoomById(_roomId)?.sendFileEvent( + await _checkIfRoomNotExistThenCreateIt(); + await client.getRoomById(_roomId!)?.sendFileEvent( MatrixFile( bytes: File(result.files.single.path!).readAsBytesSync(), name: result.files.single.name, @@ -178,7 +179,8 @@ class LiveChatCubit extends Cubit { emit(state.copyWith(messages: [message, ...state.messages])); - await client.getRoomById(_roomId)?.sendFileEvent( + await _checkIfRoomNotExistThenCreateIt(); + await client.getRoomById(_roomId!)?.sendFileEvent( MatrixFile( bytes: bytes, name: result.name, @@ -243,13 +245,16 @@ class LiveChatCubit extends Cubit { password: await _getPasswordForDID(), ); } - _roomId = await _createRoomAndInviteSupport( - username, - ); + List retrivedMessageFromDB = []; + final savedRoomId = await _getRoomIdFromStorage(); + if (savedRoomId != null) { + _roomId = savedRoomId; + await _enableRoomEncyption(savedRoomId); + _getUnreadMessageCount(); + _subscribeToEventsOfRoom(); + retrivedMessageFromDB = await _retriveMessagesFromDB(_roomId!); + } logger.i('roomId : $_roomId'); - _getUnreadMessageCount(); - _subscribeToEventsOfRoom(); - final retrivedMessageFromDB = await _retriveMessagesFromDB(_roomId); emit( state.copyWith( status: AppStatus.init, @@ -284,8 +289,8 @@ class LiveChatCubit extends Cubit { } } - void _subscribeToEventsOfRoom() { - _onEventSubscription?.cancel(); + Future _subscribeToEventsOfRoom() async { + await _onEventSubscription?.cancel(); _onEventSubscription = client.onRoomState.stream.listen((Event event) { if (event.roomId == _roomId && event.type == 'm.room.message') { @@ -386,7 +391,7 @@ class LiveChatCubit extends Cubit { } int get unreadMessageCount => - client.getRoomById(_roomId)?.notificationCount ?? 0; + client.getRoomById(_roomId ?? '')?.notificationCount ?? 0; void _getUnreadMessageCount() { final unreadCount = unreadMessageCount; @@ -397,7 +402,7 @@ class LiveChatCubit extends Cubit { Future markMessageAsRead(List? eventIds) async { if (eventIds == null || eventIds.isEmpty) return; - final room = client.getRoomById(_roomId); + final room = client.getRoomById(_roomId ?? ''); if (room == null) return; try { for (final eventId in eventIds) { @@ -444,68 +449,81 @@ class LiveChatCubit extends Cubit { return messageEvents.map(_mapEventToMessage).toList(); } + Future _checkIfRoomNotExistThenCreateIt() async { + if (_roomId == null || _roomId!.isEmpty) { + final did = await secureStorageProvider.get(SecureStorageKeys.did) ?? ''; + final username = did.replaceAll(':', '-'); + _roomId = await _createRoomAndInviteSupport( + username, + ); + await _setRoomIdInStorage(_roomId!); + _getUnreadMessageCount(); + _subscribeToEventsOfRoom(); + } + } + + Future _getRoomIdFromStorage() async { + return secureStorageProvider.get(SecureStorageKeys.supportRoomId); + } + + Future _setRoomIdInStorage(String roomId) async { + await secureStorageProvider.set( + SecureStorageKeys.supportRoomId, + roomId, + ); + } + + /// before calling this function you need to check if + /// room not exist before with this [name] and alias Future _createRoomAndInviteSupport(String name) async { - final mRoomId = - await secureStorageProvider.get(SecureStorageKeys.supportRoomId); - if (mRoomId == null) { - try { - final roomId = await client.createRoom( - isDirect: true, - name: name, - invite: ['@support:matrix.talao.co'], - roomAliasName: name, - initialState: [ - StateEvent( - type: EventTypes.Encryption, - stateKey: '', - content: { - 'algorithm': 'm.megolm.v1.aes-sha2', - }, - ), - ], - ); - await secureStorageProvider.set( - SecureStorageKeys.supportRoomId, - roomId, + try { + final roomId = await client.createRoom( + isDirect: true, + name: name, + invite: ['@support:matrix.talao.co'], + roomAliasName: name, + initialState: [ + StateEvent( + type: EventTypes.Encryption, + stateKey: '', + content: { + 'algorithm': 'm.megolm.v1.aes-sha2', + }, + ), + ], + ); + await _enableRoomEncyption(roomId); + logger.i('room created! => id: $roomId'); + return roomId; + } catch (e, s) { + logger.e('e: $e, s: $s'); + if (e is MatrixException && e.errcode == 'M_ROOM_IN_USE') { + final millisecondsSinceEpoch = DateTime.now().millisecondsSinceEpoch; + return _createRoomAndInviteSupport( + '$name-updated-$millisecondsSinceEpoch', ); + } else { + final roomId = await client.joinRoom(name); await _enableRoomEncyption(roomId); - logger.i('room created! => id: $roomId'); return roomId; - } catch (e, s) { - logger.e('e: $e, s: $s'); - if (e is MatrixException && e.errcode == 'M_ROOM_IN_USE') { - final millisecondsSinceEpoch = DateTime.now().millisecondsSinceEpoch; - return _createRoomAndInviteSupport( - '$name-updated-$millisecondsSinceEpoch', - ); - } else { - final roomId = await client.joinRoom(name); - await _enableRoomEncyption(roomId); - return roomId; - } } - } else { - await _enableRoomEncyption(mRoomId); - return mRoomId; } } Future _enableRoomEncyption(String roomId) async { try { + if (roomId.isEmpty) return; final room = client.getRoomById(roomId); if (room == null) return; if (room.encrypted) { logger.i('the room with id: ${room.id} encyrpted before!'); return; } + await room.enableEncryption(); final verificationResponse = await DeviceKeysList(client.userID!, client).startVerification(); logger.i('verification response: $verificationResponse'); await verificationResponse.acceptVerification(); - verificationResponse.onUpdate = () { - logger.i('on update the verifcation : ${verificationResponse.state}'); - }; - await room.enableEncryption(); } catch (e, s) { logger.e('error in enabling room e2e encryption, e: $e, s: $s'); } @@ -622,6 +640,7 @@ class LiveChatCubit extends Cubit { _notificationStreamController = null; await _onEventSubscription?.cancel(); _onEventSubscription = null; + _roomId = null; } catch (e) { logger.e('e: $e'); } diff --git a/pubspec.lock b/pubspec.lock index 5e2029553..8f4f97398 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2431,5 +2431,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.19.0 <3.0.0" + dart: ">=2.19.0 <3.7.0" flutter: ">=3.7.0-0" From 37a358b8c4785195bd735e8cfbb2f454a610c9fe Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Tue, 28 Feb 2023 14:22:49 +0330 Subject: [PATCH 086/190] show message state --- .../live_chat/cubit/live_chat_cubit.dart | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart index 01f9805a0..375b8cca3 100644 --- a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart +++ b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart @@ -52,6 +52,16 @@ class LiveChatCubit extends Cubit { Future onSendPressed(PartialText partialText) async { try { + final messageId = const Uuid().v4(); + final message = TextMessage( + author: state.user!, + createdAt: DateTime.now().millisecondsSinceEpoch, + id: messageId, + text: partialText.text, + status: Status.sending, + ); + emit(state.copyWith(messages: [message, ...state.messages])); + // await _checkIfRoomNotExistThenCreateIt(); final room = client.getRoomById(_roomId!); if (room == null) { @@ -59,7 +69,7 @@ class LiveChatCubit extends Cubit { } final eventId = await client.getRoomById(_roomId!)?.sendTextEvent( partialText.text, - txid: const Uuid().v4(), + txid: messageId, ); logger.i('send text event: $eventId'); } catch (e, s) { @@ -251,7 +261,7 @@ class LiveChatCubit extends Cubit { _roomId = savedRoomId; await _enableRoomEncyption(savedRoomId); _getUnreadMessageCount(); - _subscribeToEventsOfRoom(); + await _subscribeToEventsOfRoom(); retrivedMessageFromDB = await _retriveMessagesFromDB(_roomId!); } logger.i('roomId : $_roomId'); @@ -302,7 +312,7 @@ class LiveChatCubit extends Cubit { (element) => element.id == txId, ); final updatedMessage = state.messages[index].copyWith( - status: Status.delivered, + status: _mapEventStatusToMessageStatus(event.status), ); final newMessages = List.of(state.messages); newMessages[index] = updatedMessage; @@ -458,7 +468,7 @@ class LiveChatCubit extends Cubit { ); await _setRoomIdInStorage(_roomId!); _getUnreadMessageCount(); - _subscribeToEventsOfRoom(); + await _subscribeToEventsOfRoom(); } } @@ -657,13 +667,13 @@ class LiveChatCubit extends Cubit { case EventStatus.removed: return Status.error; case EventStatus.roomState: - return Status.seen; + return Status.delivered; case EventStatus.sending: return Status.sending; case EventStatus.sent: return Status.sent; case EventStatus.synced: - return Status.delivered; + return Status.seen; } } } From 93f600903ecec8b0738b8f06d39df0a0286de108 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Tue, 28 Feb 2023 17:12:37 +0530 Subject: [PATCH 087/190] pop up disabled after shown once #1357 --- lib/dashboard/dashboard/view/dashboard_page.dart | 5 ++++- lib/splash/cubit/splash_cubit.dart | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/dashboard/dashboard/view/dashboard_page.dart b/lib/dashboard/dashboard/view/dashboard_page.dart index e16938dd9..da21b4531 100644 --- a/lib/dashboard/dashboard/view/dashboard_page.dart +++ b/lib/dashboard/dashboard/view/dashboard_page.dart @@ -43,8 +43,11 @@ class _DashboardViewState extends State { /// If there is a deepLink we give do as if it coming from QRCode context.read().deepLink(); context.read().startBeacon(); - if (context.read().state.isNewVersion) { + + final splashCubit = context.read(); + if (splashCubit.state.isNewVersion) { WhatIsNewDialog.show(context); + splashCubit.dialogOpened(); } }); }); diff --git a/lib/splash/cubit/splash_cubit.dart b/lib/splash/cubit/splash_cubit.dart index d8c608329..601ce49a1 100644 --- a/lib/splash/cubit/splash_cubit.dart +++ b/lib/splash/cubit/splash_cubit.dart @@ -102,4 +102,8 @@ class SplashCubit extends Cubit { ), ); } + + void dialogOpened() { + emit(state.copyWith(isNewVersion: false)); + } } From c4fedab5d228f4abc2b4370f070a9b625e20055e Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Tue, 28 Feb 2023 15:20:14 +0330 Subject: [PATCH 088/190] fix login failed when account recovered --- .../live_chat/cubit/live_chat_cubit.dart | 27 +++++++++++-------- .../cubit/send_receive_home_cubit.dart | 1 + 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart index 375b8cca3..32677aee3 100644 --- a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart +++ b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart @@ -629,17 +629,22 @@ class LiveChatCubit extends Cubit { required String username, required String password, }) async { - final isLogged = client.isLogged(); - if (isLogged) return client.userID!; - client.homeserver = Uri.parse(Urls.matrixHomeServer); - final deviceId = await PlatformDeviceId.getDeviceId; - final loginResonse = await client.login( - LoginType.mLoginPassword, - password: password, - deviceId: deviceId, - identifier: AuthenticationUserIdentifier(user: username), - ); - return loginResonse.userId!; + try { + final isLogged = client.isLogged(); + if (isLogged) return client.userID!; + client.homeserver = Uri.parse(Urls.matrixHomeServer); + final deviceId = await PlatformDeviceId.getDeviceId; + final loginResonse = await client.login( + LoginType.mLoginPassword, + password: password, + deviceId: deviceId, + identifier: AuthenticationUserIdentifier(user: username), + ); + return loginResonse.userId!; + } catch (e, s) { + logger.i('e: $e, s: $s'); + return '@$username:${Urls.matrixHomeServer.replaceAll('https://', '')}'; + } } Future dispose() async { diff --git a/lib/dashboard/home/tab_bar/tokens/send_receive_home/cubit/send_receive_home_cubit.dart b/lib/dashboard/home/tab_bar/tokens/send_receive_home/cubit/send_receive_home_cubit.dart index 783c3f25b..b08fa2496 100644 --- a/lib/dashboard/home/tab_bar/tokens/send_receive_home/cubit/send_receive_home_cubit.dart +++ b/lib/dashboard/home/tab_bar/tokens/send_receive_home/cubit/send_receive_home_cubit.dart @@ -141,6 +141,7 @@ class SendReceiveHomeCubit extends Cubit { params = { 'anyof.sender.target': walletAddress, 'amount.gt': 0, + 'limit': 1000 }; } else { params = { From 72ddc4cae0e7c9c5a893fde444ed9ceb7ff2b19c Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Tue, 28 Feb 2023 15:57:05 +0330 Subject: [PATCH 089/190] increase the limit of token history api call --- .../tokens/send_receive_home/cubit/send_receive_home_cubit.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/dashboard/home/tab_bar/tokens/send_receive_home/cubit/send_receive_home_cubit.dart b/lib/dashboard/home/tab_bar/tokens/send_receive_home/cubit/send_receive_home_cubit.dart index b08fa2496..11c11127a 100644 --- a/lib/dashboard/home/tab_bar/tokens/send_receive_home/cubit/send_receive_home_cubit.dart +++ b/lib/dashboard/home/tab_bar/tokens/send_receive_home/cubit/send_receive_home_cubit.dart @@ -109,6 +109,7 @@ class SendReceiveHomeCubit extends Cubit { final params = { 'anyof.from.to': walletAddress, 'token.contract.eq': state.selectedToken.contractAddress, + 'limit': 1000, }; final result = await client.get( '$baseUrl/v1/tokens/transfers', @@ -147,6 +148,7 @@ class SendReceiveHomeCubit extends Cubit { params = { 'anyof.sender.target': contractAddress, 'entrypoint': 'transfer', + 'limit': 1000, 'parameter.in': jsonEncode([ {'to': walletAddress}, {'from': walletAddress} From e967b3d22c9f954f9065bb2e945e069805b17eb1 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Tue, 28 Feb 2023 19:15:57 +0530 Subject: [PATCH 090/190] clean id token presentation submission #1375 --- lib/app/shared/constants/parameters.dart | 1 + packages/ebsi/lib/src/ebsi.dart | 10 +++++++--- packages/ebsi/pubspec.yaml | 3 ++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/app/shared/constants/parameters.dart b/lib/app/shared/constants/parameters.dart index 49e594332..399b2df33 100644 --- a/lib/app/shared/constants/parameters.dart +++ b/lib/app/shared/constants/parameters.dart @@ -13,6 +13,7 @@ class Parameters { CredentialSubjectType.nationality, CredentialSubjectType.linkedInCard, ]; + static const bool hasCryptoCallToAction = true; static const AdvanceSettingsState defaultAdvanceSettingsState = diff --git a/packages/ebsi/lib/src/ebsi.dart b/packages/ebsi/lib/src/ebsi.dart index d480ac241..98adf7e5f 100644 --- a/packages/ebsi/lib/src/ebsi.dart +++ b/packages/ebsi/lib/src/ebsi.dart @@ -14,6 +14,7 @@ import 'package:hex/hex.dart'; import 'package:jose/jose.dart'; import 'package:json_path/json_path.dart'; import 'package:secp256k1/secp256k1.dart'; +import 'package:uuid/uuid.dart'; /// {@template ebsi} /// EBSI wallet compliance @@ -542,6 +543,9 @@ class Ebsi { Future getIdToken( VerifierTokenParameters tokenParameters, ) async { + final uuid1 = const Uuid().v4(); + final uuid2 = const Uuid().v4(); + /// build id token final payload = { 'iat': DateTime.now().microsecondsSinceEpoch, @@ -552,11 +556,11 @@ class Ebsi { 'nonce': tokenParameters.nonce, '_vp_token': { 'presentation_submission': { - 'definition_id': 'conformance_mock_vp_request', - 'id': 'VA presentation Talao', + 'definition_id': 'Altme defintion for EBSI project', + 'id': uuid1, 'descriptor_map': [ { - 'id': 'conformance_mock_vp', + 'id': uuid2, 'format': 'jwt_vp', 'path': r'$', } diff --git a/packages/ebsi/pubspec.yaml b/packages/ebsi/pubspec.yaml index 8b45908d7..12bc58229 100644 --- a/packages/ebsi/pubspec.yaml +++ b/packages/ebsi/pubspec.yaml @@ -18,7 +18,7 @@ dependencies: fast_base58: ^0.2.1 flutter: sdk: flutter - hex: ^0.2.0 + hex: ^0.2.0 http_mock_adapter: ^0.3.3 jose: ^0.3.3 json_path: ^0.4.2 @@ -28,6 +28,7 @@ dependencies: git: url: https://github.com/autonomy-system/tezart.git ref: bd4b8db6e3a352590a6e556d31df8aef60db3465 + uuid: ^3.0.7 dev_dependencies: flutter_test: From a69cd83d96dadbe357fcc81f279ac8d9d150d6b1 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Wed, 1 Mar 2023 15:08:42 +0530 Subject: [PATCH 091/190] limit 3 tries in pincode #1396 --- lib/app/shared/widget/numeric_keyboard.dart | 56 +++++- lib/app/view/app.dart | 2 +- .../cubit/wallet_connect_cubit.dart | 1 - .../widgets/token_amount_calculator.dart | 3 + .../profile/cubit/profile_cubit.dart | 54 ++++- .../profile/cubit/profile_state.dart | 28 ++- lib/l10n/arb/app_en.arb | 3 +- lib/l10n/untranslated.json | 12 +- lib/pin_code/cubit/pin_code_view_cubit.dart | 10 +- lib/pin_code/cubit/pin_code_view_state.dart | 11 +- lib/pin_code/view/confirm_pin_code_page.dart | 4 +- .../view/enter_new_pin_code_page.dart | 4 +- lib/pin_code/view/pin_code_page.dart | 59 ++++-- lib/pin_code/widgets/circle_ui_config.dart | 10 +- lib/pin_code/widgets/num_keyboard.dart | 19 +- lib/pin_code/widgets/pin_code_title.dart | 16 +- lib/pin_code/widgets/pin_code_widget.dart | 190 ++++++++++-------- lib/theme/app_theme/app_theme.dart | 1 + pubspec.lock | 2 +- 19 files changed, 339 insertions(+), 146 deletions(-) diff --git a/lib/app/shared/widget/numeric_keyboard.dart b/lib/app/shared/widget/numeric_keyboard.dart index 5db623f48..f709aa60e 100644 --- a/lib/app/shared/widget/numeric_keyboard.dart +++ b/lib/app/shared/widget/numeric_keyboard.dart @@ -38,6 +38,7 @@ class NumericKeyboard extends StatelessWidget { required this.onKeyboardTap, this.leadingButton, this.trailingButton, + required this.allowAction, }); final KeyboardUIConfig keyboardUIConfig; @@ -48,11 +49,10 @@ class NumericKeyboard extends StatelessWidget { //should have a proper order [1...9, 0] final Widget? leadingButton; final Widget? trailingButton; + final bool allowAction; @override - Widget build(BuildContext context) => _buildKeyboard(context); - - Widget _buildKeyboard(BuildContext context) { + Widget build(BuildContext context) { List keyboardItems = List.filled(10, '0'); keyboardItems = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']; final screenSize = MediaQuery.of(context).size; @@ -63,6 +63,7 @@ class NumericKeyboard extends StatelessWidget { final keyboardSize = keyboardUIConfig.keyboardSize != null ? keyboardUIConfig.keyboardSize! : Size(keyboardWidth, keyboardHeight); + return Container( width: keyboardSize.width, height: keyboardSize.height, @@ -103,6 +104,7 @@ class NumericKeyboard extends StatelessWidget { onTap: onKeyboardTap, label: keyboardItems[index], digitTextStyle: keyboardUIConfig.digitTextStyle, + allowAction: allowAction, ); }); @@ -135,6 +137,7 @@ class KeyboardButton extends StatelessWidget { this.digitShape = BoxShape.circle, this.digitBorderWidth = 3.4, this.digitTextStyle, + required this.allowAction, }); final BoxShape digitShape; @@ -145,6 +148,7 @@ class KeyboardButton extends StatelessWidget { final dynamic Function(String)? onLongPress; final String semanticsLabel; final TextStyle? digitTextStyle; + final bool allowAction; @override Widget build(BuildContext context) { @@ -155,11 +159,18 @@ class KeyboardButton extends StatelessWidget { child: Material( color: Colors.transparent, child: InkWell( - highlightColor: Theme.of(context).colorScheme.primary, + highlightColor: allowAction + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.background, + splashColor: allowAction + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.background, onLongPress: () { + if (!allowAction) return; onLongPress?.call(semanticsLabel); }, onTap: () { + if (!allowAction) return; onTap?.call(semanticsLabel); }, child: DecoratedBox( @@ -168,9 +179,14 @@ class KeyboardButton extends StatelessWidget { color: Colors.transparent, border: digitBorderWidth > 0.0 ? Border.all( - color: Theme.of(context) - .colorScheme - .digitPrimaryColor, + color: allowAction + ? Theme.of(context) + .colorScheme + .digitPrimaryColor + : Theme.of(context) + .colorScheme + .digitPrimaryColor + .withOpacity(0.1), width: digitBorderWidth, ) : null, @@ -184,10 +200,28 @@ class KeyboardButton extends StatelessWidget { child: label != null ? Text( label!, - style: digitTextStyle ?? - Theme.of(context) - .textTheme - .keyboardDigitTextStyle, + style: digitTextStyle != null + ? allowAction + ? digitTextStyle + : digitTextStyle!.copyWith( + color: Theme.of(context) + .colorScheme + .digitPrimaryColor + .withOpacity(0.1), + ) + : allowAction + ? Theme.of(context) + .textTheme + .keyboardDigitTextStyle + : Theme.of(context) + .textTheme + .keyboardDigitTextStyle + .copyWith( + color: Theme.of(context) + .colorScheme + .digitPrimaryColor + .withOpacity(0.1), + ), semanticsLabel: semanticsLabel, ) : icon, diff --git a/lib/app/view/app.dart b/lib/app/view/app.dart index 75002a3bc..15a7afaf5 100644 --- a/lib/app/view/app.dart +++ b/lib/app/view/app.dart @@ -171,7 +171,7 @@ class App extends StatelessWidget { manageNetworkCubit: context.read(), ), ), - BlocProvider( + BlocProvider( lazy: false, create: (context) => LiveChatCubit( dioClient: DioClient('', Dio()), diff --git a/lib/connection_bridge/wallet_connect/cubit/wallet_connect_cubit.dart b/lib/connection_bridge/wallet_connect/cubit/wallet_connect_cubit.dart index 881ae6261..e8f00350c 100644 --- a/lib/connection_bridge/wallet_connect/cubit/wallet_connect_cubit.dart +++ b/lib/connection_bridge/wallet_connect/cubit/wallet_connect_cubit.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'package:altme/app/app.dart'; import 'package:altme/connection_bridge/connection_bridge.dart'; -import 'package:altme/wallet/wallet.dart'; import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; diff --git a/lib/dashboard/home/tab_bar/tokens/insert_withdrawal_amount/widgets/token_amount_calculator.dart b/lib/dashboard/home/tab_bar/tokens/insert_withdrawal_amount/widgets/token_amount_calculator.dart index 5a79c79a9..b034db299 100644 --- a/lib/dashboard/home/tab_bar/tokens/insert_withdrawal_amount/widgets/token_amount_calculator.dart +++ b/lib/dashboard/home/tab_bar/tokens/insert_withdrawal_amount/widgets/token_amount_calculator.dart @@ -196,6 +196,7 @@ class _TokenAmountCalculatorPageState extends State { child: LayoutBuilder( builder: (_, constraint) { return NumericKeyboard( + allowAction: true, keyboardUIConfig: KeyboardUIConfig( digitShape: BoxShape.rectangle, spacing: 40, @@ -213,6 +214,7 @@ class _TokenAmountCalculatorPageState extends State { label: '.', semanticsLabel: '.', onTap: _insertKey, + allowAction: true, ), trailingButton: KeyboardButton( digitShape: BoxShape.rectangle, @@ -223,6 +225,7 @@ class _TokenAmountCalculatorPageState extends State { width: Sizes.icon2x, color: Colors.white, ), + allowAction: true, onLongPress: (_) { context .read() diff --git a/lib/dashboard/profile/cubit/profile_cubit.dart b/lib/dashboard/profile/cubit/profile_cubit.dart index daefb86db..ab3375126 100644 --- a/lib/dashboard/profile/cubit/profile_cubit.dart +++ b/lib/dashboard/profile/cubit/profile_cubit.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:convert'; import 'package:altme/app/app.dart'; @@ -20,6 +21,32 @@ class ProfileCubit extends Cubit { final SecureStorageProvider secureStorageProvider; + Timer? _timer; + + int loginAttemptCount = 0; + + void passcodeEntered() { + loginAttemptCount++; + if (loginAttemptCount > 3) return; + + if (loginAttemptCount == 3) { + setActionAllowValue(value: false); + _timer = Timer.periodic(const Duration(minutes: 1), (timer) { + resetloginAttemptCount(); + _timer?.cancel(); + }); + } + } + + void resetloginAttemptCount() { + loginAttemptCount = 0; + setActionAllowValue(value: true); + } + + void setActionAllowValue({required bool value}) { + emit(state.copyWith(status: AppStatus.idle, allowLogin: value)); + } + Future load() async { emit(state.loading()); @@ -72,7 +99,12 @@ class ProfileCubit extends Cubit { isEnterprise: isEnterprise, ); - emit(state.success(model: profileModel)); + emit( + state.copyWith( + model: profileModel, + status: AppStatus.success, + ), + ); } catch (e) { log.e('something went wrong', e); emit( @@ -99,7 +131,12 @@ class ProfileCubit extends Cubit { .delete(SecureStorageKeys.issuerVerificationUrlKey); await secureStorageProvider.delete(SecureStorageKeys.blockchainNetworkKey); await secureStorageProvider.delete(SecureStorageKeys.isEnterpriseUser); - emit(state.success(model: ProfileModel.empty())); + emit( + state.copyWith( + model: ProfileModel.empty(), + status: AppStatus.success, + ), + ); } Future update(ProfileModel profileModel) async { @@ -149,7 +186,12 @@ class ProfileCubit extends Cubit { profileModel.isEnterprise.toString(), ); - emit(state.success(model: profileModel)); + emit( + state.copyWith( + model: profileModel, + status: AppStatus.success, + ), + ); } catch (e) { log.e('something went wrong', e); @@ -185,4 +227,10 @@ class ProfileCubit extends Cubit { state.model.copyWith(issuerVerificationUrl: issuerVerificationUrl); await update(newModel); } + + @override + Future close() async { + _timer?.cancel(); + return super.close(); + } } diff --git a/lib/dashboard/profile/cubit/profile_state.dart b/lib/dashboard/profile/cubit/profile_state.dart index 0a74e797a..972939ce1 100644 --- a/lib/dashboard/profile/cubit/profile_state.dart +++ b/lib/dashboard/profile/cubit/profile_state.dart @@ -6,6 +6,7 @@ class ProfileState extends Equatable { this.status = AppStatus.init, this.message, required this.model, + this.allowLogin = true, }); factory ProfileState.fromJson(Map json) => @@ -14,30 +15,43 @@ class ProfileState extends Equatable { final AppStatus status; final ProfileModel model; final StateMessage? message; + final bool allowLogin; Map toJson() => _$ProfileStateToJson(this); ProfileState loading() { - return ProfileState(status: AppStatus.loading, model: model); + return ProfileState( + status: AppStatus.loading, + model: model, + allowLogin: allowLogin, + ); } ProfileState error({required MessageHandler messageHandler}) { return ProfileState( - status: AppStatus.error, - message: StateMessage.error(messageHandler: messageHandler), - model: model,); + status: AppStatus.error, + message: StateMessage.error(messageHandler: messageHandler), + model: model, + allowLogin: allowLogin, + ); } - ProfileState success({MessageHandler? messageHandler, ProfileModel? model}) { + ProfileState copyWith({ + required AppStatus status, + MessageHandler? messageHandler, + ProfileModel? model, + bool? allowLogin, + }) { return ProfileState( - status: AppStatus.success, + status: status, message: messageHandler == null ? null : StateMessage.success(messageHandler: messageHandler), model: model ?? this.model, + allowLogin: allowLogin ?? this.allowLogin, ); } @override - List get props => [status, model, message]; + List get props => [status, model, message, allowLogin]; } diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 14249c817..99c9cbf78 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -800,5 +800,6 @@ "creator": "Creator", "contractAddress": "Contract address", "lastMetadataSync": "Last metadata sync", - "e2eEncyptedChat": "Chat is encrypted from end to end." + "e2eEncyptedChat": "Chat is encrypted from end to end.", + "pincodeAttemptMessage": "You have entered an incorrect PIN code three times. For security reasons, please wait for one minute before trying again." } \ No newline at end of file diff --git a/lib/l10n/untranslated.json b/lib/l10n/untranslated.json index 2ecb20335..c0c73d514 100644 --- a/lib/l10n/untranslated.json +++ b/lib/l10n/untranslated.json @@ -736,7 +736,8 @@ "creator", "contractAddress", "lastMetadataSync", - "e2eEncyptedChat" + "e2eEncyptedChat", + "pincodeAttemptMessage" ], "es": [ @@ -1476,7 +1477,8 @@ "creator", "contractAddress", "lastMetadataSync", - "e2eEncyptedChat" + "e2eEncyptedChat", + "pincodeAttemptMessage" ], "fr": [ @@ -1495,7 +1497,8 @@ "creator", "contractAddress", "lastMetadataSync", - "e2eEncyptedChat" + "e2eEncyptedChat", + "pincodeAttemptMessage" ], "it": [ @@ -2235,6 +2238,7 @@ "creator", "contractAddress", "lastMetadataSync", - "e2eEncyptedChat" + "e2eEncyptedChat", + "pincodeAttemptMessage" ] } diff --git a/lib/pin_code/cubit/pin_code_view_cubit.dart b/lib/pin_code/cubit/pin_code_view_cubit.dart index bf76655d1..b20135591 100644 --- a/lib/pin_code/cubit/pin_code_view_cubit.dart +++ b/lib/pin_code/cubit/pin_code_view_cubit.dart @@ -1,14 +1,20 @@ import 'package:altme/app/app.dart'; +import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/pin_code/pin_code.dart'; import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; part 'pin_code_view_state.dart'; class PinCodeViewCubit extends Cubit { - PinCodeViewCubit() : super(PinCodeViewState()); + PinCodeViewCubit({ + required this.profileCubit, + }) : super(const PinCodeViewState()); + + final ProfileCubit profileCubit; void setEnteredPasscode(String enteredPasscode) { - emit(state.copyWith(enteredPasscode)); + emit(state.copyWith(enteredPasscode: enteredPasscode)); } void onDeleteCancelButtonPressed(CancelCallback? cancelCallback) { diff --git a/lib/pin_code/cubit/pin_code_view_state.dart b/lib/pin_code/cubit/pin_code_view_state.dart index b5b7d7d10..ba520b7a2 100644 --- a/lib/pin_code/cubit/pin_code_view_state.dart +++ b/lib/pin_code/cubit/pin_code_view_state.dart @@ -1,13 +1,18 @@ part of 'pin_code_view_cubit.dart'; -class PinCodeViewState { - PinCodeViewState({this.enteredPasscode = ''}); +class PinCodeViewState extends Equatable { + const PinCodeViewState({ + this.enteredPasscode = '', + }); final String enteredPasscode; - PinCodeViewState copyWith(String? enteredPasscode) { + PinCodeViewState copyWith({String? enteredPasscode}) { return PinCodeViewState( enteredPasscode: enteredPasscode ?? this.enteredPasscode, ); } + + @override + List get props => [enteredPasscode]; } diff --git a/lib/pin_code/view/confirm_pin_code_page.dart b/lib/pin_code/view/confirm_pin_code_page.dart index 360eb47cd..76fa8cc61 100644 --- a/lib/pin_code/view/confirm_pin_code_page.dart +++ b/lib/pin_code/view/confirm_pin_code_page.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:altme/app/app.dart'; +import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/l10n/l10n.dart'; import 'package:altme/onboarding/onboarding.dart'; import 'package:altme/pin_code/pin_code.dart'; @@ -38,7 +39,8 @@ class ConfirmPinCodePage extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => PinCodeViewCubit(), + create: (context) => + PinCodeViewCubit(profileCubit: context.read()), child: ConfirmPinCodeView( storedPassword: storedPassword, isValidCallback: isValidCallback, diff --git a/lib/pin_code/view/enter_new_pin_code_page.dart b/lib/pin_code/view/enter_new_pin_code_page.dart index ed5723899..9f3c6cb6d 100644 --- a/lib/pin_code/view/enter_new_pin_code_page.dart +++ b/lib/pin_code/view/enter_new_pin_code_page.dart @@ -1,4 +1,5 @@ import 'package:altme/app/app.dart'; +import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/l10n/l10n.dart'; import 'package:altme/onboarding/onboarding.dart'; import 'package:altme/pin_code/pin_code.dart'; @@ -31,7 +32,8 @@ class EnterNewPinCodePage extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => PinCodeViewCubit(), + create: (context) => + PinCodeViewCubit(profileCubit: context.read()), child: EnterNewPinCodeView( isValidCallback: isValidCallback, isFromOnboarding: isFromOnboarding, diff --git a/lib/pin_code/view/pin_code_page.dart b/lib/pin_code/view/pin_code_page.dart index 8526aa29c..d7fcb113c 100644 --- a/lib/pin_code/view/pin_code_page.dart +++ b/lib/pin_code/view/pin_code_page.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:altme/app/app.dart'; +import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/l10n/l10n.dart'; import 'package:altme/pin_code/cubit/pin_code_view_cubit.dart'; import 'package:altme/pin_code/widgets/widgets.dart'; @@ -36,7 +37,8 @@ class PinCodePage extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => PinCodeViewCubit(), + create: (context) => + PinCodeViewCubit(profileCubit: context.read()), child: PinCodeView( isValidCallback: isValidCallback, restrictToBack: restrictToBack, @@ -98,33 +100,48 @@ class _PinCodeViewState extends State { child: BasePage( backgroundColor: Theme.of(context).colorScheme.background, scrollView: false, - body: PinCodeWidget( - title: l10n.enterYourPinCode, - subTitle: l10n.pinCodeMessage, - passwordEnteredCallback: _onPasscodeEntered, - deleteButton: Text( - l10n.delete, - style: Theme.of(context).textTheme.labelLarge, - ), - cancelButton: Text( - l10n.cancel, - style: Theme.of(context).textTheme.labelLarge, - ), - cancelCallback: _onPasscodeCancelled, - isValidCallback: () { - Navigator.pop(context); - widget.isValidCallback.call(); + body: BlocBuilder( + builder: (context, state) { + return PinCodeWidget( + title: l10n.enterYourPinCode, + subTitle: l10n.pinCodeMessage, + passwordEnteredCallback: _onPasscodeEntered, + deleteButton: Text( + l10n.delete, + style: Theme.of(context).textTheme.labelLarge, + ), + cancelButton: Text( + l10n.cancel, + style: Theme.of(context).textTheme.labelLarge, + ), + cancelCallback: _onPasscodeCancelled, + isValidCallback: () { + Navigator.pop(context); + widget.isValidCallback.call(); + }, + shouldTriggerVerification: _verificationNotifier.stream, + allowAction: state.allowLogin, + ); }, - shouldTriggerVerification: _verificationNotifier.stream, ), ), ); } Future _onPasscodeEntered(String enteredPasscode) async { - final bool isValid = - (await getSecureStorage.get(SecureStorageKeys.pinCode)) == - enteredPasscode; + final profileCubit = context.read(); + + profileCubit.passcodeEntered(); + + bool isValid = false; + + if (profileCubit.state.allowLogin) { + isValid = (await getSecureStorage.get(SecureStorageKeys.pinCode)) == + enteredPasscode; + if (isValid) { + profileCubit.resetloginAttemptCount(); + } + } _verificationNotifier.add(isValid); } diff --git a/lib/pin_code/widgets/circle_ui_config.dart b/lib/pin_code/widgets/circle_ui_config.dart index 4fd13e9da..190f80d75 100644 --- a/lib/pin_code/widgets/circle_ui_config.dart +++ b/lib/pin_code/widgets/circle_ui_config.dart @@ -23,11 +23,13 @@ class Circle extends StatelessWidget { this.filled = false, required this.circleUIConfig, this.extraSize = 0, + required this.allowAction, }); final bool filled; final CircleUIConfig circleUIConfig; final double extraSize; + final bool allowAction; @override Widget build(BuildContext context) { @@ -39,7 +41,13 @@ class Circle extends StatelessWidget { color: filled ? circleUIConfig.fillColor : null, shape: BoxShape.circle, border: Border.all( - color: filled ? circleUIConfig.fillColor : circleUIConfig.borderColor, + color: filled + ? allowAction + ? circleUIConfig.fillColor + : circleUIConfig.fillColor.withOpacity(0.1) + : allowAction + ? circleUIConfig.borderColor + : circleUIConfig.borderColor.withOpacity(0.1), width: circleUIConfig.borderWidth, ), ), diff --git a/lib/pin_code/widgets/num_keyboard.dart b/lib/pin_code/widgets/num_keyboard.dart index 7a1825e10..82b023866 100644 --- a/lib/pin_code/widgets/num_keyboard.dart +++ b/lib/pin_code/widgets/num_keyboard.dart @@ -10,23 +10,28 @@ class NumKeyboard extends StatelessWidget { this.passwordDigits = 4, required this.passwordEnteredCallback, this.cancelCallback, + required this.allowAction, }) : keyboardUIConfig = keyboardUIConfig ?? const KeyboardUIConfig(); final KeyboardUIConfig keyboardUIConfig; final int passwordDigits; final PasswordEnteredCallback passwordEnteredCallback; final CancelCallback? cancelCallback; + final bool allowAction; @override Widget build(BuildContext context) { return NumericKeyboard( - onKeyboardTap: (text) => - context.read().onKeyboardButtonPressed( - passwordDigits: passwordDigits, - passwordEnteredCallback: passwordEnteredCallback, - text: text, - cancelCallback: cancelCallback, - ), + allowAction: allowAction, + onKeyboardTap: (text) { + if (!allowAction) return; + context.read().onKeyboardButtonPressed( + passwordDigits: passwordDigits, + passwordEnteredCallback: passwordEnteredCallback, + text: text, + cancelCallback: cancelCallback, + ); + }, keyboardUIConfig: keyboardUIConfig, ); } diff --git a/lib/pin_code/widgets/pin_code_title.dart b/lib/pin_code/widgets/pin_code_title.dart index 6c80ebc3a..9dd1c56dc 100644 --- a/lib/pin_code/widgets/pin_code_title.dart +++ b/lib/pin_code/widgets/pin_code_title.dart @@ -6,10 +6,12 @@ class PinCodeTitle extends StatelessWidget { super.key, required this.title, required this.subTitle, + required this.allowAction, }); final String title; final String? subTitle; + final bool allowAction; @override Widget build(BuildContext context) { @@ -18,13 +20,23 @@ class PinCodeTitle extends StatelessWidget { Text( title, textAlign: TextAlign.center, - style: Theme.of(context).textTheme.pinCodeTitle, + style: allowAction + ? Theme.of(context).textTheme.pinCodeTitle + : Theme.of(context) + .textTheme + .pinCodeTitle + .copyWith(color: Theme.of(context).colorScheme.redColor), ), if (subTitle != null) ...[ const SizedBox(height: 10), Text( subTitle!, - style: Theme.of(context).textTheme.pinCodeMessage, + style: allowAction + ? Theme.of(context).textTheme.pinCodeMessage + : Theme.of(context) + .textTheme + .pinCodeMessage + .copyWith(color: Theme.of(context).colorScheme.redColor), textAlign: TextAlign.center, ), ] diff --git a/lib/pin_code/widgets/pin_code_widget.dart b/lib/pin_code/widgets/pin_code_widget.dart index d2d810edc..9f5effcd9 100644 --- a/lib/pin_code/widgets/pin_code_widget.dart +++ b/lib/pin_code/widgets/pin_code_widget.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:altme/app/app.dart'; +import 'package:altme/l10n/l10n.dart'; import 'package:altme/pin_code/pin_code.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -23,6 +24,7 @@ class PinCodeWidget extends StatefulWidget { this.cancelCallback, this.subTitle, this.header, + this.allowAction = true, }) : circleUIConfig = circleUIConfig ?? const CircleUIConfig(), keyboardUIConfig = keyboardUIConfig ?? const KeyboardUIConfig(); @@ -31,6 +33,7 @@ class PinCodeWidget extends StatefulWidget { final String? subTitle; final int passwordDigits; final PasswordEnteredCallback passwordEnteredCallback; + final bool allowAction; // Cancel button and delete button will be switched based on the screen state final Widget cancelButton; @@ -80,74 +83,101 @@ class _PinCodeWidgetState extends State @override Widget build(BuildContext context) { + final l10n = context.l10n; return BlocBuilder( builder: (context, state) { + final PinCodeViewCubit pinCodeViewCubit = + context.read(); + final enteredPasscode = pinCodeViewCubit.state.enteredPasscode; + return OrientationBuilder( builder: (context, orientation) { return orientation == Orientation.portrait - ? Stack( - children: [ - Positioned( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, + ? SingleChildScrollView( + child: Column( + children: [ + Stack( children: [ - if (widget.header != null) - widget.header! - else - const AltMeLogo(size: Sizes.logoLarge), - const SizedBox(height: Sizes.spaceNormal), - PinCodeTitle( - title: widget.title, - subTitle: widget.subTitle, + Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + if (widget.header != null) + widget.header! + else + const AltMeLogo(size: Sizes.logoLarge), + const SizedBox(height: Sizes.spaceNormal), + PinCodeTitle( + title: widget.title, + subTitle: widget.allowAction + ? widget.subTitle + : l10n.pincodeAttemptMessage, + allowAction: widget.allowAction, + ), + Container( + margin: const EdgeInsets.only(top: 16), + height: 40, + child: AnimatedBuilder( + animation: animation, + builder: (_, __) { + return Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: List.generate( + widget.passwordDigits, + (index) => Container( + margin: const EdgeInsets.all(8), + child: Circle( + allowAction: widget.allowAction, + filled: index < + enteredPasscode.length, + circleUIConfig: + widget.circleUIConfig, + extraSize: animation.value, + ), + ), + ), + ); + }, + ), + ), + NumKeyboard( + passwordEnteredCallback: + widget.passwordEnteredCallback, + keyboardUIConfig: widget.keyboardUIConfig, + passwordDigits: widget.passwordDigits, + cancelCallback: widget.cancelCallback, + allowAction: widget.allowAction, + ), + widget.bottomWidget ?? Container() + ], ), - Container( - margin: const EdgeInsets.only(top: 16), - height: 40, - child: AnimatedBuilder( - animation: animation, - builder: (_, __) { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: _buildCircles(), - ); - }, + Positioned( + bottom: 0, + right: 0, + child: Align( + alignment: Alignment.bottomRight, + child: DeleteButton( + cancelButton: widget.cancelButton, + deleteButton: widget.deleteButton, + cancelCallback: widget.cancelCallback, + keyboardUIConfig: widget.keyboardUIConfig, + ), ), ), - NumKeyboard( - passwordEnteredCallback: - widget.passwordEnteredCallback, - keyboardUIConfig: widget.keyboardUIConfig, - passwordDigits: widget.passwordDigits, - cancelCallback: widget.cancelCallback, - ), - widget.bottomWidget ?? Container() ], ), - ), - Positioned( - bottom: 0, - right: 0, - child: Align( - alignment: Alignment.bottomRight, - child: DeleteButton( - cancelButton: widget.cancelButton, - deleteButton: widget.deleteButton, - cancelCallback: widget.cancelCallback, - keyboardUIConfig: widget.keyboardUIConfig, - ), - ), - ), - ], + ], + ), ) : Stack( children: [ - Positioned( - child: Center( - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Stack( + Center( + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Expanded( + child: Stack( children: [ Positioned( child: Align( @@ -169,6 +199,7 @@ class _PinCodeWidgetState extends State PinCodeTitle( title: widget.title, subTitle: widget.subTitle, + allowAction: widget.allowAction, ), Container( margin: @@ -180,7 +211,26 @@ class _PinCodeWidgetState extends State return Row( mainAxisAlignment: MainAxisAlignment.center, - children: _buildCircles(), + children: List.generate( + widget.passwordDigits, + (index) => Container( + margin: + const EdgeInsets.all( + 8, + ), + child: Circle( + allowAction: + widget.allowAction, + filled: index < + enteredPasscode + .length, + circleUIConfig: widget + .circleUIConfig, + extraSize: + animation.value, + ), + ), + ), ); }, ), @@ -200,15 +250,18 @@ class _PinCodeWidgetState extends State Container() ], ), - NumKeyboard( + ), + Expanded( + child: NumKeyboard( passwordEnteredCallback: widget.passwordEnteredCallback, keyboardUIConfig: widget.keyboardUIConfig, passwordDigits: widget.passwordDigits, cancelCallback: widget.cancelCallback, + allowAction: widget.allowAction, ), - ], - ), + ), + ], ), ), Positioned( @@ -232,27 +285,6 @@ class _PinCodeWidgetState extends State ); } - List _buildCircles() { - final PinCodeViewCubit pinCodeViewCubit = context.read(); - final enteredPasscode = pinCodeViewCubit.state.enteredPasscode; - final list = []; - final config = widget.circleUIConfig; - final extraSize = animation.value; - for (int i = 0; i < widget.passwordDigits; i++) { - list.add( - Container( - margin: const EdgeInsets.all(8), - child: Circle( - filled: i < enteredPasscode.length, - circleUIConfig: config, - extraSize: extraSize, - ), - ), - ); - } - return list; - } - @override void didUpdateWidget(PinCodeWidget old) { super.didUpdateWidget(old); diff --git a/lib/theme/app_theme/app_theme.dart b/lib/theme/app_theme/app_theme.dart index 6ea8a5f24..42ced8dde 100644 --- a/lib/theme/app_theme/app_theme.dart +++ b/lib/theme/app_theme/app_theme.dart @@ -105,6 +105,7 @@ abstract class AppTheme { } extension CustomColorScheme on ColorScheme { + Color get redColor => const Color(0xFFFF0045); Color get transactionApplied => const Color(0xFF00B267); Color get transactionFailed => const Color(0xFFFF0045); Color get transactionSkipped => const Color(0xFFFF5F0A); diff --git a/pubspec.lock b/pubspec.lock index 8f4f97398..5e2029553 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2431,5 +2431,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.19.0 <3.7.0" + dart: ">=2.19.0 <3.0.0" flutter: ">=3.7.0-0" From ceb6e40aba963e7fab36bc837a2433fa7aa65136 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Wed, 1 Mar 2023 15:50:41 +0530 Subject: [PATCH 092/190] bloometa pass added --- assets/image/bloometa-dummy.png | Bin 0 -> 13629 bytes assets/image/bloometa-pass.png | Bin 8991 -> 13189 bytes lib/app/shared/constants/constant_list.dart | 2 +- lib/app/shared/constants/image_strings.dart | 1 + lib/app/shared/constants/urls.dart | 3 +++ lib/app/shared/dio_client/dio_client.dart | 1 + .../credential_subject_type_extension.dart | 4 ++-- .../list/cubit/credential_list_cubit.dart | 7 ++++--- .../home_credential/home_credential.dart | 3 ++- 9 files changed, 14 insertions(+), 7 deletions(-) create mode 100644 assets/image/bloometa-dummy.png diff --git a/assets/image/bloometa-dummy.png b/assets/image/bloometa-dummy.png new file mode 100644 index 0000000000000000000000000000000000000000..776567e6511eeb540da43f1fdf49d53443af3775 GIT binary patch literal 13629 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 F{{?*6G0gw~ literal 8991 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 diff --git a/lib/app/shared/constants/constant_list.dart b/lib/app/shared/constants/constant_list.dart index dbee7f454..81fb78c07 100644 --- a/lib/app/shared/constants/constant_list.dart +++ b/lib/app/shared/constants/constant_list.dart @@ -4,12 +4,12 @@ class DiscoverList { static final List gamingCategories = [ CredentialSubjectType.tezotopiaMembership, CredentialSubjectType.chainbornMembership, + CredentialSubjectType.bloometaPass, CredentialSubjectType.troopezPass, CredentialSubjectType.pigsPass, CredentialSubjectType.matterlightPass, // CredentialSubjectType.dogamiPass, CredentialSubjectType.bunnyPass, - CredentialSubjectType.bloometaPass, ]; static final List communityCategories = [ diff --git a/lib/app/shared/constants/image_strings.dart b/lib/app/shared/constants/image_strings.dart index 20ae488e8..1d943e41f 100644 --- a/lib/app/shared/constants/image_strings.dart +++ b/lib/app/shared/constants/image_strings.dart @@ -92,6 +92,7 @@ class ImageStrings { '$imagePath/dummy_nationality_card.png'; static const String tezotopiaMemberShipDummy = '$imagePath/tezotopia-membership-dummy.png'; + static const String bloometaDummy = '$imagePath/bloometa-dummy.png'; static const String chainbornMemberShipDummy = '$imagePath/chainborn-membership-dummy.png'; static const String twitterCardDummy = '$imagePath/twitter-card-dummy.png'; diff --git a/lib/app/shared/constants/urls.dart b/lib/app/shared/constants/urls.dart index 0fe3298a9..701ce6839 100644 --- a/lib/app/shared/constants/urls.dart +++ b/lib/app/shared/constants/urls.dart @@ -29,6 +29,9 @@ class Urls { static const String tezotopiaMembershipCardUrl = 'https://issuer.talao.co/tezotopia/membershipcard/'; + static const String bloometaCardUrl = + 'https://issuer.talao.co/bloometa/membershipcard/'; + static const String chainbornMembershipCardUrl = 'https://issuer.talao.co/chainborn/membershipcard/'; diff --git a/lib/app/shared/dio_client/dio_client.dart b/lib/app/shared/dio_client/dio_client.dart index cb50cf60f..e6b70062a 100644 --- a/lib/app/shared/dio_client/dio_client.dart +++ b/lib/app/shared/dio_client/dio_client.dart @@ -81,6 +81,7 @@ class DioClient { Map headers, ) async { if (uri.contains(Urls.tezotopiaMembershipCardUrl) || + uri.contains(Urls.bloometaCardUrl) || uri.contains(Urls.chainbornMembershipCardUrl) || uri.contains(Urls.twitterCardUrl)) { await dotenv.load(); 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 0da280448..a6e69ac6d 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 @@ -154,8 +154,7 @@ extension CredentialSubjectTypeExtension on CredentialSubjectType { this == CredentialSubjectType.pigsPass || this == CredentialSubjectType.bunnyPass || this == CredentialSubjectType.troopezPass || - this == CredentialSubjectType.matterlightPass || - this == CredentialSubjectType.bloometaPass) { + this == CredentialSubjectType.matterlightPass) { return true; } return false; @@ -394,6 +393,7 @@ extension CredentialSubjectTypeExtension on CredentialSubjectType { bool get byPassDeepLink { if (this == CredentialSubjectType.tezotopiaMembership || + this == CredentialSubjectType.bloometaPass || this == CredentialSubjectType.chainbornMembership || this == CredentialSubjectType.twitterCard || this == CredentialSubjectType.over13 || diff --git a/lib/dashboard/home/tab_bar/credentials/list/cubit/credential_list_cubit.dart b/lib/dashboard/home/tab_bar/credentials/list/cubit/credential_list_cubit.dart index 94f201254..dbf03c003 100644 --- a/lib/dashboard/home/tab_bar/credentials/list/cubit/credential_list_cubit.dart +++ b/lib/dashboard/home/tab_bar/credentials/list/cubit/credential_list_cubit.dart @@ -49,6 +49,7 @@ class CredentialListCubit extends Cubit { case CredentialSubjectType.tezVoucher: case CredentialSubjectType.diplomaCard: case CredentialSubjectType.tezotopiaMembership: + case CredentialSubjectType.bloometaPass: case CredentialSubjectType.chainbornMembership: gamingCategories.remove(credentialSubjectType); break; @@ -85,7 +86,6 @@ class CredentialListCubit extends Cubit { case CredentialSubjectType.ecole42LearningAchievement: case CredentialSubjectType.emailPass: case CredentialSubjectType.walletCredential: - case CredentialSubjectType.bloometaPass: case CredentialSubjectType.dogamiPass: case CredentialSubjectType.bunnyPass: case CredentialSubjectType.troopezPass: @@ -241,6 +241,7 @@ class CredentialListCubit extends Cubit { .credentialPreview.credentialSubjectModel.credentialSubjectType; switch (credentialSubjectType) { case CredentialSubjectType.tezotopiaMembership: + case CredentialSubjectType.bloometaPass: case CredentialSubjectType.chainbornMembership: case CredentialSubjectType.voucher: case CredentialSubjectType.tezVoucher: @@ -276,7 +277,6 @@ class CredentialListCubit extends Cubit { case CredentialSubjectType.ecole42LearningAchievement: case CredentialSubjectType.emailPass: case CredentialSubjectType.diplomaCard: - case CredentialSubjectType.bloometaPass: case CredentialSubjectType.walletCredential: case CredentialSubjectType.learningAchievement: case CredentialSubjectType.loyaltyCard: @@ -554,7 +554,8 @@ class CredentialListCubit extends Cubit { if (credentialSubjectType == CredentialSubjectType.tezotopiaMembership || credentialSubjectType == - CredentialSubjectType.chainbornMembership) { + CredentialSubjectType.chainbornMembership || + credentialSubjectType == CredentialSubjectType.bloometaPass) { gamingCategories.add(credentialSubjectType); } diff --git a/lib/dashboard/home/tab_bar/credentials/models/home_credential/home_credential.dart b/lib/dashboard/home/tab_bar/credentials/models/home_credential/home_credential.dart index 07203d2fb..7052db98e 100644 --- a/lib/dashboard/home/tab_bar/credentials/models/home_credential/home_credential.dart +++ b/lib/dashboard/home/tab_bar/credentials/models/home_credential/home_credential.dart @@ -247,7 +247,8 @@ class HomeCredential extends Equatable { break; case CredentialSubjectType.bloometaPass: - image = ImageStrings.bloometaPass; + image = ImageStrings.bloometaDummy; + link = Urls.bloometaCardUrl; break; case CredentialSubjectType.voucher: From 8b37b3dab02fecc7fe544b8afd51320014d4c176 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Wed, 1 Mar 2023 17:11:46 +0530 Subject: [PATCH 093/190] disable whats new popup after onboarding --- lib/app/shared/enum/status/splash_status.dart | 2 +- lib/dashboard/dashboard/view/dashboard_page.dart | 2 +- lib/import_wallet/cubit/import_wallet_cubit.dart | 6 ++++++ lib/import_wallet/view/import_from_wallet_page.dart | 2 ++ lib/import_wallet/view/import_wallet_page.dart | 2 ++ .../gen_phrase/cubit/onboarding_gen_phrase_cubit.dart | 6 ++++++ .../gen_phrase/view/onboarding_gen_phrase.dart | 2 ++ lib/splash/cubit/splash_cubit.dart | 10 ++++++++-- 8 files changed, 28 insertions(+), 4 deletions(-) diff --git a/lib/app/shared/enum/status/splash_status.dart b/lib/app/shared/enum/status/splash_status.dart index eb40dd49b..05d39eee3 100644 --- a/lib/app/shared/enum/status/splash_status.dart +++ b/lib/app/shared/enum/status/splash_status.dart @@ -1 +1 @@ -enum SplashStatus { init, routeToPassCode, routeToOnboarding } +enum SplashStatus { init, routeToPassCode, routeToOnboarding, idle } diff --git a/lib/dashboard/dashboard/view/dashboard_page.dart b/lib/dashboard/dashboard/view/dashboard_page.dart index da21b4531..e1f3daf95 100644 --- a/lib/dashboard/dashboard/view/dashboard_page.dart +++ b/lib/dashboard/dashboard/view/dashboard_page.dart @@ -47,7 +47,7 @@ class _DashboardViewState extends State { final splashCubit = context.read(); if (splashCubit.state.isNewVersion) { WhatIsNewDialog.show(context); - splashCubit.dialogOpened(); + splashCubit.disableWhatsNewPopUp(); } }); }); diff --git a/lib/import_wallet/cubit/import_wallet_cubit.dart b/lib/import_wallet/cubit/import_wallet_cubit.dart index c32029318..e61958288 100644 --- a/lib/import_wallet/cubit/import_wallet_cubit.dart +++ b/lib/import_wallet/cubit/import_wallet_cubit.dart @@ -1,6 +1,7 @@ import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/did/did.dart'; +import 'package:altme/splash/splash.dart'; import 'package:altme/wallet/wallet.dart'; import 'package:bip39/bip39.dart' as bip39; import 'package:did_kit/did_kit.dart'; @@ -22,6 +23,7 @@ class ImportWalletCubit extends Cubit { required this.homeCubit, required this.didCubit, required this.walletCubit, + required this.splashCubit, }) : super(const ImportWalletState()); final DIDKitProvider didKitProvider; @@ -30,6 +32,7 @@ class ImportWalletCubit extends Cubit { final HomeCubit homeCubit; final DIDCubit didCubit; final WalletCubit walletCubit; + final SplashCubit splashCubit; void isMnemonicsOrKeyValid(String value) { //different type of tezos private keys start with 'edsk' , @@ -94,6 +97,9 @@ class ImportWalletCubit extends Cubit { ); } + /// what's new popup disabled + splashCubit.disableWhatsNewPopUp(); + /// crypto wallet with unknown blockchain type await walletCubit.createCryptoWallet( accountName: accountName, diff --git a/lib/import_wallet/view/import_from_wallet_page.dart b/lib/import_wallet/view/import_from_wallet_page.dart index a4ff37c07..adff7b847 100644 --- a/lib/import_wallet/view/import_from_wallet_page.dart +++ b/lib/import_wallet/view/import_from_wallet_page.dart @@ -4,6 +4,7 @@ import 'package:altme/did/did.dart'; import 'package:altme/import_wallet/import_wallet.dart'; import 'package:altme/l10n/l10n.dart'; import 'package:altme/onboarding/onboarding.dart'; +import 'package:altme/splash/splash.dart'; import 'package:altme/theme/theme.dart'; import 'package:altme/wallet/cubit/wallet_cubit.dart'; import 'package:did_kit/did_kit.dart'; @@ -48,6 +49,7 @@ class ImportFromWalletPage extends StatelessWidget { keyGenerator: KeyGenerator(), homeCubit: context.read(), walletCubit: context.read(), + splashCubit: context.read(), ), child: ImportFromOtherWalletView( walletTypeModel: walletTypeModel, diff --git a/lib/import_wallet/view/import_wallet_page.dart b/lib/import_wallet/view/import_wallet_page.dart index 7117094b1..fad3afa68 100644 --- a/lib/import_wallet/view/import_wallet_page.dart +++ b/lib/import_wallet/view/import_wallet_page.dart @@ -4,6 +4,7 @@ import 'package:altme/did/did.dart'; import 'package:altme/import_wallet/import_wallet.dart'; import 'package:altme/l10n/l10n.dart'; import 'package:altme/onboarding/onboarding.dart'; +import 'package:altme/splash/splash.dart'; import 'package:altme/theme/theme.dart'; import 'package:altme/wallet/cubit/wallet_cubit.dart'; import 'package:did_kit/did_kit.dart'; @@ -44,6 +45,7 @@ class ImportWalletPage extends StatelessWidget { keyGenerator: KeyGenerator(), homeCubit: context.read(), walletCubit: context.read(), + splashCubit: context.read(), ), child: ImportWalletView( accountName: accountName, diff --git a/lib/onboarding/gen_phrase/cubit/onboarding_gen_phrase_cubit.dart b/lib/onboarding/gen_phrase/cubit/onboarding_gen_phrase_cubit.dart index 936cdb21c..075670033 100644 --- a/lib/onboarding/gen_phrase/cubit/onboarding_gen_phrase_cubit.dart +++ b/lib/onboarding/gen_phrase/cubit/onboarding_gen_phrase_cubit.dart @@ -1,6 +1,7 @@ import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/did/did.dart'; +import 'package:altme/splash/splash.dart'; import 'package:altme/wallet/wallet.dart'; import 'package:did_kit/did_kit.dart'; import 'package:equatable/equatable.dart'; @@ -22,6 +23,7 @@ class OnBoardingGenPhraseCubit extends Cubit { required this.didCubit, required this.homeCubit, required this.walletCubit, + required this.splashCubit, }) : super(const OnBoardingGenPhraseState()); final SecureStorageProvider secureStorageProvider; @@ -30,6 +32,7 @@ class OnBoardingGenPhraseCubit extends Cubit { final DIDCubit didCubit; final HomeCubit homeCubit; final WalletCubit walletCubit; + final SplashCubit splashCubit; final log = getLogger('OnBoardingGenPhraseCubit'); @@ -68,6 +71,9 @@ class OnBoardingGenPhraseCubit extends Cubit { verificationMethod: verificationMethod, ); + /// what's new popup disabled + splashCubit.disableWhatsNewPopUp(); + /// crypto wallet await walletCubit.createCryptoWallet( mnemonicOrKey: mnemonicFormatted, diff --git a/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart b/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart index 57728e926..40289180e 100644 --- a/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart +++ b/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart @@ -3,6 +3,7 @@ import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/did/did.dart'; import 'package:altme/l10n/l10n.dart'; import 'package:altme/onboarding/onboarding.dart'; +import 'package:altme/splash/splash.dart'; import 'package:altme/theme/theme.dart'; import 'package:altme/wallet/cubit/wallet_cubit.dart'; import 'package:bip39/bip39.dart' as bip39; @@ -30,6 +31,7 @@ class OnBoardingGenPhrasePage extends StatelessWidget { keyGenerator: KeyGenerator(), homeCubit: context.read(), walletCubit: context.read(), + splashCubit: context.read(), ), child: const OnBoardingGenPhraseView(), ); diff --git a/lib/splash/cubit/splash_cubit.dart b/lib/splash/cubit/splash_cubit.dart index 601ce49a1..43dbfd4e9 100644 --- a/lib/splash/cubit/splash_cubit.dart +++ b/lib/splash/cubit/splash_cubit.dart @@ -99,11 +99,17 @@ class SplashCubit extends Cubit { versionNumber: packageInfo.version, buildNumber: packageInfo.buildNumber, isNewVersion: isNewVersion, + status: SplashStatus.idle, ), ); } - void dialogOpened() { - emit(state.copyWith(isNewVersion: false)); + void disableWhatsNewPopUp() { + emit( + state.copyWith( + isNewVersion: false, + status: SplashStatus.idle, + ), + ); } } From 3e920af65b359ee1fabd71f72d5ffe2c46313e6e Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Wed, 1 Mar 2023 20:19:48 +0530 Subject: [PATCH 094/190] add choice for kyc and passbase #1297 --- .../shared/widget/custom_listtile_card.dart | 13 ++-- .../ai_age_verificatioin.dart | 1 + .../choose_verification_method.dart | 1 + .../view/choose_verification_method_page.dart | 76 +++++++++++++++++++ .../home/home/widgets/kyc_dialog.dart | 12 +-- .../helper_functions/discover_credential.dart | 59 ++++++++------ lib/l10n/arb/app_en.arb | 11 +-- lib/l10n/untranslated.json | 32 +++++--- 8 files changed, 152 insertions(+), 53 deletions(-) create mode 100644 lib/dashboard/ai_age_verification/choose_verification_method/choose_verification_method.dart create mode 100644 lib/dashboard/ai_age_verification/choose_verification_method/view/choose_verification_method_page.dart diff --git a/lib/app/shared/widget/custom_listtile_card.dart b/lib/app/shared/widget/custom_listtile_card.dart index 0ee61a6b6..fc1eecce1 100644 --- a/lib/app/shared/widget/custom_listtile_card.dart +++ b/lib/app/shared/widget/custom_listtile_card.dart @@ -1,3 +1,4 @@ +import 'package:altme/app/app.dart'; import 'package:altme/app/shared/constants/sizes.dart'; import 'package:altme/theme/theme.dart'; import 'package:flutter/material.dart'; @@ -31,13 +32,13 @@ class CustomListTileCard extends StatelessWidget { tileColor: Theme.of(context).colorScheme.surface, title: Row( children: [ - Text( - title, - style: Theme.of(context).textTheme.customListTileTitleStyle, - ), - const SizedBox( - width: 10, + Expanded( + child: MyText( + title, + style: Theme.of(context).textTheme.customListTileTitleStyle, + ), ), + const SizedBox(width: 10), if (recommended) const Icon( Icons.thumb_up, diff --git a/lib/dashboard/ai_age_verification/ai_age_verificatioin.dart b/lib/dashboard/ai_age_verification/ai_age_verificatioin.dart index ef7b30a76..35fab66ac 100644 --- a/lib/dashboard/ai_age_verification/ai_age_verificatioin.dart +++ b/lib/dashboard/ai_age_verification/ai_age_verificatioin.dart @@ -1 +1,2 @@ +export 'choose_verification_method/choose_verification_method.dart'; export 'verify_age/verify_age.dart'; diff --git a/lib/dashboard/ai_age_verification/choose_verification_method/choose_verification_method.dart b/lib/dashboard/ai_age_verification/choose_verification_method/choose_verification_method.dart new file mode 100644 index 000000000..d612e8a41 --- /dev/null +++ b/lib/dashboard/ai_age_verification/choose_verification_method/choose_verification_method.dart @@ -0,0 +1 @@ +export 'view/choose_verification_method_page.dart'; diff --git a/lib/dashboard/ai_age_verification/choose_verification_method/view/choose_verification_method_page.dart b/lib/dashboard/ai_age_verification/choose_verification_method/view/choose_verification_method_page.dart new file mode 100644 index 000000000..9207bf46a --- /dev/null +++ b/lib/dashboard/ai_age_verification/choose_verification_method/view/choose_verification_method_page.dart @@ -0,0 +1,76 @@ +import 'package:altme/app/shared/constants/image_strings.dart'; +import 'package:altme/app/shared/enum/type/type.dart'; +import 'package:altme/app/shared/widget/widget.dart'; +import 'package:altme/l10n/l10n.dart'; +import 'package:altme/theme/theme.dart'; +import 'package:flutter/material.dart'; + +class ChooseVerificationMethodPage extends StatelessWidget { + const ChooseVerificationMethodPage({ + super.key, + required this.credentialSubjectType, + required this.onSelectPassbase, + required this.onSelectKYC, + }); + + final CredentialSubjectType credentialSubjectType; + final Function onSelectPassbase; + final Function onSelectKYC; + + static Route route({ + required CredentialSubjectType credentialSubjectType, + required Function onSelectPassbase, + required Function onSelectKYC, + }) { + return MaterialPageRoute( + settings: const RouteSettings(name: '/AiAgeResultPage'), + builder: (_) => ChooseVerificationMethodPage( + credentialSubjectType: credentialSubjectType, + onSelectPassbase: onSelectPassbase, + onSelectKYC: onSelectKYC, + ), + ); + } + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + + String title = ''; + + if (credentialSubjectType == CredentialSubjectType.over13) { + title = l10n.chooseMethodPageOver13Title; + } else if (credentialSubjectType == CredentialSubjectType.over18) { + title = l10n.chooseMethodPageOver18Title; + } else { + title = l10n.chooseMethodPageAgeRangeTitle; + } + return BasePage( + title: title, + titleLeading: const BackLeadingButton(), + body: Column( + children: [ + Text( + l10n.chooseMethodPageSubtitle, + style: Theme.of(context).textTheme.customListTileSubTitleStyle, + textAlign: TextAlign.justify, + ), + const SizedBox(height: 20), + CustomListTileCard( + title: l10n.kycTitle, + subTitle: l10n.kycSubtitle, + imageAssetPath: ImageStrings.userCircleAdded, + onTap: () => onSelectKYC.call(), + ), + const SizedBox(height: 20), + CustomListTileCard( + title: l10n.passbaseTitle, + subTitle: l10n.passbaseSubtitle, + imageAssetPath: ImageStrings.userCircleAdded, + onTap: () => onSelectPassbase.call(), + ), + ], + ), + ); + } +} diff --git a/lib/dashboard/home/home/widgets/kyc_dialog.dart b/lib/dashboard/home/home/widgets/kyc_dialog.dart index d99479955..53d8575c4 100644 --- a/lib/dashboard/home/home/widgets/kyc_dialog.dart +++ b/lib/dashboard/home/home/widgets/kyc_dialog.dart @@ -99,16 +99,18 @@ class KycDialog extends StatelessWidget { ), Row( mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, children: [ Image.asset( IconStrings.lockCircle, width: Sizes.icon, ), - Text( - l10n.kycDialogFooter, - style: Theme.of(context).textTheme.kycDialogFooter, - textAlign: TextAlign.center, + Expanded( + child: Text( + l10n.kycDialogFooter, + style: Theme.of(context).textTheme.kycDialogFooter, + ), ), ], ), diff --git a/lib/dashboard/home/tab_bar/credentials/helper_functions/discover_credential.dart b/lib/dashboard/home/tab_bar/credentials/helper_functions/discover_credential.dart index 3c4761a36..d5fa0986b 100644 --- a/lib/dashboard/home/tab_bar/credentials/helper_functions/discover_credential.dart +++ b/lib/dashboard/home/tab_bar/credentials/helper_functions/discover_credential.dart @@ -20,33 +20,42 @@ Future discoverCredential({ if (credentialSubjectTypeList .contains(homeCredential.credentialSubjectType)) { getLogger('discoverCredential').i(homeCredential.credentialSubjectType); - //here check for over18 and over13 to take photo for AI KYC + //here check for over18, age range and over13 to take photo for AI KYC if (homeCredential.credentialSubjectType.checkForAIKYC) { - final passbaseStatus = - await context.read().checkPassbaseStatus(); - if (passbaseStatus != PassBaseStatus.approved) { - // start verification by Yoti AI - await Navigator.of(context).push( - VerifyAgePage.route( - credentialSubjectType: homeCredential.credentialSubjectType, - ), - ); - } else { - await launchUrlAfterDiscovery( - homeCredential: homeCredential, - context: context, - ); - } + // final passbaseStatus = + // await context.read().checkPassbaseStatus(); + await Navigator.push( + context, + ChooseVerificationMethodPage.route( + credentialSubjectType: homeCredential.credentialSubjectType, + onSelectPassbase: () async { + await context.read().checkForPassBaseStatusThenLaunchUrl( + link: homeCredential.link!, + onPassBaseApproved: () async { + await launchUrlAfterDiscovery( + homeCredential: homeCredential, + context: context, + ); + }, + ); + + Navigator.pop(context); + }, + onSelectKYC: () async { + // start verification by Yoti AI + await Navigator.of(context).push( + VerifyAgePage.route( + credentialSubjectType: homeCredential.credentialSubjectType, + ), + ); + }, + ), + ); } else { - await context.read().checkForPassBaseStatusThenLaunchUrl( - link: homeCredential.link!, - onPassBaseApproved: () async { - await launchUrlAfterDiscovery( - homeCredential: homeCredential, - context: context, - ); - }, - ); + await launchUrlAfterDiscovery( + homeCredential: homeCredential, + context: context, + ); } } else { await launchUrlAfterDiscovery( diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 99c9cbf78..9d305db6d 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -660,11 +660,12 @@ "failedToInitCamera": "Camera initialization failed!", "chooseMethodPageOver18Title": "Choose a method to get your Over 18 Proof", "chooseMethodPageOver13Title": "Choose a method to get your Over 13 Proof", - "chooseMethodPageSubtitle": "Get verified by taking a real-time photo\nor by a document check .", - "idDocumentCheck": "ID document check\n+ Real-time photo", - "idDocumentCheckDescription": "Get verified by using an ID card or a passeport.", - "realTimePhoto": "Real-time photo", - "realTimePhotoDescription": "Get verified by taking a photo of yourself.", + "chooseMethodPageAgeRangeTitle": "Choose a method to get your Age Range Proof", + "chooseMethodPageSubtitle": "Get verified by taking a real-time photo of yourself or by a classic ID document check .", + "kycTitle": "Quick photo of you (1min)", + "kycSubtitle": "Get instantly verified by taking a photo of yourself.", + "passbaseTitle": "Full ID document check", + "passbaseSubtitle": "Get verified with an ID card, passport or driving license.", "verifyYourAge": "Verify your age", "verifyYourAgeSubtitle": "This age verification process is very easy and simple. All it requires is a real-time photo.", "verifyYourAgeDescription": "By accepting, you agree that we use a picture to estimate your age. The estimation is done by our partner Yoti that uses your picture only for this purpose and immediately deletes it.\n\nFor more info, review our Privacy Policy.", diff --git a/lib/l10n/untranslated.json b/lib/l10n/untranslated.json index c0c73d514..473ab23e0 100644 --- a/lib/l10n/untranslated.json +++ b/lib/l10n/untranslated.json @@ -596,11 +596,12 @@ "failedToInitCamera", "chooseMethodPageOver18Title", "chooseMethodPageOver13Title", + "chooseMethodPageAgeRangeTitle", "chooseMethodPageSubtitle", - "idDocumentCheck", - "idDocumentCheckDescription", - "realTimePhoto", - "realTimePhotoDescription", + "kycTitle", + "kycSubtitle", + "passbaseTitle", + "passbaseSubtitle", "verifyYourAge", "verifyYourAgeSubtitle", "verifyYourAgeDescription", @@ -1337,11 +1338,12 @@ "failedToInitCamera", "chooseMethodPageOver18Title", "chooseMethodPageOver13Title", + "chooseMethodPageAgeRangeTitle", "chooseMethodPageSubtitle", - "idDocumentCheck", - "idDocumentCheckDescription", - "realTimePhoto", - "realTimePhotoDescription", + "kycTitle", + "kycSubtitle", + "passbaseTitle", + "passbaseSubtitle", "verifyYourAge", "verifyYourAgeSubtitle", "verifyYourAgeDescription", @@ -1484,6 +1486,11 @@ "fr": [ "educationCredentials", "educationCredentialHomeSubtitle", + "chooseMethodPageAgeRangeTitle", + "kycTitle", + "kycSubtitle", + "passbaseTitle", + "passbaseSubtitle", "altmeSupport", "manageKeyDecentralizedId", "manageEbsiDecentralizedId", @@ -2098,11 +2105,12 @@ "failedToInitCamera", "chooseMethodPageOver18Title", "chooseMethodPageOver13Title", + "chooseMethodPageAgeRangeTitle", "chooseMethodPageSubtitle", - "idDocumentCheck", - "idDocumentCheckDescription", - "realTimePhoto", - "realTimePhotoDescription", + "kycTitle", + "kycSubtitle", + "passbaseTitle", + "passbaseSubtitle", "verifyYourAge", "verifyYourAgeSubtitle", "verifyYourAgeDescription", From c2a553884480b6a6f2b8bed586755f51992743f2 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Wed, 1 Mar 2023 19:30:32 +0330 Subject: [PATCH 095/190] update whats new popup UI --- .../widget/add_account/add_account_popup.dart | 2 +- .../widget/button/my_gradient_button.dart | 1 - lib/app/shared/widget/default_dialog.dart | 2 +- .../widget/dialog/becareful_dialog.dart | 2 +- .../shared/widget/dialog/confirm_dialog.dart | 2 +- lib/app/shared/widget/dialog/info_dialog.dart | 2 +- .../widget/dialog/text_field_dialog.dart | 3 +- .../dashboard/view/dashboard_page.dart | 1 - .../dashboard/widgets/new_feature.dart | 2 +- .../dashboard/widgets/new_version_text.dart | 26 + .../dashboard/widgets/what_is_new_dialog.dart | 525 +++++++----------- lib/dashboard/dashboard/widgets/widgets.dart | 1 + .../home/home/widgets/finish_kyc_dialog.dart | 2 +- .../home/home/widgets/kyc_dialog.dart | 2 +- .../home/widgets/token_reward_dialog.dart | 2 +- .../home/home/widgets/wallet_dialog.dart | 2 +- .../widgets/descriptioni_dialog.dart | 3 +- .../widgets/transaction_done_dialog.dart | 2 +- .../widget/kyc_button.dart | 4 +- lib/l10n/arb/app_en.arb | 2 +- lib/theme/app_theme/app_theme.dart | 10 +- pubspec.lock | 2 +- 22 files changed, 244 insertions(+), 356 deletions(-) create mode 100644 lib/dashboard/dashboard/widgets/new_version_text.dart diff --git a/lib/app/shared/widget/add_account/add_account_popup.dart b/lib/app/shared/widget/add_account/add_account_popup.dart index 15de7bfb9..4fe0515b0 100644 --- a/lib/app/shared/widget/add_account/add_account_popup.dart +++ b/lib/app/shared/widget/add_account/add_account_popup.dart @@ -28,7 +28,7 @@ class _AddAccountPopUpState extends State { final l10n = context.l10n; return AlertDialog( - backgroundColor: Theme.of(context).colorScheme.surface, + backgroundColor: Theme.of(context).colorScheme.popupBackground, contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15), shape: RoundedRectangleBorder( side: BorderSide( diff --git a/lib/app/shared/widget/button/my_gradient_button.dart b/lib/app/shared/widget/button/my_gradient_button.dart index ae3ec994e..6a6892601 100644 --- a/lib/app/shared/widget/button/my_gradient_button.dart +++ b/lib/app/shared/widget/button/my_gradient_button.dart @@ -48,7 +48,6 @@ class MyGradientButton extends StatelessWidget { Theme.of(context).colorScheme.startButtonColorA, Theme.of(context).colorScheme.startButtonColorB, ], - stops: const [0.0, 0.4], ); return SizedBox( width: MediaQuery.of(context).size.width, diff --git a/lib/app/shared/widget/default_dialog.dart b/lib/app/shared/widget/default_dialog.dart index 3ecd9c340..ee984a78e 100644 --- a/lib/app/shared/widget/default_dialog.dart +++ b/lib/app/shared/widget/default_dialog.dart @@ -19,7 +19,7 @@ class DefaultDialog extends StatelessWidget { @override Widget build(BuildContext context) { return AlertDialog( - backgroundColor: Theme.of(context).colorScheme.onBackground, + backgroundColor: Theme.of(context).colorScheme.popupBackground, contentPadding: const EdgeInsets.symmetric( horizontal: Sizes.spaceNormal, vertical: Sizes.spaceSmall, diff --git a/lib/app/shared/widget/dialog/becareful_dialog.dart b/lib/app/shared/widget/dialog/becareful_dialog.dart index bf26e710b..f7945ed04 100644 --- a/lib/app/shared/widget/dialog/becareful_dialog.dart +++ b/lib/app/shared/widget/dialog/becareful_dialog.dart @@ -43,7 +43,7 @@ class BeCarefulDialog extends StatelessWidget { Widget build(BuildContext context) { final l10n = context.l10n; return AlertDialog( - backgroundColor: Theme.of(context).colorScheme.onBackground, + backgroundColor: Theme.of(context).colorScheme.popupBackground, contentPadding: const EdgeInsets.symmetric( horizontal: Sizes.spaceNormal, vertical: Sizes.spaceSmall, diff --git a/lib/app/shared/widget/dialog/confirm_dialog.dart b/lib/app/shared/widget/dialog/confirm_dialog.dart index 7c81f9865..5ec81608b 100644 --- a/lib/app/shared/widget/dialog/confirm_dialog.dart +++ b/lib/app/shared/widget/dialog/confirm_dialog.dart @@ -28,7 +28,7 @@ class ConfirmDialog extends StatelessWidget { @override Widget build(BuildContext context) { final color = dialogColor ?? Theme.of(context).colorScheme.primary; - final background = bgColor ?? Theme.of(context).colorScheme.onBackground; + final background = bgColor ?? Theme.of(context).colorScheme.popupBackground; final text = textColor ?? Theme.of(context).colorScheme.dialogText; final l10n = context.l10n; diff --git a/lib/app/shared/widget/dialog/info_dialog.dart b/lib/app/shared/widget/dialog/info_dialog.dart index abc55b407..fbb5e696a 100644 --- a/lib/app/shared/widget/dialog/info_dialog.dart +++ b/lib/app/shared/widget/dialog/info_dialog.dart @@ -25,7 +25,7 @@ class InfoDialog extends StatelessWidget { @override Widget build(BuildContext context) { final color = dialogColor ?? Theme.of(context).colorScheme.primary; - final background = bgColor ?? Theme.of(context).colorScheme.onBackground; + final background = bgColor ?? Theme.of(context).colorScheme.popupBackground; final text = textColor ?? Theme.of(context).colorScheme.dialogText; return AlertDialog( backgroundColor: background, diff --git a/lib/app/shared/widget/dialog/text_field_dialog.dart b/lib/app/shared/widget/dialog/text_field_dialog.dart index 40e5766a9..59a37f3f8 100644 --- a/lib/app/shared/widget/dialog/text_field_dialog.dart +++ b/lib/app/shared/widget/dialog/text_field_dialog.dart @@ -53,7 +53,8 @@ class _TextFieldDialogState extends State { final no = widget.no ?? l10n.no; final color = widget.dialogColor ?? Theme.of(context).colorScheme.primary; - final background = widget.bgColor ?? Theme.of(context).colorScheme.surface; + final background = + widget.bgColor ?? Theme.of(context).colorScheme.popupBackground; final text = widget.textColor ?? Theme.of(context).colorScheme.label; return AlertDialog( diff --git a/lib/dashboard/dashboard/view/dashboard_page.dart b/lib/dashboard/dashboard/view/dashboard_page.dart index e1f3daf95..cb67a6128 100644 --- a/lib/dashboard/dashboard/view/dashboard_page.dart +++ b/lib/dashboard/dashboard/view/dashboard_page.dart @@ -51,7 +51,6 @@ class _DashboardViewState extends State { } }); }); - super.initState(); } diff --git a/lib/dashboard/dashboard/widgets/new_feature.dart b/lib/dashboard/dashboard/widgets/new_feature.dart index 9dd23bf33..39422a598 100644 --- a/lib/dashboard/dashboard/widgets/new_feature.dart +++ b/lib/dashboard/dashboard/widgets/new_feature.dart @@ -11,7 +11,7 @@ class NewFeature extends StatelessWidget { @override Widget build(BuildContext context) { return Padding( - padding: const EdgeInsets.only(bottom: 15), + padding: const EdgeInsets.only(bottom: 20), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/lib/dashboard/dashboard/widgets/new_version_text.dart b/lib/dashboard/dashboard/widgets/new_version_text.dart new file mode 100644 index 000000000..815b17d07 --- /dev/null +++ b/lib/dashboard/dashboard/widgets/new_version_text.dart @@ -0,0 +1,26 @@ +import 'package:altme/app/shared/constants/sizes.dart'; +import 'package:altme/theme/theme.dart'; +import 'package:flutter/material.dart'; + +class NewVersionText extends StatelessWidget { + const NewVersionText({ + super.key, + required this.versionNumber, + }); + + final String versionNumber; + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only( + top: Sizes.spaceLarge, + bottom: Sizes.spaceXSmall, + ), + child: Text( + versionNumber, + style: Theme.of(context).textTheme.newVersionTitle, + textAlign: TextAlign.center, + ), + ); + } +} diff --git a/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart b/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart index 8006737e9..73137df5b 100644 --- a/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart +++ b/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart @@ -28,359 +28,212 @@ class WhatIsNewDialog extends StatelessWidget { return SafeArea( child: AlertDialog( - backgroundColor: Theme.of(context).colorScheme.whatsNewPopupBackground, + backgroundColor: Theme.of(context).colorScheme.popupBackground, contentPadding: const EdgeInsets.all(Sizes.spaceXSmall), insetPadding: const EdgeInsets.symmetric( horizontal: Sizes.spaceSmall, - vertical: Sizes.spaceNormal, + vertical: Sizes.spaceSmall, ), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all( Radius.circular(Sizes.normalRadius), ), ), - content: Column( - mainAxisSize: MainAxisSize.max, + content: Stack( children: [ const Align( alignment: Alignment.topRight, child: WhiteCloseButton(), ), - const SizedBox(height: Sizes.spaceSmall), - Expanded( - child: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: Sizes.spaceSmall, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const AltMeLogo( - color: Colors.white, - ), - Text( - l10n.whatsNew, - style: Theme.of(context) - .textTheme - .defaultDialogTitle - .copyWith( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: Sizes.spaceNormal), - Text( - versionNumber, - style: Theme.of(context) - .textTheme - .defaultDialogSubtitle - .copyWith( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: Sizes.spaceXSmall), - const NewFeature( - 'Integration of Matrix.org to give users access to a decentralized chat in Altme', // ignore: lines_longer_than_80_chars - ), - const NewFeature( - 'Compliance with EBSI and support of new official ID documents (diplomas...)', // ignore: lines_longer_than_80_chars - ), - Text( - '1.8.13', - style: Theme.of(context) - .textTheme - .defaultDialogSubtitle - .copyWith( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: Sizes.spaceXSmall), - const NewFeature( - 'Integration of an on-ramp solution to buy crypto', - ), - const NewFeature( - 'New features : Help center', - ), - const NewFeature( - 'New wallet certificate credential', - ), - Text( - '1.7.6', - style: Theme.of(context) - .textTheme - .defaultDialogSubtitle - .copyWith( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: Sizes.spaceXSmall), - const NewFeature( - 'Bug correction', - ), - Text( - '1.7.5', - style: Theme.of(context) - .textTheme - .defaultDialogSubtitle - .copyWith( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: Sizes.spaceXSmall), - const NewFeature( - 'New Chainborn gaming membership card', - ), - const NewFeature( - 'Credential manifest input descriptors update', - ), - const NewFeature( - 'Beacon pairing improvement', - ), - Text( - '1.7.1', - style: Theme.of(context) - .textTheme - .defaultDialogSubtitle - .copyWith( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: Sizes.spaceXSmall), - const NewFeature( - 'Improve compatibility with more wallets', - ), - const NewFeature( - 'Update Altme’s privacy, terms and conditions', - ), - const NewFeature( - 'Update NFT detail screen information', - ), - const NewFeature( - 'New category for Professional credentials', - ), - Text( - '1.6.5', - style: Theme.of(context) - .textTheme - .defaultDialogSubtitle - .copyWith( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: Sizes.spaceXSmall), - const NewFeature( - 'Bug correction', - ), - Text( - '1.6.3', - style: Theme.of(context) - .textTheme - .defaultDialogSubtitle - .copyWith( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: Sizes.spaceXSmall), - const NewFeature( - 'Support SBT (Soulbound Tokens)', - ), - const NewFeature( - 'New Drawer', - ), - const NewFeature( - 'New Device Info credential', - ), - const NewFeature( - 'Bug fix', - ), - Text( - '1.5.7', - style: Theme.of(context) - .textTheme - .defaultDialogSubtitle - .copyWith( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: Sizes.spaceXSmall), - const NewFeature( - 'Upgrade Beacon behavior', - ), - Text( - '1.5.6', - style: Theme.of(context) - .textTheme - .defaultDialogSubtitle - .copyWith( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: Sizes.spaceXSmall), - const NewFeature( - 'Age range with Al as 551 issuer', - ), - const NewFeature('Al issuer optimization'), - Text( - '1.5.1', - style: Theme.of(context) - .textTheme - .defaultDialogSubtitle - .copyWith( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: Sizes.spaceXSmall), - const NewFeature( - 'Al verification to get Over13 and Over18 pass', - ), - const NewFeature( - 'Ethereum support', - ), - const NewFeature( - 'Privacy and terms update', - ), - const NewFeature( - 'Enforced security', - ), - const NewFeature('User experience improvements'), - Text( - '1.4.8', - style: Theme.of(context) - .textTheme - .defaultDialogSubtitle - .copyWith( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: Sizes.spaceXSmall), - const NewFeature( - 'Add Tezotopia membership card in Discover', - ), - const NewFeature('Update design of credentials'), - Text( - '1.4.4', - style: Theme.of(context) - .textTheme - .defaultDialogSubtitle - .copyWith( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: Sizes.spaceXSmall), - const NewFeature( - 'Add the possibility to SEND an NFT to tezos blockchain address', - ), - const NewFeature( - 'Improvements of user experience', - ), - Text( - '1.4.1', - style: Theme.of(context) - .textTheme - .defaultDialogSubtitle - .copyWith( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: Sizes.spaceXSmall), - const NewFeature('New feature : NFT display in wallet'), - Text( - '1.3.7', - style: Theme.of(context) - .textTheme - .defaultDialogSubtitle - .copyWith( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: Sizes.spaceXSmall), - const NewFeature( - 'FA1.2 and FA2 token support', - ), - const NewFeature( - 'Beacon integration to connect to Tezos dApps', - ), - const NewFeature( - 'Get multiple identity credentials after identity verification (OpenID for VC Issuance)', // ignore: lines_longer_than_80_chars - ), - const NewFeature( - 'Choose card categories to display', - ), - const NewFeature( - 'New cards design', - ), - const NewFeature( - 'Nationality card', - ), - const NewFeature( - 'Age range card', - ), - const NewFeature( - 'Liveness test', - ), - const NewFeature( - 'Display card and token history', - ), - Text( - '1.1.0', - style: Theme.of(context) - .textTheme - .defaultDialogSubtitle - .copyWith( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: Sizes.spaceXSmall), - const NewFeature( - 'USD value of tokens', - ), - const NewFeature( - 'Multiple credentials presentation', - ), - const NewFeature( - 'Wording', - ), - const NewFeature( - 'Bug correction', - ), - const SizedBox(height: Sizes.spaceSmall), - ], + Column( + mainAxisSize: MainAxisSize.max, + children: [ + const SizedBox(height: Sizes.spaceSmall), + Expanded( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.only( + left: Sizes.spaceNormal, + right: Sizes.spaceXLarge, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const AltMeLogo( + color: Colors.white, + size: Sizes.logoLarge * 1.05, + ), + Text( + l10n.whatsNew, + style: Theme.of(context) + .textTheme + .defaultDialogTitle + .copyWith( + color: Colors.white, + ), + textAlign: TextAlign.center, + ), + NewVersionText(versionNumber: versionNumber), + const NewFeature( + 'Integration of Matrix.org to give users access to a decentralized chat in Altme', // ignore: lines_longer_than_80_chars + ), + const NewFeature( + 'Compliance with EBSI and support of new official ID documents (diplomas...)', // ignore: lines_longer_than_80_chars + ), + const NewVersionText(versionNumber: '1.8.13'), + const NewFeature( + 'Integration of an on-ramp solution to buy crypto', + ), + const NewFeature( + 'New features : Help center', + ), + const NewFeature( + 'New wallet certificate credential', + ), + const NewVersionText(versionNumber: '1.7.6'), + const NewFeature( + 'Bug correction', + ), + const NewVersionText(versionNumber: '1.7.5'), + const NewFeature( + 'New Chainborn gaming membership card', + ), + const NewFeature( + 'Credential manifest input descriptors update', + ), + const NewFeature( + 'Beacon pairing improvement', + ), + const NewVersionText(versionNumber: '1.7.1'), + const NewFeature( + 'Improve compatibility with more wallets', + ), + const NewFeature( + 'Update Altme’s privacy, terms and conditions', + ), + const NewFeature( + 'Update NFT detail screen information', + ), + const NewFeature( + 'New category for Professional credentials', + ), + const NewVersionText(versionNumber: '1.6.5'), + const NewFeature( + 'Bug correction', + ), + const NewVersionText(versionNumber: '1.6.3'), + const NewFeature( + 'Support SBT (Soulbound Tokens)', + ), + const NewFeature( + 'New Drawer', + ), + const NewFeature( + 'New Device Info credential', + ), + const NewFeature( + 'Bug fix', + ), + const NewVersionText(versionNumber: '1.5.7'), + const NewFeature( + 'Upgrade Beacon behavior', + ), + const NewVersionText(versionNumber: '1.5.6'), + const NewFeature( + 'Age range with Al as 551 issuer', + ), + const NewFeature('Al issuer optimization'), + const NewVersionText(versionNumber: '1.5.1'), + const NewFeature( + 'Al verification to get Over13 and Over18 pass', + ), + const NewFeature( + 'Ethereum support', + ), + const NewFeature( + 'Privacy and terms update', + ), + const NewFeature( + 'Enforced security', + ), + const NewFeature('User experience improvements'), + const NewVersionText(versionNumber: '1.4.8'), + const NewFeature( + 'Add Tezotopia membership card in Discover', + ), + const NewFeature('Update design of credentials'), + const NewVersionText(versionNumber: '1.4.4'), + const NewFeature( + 'Add the possibility to SEND an NFT to tezos blockchain address', + ), + const NewFeature( + 'Improvements of user experience', + ), + const NewVersionText(versionNumber: '1.4.1'), + const NewFeature( + 'New feature : NFT display in wallet', + ), + const NewVersionText(versionNumber: '1.3.7'), + const NewFeature( + 'FA1.2 and FA2 token support', + ), + const NewFeature( + 'Beacon integration to connect to Tezos dApps', + ), + const NewFeature( + 'Get multiple identity credentials after identity verification (OpenID for VC Issuance)', // ignore: lines_longer_than_80_chars + ), + const NewFeature( + 'Choose card categories to display', + ), + const NewFeature( + 'New cards design', + ), + const NewFeature( + 'Nationality card', + ), + const NewFeature( + 'Age range card', + ), + const NewFeature( + 'Liveness test', + ), + const NewFeature( + 'Display card and token history', + ), + const NewVersionText(versionNumber: '1.1.0'), + const NewFeature( + 'USD value of tokens', + ), + const NewFeature( + 'Multiple credentials presentation', + ), + const NewFeature( + 'Wording', + ), + const NewFeature( + 'Bug correction', + ), + const SizedBox(height: Sizes.spaceSmall), + ], + ), + ), ), ), - ), + Padding( + padding: const EdgeInsets.all(Sizes.spaceNormal), + child: MyGradientButton( + text: l10n.okGotIt, + verticalSpacing: 16, + fontSize: 18, + borderRadius: Sizes.normalRadius, + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ) + ], ), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: Sizes.spaceLarge, - vertical: Sizes.spaceXSmall, - ), - child: MyElevatedButton( - text: l10n.okGotIt, - verticalSpacing: 12, - fontSize: 18, - borderRadius: 20, - backgroundColor: Theme.of(context).colorScheme.primary, - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ) ], ), ), diff --git a/lib/dashboard/dashboard/widgets/widgets.dart b/lib/dashboard/dashboard/widgets/widgets.dart index faa27cf3f..8626955cd 100644 --- a/lib/dashboard/dashboard/widgets/widgets.dart +++ b/lib/dashboard/dashboard/widgets/widgets.dart @@ -2,4 +2,5 @@ export 'bottom_bar_decoration.dart'; export 'bottom_bar_item.dart'; export 'home_title_leading.dart'; export 'new_feature.dart'; +export 'new_version_text.dart'; export 'what_is_new_dialog.dart'; diff --git a/lib/dashboard/home/home/widgets/finish_kyc_dialog.dart b/lib/dashboard/home/home/widgets/finish_kyc_dialog.dart index df71e0165..add0ebcec 100644 --- a/lib/dashboard/home/home/widgets/finish_kyc_dialog.dart +++ b/lib/dashboard/home/home/widgets/finish_kyc_dialog.dart @@ -12,7 +12,7 @@ class FinishKycDialog extends StatelessWidget { Widget build(BuildContext context) { final l10n = context.l10n; return AlertDialog( - backgroundColor: Theme.of(context).colorScheme.onBackground, + backgroundColor: Theme.of(context).colorScheme.popupBackground, contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(25)), diff --git a/lib/dashboard/home/home/widgets/kyc_dialog.dart b/lib/dashboard/home/home/widgets/kyc_dialog.dart index d99479955..b33c0fe10 100644 --- a/lib/dashboard/home/home/widgets/kyc_dialog.dart +++ b/lib/dashboard/home/home/widgets/kyc_dialog.dart @@ -15,7 +15,7 @@ class KycDialog extends StatelessWidget { Widget build(BuildContext context) { final l10n = context.l10n; return AlertDialog( - backgroundColor: Theme.of(context).colorScheme.onBackground, + backgroundColor: Theme.of(context).colorScheme.popupBackground, contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(25)), diff --git a/lib/dashboard/home/home/widgets/token_reward_dialog.dart b/lib/dashboard/home/home/widgets/token_reward_dialog.dart index 4b5514ec7..76dd24bae 100644 --- a/lib/dashboard/home/home/widgets/token_reward_dialog.dart +++ b/lib/dashboard/home/home/widgets/token_reward_dialog.dart @@ -29,7 +29,7 @@ class TokenRewardDialog extends StatelessWidget { Widget build(BuildContext context) { final l10n = context.l10n; return AlertDialog( - backgroundColor: Theme.of(context).colorScheme.onBackground, + backgroundColor: Theme.of(context).colorScheme.popupBackground, contentPadding: const EdgeInsets.symmetric( horizontal: Sizes.spaceNormal, vertical: Sizes.spaceSmall, diff --git a/lib/dashboard/home/home/widgets/wallet_dialog.dart b/lib/dashboard/home/home/widgets/wallet_dialog.dart index 63184ba18..46e8f58c4 100644 --- a/lib/dashboard/home/home/widgets/wallet_dialog.dart +++ b/lib/dashboard/home/home/widgets/wallet_dialog.dart @@ -14,7 +14,7 @@ class WalletDialog extends StatelessWidget { Widget build(BuildContext context) { final l10n = context.l10n; return AlertDialog( - backgroundColor: Theme.of(context).colorScheme.onBackground, + backgroundColor: Theme.of(context).colorScheme.popupBackground, contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(25)), diff --git a/lib/dashboard/home/tab_bar/credentials/widgets/descriptioni_dialog.dart b/lib/dashboard/home/tab_bar/credentials/widgets/descriptioni_dialog.dart index fc329cbfa..fe70d1c9d 100644 --- a/lib/dashboard/home/tab_bar/credentials/widgets/descriptioni_dialog.dart +++ b/lib/dashboard/home/tab_bar/credentials/widgets/descriptioni_dialog.dart @@ -1,5 +1,6 @@ import 'package:altme/app/app.dart'; import 'package:altme/l10n/l10n.dart'; +import 'package:altme/theme/theme.dart'; import 'package:flutter/material.dart'; class DescriptionDialog extends StatelessWidget { @@ -14,7 +15,7 @@ class DescriptionDialog extends StatelessWidget { Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; - final background = Theme.of(context).colorScheme.surface; + final background = Theme.of(context).colorScheme.popupBackground; return AlertDialog( backgroundColor: background, diff --git a/lib/dashboard/home/tab_bar/tokens/confirm_token_transaction/widgets/transaction_done_dialog.dart b/lib/dashboard/home/tab_bar/tokens/confirm_token_transaction/widgets/transaction_done_dialog.dart index 7c76da42a..a8c6b4c3c 100644 --- a/lib/dashboard/home/tab_bar/tokens/confirm_token_transaction/widgets/transaction_done_dialog.dart +++ b/lib/dashboard/home/tab_bar/tokens/confirm_token_transaction/widgets/transaction_done_dialog.dart @@ -40,7 +40,7 @@ class TransactionDoneDialog extends StatelessWidget { Widget build(BuildContext context) { final l10n = context.l10n; return AlertDialog( - backgroundColor: Theme.of(context).colorScheme.onBackground, + backgroundColor: Theme.of(context).colorScheme.popupBackground, contentPadding: const EdgeInsets.symmetric( horizontal: Sizes.spaceNormal, vertical: Sizes.spaceSmall, diff --git a/lib/issuer_websites_page/widget/kyc_button.dart b/lib/issuer_websites_page/widget/kyc_button.dart index 9cb075513..1be0a0cc8 100644 --- a/lib/issuer_websites_page/widget/kyc_button.dart +++ b/lib/issuer_websites_page/widget/kyc_button.dart @@ -1,6 +1,7 @@ import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/l10n/l10n.dart'; +import 'package:altme/theme/theme.dart'; import 'package:altme/wallet/cubit/wallet_cubit.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -65,7 +66,8 @@ class KYCButton extends StatelessWidget { await showDialog( context: context, builder: (context) => AlertDialog( - backgroundColor: Theme.of(context).colorScheme.background, + backgroundColor: + Theme.of(context).colorScheme.popupBackground, contentPadding: const EdgeInsets.only( top: 24, bottom: 16, diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 99c9cbf78..9d37a9149 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -782,7 +782,7 @@ "linkedInProfile": "Linkedin Profile", "checkLinkedinProfile": "Check a Linkedin Profile", "scanAndDisplay": "Scan and Display", - "whatsNew": "What's New", + "whatsNew": "What's new", "okGotIt": "OK, GOT IT!", "altmeSupport": "AltMe support", "transactionDoneDialogDescription": "It can take a few minutes for the transfer to complete", diff --git a/lib/theme/app_theme/app_theme.dart b/lib/theme/app_theme/app_theme.dart index 42ced8dde..a7f5df2b4 100644 --- a/lib/theme/app_theme/app_theme.dart +++ b/lib/theme/app_theme/app_theme.dart @@ -113,7 +113,7 @@ extension CustomColorScheme on ColorScheme { Color get greyText => const Color(0xFFD1CCE3); - Color get whatsNewPopupBackground => const Color(0xFF271C38); + Color get popupBackground => const Color(0xff271C38); Color get cardHighlighted => const Color(0xFF251F38); @@ -217,7 +217,7 @@ extension CustomColorScheme on ColorScheme { Color get cryptoAccountNotSelected => Colors.grey.withOpacity(0.15); - Color get startButtonColorA => const Color(0xff8436F8); + Color get startButtonColorA => const Color(0xff18ACFF); Color get startButtonColorB => const Color(0xff6600FF); @@ -701,6 +701,12 @@ extension CustomTextTheme on TextTheme { fontWeight: FontWeight.bold, color: const Color(0xff180B2B), ); + + TextStyle get newVersionTitle => GoogleFonts.nunito( + fontSize: 18, + fontWeight: FontWeight.w800, + color: const Color(0xFFFFFFFF), + ); TextStyle get kycDialogTitle => GoogleFonts.nunito( fontSize: 25, diff --git a/pubspec.lock b/pubspec.lock index 5e2029553..8f4f97398 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2431,5 +2431,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.19.0 <3.0.0" + dart: ">=2.19.0 <3.7.0" flutter: ">=3.7.0-0" From 00800194709179479c55436951438f0fd20e3b29 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Thu, 2 Mar 2023 11:06:26 +0530 Subject: [PATCH 096/190] bug fix: passbase check for identity card --- .../helper_functions/discover_credential.dart | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/dashboard/home/tab_bar/credentials/helper_functions/discover_credential.dart b/lib/dashboard/home/tab_bar/credentials/helper_functions/discover_credential.dart index d5fa0986b..aed4ae26f 100644 --- a/lib/dashboard/home/tab_bar/credentials/helper_functions/discover_credential.dart +++ b/lib/dashboard/home/tab_bar/credentials/helper_functions/discover_credential.dart @@ -52,10 +52,15 @@ Future discoverCredential({ ), ); } else { - await launchUrlAfterDiscovery( - homeCredential: homeCredential, - context: context, - ); + await context.read().checkForPassBaseStatusThenLaunchUrl( + link: homeCredential.link!, + onPassBaseApproved: () async { + await launchUrlAfterDiscovery( + homeCredential: homeCredential, + context: context, + ); + }, + ); } } else { await launchUrlAfterDiscovery( From 34d2afee0696bc76c8713aa08151e7f641620829 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Thu, 2 Mar 2023 13:24:22 +0530 Subject: [PATCH 097/190] whats new popup optimize --- .../dashboard/widgets/new_content.dart | 66 ++++ .../dashboard/widgets/new_feature.dart | 40 -- .../dashboard/widgets/new_version_text.dart | 26 -- .../dashboard/widgets/what_is_new_dialog.dart | 365 +++++++++--------- lib/dashboard/dashboard/widgets/widgets.dart | 3 +- pubspec.lock | 2 +- 6 files changed, 240 insertions(+), 262 deletions(-) create mode 100644 lib/dashboard/dashboard/widgets/new_content.dart delete mode 100644 lib/dashboard/dashboard/widgets/new_feature.dart delete mode 100644 lib/dashboard/dashboard/widgets/new_version_text.dart diff --git a/lib/dashboard/dashboard/widgets/new_content.dart b/lib/dashboard/dashboard/widgets/new_content.dart new file mode 100644 index 000000000..c6a261e51 --- /dev/null +++ b/lib/dashboard/dashboard/widgets/new_content.dart @@ -0,0 +1,66 @@ +import 'package:altme/app/app.dart'; +import 'package:altme/theme/theme.dart'; +import 'package:flutter/material.dart'; + +class NewContent extends StatelessWidget { + const NewContent({required this.version, required this.features, super.key}); + + final String version; + final List features; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Padding( + padding: const EdgeInsets.only( + top: Sizes.spaceLarge, + bottom: Sizes.spaceXSmall, + ), + child: Text( + version, + style: Theme.of(context).textTheme.newVersionTitle, + textAlign: TextAlign.center, + ), + ), + ListView.builder( + itemCount: features.length, + padding: EdgeInsets.zero, + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (BuildContext context, int index) { + return Padding( + padding: const EdgeInsets.only(bottom: 10), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Padding( + padding: EdgeInsets.only(top: 3), + child: Icon( + Icons.check_circle, + color: Colors.white, + size: 18, + ), + ), + const SizedBox(width: 5), + Expanded( + child: Text( + features[index], + textAlign: TextAlign.left, + style: Theme.of(context) + .textTheme + .defaultDialogBody + .copyWith( + color: Colors.white, + ), + ), + ), + ], + ), + ); + }, + ) + ], + ); + } +} diff --git a/lib/dashboard/dashboard/widgets/new_feature.dart b/lib/dashboard/dashboard/widgets/new_feature.dart deleted file mode 100644 index 39422a598..000000000 --- a/lib/dashboard/dashboard/widgets/new_feature.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'package:altme/theme/theme.dart'; -import 'package:flutter/material.dart'; - -class NewFeature extends StatelessWidget { - const NewFeature( - this.feature, { - super.key, - }); - - final String feature; - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.only(bottom: 20), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Padding( - padding: EdgeInsets.only(top: 3), - child: Icon( - Icons.check_circle, - color: Colors.white, - size: 20, - ), - ), - const SizedBox(width: 5), - Expanded( - child: Text( - feature, - textAlign: TextAlign.left, - style: Theme.of(context).textTheme.defaultDialogBody.copyWith( - color: Colors.white, - ), - ), - ), - ], - ), - ); - } -} diff --git a/lib/dashboard/dashboard/widgets/new_version_text.dart b/lib/dashboard/dashboard/widgets/new_version_text.dart deleted file mode 100644 index 815b17d07..000000000 --- a/lib/dashboard/dashboard/widgets/new_version_text.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:altme/app/shared/constants/sizes.dart'; -import 'package:altme/theme/theme.dart'; -import 'package:flutter/material.dart'; - -class NewVersionText extends StatelessWidget { - const NewVersionText({ - super.key, - required this.versionNumber, - }); - - final String versionNumber; - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.only( - top: Sizes.spaceLarge, - bottom: Sizes.spaceXSmall, - ), - child: Text( - versionNumber, - style: Theme.of(context).textTheme.newVersionTitle, - textAlign: TextAlign.center, - ), - ); - } -} diff --git a/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart b/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart index 73137df5b..ed95d3280 100644 --- a/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart +++ b/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart @@ -1,3 +1,5 @@ +// ignore_for_file: lines_longer_than_80_chars + import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/l10n/l10n.dart'; @@ -39,202 +41,179 @@ class WhatIsNewDialog extends StatelessWidget { Radius.circular(Sizes.normalRadius), ), ), - content: Stack( - children: [ - const Align( - alignment: Alignment.topRight, - child: WhiteCloseButton(), - ), - Column( - mainAxisSize: MainAxisSize.max, - children: [ - const SizedBox(height: Sizes.spaceSmall), - Expanded( - child: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.only( - left: Sizes.spaceNormal, - right: Sizes.spaceXLarge, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const AltMeLogo( - color: Colors.white, - size: Sizes.logoLarge * 1.05, - ), - Text( - l10n.whatsNew, - style: Theme.of(context) - .textTheme - .defaultDialogTitle - .copyWith( - color: Colors.white, - ), - textAlign: TextAlign.center, - ), - NewVersionText(versionNumber: versionNumber), - const NewFeature( - 'Integration of Matrix.org to give users access to a decentralized chat in Altme', // ignore: lines_longer_than_80_chars - ), - const NewFeature( - 'Compliance with EBSI and support of new official ID documents (diplomas...)', // ignore: lines_longer_than_80_chars - ), - const NewVersionText(versionNumber: '1.8.13'), - const NewFeature( - 'Integration of an on-ramp solution to buy crypto', - ), - const NewFeature( - 'New features : Help center', - ), - const NewFeature( - 'New wallet certificate credential', - ), - const NewVersionText(versionNumber: '1.7.6'), - const NewFeature( - 'Bug correction', - ), - const NewVersionText(versionNumber: '1.7.5'), - const NewFeature( - 'New Chainborn gaming membership card', - ), - const NewFeature( - 'Credential manifest input descriptors update', - ), - const NewFeature( - 'Beacon pairing improvement', - ), - const NewVersionText(versionNumber: '1.7.1'), - const NewFeature( - 'Improve compatibility with more wallets', - ), - const NewFeature( - 'Update Altme’s privacy, terms and conditions', - ), - const NewFeature( - 'Update NFT detail screen information', - ), - const NewFeature( - 'New category for Professional credentials', - ), - const NewVersionText(versionNumber: '1.6.5'), - const NewFeature( - 'Bug correction', - ), - const NewVersionText(versionNumber: '1.6.3'), - const NewFeature( - 'Support SBT (Soulbound Tokens)', - ), - const NewFeature( - 'New Drawer', - ), - const NewFeature( - 'New Device Info credential', - ), - const NewFeature( - 'Bug fix', - ), - const NewVersionText(versionNumber: '1.5.7'), - const NewFeature( - 'Upgrade Beacon behavior', - ), - const NewVersionText(versionNumber: '1.5.6'), - const NewFeature( - 'Age range with Al as 551 issuer', - ), - const NewFeature('Al issuer optimization'), - const NewVersionText(versionNumber: '1.5.1'), - const NewFeature( - 'Al verification to get Over13 and Over18 pass', - ), - const NewFeature( - 'Ethereum support', - ), - const NewFeature( - 'Privacy and terms update', - ), - const NewFeature( - 'Enforced security', - ), - const NewFeature('User experience improvements'), - const NewVersionText(versionNumber: '1.4.8'), - const NewFeature( - 'Add Tezotopia membership card in Discover', - ), - const NewFeature('Update design of credentials'), - const NewVersionText(versionNumber: '1.4.4'), - const NewFeature( - 'Add the possibility to SEND an NFT to tezos blockchain address', - ), - const NewFeature( - 'Improvements of user experience', - ), - const NewVersionText(versionNumber: '1.4.1'), - const NewFeature( - 'New feature : NFT display in wallet', - ), - const NewVersionText(versionNumber: '1.3.7'), - const NewFeature( - 'FA1.2 and FA2 token support', - ), - const NewFeature( - 'Beacon integration to connect to Tezos dApps', - ), - const NewFeature( - 'Get multiple identity credentials after identity verification (OpenID for VC Issuance)', // ignore: lines_longer_than_80_chars - ), - const NewFeature( - 'Choose card categories to display', - ), - const NewFeature( - 'New cards design', - ), - const NewFeature( - 'Nationality card', - ), - const NewFeature( - 'Age range card', - ), - const NewFeature( - 'Liveness test', - ), - const NewFeature( - 'Display card and token history', - ), - const NewVersionText(versionNumber: '1.1.0'), - const NewFeature( - 'USD value of tokens', - ), - const NewFeature( - 'Multiple credentials presentation', - ), - const NewFeature( - 'Wording', - ), - const NewFeature( - 'Bug correction', - ), - const SizedBox(height: Sizes.spaceSmall), - ], + content: SizedBox( + width: double.maxFinite, + child: Stack( + children: [ + const Align( + alignment: Alignment.topRight, + child: WhiteCloseButton(), + ), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(height: Sizes.spaceSmall), + Expanded( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.only( + left: Sizes.spaceNormal, + right: Sizes.spaceXLarge, + ), + child: Column( + //mainAxisSize: MainAxisSize.min, + children: [ + const AltMeLogo( + color: Colors.white, + size: Sizes.logoLarge * 1.05, + ), + Text( + l10n.whatsNew, + style: Theme.of(context) + .textTheme + .defaultDialogTitle + .copyWith( + color: Colors.white, + ), + textAlign: TextAlign.center, + ), + NewContent( + version: versionNumber, + features: const [ + 'Integration of Matrix.org to give users access to a decentralized chat in Altme', + 'Compliance with EBSI and support of new official ID documents (diplomas...)', + ], + ), + const NewContent( + version: '1.8.13', + features: [ + 'Integration of an on-ramp solution to buy crypto', + 'New features : Help center', + 'New wallet certificate credential', + ], + ), + const NewContent( + version: '1.7.6', + features: [ + 'Bug correction', + ], + ), + const NewContent( + version: '1.7.5', + features: [ + 'New Chainborn gaming membership card', + 'Credential manifest input descriptors update', + 'Beacon pairing improvement', + ], + ), + const NewContent( + version: '1.7.1', + features: [ + 'Improve compatibility with more wallets', + 'Update Altme’s privacy, terms and conditions', + 'Update NFT detail screen information', + 'New category for Professional credentials', + ], + ), + const NewContent( + version: '1.6.5', + features: [ + 'Bug correction', + ], + ), + const NewContent( + version: '1.6.3', + features: [ + 'Support SBT (Soulbound Tokens)', + 'New Drawer', + 'New Device Info credential', + 'Bug fix', + ], + ), + const NewContent( + version: '1.5.7', + features: ['Upgrade Beacon behavior'], + ), + const NewContent( + version: '1.5.6', + features: [ + 'Age range with Al as 551 issuer', + 'Al issuer optimization' + ], + ), + const NewContent( + version: '1.5.1', + features: [ + 'Al verification to get Over13 and Over18 pass', + 'Ethereum support', + 'Privacy and terms update', + 'Enforced security', + 'User experience improvements' + ], + ), + const NewContent( + version: '1.4.8', + features: [ + 'Add Tezotopia membership card in Discover', + 'Update design of credentials' + ], + ), + const NewContent( + version: '1.4.4', + features: [ + 'Add the possibility to SEND an NFT to tezos blockchain address', + 'Improvements of user experience', + ], + ), + const NewContent( + version: '1.4.1', + features: ['New feature : NFT display in wallet'], + ), + const NewContent( + version: '1.3.7', + features: [ + 'FA1.2 and FA2 token support', + 'Beacon integration to connect to Tezos dApps', + 'Get multiple identity credentials after identity verification (OpenID for VC Issuance)', + 'Choose card categories to display', + 'New cards design', + 'Nationality card', + 'Age range card', + 'Liveness test', + 'Display card and token history', + ], + ), + const NewContent( + version: '1.1.0', + features: [ + 'USD value of tokens', + 'Multiple credentials presentation', + 'Wording', + 'Bug correction', + ], + ), + const SizedBox(height: Sizes.spaceSmall), + ], + ), ), ), ), - ), - Padding( - padding: const EdgeInsets.all(Sizes.spaceNormal), - child: MyGradientButton( - text: l10n.okGotIt, - verticalSpacing: 16, - fontSize: 18, - borderRadius: Sizes.normalRadius, - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ) - ], - ), - ], + Padding( + padding: const EdgeInsets.all(Sizes.spaceNormal), + child: MyGradientButton( + text: l10n.okGotIt, + verticalSpacing: 16, + fontSize: 18, + borderRadius: Sizes.normalRadius, + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ) + ], + ), + ], + ), ), ), ); diff --git a/lib/dashboard/dashboard/widgets/widgets.dart b/lib/dashboard/dashboard/widgets/widgets.dart index 8626955cd..f4e1e3918 100644 --- a/lib/dashboard/dashboard/widgets/widgets.dart +++ b/lib/dashboard/dashboard/widgets/widgets.dart @@ -1,6 +1,5 @@ export 'bottom_bar_decoration.dart'; export 'bottom_bar_item.dart'; export 'home_title_leading.dart'; -export 'new_feature.dart'; -export 'new_version_text.dart'; +export 'new_content.dart'; export 'what_is_new_dialog.dart'; diff --git a/pubspec.lock b/pubspec.lock index 8f4f97398..5e2029553 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2431,5 +2431,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.19.0 <3.7.0" + dart: ">=2.19.0 <3.0.0" flutter: ">=3.7.0-0" From 034859318a1ba708327c3e74d23683b6e9934e80 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Thu, 2 Mar 2023 12:09:22 +0330 Subject: [PATCH 098/190] catch exception when initialize the Matrix --- .../live_chat/cubit/live_chat_cubit.dart | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart index 32677aee3..854b15bd7 100644 --- a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart +++ b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart @@ -540,18 +540,25 @@ class LiveChatCubit extends Cubit { } Future _initClient() async { - client = Client( - 'AltMeUser', - databaseBuilder: (_) async { - final dir = await getApplicationSupportDirectory(); - final db = HiveCollectionsDatabase('matrix_support_chat', dir.path); - await db.open(); - return db; - }, - ); - client.homeserver = Uri.parse(Urls.matrixHomeServer); - await client.init(); - _notificationStreamController ??= StreamController.broadcast(); + try { + client = Client( + 'AltMeUser', + databaseBuilder: (_) async { + final dir = await getApplicationSupportDirectory(); + final db = HiveCollectionsDatabase('matrix_support_chat', dir.path); + await db.open(); + return db; + }, + ); + client.homeserver = Uri.parse(Urls.matrixHomeServer); + await client.init(); + _notificationStreamController ??= StreamController.broadcast(); + } catch (e, s) { + logger.e('e: $e , s: $s'); + await client.init( + newHomeserver: Uri.parse(Urls.matrixHomeServer), + ); + } } Future _getDidAuth(String did, String nonce) async { @@ -649,11 +656,11 @@ class LiveChatCubit extends Cubit { Future dispose() async { try { - await client.logout(); - await client.dispose(); - await _notificationStreamController?.close(); + await client.logout().catchError((_) => null); + await client.dispose().catchError((_) => null); + await _notificationStreamController?.close().catchError((_) => null); _notificationStreamController = null; - await _onEventSubscription?.cancel(); + await _onEventSubscription?.cancel().catchError((_) => null); _onEventSubscription = null; _roomId = null; } catch (e) { From 95f8c521728490105659843575140740cfea2906 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Thu, 2 Mar 2023 15:03:52 +0530 Subject: [PATCH 099/190] bypass recovery key phase for talao #1351 --- .../view/activate_biometrics_page.dart | 6 +- .../view/onboarding_gen_phrase.dart | 262 ++++++++++-------- lib/pin_code/view/confirm_pin_code_page.dart | 6 +- .../view/enter_new_pin_code_page.dart | 6 +- pubspec.lock | 2 +- 5 files changed, 155 insertions(+), 127 deletions(-) diff --git a/lib/onboarding/activate_biometircs/view/activate_biometrics_page.dart b/lib/onboarding/activate_biometircs/view/activate_biometrics_page.dart index 609d709b6..8f767aea2 100644 --- a/lib/onboarding/activate_biometircs/view/activate_biometrics_page.dart +++ b/lib/onboarding/activate_biometircs/view/activate_biometrics_page.dart @@ -44,6 +44,8 @@ class ActivateBiometricsView extends StatelessWidget { final LocalAuthApi localAuthApi; final WalletRouteType routeType; + bool get byPassScreen => !Parameters.hasCryptoCallToAction; + @override Widget build(BuildContext context) { final l10n = context.l10n; @@ -61,9 +63,9 @@ class ActivateBiometricsView extends StatelessWidget { builder: (context, state) { return Column( children: [ - const MStepper( + MStepper( step: 2, - totalStep: 3, + totalStep: byPassScreen ? 2 : 3, ), const Spacer(), Text( diff --git a/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart b/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart index 40289180e..8c47bbcd8 100644 --- a/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart +++ b/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart @@ -49,12 +49,20 @@ class OnBoardingGenPhraseView extends StatefulWidget { class _OnBoardingGenPhraseViewState extends State { late List? mnemonic; + bool get byPassScreen => !Parameters.hasCryptoCallToAction; + @override void initState() { super.initState(); mnemonic = bip39.generateMnemonic().split(' '); Future.microtask(() => context.read().initialize()) .catchError((_) => null); + + WidgetsBinding.instance.addPostFrameCallback((_) async { + await context + .read() + .generateSSIAndCryptoAccount(mnemonic!); + }); } @override @@ -74,7 +82,12 @@ class _OnBoardingGenPhraseViewState extends State { context: context, stateMessage: state.message!, ); + + if (byPassScreen) { + Navigator.pop(context); + } } + if (state.status == AppStatus.success) { context.read().init(); Navigator.pushAndRemoveUntil( @@ -85,136 +98,145 @@ class _OnBoardingGenPhraseViewState extends State { } }, builder: (context, state) { - return BasePage( - scrollView: false, - useSafeArea: true, - padding: const EdgeInsets.symmetric(horizontal: Sizes.spaceXSmall), - titleLeading: const BackLeadingButton(), - secureScreen: true, - body: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisSize: MainAxisSize.max, - children: [ - Expanded( - child: SingleChildScrollView( - child: Column( - children: [ - const MStepper( - step: 3, - totalStep: 3, - ), - const SizedBox( - height: Sizes.spaceNormal, - ), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: Sizes.spaceNormal, - ), - child: Text( - l10n.onboardingPleaseStoreMessage, - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.headlineSmall, - ), - ), - const SizedBox(height: Sizes.spaceNormal), - if (mnemonic != null) - MnemonicDisplay(mnemonic: mnemonic!), - // const SizedBox( - // height: Sizes.spaceSmall, - // ), - // TextButton( - // onPressed: () { - // Clipboard.setData( - // ClipboardData( - // text: state.mnemonic.join(' '), - // ), - // ); - // }, - // child: Text( - // l10n.copyToClipboard, - // style: Theme.of(context).textTheme.copyToClipBoard, - // ), - // ), - const SizedBox(height: Sizes.spaceLarge), - Text( - l10n.onboardingAltmeMessage, - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.genPhraseSubmessage, - ), - ], - ), - ), - ), - //const Spacer(), - Padding( - padding: const EdgeInsets.all( - Sizes.spaceNormal, - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, + return byPassScreen + ? BasePage(body: Container()) + : BasePage( + scrollView: false, + useSafeArea: true, + padding: + const EdgeInsets.symmetric(horizontal: Sizes.spaceXSmall), + titleLeading: const BackLeadingButton(), + secureScreen: true, + body: Column( mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.max, children: [ - Transform.scale( - scale: 1.5, - child: Checkbox( - value: state.isTicked, - fillColor: MaterialStateProperty.all( - Theme.of(context).colorScheme.primary, - ), - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all( - Radius.circular(6), - ), + Expanded( + child: SingleChildScrollView( + child: Column( + children: [ + const MStepper( + step: 3, + totalStep: 3, + ), + const SizedBox( + height: Sizes.spaceNormal, + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: Sizes.spaceNormal, + ), + child: Text( + l10n.onboardingPleaseStoreMessage, + textAlign: TextAlign.center, + style: + Theme.of(context).textTheme.headlineSmall, + ), + ), + const SizedBox(height: Sizes.spaceNormal), + if (mnemonic != null) + MnemonicDisplay(mnemonic: mnemonic!), + // const SizedBox( + // height: Sizes.spaceSmall, + // ), + // TextButton( + // onPressed: () { + // Clipboard.setData( + // ClipboardData( + // text: state.mnemonic.join(' '), + // ), + // ); + // }, + // child: Text( + // l10n.copyToClipboard, + // style: Theme.of(context).textTheme.copyToClipBoard, + // ), + // ), + const SizedBox(height: Sizes.spaceLarge), + Text( + l10n.onboardingAltmeMessage, + textAlign: TextAlign.center, + style: Theme.of(context) + .textTheme + .genPhraseSubmessage, + ), + ], ), - onChanged: (newValue) => context - .read() - .switchTick(), ), ), - const SizedBox( - width: Sizes.spaceXSmall, - ), - Expanded( - child: InkWell( - onTap: () { - context.read().switchTick(); - }, - child: MyText( - l10n.onboardingWroteDownMessage, - style: Theme.of(context) - .textTheme - .onBoardingCheckMessage, - ), + //const Spacer(), + Padding( + padding: const EdgeInsets.all( + Sizes.spaceNormal, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + Transform.scale( + scale: 1.5, + child: Checkbox( + value: state.isTicked, + fillColor: MaterialStateProperty.all( + Theme.of(context).colorScheme.primary, + ), + materialTapTargetSize: + MaterialTapTargetSize.shrinkWrap, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all( + Radius.circular(6), + ), + ), + onChanged: (newValue) => context + .read() + .switchTick(), + ), + ), + const SizedBox( + width: Sizes.spaceXSmall, + ), + Expanded( + child: InkWell( + onTap: () { + context + .read() + .switchTick(); + }, + child: MyText( + l10n.onboardingWroteDownMessage, + style: Theme.of(context) + .textTheme + .onBoardingCheckMessage, + ), + ), + ), + ], ), ), ], ), - ), - ], - ), - navigation: SafeArea( - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: Sizes.spaceSmall, - vertical: Sizes.spaceSmall, - ), - child: MyGradientButton( - text: l10n.onBoardingGenPhraseButton, - verticalSpacing: 18, - onPressed: state.isTicked || mnemonic != null - ? () async { - await context - .read() - .generateSSIAndCryptoAccount(mnemonic!); - } - : null, - ), - ), - ), - ); + navigation: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: Sizes.spaceSmall, + vertical: Sizes.spaceSmall, + ), + child: MyGradientButton( + text: l10n.onBoardingGenPhraseButton, + verticalSpacing: 18, + onPressed: state.isTicked || mnemonic != null + ? () async { + await context + .read() + .generateSSIAndCryptoAccount(mnemonic!); + } + : null, + ), + ), + ), + ); }, ); } diff --git a/lib/pin_code/view/confirm_pin_code_page.dart b/lib/pin_code/view/confirm_pin_code_page.dart index 76fa8cc61..52dc7077d 100644 --- a/lib/pin_code/view/confirm_pin_code_page.dart +++ b/lib/pin_code/view/confirm_pin_code_page.dart @@ -70,6 +70,8 @@ class _ConfirmPinCodeViewState extends State { final StreamController _verificationNotifier = StreamController.broadcast(); + bool get byPassScreen => !Parameters.hasCryptoCallToAction; + @override void initState() { super.initState(); @@ -93,9 +95,9 @@ class _ConfirmPinCodeViewState extends State { title: l10n.confirmYourPinCode, passwordEnteredCallback: _onPasscodeEntered, header: widget.isFromOnboarding - ? const MStepper( + ? MStepper( step: 1, - totalStep: 3, + totalStep: byPassScreen ? 2 : 3, ) : null, deleteButton: Text( diff --git a/lib/pin_code/view/enter_new_pin_code_page.dart b/lib/pin_code/view/enter_new_pin_code_page.dart index 9f3c6cb6d..529946920 100644 --- a/lib/pin_code/view/enter_new_pin_code_page.dart +++ b/lib/pin_code/view/enter_new_pin_code_page.dart @@ -57,6 +57,8 @@ class EnterNewPinCodeView extends StatefulWidget { } class _EnterNewPinCodeViewState extends State { + bool get byPassScreen => !Parameters.hasCryptoCallToAction; + @override void initState() { super.initState(); @@ -79,9 +81,9 @@ class _EnterNewPinCodeViewState extends State { title: l10n.enterNewPinCode, passwordEnteredCallback: _onPasscodeEntered, header: widget.isFromOnboarding - ? const MStepper( + ? MStepper( step: 1, - totalStep: 3, + totalStep: byPassScreen ? 2 : 3, ) : null, deleteButton: Text( diff --git a/pubspec.lock b/pubspec.lock index 5e2029553..8f4f97398 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2431,5 +2431,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.19.0 <3.0.0" + dart: ">=2.19.0 <3.7.0" flutter: ">=3.7.0-0" From 94620fa60f35441e60d75d9cd92a621ca9a52ccb Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Thu, 2 Mar 2023 16:14:22 +0530 Subject: [PATCH 100/190] add import passphrase #1299 --- .../view/import_wallet_page.dart | 50 +++++++++-------- lib/l10n/arb/app_en.arb | 2 + lib/l10n/untranslated.json | 8 +++ lib/onboarding/starter/view/starter_page.dart | 54 +++++++++---------- 4 files changed, 63 insertions(+), 51 deletions(-) diff --git a/lib/import_wallet/view/import_wallet_page.dart b/lib/import_wallet/view/import_wallet_page.dart index fad3afa68..bd7d26ad4 100644 --- a/lib/import_wallet/view/import_wallet_page.dart +++ b/lib/import_wallet/view/import_wallet_page.dart @@ -152,7 +152,9 @@ class _ImportWalletViewState extends State { horizontal: Sizes.spaceLarge, ), child: Text( - l10n.importWalletText, + Parameters.hasCryptoCallToAction + ? l10n.importWalletText + : l10n.importWalletTextRecoveryPhraseOnly, textAlign: TextAlign.center, style: Theme.of(context).textTheme.titleLarge?.copyWith( letterSpacing: 1.2, @@ -169,7 +171,9 @@ class _ImportWalletViewState extends State { children: [ BaseTextField( height: Sizes.recoveryPhraseTextFieldHeight, - hint: l10n.importWalletHintText(54), + hint: Parameters.hasCryptoCallToAction + ? l10n.importWalletHintText(54) + : l10n.importWalletHintTextRecoveryPhraseOnly, fillColor: Colors.transparent, hintStyle: Theme.of(context).textTheme.hintTextFieldStyle, @@ -213,18 +217,20 @@ class _ImportWalletViewState extends State { l10n.importEasilyFrom, style: Theme.of(context).textTheme.titleMedium, ), - const SizedBox(height: Sizes.spaceSmall), - WalletTypeList( - onItemTap: (wallet) { - Navigator.of(context).push( - ImportFromWalletPage.route( - walletTypeModel: wallet, - accountName: widget.accountName, - isFromOnboard: widget.isFromOnboarding, - ), - ); - }, - ), + if (Parameters.hasCryptoCallToAction) ...[ + const SizedBox(height: Sizes.spaceSmall), + WalletTypeList( + onItemTap: (wallet) { + Navigator.of(context).push( + ImportFromWalletPage.route( + walletTypeModel: wallet, + accountName: widget.accountName, + isFromOnboard: widget.isFromOnboarding, + ), + ); + }, + ), + ], const SizedBox(height: Sizes.spaceLarge), Text( l10n.recoveryPhraseDescriptions, @@ -232,13 +238,15 @@ class _ImportWalletViewState extends State { fontSize: 12, ), ), - const SizedBox(height: Sizes.spaceLarge), - Text( - l10n.privateKeyDescriptions, - style: Theme.of(context).textTheme.infoSubtitle.copyWith( - fontSize: 12, - ), - ), + if (Parameters.hasCryptoCallToAction) ...[ + const SizedBox(height: Sizes.spaceLarge), + Text( + l10n.privateKeyDescriptions, + style: Theme.of(context).textTheme.infoSubtitle.copyWith( + fontSize: 12, + ), + ), + ], const SizedBox(height: Sizes.spaceNormal), ], ), diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 54a810dc4..75af464da 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -388,6 +388,7 @@ "import": "Import", "accountName": "Account name", "importWalletText": "Enter your recovery phrase or private key here.", + "importWalletTextRecoveryPhraseOnly": "Enter your recovery phrase here.", "recoveryPhraseDescriptions": "A recovery phrase (sometimes known as a seed phrase, private key or backup phrase) is a list of 12 words generated by your cryptocurrency wallet that gives you access to your funds", "importEasilyFrom": "Import your account from :", "templeWallet": "Temple wallet", @@ -406,6 +407,7 @@ "numberCharacters": {} } }, + "importWalletHintTextRecoveryPhraseOnly": "Once you have entered your 12 words (recovery phrase), tap Import.", "kycDialogTitle": "To get this card, and other identity cards, you need to verify your ID", "idVerificationProcess": "ID Verification Process", "idCheck": "ID Check", diff --git a/lib/l10n/untranslated.json b/lib/l10n/untranslated.json index 473ab23e0..3324cd31a 100644 --- a/lib/l10n/untranslated.json +++ b/lib/l10n/untranslated.json @@ -338,6 +338,7 @@ "import", "accountName", "importWalletText", + "importWalletTextRecoveryPhraseOnly", "recoveryPhraseDescriptions", "importEasilyFrom", "templeWallet", @@ -349,6 +350,7 @@ "other", "otherWalletApp", "importWalletHintText", + "importWalletHintTextRecoveryPhraseOnly", "kycDialogTitle", "idVerificationProcess", "idCheck", @@ -1080,6 +1082,7 @@ "import", "accountName", "importWalletText", + "importWalletTextRecoveryPhraseOnly", "recoveryPhraseDescriptions", "importEasilyFrom", "templeWallet", @@ -1091,6 +1094,7 @@ "other", "otherWalletApp", "importWalletHintText", + "importWalletHintTextRecoveryPhraseOnly", "kycDialogTitle", "idVerificationProcess", "idCheck", @@ -1485,6 +1489,8 @@ "fr": [ "educationCredentials", + "importWalletTextRecoveryPhraseOnly", + "importWalletHintTextRecoveryPhraseOnly", "educationCredentialHomeSubtitle", "chooseMethodPageAgeRangeTitle", "kycTitle", @@ -1847,6 +1853,7 @@ "import", "accountName", "importWalletText", + "importWalletTextRecoveryPhraseOnly", "recoveryPhraseDescriptions", "importEasilyFrom", "templeWallet", @@ -1858,6 +1865,7 @@ "other", "otherWalletApp", "importWalletHintText", + "importWalletHintTextRecoveryPhraseOnly", "kycDialogTitle", "idVerificationProcess", "idCheck", diff --git a/lib/onboarding/starter/view/starter_page.dart b/lib/onboarding/starter/view/starter_page.dart index 3e50f39f0..c15da0a70 100644 --- a/lib/onboarding/starter/view/starter_page.dart +++ b/lib/onboarding/starter/view/starter_page.dart @@ -49,35 +49,32 @@ class StarterPage extends StatelessWidget { const Spacer(flex: 3), const SplashImage(), const Spacer(flex: 2), - if (Parameters.hasCryptoCallToAction) - InkWell( - onTap: () { - Navigator.of(context).push( - EnterNewPinCodePage.route( - isFromOnboarding: true, - isValidCallback: () { - Navigator.of(context).pushReplacement( - ActiviateBiometricsPage.route( - routeType: WalletRouteType.recover, - ), - ); - }, - restrictToBack: false, - ), - ); - }, - child: Padding( - padding: const EdgeInsets.all(Sizes.spaceLarge), - child: GradientButtonText( - text: l10n.import_wallet, - onPressed: () {}, - fontSize: 18, - upperCase: true, + InkWell( + onTap: () { + Navigator.of(context).push( + EnterNewPinCodePage.route( + isFromOnboarding: true, + isValidCallback: () { + Navigator.of(context).pushReplacement( + ActiviateBiometricsPage.route( + routeType: WalletRouteType.recover, + ), + ); + }, + restrictToBack: false, ), + ); + }, + child: Padding( + padding: const EdgeInsets.all(Sizes.spaceLarge), + child: GradientButtonText( + text: l10n.import_wallet, + onPressed: () {}, + fontSize: 18, + upperCase: true, ), - ) - else - const SizedBox.shrink(), + ), + ), MyGradientButton( text: l10n.create_wallet, onPressed: () { @@ -97,9 +94,6 @@ class StarterPage extends StatelessWidget { }, ), const Spacer(), - const SizedBox( - height: Sizes.spaceSmall, - ), ], ), ), From b93f72843c50ff8fc12eb64d51848b4ce380431a Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Thu, 2 Mar 2023 16:16:45 +0530 Subject: [PATCH 101/190] restrict secret key addition #1299 --- lib/import_wallet/cubit/import_wallet_cubit.dart | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/import_wallet/cubit/import_wallet_cubit.dart b/lib/import_wallet/cubit/import_wallet_cubit.dart index e61958288..021dd6aac 100644 --- a/lib/import_wallet/cubit/import_wallet_cubit.dart +++ b/lib/import_wallet/cubit/import_wallet_cubit.dart @@ -69,6 +69,12 @@ class ImportWalletCubit extends Cubit { mnemonicOrKey.startsWith('0x'); if (isSecretKey) { + if (!Parameters.hasCryptoCallToAction) { + throw ResponseMessage( + ResponseString + .RESPONSE_STRING_SOMETHING_WENT_WRONG_TRY_AGAIN_LATER, + ); + } mnemonic = bip39.generateMnemonic(); } else { mnemonic = mnemonicOrKey; From 53f7e42846183ecb9eb71124dcd0a769abd99874 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Thu, 2 Mar 2023 16:37:12 +0530 Subject: [PATCH 102/190] bug fix #1299 --- lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart b/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart index 8c47bbcd8..4722e8886 100644 --- a/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart +++ b/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart @@ -59,9 +59,11 @@ class _OnBoardingGenPhraseViewState extends State { .catchError((_) => null); WidgetsBinding.instance.addPostFrameCallback((_) async { - await context - .read() - .generateSSIAndCryptoAccount(mnemonic!); + if (byPassScreen) { + await context + .read() + .generateSSIAndCryptoAccount(mnemonic!); + } }); } From ce80c13968343916c735f5c625325bb5551cabe3 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Thu, 2 Mar 2023 18:09:00 +0530 Subject: [PATCH 103/190] handle generate account with helperfunction --- lib/app/shared/constants/parameters.dart | 2 +- .../cubit/biometrics_cubit.dart | 54 +++- .../cubit/biometrics_state.dart | 40 ++- .../view/activate_biometrics_page.dart | 79 ++++-- .../cubit/onboarding_gen_phrase_cubit.dart | 47 +--- .../view/onboarding_gen_phrase.dart | 263 ++++++++---------- .../helper_function/helper_function.dart | 58 ++++ lib/onboarding/onboarding.dart | 1 + 8 files changed, 343 insertions(+), 201 deletions(-) create mode 100644 lib/onboarding/helper_function/helper_function.dart diff --git a/lib/app/shared/constants/parameters.dart b/lib/app/shared/constants/parameters.dart index 399b2df33..7ec2c5781 100644 --- a/lib/app/shared/constants/parameters.dart +++ b/lib/app/shared/constants/parameters.dart @@ -14,7 +14,7 @@ class Parameters { CredentialSubjectType.linkedInCard, ]; - static const bool hasCryptoCallToAction = true; + static const bool hasCryptoCallToAction = false; static const AdvanceSettingsState defaultAdvanceSettingsState = AdvanceSettingsState( diff --git a/lib/onboarding/activate_biometircs/cubit/biometrics_cubit.dart b/lib/onboarding/activate_biometircs/cubit/biometrics_cubit.dart index 5b9556efd..de9ae064b 100644 --- a/lib/onboarding/activate_biometircs/cubit/biometrics_cubit.dart +++ b/lib/onboarding/activate_biometircs/cubit/biometrics_cubit.dart @@ -1,17 +1,41 @@ import 'package:altme/app/app.dart'; +import 'package:altme/dashboard/dashboard.dart'; +import 'package:altme/did/did.dart'; +import 'package:altme/onboarding/helper_function/helper_function.dart'; +import 'package:altme/splash/splash.dart'; +import 'package:altme/wallet/wallet.dart'; import 'package:bloc/bloc.dart'; +import 'package:did_kit/did_kit.dart'; import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:json_annotation/json_annotation.dart'; +import 'package:key_generator/key_generator.dart'; import 'package:secure_storage/secure_storage.dart'; part 'biometrics_cubit.g.dart'; part 'biometrics_state.dart'; class BiometricsCubit extends Cubit { - BiometricsCubit() : super(const BiometricsState()) { + BiometricsCubit({ + required this.secureStorageProvider, + required this.keyGenerator, + required this.didKitProvider, + required this.didCubit, + required this.homeCubit, + required this.walletCubit, + required this.splashCubit, + }) : super(const BiometricsState()) { init(); } + final SecureStorageProvider secureStorageProvider; + final KeyGenerator keyGenerator; + final DIDKitProvider didKitProvider; + final DIDCubit didCubit; + final HomeCubit homeCubit; + final WalletCubit walletCubit; + final SplashCubit splashCubit; + Future init() async { final fingerprintEnabled = await getSecureStorage.get(SecureStorageKeys.fingerprintEnabled); @@ -21,4 +45,32 @@ class BiometricsCubit extends Cubit { void setFingerprintEnabled({bool enabled = false}) { emit(state.copyWith(isBiometricsEnabled: enabled)); } + + Future generateSSIAndCryptoAccount(List mnemonic) async { + final log = getLogger('BiometricsCubit'); + emit(state.loading()); + await Future.delayed(const Duration(milliseconds: 500)); + try { + await generateAccount( + mnemonic: mnemonic, + secureStorageProvider: secureStorageProvider, + keyGenerator: keyGenerator, + didKitProvider: didKitProvider, + didCubit: didCubit, + homeCubit: homeCubit, + walletCubit: walletCubit, + splashCubit: splashCubit, + ); + emit(state.success()); + } catch (error) { + log.e('something went wrong when generating a key', error); + emit( + state.error( + messageHandler: ResponseMessage( + ResponseString.RESPONSE_STRING_ERROR_GENERATING_KEY, + ), + ), + ); + } + } } diff --git a/lib/onboarding/activate_biometircs/cubit/biometrics_state.dart b/lib/onboarding/activate_biometircs/cubit/biometrics_state.dart index 7d953dd36..47f442885 100644 --- a/lib/onboarding/activate_biometircs/cubit/biometrics_state.dart +++ b/lib/onboarding/activate_biometircs/cubit/biometrics_state.dart @@ -2,12 +2,48 @@ part of 'biometrics_cubit.dart'; @JsonSerializable() class BiometricsState extends Equatable { - const BiometricsState({this.isBiometricsEnabled = false}); + const BiometricsState({ + this.status = AppStatus.init, + this.message, + this.isBiometricsEnabled = false, + }); factory BiometricsState.fromJson(Map json) => _$BiometricsStateFromJson(json); final bool isBiometricsEnabled; + final AppStatus status; + final StateMessage? message; + + BiometricsState loading() { + return BiometricsState( + status: AppStatus.loading, + isBiometricsEnabled: isBiometricsEnabled, + ); + } + + BiometricsState error({ + required MessageHandler messageHandler, + }) { + return BiometricsState( + status: AppStatus.error, + message: StateMessage.error(messageHandler: messageHandler), + isBiometricsEnabled: isBiometricsEnabled, + ); + } + + BiometricsState success({ + MessageHandler? messageHandler, + String? filePath, + }) { + return BiometricsState( + status: AppStatus.success, + message: messageHandler == null + ? null + : StateMessage.success(messageHandler: messageHandler), + isBiometricsEnabled: isBiometricsEnabled, + ); + } BiometricsState copyWith({bool? isBiometricsEnabled}) { return BiometricsState( @@ -18,5 +54,5 @@ class BiometricsState extends Equatable { Map toJson() => _$BiometricsStateToJson(this); @override - List get props => [isBiometricsEnabled]; + List get props => [status, message, isBiometricsEnabled]; } diff --git a/lib/onboarding/activate_biometircs/view/activate_biometrics_page.dart b/lib/onboarding/activate_biometircs/view/activate_biometrics_page.dart index 8f767aea2..2982f25fc 100644 --- a/lib/onboarding/activate_biometircs/view/activate_biometrics_page.dart +++ b/lib/onboarding/activate_biometircs/view/activate_biometrics_page.dart @@ -1,10 +1,17 @@ import 'package:altme/app/app.dart'; +import 'package:altme/dashboard/dashboard.dart'; +import 'package:altme/did/did.dart'; import 'package:altme/import_wallet/import_wallet.dart'; import 'package:altme/l10n/l10n.dart'; import 'package:altme/onboarding/onboarding.dart'; import 'package:altme/route/route.dart'; +import 'package:altme/splash/splash.dart'; +import 'package:altme/wallet/cubit/wallet_cubit.dart'; +import 'package:bip39/bip39.dart' as bip39; +import 'package:did_kit/did_kit.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:key_generator/key_generator.dart'; import 'package:secure_storage/secure_storage.dart'; class ActiviateBiometricsPage extends StatelessWidget { @@ -26,7 +33,15 @@ class ActiviateBiometricsPage extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (_) => BiometricsCubit(), + create: (_) => BiometricsCubit( + secureStorageProvider: getSecureStorage, + didCubit: context.read(), + didKitProvider: DIDKitProvider(), + keyGenerator: KeyGenerator(), + homeCubit: context.read(), + walletCubit: context.read(), + splashCubit: context.read(), + ), child: ActivateBiometricsView( localAuthApi: LocalAuthApi(), routeType: routeType, @@ -53,15 +68,38 @@ class ActivateBiometricsView extends StatelessWidget { onWillPop: () async { return false; }, - child: BasePage( - scrollView: false, - padding: const EdgeInsets.symmetric( - horizontal: Sizes.spaceXSmall, - ), - titleLeading: const BackLeadingButton(), - body: BlocBuilder( - builder: (context, state) { - return Column( + child: BlocConsumer( + listener: (context, state) { + if (state.status == AppStatus.loading) { + LoadingView().show(context: context); + } else { + LoadingView().hide(); + } + + if (state.message != null) { + AlertMessage.showStateMessage( + context: context, + stateMessage: state.message!, + ); + } + + if (state.status == AppStatus.success) { + context.read().init(); + Navigator.pushAndRemoveUntil( + context, + WalletReadyPage.route(), + (Route route) => route.isFirst, + ); + } + }, + builder: (context, state) { + return BasePage( + scrollView: false, + padding: const EdgeInsets.symmetric( + horizontal: Sizes.spaceXSmall, + ), + titleLeading: const BackLeadingButton(), + body: Column( children: [ MStepper( step: 2, @@ -126,12 +164,19 @@ class ActivateBiometricsView extends StatelessWidget { ), MyGradientButton( text: l10n.next, - onPressed: () { + onPressed: () async { if (routeType == WalletRouteType.create) { - Navigator.of(context) - .push(OnBoardingGenPhrasePage.route()); + if (byPassScreen) { + final mnemonic = bip39.generateMnemonic().split(' '); + await context + .read() + .generateSSIAndCryptoAccount(mnemonic); + } else { + await Navigator.of(context) + .push(OnBoardingGenPhrasePage.route()); + } } else { - Navigator.of(context).push( + await Navigator.of(context).push( ImportWalletPage.route( isFromOnboarding: true, ), @@ -143,9 +188,9 @@ class ActivateBiometricsView extends StatelessWidget { height: Sizes.spaceXSmall, ) ], - ); - }, - ), + ), + ); + }, ), ); } diff --git a/lib/onboarding/gen_phrase/cubit/onboarding_gen_phrase_cubit.dart b/lib/onboarding/gen_phrase/cubit/onboarding_gen_phrase_cubit.dart index 075670033..7d0b1e094 100644 --- a/lib/onboarding/gen_phrase/cubit/onboarding_gen_phrase_cubit.dart +++ b/lib/onboarding/gen_phrase/cubit/onboarding_gen_phrase_cubit.dart @@ -1,6 +1,7 @@ import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/did/did.dart'; +import 'package:altme/onboarding/helper_function/helper_function.dart'; import 'package:altme/splash/splash.dart'; import 'package:altme/wallet/wallet.dart'; import 'package:did_kit/did_kit.dart'; @@ -44,44 +45,16 @@ class OnBoardingGenPhraseCubit extends Cubit { emit(state.loading()); await Future.delayed(const Duration(milliseconds: 500)); try { - final mnemonicFormatted = mnemonic.join(' '); - - /// ssi wallet - await secureStorageProvider.set( - SecureStorageKeys.ssiMnemonic, - mnemonicFormatted, - ); - - final ssiKey = await keyGenerator.jwkFromMnemonic( - mnemonic: mnemonicFormatted, - accountType: AccountType.ssi, - ); - await secureStorageProvider.set(SecureStorageKeys.ssiKey, ssiKey); - - const didMethod = AltMeStrings.defaultDIDMethod; - final did = didKitProvider.keyToDID(didMethod, ssiKey); - const didMethodName = AltMeStrings.defaultDIDMethodName; - final verificationMethod = - await didKitProvider.keyToVerificationMethod(didMethod, ssiKey); - - await didCubit.set( - did: did, - didMethod: didMethod, - didMethodName: didMethodName, - verificationMethod: verificationMethod, + await generateAccount( + mnemonic: mnemonic, + secureStorageProvider: secureStorageProvider, + keyGenerator: keyGenerator, + didKitProvider: didKitProvider, + didCubit: didCubit, + homeCubit: homeCubit, + walletCubit: walletCubit, + splashCubit: splashCubit, ); - - /// what's new popup disabled - splashCubit.disableWhatsNewPopUp(); - - /// crypto wallet - await walletCubit.createCryptoWallet( - mnemonicOrKey: mnemonicFormatted, - isImported: false, - isFromOnboarding: true, - ); - - await homeCubit.emitHasWallet(); emit(state.success()); } catch (error) { log.e('something went wrong when generating a key', error); diff --git a/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart b/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart index 4722e8886..df688b54c 100644 --- a/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart +++ b/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart @@ -49,22 +49,12 @@ class OnBoardingGenPhraseView extends StatefulWidget { class _OnBoardingGenPhraseViewState extends State { late List? mnemonic; - bool get byPassScreen => !Parameters.hasCryptoCallToAction; - @override void initState() { super.initState(); mnemonic = bip39.generateMnemonic().split(' '); Future.microtask(() => context.read().initialize()) .catchError((_) => null); - - WidgetsBinding.instance.addPostFrameCallback((_) async { - if (byPassScreen) { - await context - .read() - .generateSSIAndCryptoAccount(mnemonic!); - } - }); } @override @@ -84,10 +74,6 @@ class _OnBoardingGenPhraseViewState extends State { context: context, stateMessage: state.message!, ); - - if (byPassScreen) { - Navigator.pop(context); - } } if (state.status == AppStatus.success) { @@ -100,145 +86,136 @@ class _OnBoardingGenPhraseViewState extends State { } }, builder: (context, state) { - return byPassScreen - ? BasePage(body: Container()) - : BasePage( - scrollView: false, - useSafeArea: true, - padding: - const EdgeInsets.symmetric(horizontal: Sizes.spaceXSmall), - titleLeading: const BackLeadingButton(), - secureScreen: true, - body: Column( + return BasePage( + scrollView: false, + useSafeArea: true, + padding: const EdgeInsets.symmetric(horizontal: Sizes.spaceXSmall), + titleLeading: const BackLeadingButton(), + secureScreen: true, + body: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.max, + children: [ + Expanded( + child: SingleChildScrollView( + child: Column( + children: [ + const MStepper( + step: 3, + totalStep: 3, + ), + const SizedBox( + height: Sizes.spaceNormal, + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: Sizes.spaceNormal, + ), + child: Text( + l10n.onboardingPleaseStoreMessage, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.headlineSmall, + ), + ), + const SizedBox(height: Sizes.spaceNormal), + if (mnemonic != null) + MnemonicDisplay(mnemonic: mnemonic!), + // const SizedBox( + // height: Sizes.spaceSmall, + // ), + // TextButton( + // onPressed: () { + // Clipboard.setData( + // ClipboardData( + // text: state.mnemonic.join(' '), + // ), + // ); + // }, + // child: Text( + // l10n.copyToClipboard, + // style: Theme.of(context).textTheme.copyToClipBoard, + // ), + // ), + const SizedBox(height: Sizes.spaceLarge), + Text( + l10n.onboardingAltmeMessage, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.genPhraseSubmessage, + ), + ], + ), + ), + ), + //const Spacer(), + Padding( + padding: const EdgeInsets.all( + Sizes.spaceNormal, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.max, children: [ - Expanded( - child: SingleChildScrollView( - child: Column( - children: [ - const MStepper( - step: 3, - totalStep: 3, - ), - const SizedBox( - height: Sizes.spaceNormal, - ), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: Sizes.spaceNormal, - ), - child: Text( - l10n.onboardingPleaseStoreMessage, - textAlign: TextAlign.center, - style: - Theme.of(context).textTheme.headlineSmall, - ), - ), - const SizedBox(height: Sizes.spaceNormal), - if (mnemonic != null) - MnemonicDisplay(mnemonic: mnemonic!), - // const SizedBox( - // height: Sizes.spaceSmall, - // ), - // TextButton( - // onPressed: () { - // Clipboard.setData( - // ClipboardData( - // text: state.mnemonic.join(' '), - // ), - // ); - // }, - // child: Text( - // l10n.copyToClipboard, - // style: Theme.of(context).textTheme.copyToClipBoard, - // ), - // ), - const SizedBox(height: Sizes.spaceLarge), - Text( - l10n.onboardingAltmeMessage, - textAlign: TextAlign.center, - style: Theme.of(context) - .textTheme - .genPhraseSubmessage, - ), - ], + Transform.scale( + scale: 1.5, + child: Checkbox( + value: state.isTicked, + fillColor: MaterialStateProperty.all( + Theme.of(context).colorScheme.primary, ), - ), - ), - //const Spacer(), - Padding( - padding: const EdgeInsets.all( - Sizes.spaceNormal, - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.max, - children: [ - Transform.scale( - scale: 1.5, - child: Checkbox( - value: state.isTicked, - fillColor: MaterialStateProperty.all( - Theme.of(context).colorScheme.primary, - ), - materialTapTargetSize: - MaterialTapTargetSize.shrinkWrap, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all( - Radius.circular(6), - ), - ), - onChanged: (newValue) => context - .read() - .switchTick(), - ), - ), - const SizedBox( - width: Sizes.spaceXSmall, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all( + Radius.circular(6), ), - Expanded( - child: InkWell( - onTap: () { - context - .read() - .switchTick(); - }, - child: MyText( - l10n.onboardingWroteDownMessage, - style: Theme.of(context) - .textTheme - .onBoardingCheckMessage, - ), - ), - ), - ], + ), + onChanged: (newValue) => context + .read() + .switchTick(), ), ), - ], - ), - navigation: SafeArea( - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: Sizes.spaceSmall, - vertical: Sizes.spaceSmall, + const SizedBox( + width: Sizes.spaceXSmall, ), - child: MyGradientButton( - text: l10n.onBoardingGenPhraseButton, - verticalSpacing: 18, - onPressed: state.isTicked || mnemonic != null - ? () async { - await context - .read() - .generateSSIAndCryptoAccount(mnemonic!); - } - : null, + Expanded( + child: InkWell( + onTap: () { + context.read().switchTick(); + }, + child: MyText( + l10n.onboardingWroteDownMessage, + style: Theme.of(context) + .textTheme + .onBoardingCheckMessage, + ), + ), ), - ), + ], ), - ); + ), + ], + ), + navigation: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: Sizes.spaceSmall, + vertical: Sizes.spaceSmall, + ), + child: MyGradientButton( + text: l10n.onBoardingGenPhraseButton, + verticalSpacing: 18, + onPressed: state.isTicked + ? () async { + await context + .read() + .generateSSIAndCryptoAccount(mnemonic!); + } + : null, + ), + ), + ), + ); }, ); } diff --git a/lib/onboarding/helper_function/helper_function.dart b/lib/onboarding/helper_function/helper_function.dart new file mode 100644 index 000000000..6af04123a --- /dev/null +++ b/lib/onboarding/helper_function/helper_function.dart @@ -0,0 +1,58 @@ +import 'package:altme/app/app.dart'; +import 'package:altme/dashboard/dashboard.dart'; +import 'package:altme/did/did.dart'; +import 'package:altme/splash/splash.dart'; +import 'package:altme/wallet/wallet.dart'; +import 'package:did_kit/did_kit.dart'; +import 'package:key_generator/key_generator.dart'; +import 'package:secure_storage/secure_storage.dart'; + +Future generateAccount({ + required List mnemonic, + required SecureStorageProvider secureStorageProvider, + required KeyGenerator keyGenerator, + required DIDKitProvider didKitProvider, + required DIDCubit didCubit, + required HomeCubit homeCubit, + required WalletCubit walletCubit, + required SplashCubit splashCubit, +}) async { + final mnemonicFormatted = mnemonic.join(' '); + + /// ssi wallet + await secureStorageProvider.set( + SecureStorageKeys.ssiMnemonic, + mnemonicFormatted, + ); + + final ssiKey = await keyGenerator.jwkFromMnemonic( + mnemonic: mnemonicFormatted, + accountType: AccountType.ssi, + ); + await secureStorageProvider.set(SecureStorageKeys.ssiKey, ssiKey); + + const didMethod = AltMeStrings.defaultDIDMethod; + final did = didKitProvider.keyToDID(didMethod, ssiKey); + const didMethodName = AltMeStrings.defaultDIDMethodName; + final verificationMethod = + await didKitProvider.keyToVerificationMethod(didMethod, ssiKey); + + await didCubit.set( + did: did, + didMethod: didMethod, + didMethodName: didMethodName, + verificationMethod: verificationMethod, + ); + + /// what's new popup disabled + splashCubit.disableWhatsNewPopUp(); + + /// crypto wallet + await walletCubit.createCryptoWallet( + mnemonicOrKey: mnemonicFormatted, + isImported: false, + isFromOnboarding: true, + ); + + await homeCubit.emitHasWallet(); +} diff --git a/lib/onboarding/onboarding.dart b/lib/onboarding/onboarding.dart index 947eae23b..5f1b012e0 100644 --- a/lib/onboarding/onboarding.dart +++ b/lib/onboarding/onboarding.dart @@ -1,5 +1,6 @@ export 'activate_biometircs/activate_biometrics.dart'; export 'gen_phrase/onboarding_gen_phrase.dart'; +export 'helper_function/helper_function.dart'; export 'starter/starter.dart'; export 'submit_enterprise_user/submit_enterprise_user.dart'; export 'third/onboarding_third.dart'; From 8c7f9ab06daa6178f8669eb52d9632c73313b2e5 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Thu, 2 Mar 2023 18:10:52 +0530 Subject: [PATCH 104/190] hasCryptoCallToAction=true --- lib/app/shared/constants/parameters.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/app/shared/constants/parameters.dart b/lib/app/shared/constants/parameters.dart index 7ec2c5781..399b2df33 100644 --- a/lib/app/shared/constants/parameters.dart +++ b/lib/app/shared/constants/parameters.dart @@ -14,7 +14,7 @@ class Parameters { CredentialSubjectType.linkedInCard, ]; - static const bool hasCryptoCallToAction = false; + static const bool hasCryptoCallToAction = true; static const AdvanceSettingsState defaultAdvanceSettingsState = AdvanceSettingsState( From e5e3534105536a47f0361660a1f63d12ee9f0505 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Fri, 3 Mar 2023 12:29:36 +0530 Subject: [PATCH 105/190] ask user to write last four mnemonics #1421 --- lib/app/shared/widget/mnemonic.dart | 17 +- lib/app/shared/widget/phrase_word.dart | 13 +- .../contact_us/view/contact_us_page.dart | 25 -- lib/l10n/arb/app_en.arb | 1 + lib/l10n/untranslated.json | 4 + .../view/activate_biometrics_page.dart | 2 +- .../cubit/onboarding_gen_phrase_cubit.dart | 54 +--- .../view/onboarding_gen_phrase.dart | 28 +- lib/onboarding/onboarding.dart | 1 + .../cubit/onboarding_verify_phrase_cubit.dart | 80 +++++ .../cubit/onboarding_verify_phrase_state.dart | 64 ++++ .../onboarding_verify_phrase.dart | 2 + .../view/onboarding_verify_phrase.dart | 274 ++++++++++++++++++ .../wallet_ready/view/wallet_ready_page.dart | 242 ++++++++-------- .../widgets/mnemonic_text_field.dart | 68 +++++ lib/onboarding/widgets/widgets.dart | 1 + lib/pin_code/view/confirm_pin_code_page.dart | 2 +- .../view/enter_new_pin_code_page.dart | 2 +- pubspec.lock | 2 +- 19 files changed, 643 insertions(+), 239 deletions(-) create mode 100644 lib/onboarding/verify_phrase/cubit/onboarding_verify_phrase_cubit.dart create mode 100644 lib/onboarding/verify_phrase/cubit/onboarding_verify_phrase_state.dart create mode 100644 lib/onboarding/verify_phrase/onboarding_verify_phrase.dart create mode 100644 lib/onboarding/verify_phrase/view/onboarding_verify_phrase.dart create mode 100644 lib/onboarding/widgets/mnemonic_text_field.dart diff --git a/lib/app/shared/widget/mnemonic.dart b/lib/app/shared/widget/mnemonic.dart index 309e23b8b..25384acb1 100644 --- a/lib/app/shared/widget/mnemonic.dart +++ b/lib/app/shared/widget/mnemonic.dart @@ -20,25 +20,14 @@ class MnemonicDisplay extends StatelessWidget { padding: const EdgeInsets.symmetric(vertical: 12), child: Row( children: [ - Expanded( - child: PhraseWord( - order: j + 1, - word: mnemonic[j], - ), - ), + Expanded(child: PhraseWord(order: j + 1, word: mnemonic[j])), const SizedBox(width: 12), Expanded( - child: PhraseWord( - order: j + 2, - word: mnemonic[j + 1], - ), + child: PhraseWord(order: j + 2, word: mnemonic[j + 1]), ), const SizedBox(width: 12), Expanded( - child: PhraseWord( - order: j + 3, - word: mnemonic[j + 2], - ), + child: PhraseWord(order: j + 3, word: mnemonic[j + 2]), ), ], ), diff --git a/lib/app/shared/widget/phrase_word.dart b/lib/app/shared/widget/phrase_word.dart index 543c24933..5774afd81 100644 --- a/lib/app/shared/widget/phrase_word.dart +++ b/lib/app/shared/widget/phrase_word.dart @@ -26,10 +26,15 @@ class PhraseWord extends StatelessWidget { ), borderRadius: BorderRadius.circular(128), ), - child: MyText( - '$order. $word', - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.passPhraseText, + child: SizedBox( + height: 25, + child: Center( + child: MyText( + '$order. $word', + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.passPhraseText, + ), + ), ), ); } diff --git a/lib/dashboard/drawer/contact_us/view/contact_us_page.dart b/lib/dashboard/drawer/contact_us/view/contact_us_page.dart index 4d960884f..2b253ffae 100644 --- a/lib/dashboard/drawer/contact_us/view/contact_us_page.dart +++ b/lib/dashboard/drawer/contact_us/view/contact_us_page.dart @@ -51,31 +51,6 @@ class _ContactUsViewState extends State { const SizedBox( height: Sizes.spaceXSmall, ), - TextFormField( - textInputAction: TextInputAction.next, - keyboardType: TextInputType.emailAddress, - style: Theme.of(context).textTheme.titleSmall, - onSaved: (value) { - subject = value ?? ''; - }, - validator: (value) { - if (value == null || value.isEmpty) { - return l10n.fillingThisFieldIsMandatory; - } else { - return null; - } - }, - decoration: InputDecoration( - hintText: '${l10n.subject} :', - border: const OutlineInputBorder( - borderRadius: BorderRadius.all( - Radius.circular( - Sizes.smallRadius, - ), - ), - ), - ), - ), const SizedBox( height: Sizes.spaceLarge, ), diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 75af464da..36d181343 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -379,6 +379,7 @@ "selectAccount": "Select account", "onbordingSeedPhrase": "Seed Phrase", "onboardingPleaseStoreMessage": "Please, write down your Recovery Phrase", + "onboardingVerifyPhraseMessage": "Please, verify your Recovery Phrase", "onboardingAltmeMessage": "Altme is non-custodial. Your Recovery Phrase is the only way to recover your account.", "onboardingWroteDownMessage": "I wrote down my Recovery Phrase", "copyToClipboard": "Copy to clipboard", diff --git a/lib/l10n/untranslated.json b/lib/l10n/untranslated.json index 3324cd31a..cff28beed 100644 --- a/lib/l10n/untranslated.json +++ b/lib/l10n/untranslated.json @@ -329,6 +329,7 @@ "selectAccount", "onbordingSeedPhrase", "onboardingPleaseStoreMessage", + "onboardingVerifyPhraseMessage", "onboardingAltmeMessage", "onboardingWroteDownMessage", "copyToClipboard", @@ -1073,6 +1074,7 @@ "selectAccount", "onbordingSeedPhrase", "onboardingPleaseStoreMessage", + "onboardingVerifyPhraseMessage", "onboardingAltmeMessage", "onboardingWroteDownMessage", "copyToClipboard", @@ -1489,6 +1491,7 @@ "fr": [ "educationCredentials", + "onboardingVerifyPhraseMessage", "importWalletTextRecoveryPhraseOnly", "importWalletHintTextRecoveryPhraseOnly", "educationCredentialHomeSubtitle", @@ -1844,6 +1847,7 @@ "selectAccount", "onbordingSeedPhrase", "onboardingPleaseStoreMessage", + "onboardingVerifyPhraseMessage", "onboardingAltmeMessage", "onboardingWroteDownMessage", "copyToClipboard", diff --git a/lib/onboarding/activate_biometircs/view/activate_biometrics_page.dart b/lib/onboarding/activate_biometircs/view/activate_biometrics_page.dart index 2982f25fc..4210afdd1 100644 --- a/lib/onboarding/activate_biometircs/view/activate_biometrics_page.dart +++ b/lib/onboarding/activate_biometircs/view/activate_biometrics_page.dart @@ -103,7 +103,7 @@ class ActivateBiometricsView extends StatelessWidget { children: [ MStepper( step: 2, - totalStep: byPassScreen ? 2 : 3, + totalStep: byPassScreen ? 2 : 4, ), const Spacer(), Text( diff --git a/lib/onboarding/gen_phrase/cubit/onboarding_gen_phrase_cubit.dart b/lib/onboarding/gen_phrase/cubit/onboarding_gen_phrase_cubit.dart index 7d0b1e094..9ce8aa762 100644 --- a/lib/onboarding/gen_phrase/cubit/onboarding_gen_phrase_cubit.dart +++ b/lib/onboarding/gen_phrase/cubit/onboarding_gen_phrase_cubit.dart @@ -1,70 +1,18 @@ import 'package:altme/app/app.dart'; -import 'package:altme/dashboard/dashboard.dart'; -import 'package:altme/did/did.dart'; -import 'package:altme/onboarding/helper_function/helper_function.dart'; -import 'package:altme/splash/splash.dart'; -import 'package:altme/wallet/wallet.dart'; -import 'package:did_kit/did_kit.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:json_annotation/json_annotation.dart'; -import 'package:key_generator/key_generator.dart'; - -import 'package:secure_storage/secure_storage.dart'; part 'onboarding_gen_phrase_cubit.g.dart'; part 'onboarding_gen_phrase_state.dart'; class OnBoardingGenPhraseCubit extends Cubit { - OnBoardingGenPhraseCubit({ - required this.secureStorageProvider, - required this.keyGenerator, - required this.didKitProvider, - required this.didCubit, - required this.homeCubit, - required this.walletCubit, - required this.splashCubit, - }) : super(const OnBoardingGenPhraseState()); - - final SecureStorageProvider secureStorageProvider; - final KeyGenerator keyGenerator; - final DIDKitProvider didKitProvider; - final DIDCubit didCubit; - final HomeCubit homeCubit; - final WalletCubit walletCubit; - final SplashCubit splashCubit; + OnBoardingGenPhraseCubit() : super(const OnBoardingGenPhraseState()); final log = getLogger('OnBoardingGenPhraseCubit'); Future switchTick() async { emit(state.copyWith(isTicked: !state.isTicked)); } - - Future generateSSIAndCryptoAccount(List mnemonic) async { - emit(state.loading()); - await Future.delayed(const Duration(milliseconds: 500)); - try { - await generateAccount( - mnemonic: mnemonic, - secureStorageProvider: secureStorageProvider, - keyGenerator: keyGenerator, - didKitProvider: didKitProvider, - didCubit: didCubit, - homeCubit: homeCubit, - walletCubit: walletCubit, - splashCubit: splashCubit, - ); - emit(state.success()); - } catch (error) { - log.e('something went wrong when generating a key', error); - emit( - state.error( - messageHandler: ResponseMessage( - ResponseString.RESPONSE_STRING_ERROR_GENERATING_KEY, - ), - ), - ); - } - } } diff --git a/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart b/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart index df688b54c..7e19cd1dc 100644 --- a/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart +++ b/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart @@ -1,17 +1,11 @@ import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; -import 'package:altme/did/did.dart'; import 'package:altme/l10n/l10n.dart'; import 'package:altme/onboarding/onboarding.dart'; -import 'package:altme/splash/splash.dart'; import 'package:altme/theme/theme.dart'; -import 'package:altme/wallet/cubit/wallet_cubit.dart'; import 'package:bip39/bip39.dart' as bip39; -import 'package:did_kit/did_kit.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:key_generator/key_generator.dart'; -import 'package:secure_storage/secure_storage.dart'; class OnBoardingGenPhrasePage extends StatelessWidget { const OnBoardingGenPhrasePage({super.key}); @@ -24,15 +18,7 @@ class OnBoardingGenPhrasePage extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => OnBoardingGenPhraseCubit( - secureStorageProvider: getSecureStorage, - didCubit: context.read(), - didKitProvider: DIDKitProvider(), - keyGenerator: KeyGenerator(), - homeCubit: context.read(), - walletCubit: context.read(), - splashCubit: context.read(), - ), + create: (context) => OnBoardingGenPhraseCubit(), child: const OnBoardingGenPhraseView(), ); } @@ -103,7 +89,7 @@ class _OnBoardingGenPhraseViewState extends State { children: [ const MStepper( step: 3, - totalStep: 3, + totalStep: 4, ), const SizedBox( height: Sizes.spaceNormal, @@ -206,10 +192,12 @@ class _OnBoardingGenPhraseViewState extends State { text: l10n.onBoardingGenPhraseButton, verticalSpacing: 18, onPressed: state.isTicked - ? () async { - await context - .read() - .generateSSIAndCryptoAccount(mnemonic!); + ? () { + Navigator.of(context).push( + OnBoardingVerifyPhrasePage.route( + mnemonic: mnemonic!, + ), + ); } : null, ), diff --git a/lib/onboarding/onboarding.dart b/lib/onboarding/onboarding.dart index 5f1b012e0..673cacdab 100644 --- a/lib/onboarding/onboarding.dart +++ b/lib/onboarding/onboarding.dart @@ -5,5 +5,6 @@ export 'starter/starter.dart'; export 'submit_enterprise_user/submit_enterprise_user.dart'; export 'third/onboarding_third.dart'; export 'tos/onboarding_terms.dart'; +export 'verify_phrase/onboarding_verify_phrase.dart'; export 'wallet_ready/wallet_ready.dart'; export 'widgets/widgets.dart'; diff --git a/lib/onboarding/verify_phrase/cubit/onboarding_verify_phrase_cubit.dart b/lib/onboarding/verify_phrase/cubit/onboarding_verify_phrase_cubit.dart new file mode 100644 index 000000000..06775925a --- /dev/null +++ b/lib/onboarding/verify_phrase/cubit/onboarding_verify_phrase_cubit.dart @@ -0,0 +1,80 @@ +import 'package:altme/app/app.dart'; +import 'package:altme/dashboard/dashboard.dart'; +import 'package:altme/did/did.dart'; +import 'package:altme/onboarding/helper_function/helper_function.dart'; +import 'package:altme/splash/splash.dart'; +import 'package:altme/wallet/wallet.dart'; +import 'package:did_kit/did_kit.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:key_generator/key_generator.dart'; + +import 'package:secure_storage/secure_storage.dart'; + +part 'onboarding_verify_phrase_cubit.g.dart'; + +part 'onboarding_verify_phrase_state.dart'; + +class OnBoardingVerifyPhraseCubit extends Cubit { + OnBoardingVerifyPhraseCubit({ + required this.secureStorageProvider, + required this.keyGenerator, + required this.didKitProvider, + required this.didCubit, + required this.homeCubit, + required this.walletCubit, + required this.splashCubit, + }) : super(const OnBoardingVerifyPhraseState()); + + final SecureStorageProvider secureStorageProvider; + final KeyGenerator keyGenerator; + final DIDKitProvider didKitProvider; + final DIDCubit didCubit; + final HomeCubit homeCubit; + final WalletCubit walletCubit; + final SplashCubit splashCubit; + + final log = getLogger('OnBoardingVerifyPhraseCubit'); + + Future verify({ + required List mnemonic, + required List lastFourMnemonics, + }) async { + if (mnemonic[8] == lastFourMnemonics[0] && + mnemonic[9] == lastFourMnemonics[1] && + mnemonic[10] == lastFourMnemonics[2] && + mnemonic[11] == lastFourMnemonics[3]) { + emit(state.copyWith(isVerified: true)); + } else { + emit(state.copyWith(isVerified: false)); + } + } + + Future generateSSIAndCryptoAccount(List mnemonic) async { + emit(state.loading()); + await Future.delayed(const Duration(milliseconds: 500)); + try { + await generateAccount( + mnemonic: mnemonic, + secureStorageProvider: secureStorageProvider, + keyGenerator: keyGenerator, + didKitProvider: didKitProvider, + didCubit: didCubit, + homeCubit: homeCubit, + walletCubit: walletCubit, + splashCubit: splashCubit, + ); + emit(state.success()); + } catch (error) { + log.e('something went wrong when generating a key', error); + emit( + state.error( + messageHandler: ResponseMessage( + ResponseString.RESPONSE_STRING_ERROR_GENERATING_KEY, + ), + ), + ); + } + } +} diff --git a/lib/onboarding/verify_phrase/cubit/onboarding_verify_phrase_state.dart b/lib/onboarding/verify_phrase/cubit/onboarding_verify_phrase_state.dart new file mode 100644 index 000000000..d8eaca7e0 --- /dev/null +++ b/lib/onboarding/verify_phrase/cubit/onboarding_verify_phrase_state.dart @@ -0,0 +1,64 @@ +part of 'onboarding_verify_phrase_cubit.dart'; + +@JsonSerializable() +class OnBoardingVerifyPhraseState extends Equatable { + const OnBoardingVerifyPhraseState({ + this.status = AppStatus.init, + this.message, + this.isVerified = false, + }); + + factory OnBoardingVerifyPhraseState.fromJson(Map json) => + _$OnBoardingVerifyPhraseStateFromJson(json); + + final AppStatus status; + final StateMessage? message; + final bool isVerified; + + OnBoardingVerifyPhraseState loading() { + return OnBoardingVerifyPhraseState( + status: AppStatus.loading, + isVerified: isVerified, + ); + } + + OnBoardingVerifyPhraseState error({ + required MessageHandler messageHandler, + }) { + return OnBoardingVerifyPhraseState( + status: AppStatus.error, + message: StateMessage.error(messageHandler: messageHandler), + isVerified: isVerified, + ); + } + + OnBoardingVerifyPhraseState success({ + MessageHandler? messageHandler, + String? filePath, + }) { + return OnBoardingVerifyPhraseState( + status: AppStatus.success, + message: messageHandler == null + ? null + : StateMessage.success(messageHandler: messageHandler), + isVerified: isVerified, + ); + } + + OnBoardingVerifyPhraseState copyWith({ + AppStatus? status, + StateMessage? message, + bool? isVerified, + }) { + return OnBoardingVerifyPhraseState( + status: status ?? this.status, + message: message ?? this.message, + isVerified: isVerified ?? this.isVerified, + ); + } + + Map toJson() => _$OnBoardingVerifyPhraseStateToJson(this); + + @override + List get props => [status, isVerified, message]; +} diff --git a/lib/onboarding/verify_phrase/onboarding_verify_phrase.dart b/lib/onboarding/verify_phrase/onboarding_verify_phrase.dart new file mode 100644 index 000000000..7965b2a3c --- /dev/null +++ b/lib/onboarding/verify_phrase/onboarding_verify_phrase.dart @@ -0,0 +1,2 @@ +export 'cubit/onboarding_verify_phrase_cubit.dart'; +export 'view/onboarding_verify_phrase.dart'; diff --git a/lib/onboarding/verify_phrase/view/onboarding_verify_phrase.dart b/lib/onboarding/verify_phrase/view/onboarding_verify_phrase.dart new file mode 100644 index 000000000..84419b15e --- /dev/null +++ b/lib/onboarding/verify_phrase/view/onboarding_verify_phrase.dart @@ -0,0 +1,274 @@ +import 'package:altme/app/app.dart'; +import 'package:altme/dashboard/dashboard.dart'; +import 'package:altme/did/did.dart'; +import 'package:altme/l10n/l10n.dart'; +import 'package:altme/onboarding/onboarding.dart'; +import 'package:altme/splash/splash.dart'; +import 'package:altme/theme/theme.dart'; +import 'package:altme/wallet/cubit/wallet_cubit.dart'; +import 'package:did_kit/did_kit.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:key_generator/key_generator.dart'; +import 'package:secure_storage/secure_storage.dart'; + +class OnBoardingVerifyPhrasePage extends StatelessWidget { + const OnBoardingVerifyPhrasePage({required this.mnemonic, super.key}); + + final List mnemonic; + + static Route route({required List mnemonic}) => + MaterialPageRoute( + builder: (context) => OnBoardingVerifyPhrasePage(mnemonic: mnemonic), + settings: const RouteSettings(name: '/OnBoardingVerifyPhrasePage'), + ); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => OnBoardingVerifyPhraseCubit( + secureStorageProvider: getSecureStorage, + didCubit: context.read(), + didKitProvider: DIDKitProvider(), + keyGenerator: KeyGenerator(), + homeCubit: context.read(), + walletCubit: context.read(), + splashCubit: context.read(), + ), + child: OnBoardingVerifyPhraseView(mnemonic: mnemonic), + ); + } +} + +class OnBoardingVerifyPhraseView extends StatelessWidget { + OnBoardingVerifyPhraseView({required this.mnemonic, super.key}); + + final fourthLastController = TextEditingController(); + final thirdLastController = TextEditingController(); + final secondLastController = TextEditingController(); + final lastController = TextEditingController(); + + final List mnemonic; + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + + return BlocConsumer( + listener: (context, state) { + if (state.status == AppStatus.loading) { + LoadingView().show(context: context); + } else { + LoadingView().hide(); + } + + if (state.message != null) { + AlertMessage.showStateMessage( + context: context, + stateMessage: state.message!, + ); + } + + if (state.status == AppStatus.success) { + context.read().init(); + Navigator.pushAndRemoveUntil( + context, + WalletReadyPage.route(), + (Route route) => route.isFirst, + ); + } + }, + builder: (context, state) { + return BasePage( + scrollView: false, + useSafeArea: true, + padding: const EdgeInsets.symmetric(horizontal: Sizes.spaceXSmall), + titleLeading: const BackLeadingButton(), + secureScreen: true, + body: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.max, + children: [ + Expanded( + child: SingleChildScrollView( + child: Column( + children: [ + const MStepper( + step: 4, + totalStep: 4, + ), + const SizedBox(height: Sizes.spaceNormal), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: Sizes.spaceNormal, + ), + child: Text( + l10n.onboardingVerifyPhraseMessage, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.headlineSmall, + ), + ), + const SizedBox(height: Sizes.spaceNormal), + const SizedBox(height: Sizes.spaceSmall), + Row( + children: [ + Expanded( + child: PhraseWord(order: 1, word: mnemonic[0]), + ), + const SizedBox(width: 12), + Expanded( + child: PhraseWord(order: 2, word: mnemonic[1]), + ), + const SizedBox(width: 12), + Expanded( + child: PhraseWord(order: 3, word: mnemonic[2]), + ), + ], + ), + const SizedBox(height: Sizes.spaceNormal), + Row( + children: [ + Expanded( + child: PhraseWord(order: 4, word: mnemonic[3]), + ), + const SizedBox(width: 12), + Expanded( + child: PhraseWord(order: 5, word: mnemonic[4]), + ), + const SizedBox(width: 12), + Expanded( + child: PhraseWord(order: 6, word: mnemonic[5]), + ), + ], + ), + const SizedBox(height: Sizes.spaceNormal), + Row( + children: [ + Expanded( + child: PhraseWord(order: 7, word: mnemonic[6]), + ), + const SizedBox(width: 12), + Expanded( + child: PhraseWord(order: 8, word: mnemonic[7]), + ), + const SizedBox(width: 12), + Expanded( + child: MnemonicTextField( + controller: fourthLastController, + onChanged: (value) { + context + .read() + .verify( + mnemonic: mnemonic, + lastFourMnemonics: [ + fourthLastController.text, + thirdLastController.text, + secondLastController.text, + lastController.text, + ], + ); + }, + ), + ), + ], + ), + const SizedBox(height: Sizes.spaceNormal), + Row( + children: [ + Expanded( + child: MnemonicTextField( + controller: thirdLastController, + onChanged: (value) { + context + .read() + .verify( + mnemonic: mnemonic, + lastFourMnemonics: [ + fourthLastController.text, + value, + secondLastController.text, + lastController.text, + ], + ); + }, + ), + ), + const SizedBox(width: 12), + Expanded( + child: MnemonicTextField( + controller: secondLastController, + onChanged: (value) { + context + .read() + .verify( + mnemonic: mnemonic, + lastFourMnemonics: [ + fourthLastController.text, + thirdLastController.text, + value, + lastController.text, + ], + ); + }, + ), + ), + const SizedBox(width: 12), + Expanded( + child: MnemonicTextField( + controller: lastController, + onChanged: (value) { + context + .read() + .verify( + mnemonic: mnemonic, + lastFourMnemonics: [ + fourthLastController.text, + thirdLastController.text, + secondLastController.text, + value, + ], + ); + }, + ), + ), + ], + ), + const SizedBox(height: Sizes.spaceLarge), + const SizedBox(height: Sizes.spaceSmall), + Text( + l10n.onboardingAltmeMessage, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.genPhraseSubmessage, + ), + ], + ), + ), + ), + ], + ), + navigation: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: Sizes.spaceSmall, + vertical: Sizes.spaceSmall, + ), + child: MyGradientButton( + text: l10n.onBoardingGenPhraseButton, + verticalSpacing: 18, + onPressed: state.isVerified + ? () async { + await context + .read() + .generateSSIAndCryptoAccount(mnemonic); + } + : null, + ), + ), + ), + ); + }, + ); + } +} diff --git a/lib/onboarding/wallet_ready/view/wallet_ready_page.dart b/lib/onboarding/wallet_ready/view/wallet_ready_page.dart index ed3e45196..90df9ccbb 100644 --- a/lib/onboarding/wallet_ready/view/wallet_ready_page.dart +++ b/lib/onboarding/wallet_ready/view/wallet_ready_page.dart @@ -33,137 +33,141 @@ class WalletReadyView extends StatelessWidget { final l10n = context.l10n; return BlocBuilder( builder: (context, state) { - return BasePage( - scrollView: false, - body: Center( - child: Column( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const AltMeLogo( - size: Sizes.logo2XLarge, - ), - const SizedBox( - height: Sizes.spaceNormal, - ), - Text( - l10n.walletReadyTitle, - style: Theme.of(context).textTheme.headlineMedium, - ), - const SizedBox( - height: Sizes.spaceNormal, - ), - Text( - l10n.walletReadySubtitle, - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.headlineSmall?.copyWith( - fontWeight: FontWeight.normal, - color: Theme.of(context).colorScheme.onTertiary, - ), - ), - const SizedBox( - height: Sizes.space3XLarge, - ), - ], - ), - ), - navigation: SafeArea( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.all( - Sizes.spaceNormal, + return WillPopScope( + onWillPop: () async => false, + child: BasePage( + scrollView: false, + body: Center( + child: Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const AltMeLogo( + size: Sizes.logo2XLarge, ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.max, - children: [ - Transform.scale( - scale: 1.3, - child: Checkbox( - value: state.isAgreeWithTerms, - fillColor: MaterialStateProperty.all( - Theme.of(context).colorScheme.primary, - ), - materialTapTargetSize: - MaterialTapTargetSize.shrinkWrap, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all( - Radius.circular(6), - ), - ), - onChanged: (newValue) => context - .read() - .toggleAgreement(), + const SizedBox( + height: Sizes.spaceNormal, + ), + Text( + l10n.walletReadyTitle, + style: Theme.of(context).textTheme.headlineMedium, + ), + const SizedBox( + height: Sizes.spaceNormal, + ), + Text( + l10n.walletReadySubtitle, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.headlineSmall?.copyWith( + fontWeight: FontWeight.normal, + color: Theme.of(context).colorScheme.onTertiary, ), - ), - Expanded( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - InkWell( - onTap: () { - context - .read() - .toggleAgreement(); - }, - child: MyText( - l10n.iAgreeToThe, - style: Theme.of(context).textTheme.titleMedium, + ), + const SizedBox( + height: Sizes.space3XLarge, + ), + ], + ), + ), + navigation: SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.all( + Sizes.spaceNormal, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + Transform.scale( + scale: 1.3, + child: Checkbox( + value: state.isAgreeWithTerms, + fillColor: MaterialStateProperty.all( + Theme.of(context).colorScheme.primary, + ), + materialTapTargetSize: + MaterialTapTargetSize.shrinkWrap, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all( + Radius.circular(6), ), ), - Flexible( - child: InkWell( + onChanged: (newValue) => context + .read() + .toggleAgreement(), + ), + ), + Expanded( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + InkWell( onTap: () { - Navigator.pushAndRemoveUntil( - context, - OnBoardingTosPage.route(), - (Route route) => route.isFirst, - ); + context + .read() + .toggleAgreement(); }, child: MyText( - l10n.termsAndConditions.toLowerCase(), - style: Theme.of(context) - .textTheme - .titleLarge - ?.copyWith( - fontWeight: FontWeight.bold, - color: Theme.of(context) - .colorScheme - .primary, - ), + l10n.iAgreeToThe, + style: + Theme.of(context).textTheme.titleMedium, ), ), - ) - ], + Flexible( + child: InkWell( + onTap: () { + Navigator.pushAndRemoveUntil( + context, + OnBoardingTosPage.route(), + (Route route) => route.isFirst, + ); + }, + child: MyText( + l10n.termsAndConditions.toLowerCase(), + style: Theme.of(context) + .textTheme + .titleLarge + ?.copyWith( + fontWeight: FontWeight.bold, + color: Theme.of(context) + .colorScheme + .primary, + ), + ), + ), + ) + ], + ), ), - ), - ], - ), - ), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: Sizes.spaceSmall, - vertical: Sizes.space2XSmall, + ], + ), ), - child: MyGradientButton( - text: l10n.start, - verticalSpacing: 18, - onPressed: state.isAgreeWithTerms - ? () { - Navigator.pushAndRemoveUntil( - context, - DashboardPage.route(), - (Route route) => route.isFirst, - ); - } - : null, + Padding( + padding: const EdgeInsets.symmetric( + horizontal: Sizes.spaceSmall, + vertical: Sizes.space2XSmall, + ), + child: MyGradientButton( + text: l10n.start, + verticalSpacing: 18, + onPressed: state.isAgreeWithTerms + ? () { + Navigator.pushAndRemoveUntil( + context, + DashboardPage.route(), + (Route route) => route.isFirst, + ); + } + : null, + ), ), - ), - ], + ], + ), ), ), ); diff --git a/lib/onboarding/widgets/mnemonic_text_field.dart b/lib/onboarding/widgets/mnemonic_text_field.dart new file mode 100644 index 000000000..58b7c9b60 --- /dev/null +++ b/lib/onboarding/widgets/mnemonic_text_field.dart @@ -0,0 +1,68 @@ +import 'package:altme/app/app.dart'; +import 'package:altme/theme/theme.dart'; +import 'package:flutter/material.dart'; + +class MnemonicTextField extends StatelessWidget { + const MnemonicTextField({ + super.key, + required this.controller, + this.type = TextInputType.text, + this.validator, + this.focusNode, + this.fillColor, + this.onChanged, + }); + + final TextEditingController controller; + final TextInputType type; + final String? Function(String?)? validator; + final FocusNode? focusNode; + final Color? fillColor; + final void Function(String)? onChanged; + + @override + Widget build(BuildContext context) { + return SizedBox( + child: TextFormField( + textAlign: TextAlign.center, + focusNode: focusNode, + textInputAction: TextInputAction.next, + controller: controller, + enableInteractiveSelection: true, + textCapitalization: TextCapitalization.none, + validator: validator, + keyboardType: TextInputType.text, + style: Theme.of(context).textTheme.passPhraseText, + cursorColor: Theme.of(context).colorScheme.titleColor, + onChanged: onChanged, + decoration: InputDecoration( + contentPadding: const EdgeInsets.symmetric( + horizontal: Sizes.spaceSmall, + vertical: Sizes.spaceSmall + 4, + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(128), + borderSide: BorderSide( + color: Theme.of(context).colorScheme.primary, + width: 1.5, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(128), + borderSide: BorderSide( + color: Theme.of(context).colorScheme.primary, + width: 1.5, + ), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(128), + borderSide: BorderSide( + color: Theme.of(context).colorScheme.primary, + width: 1.5, + ), + ), + ), + ), + ); + } +} diff --git a/lib/onboarding/widgets/widgets.dart b/lib/onboarding/widgets/widgets.dart index 45898e847..a9a6ebdd5 100644 --- a/lib/onboarding/widgets/widgets.dart +++ b/lib/onboarding/widgets/widgets.dart @@ -1,3 +1,4 @@ export 'm_stepper.dart'; +export 'mnemonic_text_field.dart'; export 'onboarding_widget.dart'; export 'page_tracker.dart'; diff --git a/lib/pin_code/view/confirm_pin_code_page.dart b/lib/pin_code/view/confirm_pin_code_page.dart index 52dc7077d..b7d6a5a81 100644 --- a/lib/pin_code/view/confirm_pin_code_page.dart +++ b/lib/pin_code/view/confirm_pin_code_page.dart @@ -97,7 +97,7 @@ class _ConfirmPinCodeViewState extends State { header: widget.isFromOnboarding ? MStepper( step: 1, - totalStep: byPassScreen ? 2 : 3, + totalStep: byPassScreen ? 2 : 4, ) : null, deleteButton: Text( diff --git a/lib/pin_code/view/enter_new_pin_code_page.dart b/lib/pin_code/view/enter_new_pin_code_page.dart index 529946920..5609609b9 100644 --- a/lib/pin_code/view/enter_new_pin_code_page.dart +++ b/lib/pin_code/view/enter_new_pin_code_page.dart @@ -83,7 +83,7 @@ class _EnterNewPinCodeViewState extends State { header: widget.isFromOnboarding ? MStepper( step: 1, - totalStep: byPassScreen ? 2 : 3, + totalStep: byPassScreen ? 2 : 4, ) : null, deleteButton: Text( diff --git a/pubspec.lock b/pubspec.lock index 8f4f97398..5e2029553 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2431,5 +2431,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.19.0 <3.7.0" + dart: ">=2.19.0 <3.0.0" flutter: ">=3.7.0-0" From 9f79ba210b9fe4cd87ba686f63f9325887538367 Mon Sep 17 00:00:00 2001 From: hawkbee1 Date: Fri, 3 Mar 2023 11:07:52 +0100 Subject: [PATCH 106/190] version: 1.10.5+156 --- .../dashboard/widgets/what_is_new_dialog.dart | 17 +++++++++++++++++ pubspec.yaml | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart b/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart index 8006737e9..f47040dbd 100644 --- a/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart +++ b/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart @@ -81,6 +81,23 @@ class WhatIsNewDialog extends StatelessWidget { textAlign: TextAlign.center, ), const SizedBox(height: Sizes.spaceXSmall), + const NewFeature( + 'End to end encryption of decentralized chat in Altme', + ), + const NewFeature( + 'Specific design for EBSI diploma card', // ignore: lines_longer_than_80_chars + ), + Text( + '1.9.9', + style: Theme.of(context) + .textTheme + .defaultDialogSubtitle + .copyWith( + color: Colors.white, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: Sizes.spaceXSmall), const NewFeature( 'Integration of Matrix.org to give users access to a decentralized chat in Altme', // ignore: lines_longer_than_80_chars ), diff --git a/pubspec.yaml b/pubspec.yaml index 5a22c559d..c1d06e32c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: altme description: AltMe Flutter App -version: 1.10.4+155 +version: 1.10.5+156 publish_to: none environment: From e73ded28fcb5b63d0c08764c51e45cc682f658db Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Fri, 3 Mar 2023 18:00:09 +0530 Subject: [PATCH 107/190] add jumble and selection logic for mnemonics #1421 --- .../shared/enum/status/mnemonic_status.dart | 30 ++ lib/app/shared/enum/status/status.dart | 1 + lib/app/shared/widget/phrase_word.dart | 20 +- lib/l10n/arb/app_en.arb | 3 +- lib/l10n/untranslated.json | 4 + .../cubit/onboarding_verify_phrase_cubit.dart | 64 +++- .../cubit/onboarding_verify_phrase_state.dart | 45 ++- .../view/onboarding_verify_phrase.dart | 286 ++++++++---------- .../widgets/mnemonic_text_field.dart | 68 ----- lib/onboarding/widgets/widgets.dart | 3 +- lib/theme/app_theme/app_theme.dart | 8 +- pubspec.lock | 2 +- 12 files changed, 277 insertions(+), 257 deletions(-) create mode 100644 lib/app/shared/enum/status/mnemonic_status.dart delete mode 100644 lib/onboarding/widgets/mnemonic_text_field.dart diff --git a/lib/app/shared/enum/status/mnemonic_status.dart b/lib/app/shared/enum/status/mnemonic_status.dart new file mode 100644 index 000000000..47d3ae4a6 --- /dev/null +++ b/lib/app/shared/enum/status/mnemonic_status.dart @@ -0,0 +1,30 @@ +import 'dart:ui'; + +enum MnemonicStatus { + unselected, + selected, + wrongSelection, +} + +extension MnemonicStatusX on MnemonicStatus { + bool get showOrder { + switch (this) { + case MnemonicStatus.unselected: + case MnemonicStatus.wrongSelection: + return false; + case MnemonicStatus.selected: + return true; + } + } + + Color get color { + switch (this) { + case MnemonicStatus.unselected: + return const Color(0xff86809D); + case MnemonicStatus.wrongSelection: + return const Color(0xffFF0045); + case MnemonicStatus.selected: + return const Color(0xff6600FF); + } + } +} diff --git a/lib/app/shared/enum/status/status.dart b/lib/app/shared/enum/status/status.dart index 34be1f6e2..b18a1509c 100644 --- a/lib/app/shared/enum/status/status.dart +++ b/lib/app/shared/enum/status/status.dart @@ -3,6 +3,7 @@ export 'beacon_status.dart'; export 'credential_detail_tab_status.dart'; export 'credential_status.dart'; export 'home_status.dart'; +export 'mnemonic_status.dart'; export 'pass_base_status.dart'; export 'qr_scan_status.dart'; export 'revocation_status.dart'; diff --git a/lib/app/shared/widget/phrase_word.dart b/lib/app/shared/widget/phrase_word.dart index 5774afd81..efc373003 100644 --- a/lib/app/shared/widget/phrase_word.dart +++ b/lib/app/shared/widget/phrase_word.dart @@ -7,13 +7,23 @@ class PhraseWord extends StatelessWidget { super.key, required this.order, required this.word, + this.color, + this.showOrder = true, + this.onTap, }); final int order; final String word; + final Color? color; + final bool showOrder; + final void Function()? onTap; @override - Widget build(BuildContext context) => Container( + Widget build(BuildContext context) { + final color = this.color ?? Theme.of(context).colorScheme.primary; + return TransparentInkWell( + onTap: onTap, + child: Container( padding: const EdgeInsets.symmetric( horizontal: Sizes.spaceSmall, vertical: Sizes.spaceSmall, @@ -22,7 +32,7 @@ class PhraseWord extends StatelessWidget { color: Theme.of(context).colorScheme.transparent, border: Border.all( width: 1.5, - color: Theme.of(context).colorScheme.primary, + color: color, ), borderRadius: BorderRadius.circular(128), ), @@ -30,11 +40,13 @@ class PhraseWord extends StatelessWidget { height: 25, child: Center( child: MyText( - '$order. $word', + showOrder ? '$order. $word' : word, textAlign: TextAlign.center, style: Theme.of(context).textTheme.passPhraseText, ), ), ), - ); + ), + ); + } } diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 36d181343..133ef1a7a 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -379,7 +379,8 @@ "selectAccount": "Select account", "onbordingSeedPhrase": "Seed Phrase", "onboardingPleaseStoreMessage": "Please, write down your Recovery Phrase", - "onboardingVerifyPhraseMessage": "Please, verify your Recovery Phrase", + "onboardingVerifyPhraseMessage": "Confirm Recovery Words", + "onboardingVerifyPhraseMessageDetails": "To ensure your Recovery Phrase is written correctly, select the words in correct order.", "onboardingAltmeMessage": "Altme is non-custodial. Your Recovery Phrase is the only way to recover your account.", "onboardingWroteDownMessage": "I wrote down my Recovery Phrase", "copyToClipboard": "Copy to clipboard", diff --git a/lib/l10n/untranslated.json b/lib/l10n/untranslated.json index cff28beed..0552364dc 100644 --- a/lib/l10n/untranslated.json +++ b/lib/l10n/untranslated.json @@ -330,6 +330,7 @@ "onbordingSeedPhrase", "onboardingPleaseStoreMessage", "onboardingVerifyPhraseMessage", + "onboardingVerifyPhraseMessageDetails", "onboardingAltmeMessage", "onboardingWroteDownMessage", "copyToClipboard", @@ -1075,6 +1076,7 @@ "onbordingSeedPhrase", "onboardingPleaseStoreMessage", "onboardingVerifyPhraseMessage", + "onboardingVerifyPhraseMessageDetails", "onboardingAltmeMessage", "onboardingWroteDownMessage", "copyToClipboard", @@ -1492,6 +1494,7 @@ "fr": [ "educationCredentials", "onboardingVerifyPhraseMessage", + "onboardingVerifyPhraseMessageDetails", "importWalletTextRecoveryPhraseOnly", "importWalletHintTextRecoveryPhraseOnly", "educationCredentialHomeSubtitle", @@ -1848,6 +1851,7 @@ "onbordingSeedPhrase", "onboardingPleaseStoreMessage", "onboardingVerifyPhraseMessage", + "onboardingVerifyPhraseMessageDetails", "onboardingAltmeMessage", "onboardingWroteDownMessage", "copyToClipboard", diff --git a/lib/onboarding/verify_phrase/cubit/onboarding_verify_phrase_cubit.dart b/lib/onboarding/verify_phrase/cubit/onboarding_verify_phrase_cubit.dart index 06775925a..6f3cb8b30 100644 --- a/lib/onboarding/verify_phrase/cubit/onboarding_verify_phrase_cubit.dart +++ b/lib/onboarding/verify_phrase/cubit/onboarding_verify_phrase_cubit.dart @@ -25,7 +25,7 @@ class OnBoardingVerifyPhraseCubit extends Cubit { required this.homeCubit, required this.walletCubit, required this.splashCubit, - }) : super(const OnBoardingVerifyPhraseState()); + }) : super(OnBoardingVerifyPhraseState()); final SecureStorageProvider secureStorageProvider; final KeyGenerator keyGenerator; @@ -37,17 +37,65 @@ class OnBoardingVerifyPhraseCubit extends Cubit { final log = getLogger('OnBoardingVerifyPhraseCubit'); + void orderMnemonics() { + emit(state.loading()); + //create list + final oldState = List.from(state.mnemonicStates); + for (var i = 1; i <= 12; i++) { + oldState.add(MnemonicState(order: i)); + } + oldState.shuffle(); + emit(state.copyWith(mnemonicStates: oldState, status: AppStatus.idle)); + } + + List tempMnemonics = []; + Future verify({ required List mnemonic, - required List lastFourMnemonics, + required int index, }) async { - if (mnemonic[8] == lastFourMnemonics[0] && - mnemonic[9] == lastFourMnemonics[1] && - mnemonic[10] == lastFourMnemonics[2] && - mnemonic[11] == lastFourMnemonics[3]) { - emit(state.copyWith(isVerified: true)); + final mnemonicState = state.mnemonicStates[index]; + final int clickCount = tempMnemonics.length; + + if (mnemonicState.order <= clickCount) return; + + if (mnemonicState.mnemonicStatus != MnemonicStatus.wrongSelection) { + for (final mnemonicState in state.mnemonicStates) { + if (mnemonicState.mnemonicStatus == MnemonicStatus.wrongSelection) { + return; + } + } + } + + final updatedList = List.from(state.mnemonicStates); + if (mnemonicState.order == clickCount + 1) { + tempMnemonics.add(mnemonic[mnemonicState.order - 1]); + updatedList[index] = + mnemonicState.copyWith(mnemonicStatus: MnemonicStatus.selected); } else { - emit(state.copyWith(isVerified: false)); + if (mnemonicState.mnemonicStatus == MnemonicStatus.unselected) { + updatedList[index] = mnemonicState.copyWith( + mnemonicStatus: MnemonicStatus.wrongSelection, + ); + } else { + updatedList[index] = + mnemonicState.copyWith(mnemonicStatus: MnemonicStatus.unselected); + } + } + + emit(state.copyWith(status: AppStatus.idle, mnemonicStates: updatedList)); + + if (tempMnemonics.length >= 6) { + if (mnemonic[0] == tempMnemonics[0] && + mnemonic[1] == tempMnemonics[1] && + mnemonic[2] == tempMnemonics[2] && + mnemonic[3] == tempMnemonics[3] && + mnemonic[4] == tempMnemonics[4] && + mnemonic[5] == tempMnemonics[5]) { + emit(state.copyWith(isVerified: true, status: AppStatus.idle)); + } else { + emit(state.copyWith(isVerified: false, status: AppStatus.idle)); + } } } diff --git a/lib/onboarding/verify_phrase/cubit/onboarding_verify_phrase_state.dart b/lib/onboarding/verify_phrase/cubit/onboarding_verify_phrase_state.dart index d8eaca7e0..9b8a77e07 100644 --- a/lib/onboarding/verify_phrase/cubit/onboarding_verify_phrase_state.dart +++ b/lib/onboarding/verify_phrase/cubit/onboarding_verify_phrase_state.dart @@ -2,11 +2,12 @@ part of 'onboarding_verify_phrase_cubit.dart'; @JsonSerializable() class OnBoardingVerifyPhraseState extends Equatable { - const OnBoardingVerifyPhraseState({ + OnBoardingVerifyPhraseState({ this.status = AppStatus.init, this.message, this.isVerified = false, - }); + List? mnemonicStates, + }) : mnemonicStates = mnemonicStates ?? []; factory OnBoardingVerifyPhraseState.fromJson(Map json) => _$OnBoardingVerifyPhraseStateFromJson(json); @@ -14,11 +15,13 @@ class OnBoardingVerifyPhraseState extends Equatable { final AppStatus status; final StateMessage? message; final bool isVerified; + final List mnemonicStates; OnBoardingVerifyPhraseState loading() { return OnBoardingVerifyPhraseState( status: AppStatus.loading, isVerified: isVerified, + mnemonicStates: mnemonicStates, ); } @@ -29,6 +32,7 @@ class OnBoardingVerifyPhraseState extends Equatable { status: AppStatus.error, message: StateMessage.error(messageHandler: messageHandler), isVerified: isVerified, + mnemonicStates: mnemonicStates, ); } @@ -42,23 +46,54 @@ class OnBoardingVerifyPhraseState extends Equatable { ? null : StateMessage.success(messageHandler: messageHandler), isVerified: isVerified, + mnemonicStates: mnemonicStates, ); } OnBoardingVerifyPhraseState copyWith({ - AppStatus? status, + required AppStatus status, StateMessage? message, bool? isVerified, + List? mnemonicStates, }) { return OnBoardingVerifyPhraseState( - status: status ?? this.status, + status: status, message: message ?? this.message, isVerified: isVerified ?? this.isVerified, + mnemonicStates: mnemonicStates ?? this.mnemonicStates, ); } Map toJson() => _$OnBoardingVerifyPhraseStateToJson(this); @override - List get props => [status, isVerified, message]; + List get props => [status, isVerified, message, mnemonicStates]; +} + +@JsonSerializable() +class MnemonicState extends Equatable { + const MnemonicState({ + this.mnemonicStatus = MnemonicStatus.unselected, + required this.order, + }); + + factory MnemonicState.fromJson(Map json) => + _$MnemonicStateFromJson(json); + + final MnemonicStatus mnemonicStatus; + final int order; + + MnemonicState copyWith({ + required MnemonicStatus mnemonicStatus, + }) { + return MnemonicState( + order: order, + mnemonicStatus: mnemonicStatus, + ); + } + + Map toJson() => _$MnemonicStateToJson(this); + + @override + List get props => [mnemonicStatus, order]; } diff --git a/lib/onboarding/verify_phrase/view/onboarding_verify_phrase.dart b/lib/onboarding/verify_phrase/view/onboarding_verify_phrase.dart index 84419b15e..a69af31f8 100644 --- a/lib/onboarding/verify_phrase/view/onboarding_verify_phrase.dart +++ b/lib/onboarding/verify_phrase/view/onboarding_verify_phrase.dart @@ -40,16 +40,26 @@ class OnBoardingVerifyPhrasePage extends StatelessWidget { } } -class OnBoardingVerifyPhraseView extends StatelessWidget { - OnBoardingVerifyPhraseView({required this.mnemonic, super.key}); - - final fourthLastController = TextEditingController(); - final thirdLastController = TextEditingController(); - final secondLastController = TextEditingController(); - final lastController = TextEditingController(); +class OnBoardingVerifyPhraseView extends StatefulWidget { + const OnBoardingVerifyPhraseView({required this.mnemonic, super.key}); final List mnemonic; + @override + State createState() => + _OnBoardingVerifyPhraseViewState(); +} + +class _OnBoardingVerifyPhraseViewState + extends State { + @override + void initState() { + WidgetsBinding.instance.addPostFrameCallback((_) { + context.read().orderMnemonics(); + }); + super.initState(); + } + @override Widget build(BuildContext context) { final l10n = context.l10n; @@ -81,173 +91,115 @@ class OnBoardingVerifyPhraseView extends StatelessWidget { }, builder: (context, state) { return BasePage( - scrollView: false, + scrollView: true, useSafeArea: true, padding: const EdgeInsets.symmetric(horizontal: Sizes.spaceXSmall), titleLeading: const BackLeadingButton(), secureScreen: true, - body: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisSize: MainAxisSize.max, - children: [ - Expanded( - child: SingleChildScrollView( - child: Column( - children: [ - const MStepper( - step: 4, - totalStep: 4, - ), - const SizedBox(height: Sizes.spaceNormal), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: Sizes.spaceNormal, - ), - child: Text( - l10n.onboardingVerifyPhraseMessage, - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.headlineSmall, - ), + body: state.mnemonicStates.length < 12 + ? Container() + : Column( + children: [ + const MStepper( + step: 4, + totalStep: 4, + ), + const SizedBox(height: Sizes.spaceNormal), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: Sizes.spaceNormal, ), - const SizedBox(height: Sizes.spaceNormal), - const SizedBox(height: Sizes.spaceSmall), - Row( - children: [ - Expanded( - child: PhraseWord(order: 1, word: mnemonic[0]), - ), - const SizedBox(width: 12), - Expanded( - child: PhraseWord(order: 2, word: mnemonic[1]), - ), - const SizedBox(width: 12), - Expanded( - child: PhraseWord(order: 3, word: mnemonic[2]), - ), - ], - ), - const SizedBox(height: Sizes.spaceNormal), - Row( - children: [ - Expanded( - child: PhraseWord(order: 4, word: mnemonic[3]), - ), - const SizedBox(width: 12), - Expanded( - child: PhraseWord(order: 5, word: mnemonic[4]), - ), - const SizedBox(width: 12), - Expanded( - child: PhraseWord(order: 6, word: mnemonic[5]), - ), - ], - ), - const SizedBox(height: Sizes.spaceNormal), - Row( - children: [ - Expanded( - child: PhraseWord(order: 7, word: mnemonic[6]), - ), - const SizedBox(width: 12), - Expanded( - child: PhraseWord(order: 8, word: mnemonic[7]), - ), - const SizedBox(width: 12), - Expanded( - child: MnemonicTextField( - controller: fourthLastController, - onChanged: (value) { - context - .read() - .verify( - mnemonic: mnemonic, - lastFourMnemonics: [ - fourthLastController.text, - thirdLastController.text, - secondLastController.text, - lastController.text, - ], - ); - }, - ), - ), - ], - ), - const SizedBox(height: Sizes.spaceNormal), - Row( - children: [ - Expanded( - child: MnemonicTextField( - controller: thirdLastController, - onChanged: (value) { - context - .read() - .verify( - mnemonic: mnemonic, - lastFourMnemonics: [ - fourthLastController.text, - value, - secondLastController.text, - lastController.text, - ], - ); - }, - ), - ), - const SizedBox(width: 12), - Expanded( - child: MnemonicTextField( - controller: secondLastController, - onChanged: (value) { - context - .read() - .verify( - mnemonic: mnemonic, - lastFourMnemonics: [ - fourthLastController.text, - thirdLastController.text, - value, - lastController.text, - ], - ); - }, - ), - ), - const SizedBox(width: 12), - Expanded( - child: MnemonicTextField( - controller: lastController, - onChanged: (value) { - context - .read() - .verify( - mnemonic: mnemonic, - lastFourMnemonics: [ - fourthLastController.text, - thirdLastController.text, - secondLastController.text, - value, - ], - ); - }, - ), - ), - ], - ), - const SizedBox(height: Sizes.spaceLarge), - const SizedBox(height: Sizes.spaceSmall), - Text( - l10n.onboardingAltmeMessage, + child: Text( + l10n.onboardingVerifyPhraseMessage, textAlign: TextAlign.center, - style: Theme.of(context).textTheme.genPhraseSubmessage, + style: Theme.of(context).textTheme.headlineSmall, ), - ], - ), + ), + const SizedBox(height: Sizes.spaceSmall), + Text( + l10n.onboardingVerifyPhraseMessageDetails, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.pheaseVerifySubmessage, + ), + const SizedBox(height: Sizes.spaceNormal), + ListView.builder( + itemCount: 4, + shrinkWrap: true, + physics: const ScrollPhysics(), + itemBuilder: (context, index) { + final j = 3 * index; + + final col1Mnemonics = state.mnemonicStates[j]; + final col2Mnemonics = state.mnemonicStates[j + 1]; + final col3Mnemonics = state.mnemonicStates[j + 2]; + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 12), + child: Row( + children: [ + Expanded( + child: PhraseWord( + order: col1Mnemonics.order, + word: + widget.mnemonic[col1Mnemonics.order - 1], + showOrder: + col1Mnemonics.mnemonicStatus.showOrder, + color: col1Mnemonics.mnemonicStatus.color, + onTap: () { + context + .read() + .verify( + mnemonic: widget.mnemonic, + index: j, + ); + }, + ), + ), + const SizedBox(width: 12), + Expanded( + child: PhraseWord( + order: col2Mnemonics.order, + word: + widget.mnemonic[col2Mnemonics.order - 1], + showOrder: + col2Mnemonics.mnemonicStatus.showOrder, + color: col2Mnemonics.mnemonicStatus.color, + onTap: () { + context + .read() + .verify( + mnemonic: widget.mnemonic, + index: j + 1, + ); + }, + ), + ), + const SizedBox(width: 12), + Expanded( + child: PhraseWord( + order: col3Mnemonics.order, + word: + widget.mnemonic[col3Mnemonics.order - 1], + showOrder: + col3Mnemonics.mnemonicStatus.showOrder, + color: col3Mnemonics.mnemonicStatus.color, + onTap: () { + context + .read() + .verify( + mnemonic: widget.mnemonic, + index: j + 2, + ); + }, + ), + ), + ], + ), + ); + }, + ), + ], ), - ), - ], - ), navigation: SafeArea( child: Padding( padding: const EdgeInsets.symmetric( @@ -261,7 +213,7 @@ class OnBoardingVerifyPhraseView extends StatelessWidget { ? () async { await context .read() - .generateSSIAndCryptoAccount(mnemonic); + .generateSSIAndCryptoAccount(widget.mnemonic); } : null, ), diff --git a/lib/onboarding/widgets/mnemonic_text_field.dart b/lib/onboarding/widgets/mnemonic_text_field.dart deleted file mode 100644 index 58b7c9b60..000000000 --- a/lib/onboarding/widgets/mnemonic_text_field.dart +++ /dev/null @@ -1,68 +0,0 @@ -import 'package:altme/app/app.dart'; -import 'package:altme/theme/theme.dart'; -import 'package:flutter/material.dart'; - -class MnemonicTextField extends StatelessWidget { - const MnemonicTextField({ - super.key, - required this.controller, - this.type = TextInputType.text, - this.validator, - this.focusNode, - this.fillColor, - this.onChanged, - }); - - final TextEditingController controller; - final TextInputType type; - final String? Function(String?)? validator; - final FocusNode? focusNode; - final Color? fillColor; - final void Function(String)? onChanged; - - @override - Widget build(BuildContext context) { - return SizedBox( - child: TextFormField( - textAlign: TextAlign.center, - focusNode: focusNode, - textInputAction: TextInputAction.next, - controller: controller, - enableInteractiveSelection: true, - textCapitalization: TextCapitalization.none, - validator: validator, - keyboardType: TextInputType.text, - style: Theme.of(context).textTheme.passPhraseText, - cursorColor: Theme.of(context).colorScheme.titleColor, - onChanged: onChanged, - decoration: InputDecoration( - contentPadding: const EdgeInsets.symmetric( - horizontal: Sizes.spaceSmall, - vertical: Sizes.spaceSmall + 4, - ), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(128), - borderSide: BorderSide( - color: Theme.of(context).colorScheme.primary, - width: 1.5, - ), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(128), - borderSide: BorderSide( - color: Theme.of(context).colorScheme.primary, - width: 1.5, - ), - ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(128), - borderSide: BorderSide( - color: Theme.of(context).colorScheme.primary, - width: 1.5, - ), - ), - ), - ), - ); - } -} diff --git a/lib/onboarding/widgets/widgets.dart b/lib/onboarding/widgets/widgets.dart index a9a6ebdd5..2d42d490f 100644 --- a/lib/onboarding/widgets/widgets.dart +++ b/lib/onboarding/widgets/widgets.dart @@ -1,4 +1,3 @@ -export 'm_stepper.dart'; -export 'mnemonic_text_field.dart'; +export 'm_stepper.dart'; export 'onboarding_widget.dart'; export 'page_tracker.dart'; diff --git a/lib/theme/app_theme/app_theme.dart b/lib/theme/app_theme/app_theme.dart index a7f5df2b4..64de24684 100644 --- a/lib/theme/app_theme/app_theme.dart +++ b/lib/theme/app_theme/app_theme.dart @@ -701,7 +701,7 @@ extension CustomTextTheme on TextTheme { fontWeight: FontWeight.bold, color: const Color(0xff180B2B), ); - + TextStyle get newVersionTitle => GoogleFonts.nunito( fontSize: 18, fontWeight: FontWeight.w800, @@ -822,6 +822,12 @@ extension CustomTextTheme on TextTheme { color: const Color(0xff71CBFF), ); + TextStyle get pheaseVerifySubmessage => GoogleFonts.nunito( + fontSize: 18, + fontWeight: FontWeight.w400, + color: const Color(0xff86809D), + ); + TextStyle get identitiyBaseLightText => GoogleFonts.roboto( fontSize: 16, fontWeight: FontWeight.w300, diff --git a/pubspec.lock b/pubspec.lock index 5e2029553..8f4f97398 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2431,5 +2431,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.19.0 <3.0.0" + dart: ">=2.19.0 <3.7.0" flutter: ">=3.7.0-0" From 4265896d5a2fb2eb8eea1b236bf264ec32a404c4 Mon Sep 17 00:00:00 2001 From: hawkbee <49282360+hawkbee1@users.noreply.github.com> Date: Fri, 3 Mar 2023 17:21:04 +0100 Subject: [PATCH 108/190] version: 1.10.5+156 (#1427) --- lib/dashboard/dashboard/widgets/what_is_new_dialog.dart | 7 +++++++ pubspec.yaml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart b/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart index ed95d3280..181579fb5 100644 --- a/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart +++ b/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart @@ -80,6 +80,13 @@ class WhatIsNewDialog extends StatelessWidget { NewContent( version: versionNumber, features: const [ + 'End to end encryption of decentralized chat in Altme', + 'Specific design for EBSI diploma card', + ], + ), + const NewContent( + version: '1.9.9', + features: [ 'Integration of Matrix.org to give users access to a decentralized chat in Altme', 'Compliance with EBSI and support of new official ID documents (diplomas...)', ], diff --git a/pubspec.yaml b/pubspec.yaml index 5a22c559d..c1d06e32c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: altme description: AltMe Flutter App -version: 1.10.4+155 +version: 1.10.5+156 publish_to: none environment: From 560108f77e91e834ab6e0c0d1b7a0351402b3c8a Mon Sep 17 00:00:00 2001 From: hawkbee1 Date: Fri, 3 Mar 2023 17:22:09 +0100 Subject: [PATCH 109/190] version: 1.11.0+157 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index c1d06e32c..d1eba782b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: altme description: AltMe Flutter App -version: 1.10.5+156 +version: 1.11.0+157 publish_to: none environment: From 5bb34b6772adaa374714973c6cf45a3a630e3f30 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Mon, 6 Mar 2023 11:28:18 +0330 Subject: [PATCH 110/190] remove openSsl package --- pubspec.lock | 8 -------- pubspec.yaml | 1 - 2 files changed, 9 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 8f4f97398..2568f8ecd 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -864,14 +864,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" - flutter_openssl_crypto: - dependency: "direct main" - description: - name: flutter_openssl_crypto - sha256: b64a0825d79f10b6d5f5951f7ce2d5ddc12ed532129fc5a7e0ce472f5b97d78e - url: "https://pub.dev" - source: hosted - version: "0.1.0" flutter_parsed_text: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index d1eba782b..4e7a0a2d0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -57,7 +57,6 @@ dependencies: flutter_markdown: ^0.6.14 flutter_native_timezone: ^2.0.0 flutter_olm: ^1.2.0 - flutter_openssl_crypto: ^0.1.0 flutter_svg: ^2.0.0+1 google_fonts: ^4.0.3 #google_mlkit_face_detection: ^0.5.0 From 9b50bc97274acfbef767ee2625c22b698dabba21 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Mon, 6 Mar 2023 11:42:53 +0330 Subject: [PATCH 111/190] fixed creating new chat after reset wallet --- .../live_chat/cubit/live_chat_cubit.dart | 70 ++++++++----------- 1 file changed, 29 insertions(+), 41 deletions(-) diff --git a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart index 854b15bd7..843fd6010 100644 --- a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart +++ b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart @@ -37,7 +37,7 @@ class LiveChatCubit extends Cubit { } final SecureStorageProvider secureStorageProvider; - late Client client; + Client? client; final DioClient dioClient; final logger = getLogger('LiveChatCubit'); String? _roomId; @@ -63,11 +63,11 @@ class LiveChatCubit extends Cubit { emit(state.copyWith(messages: [message, ...state.messages])); // await _checkIfRoomNotExistThenCreateIt(); - final room = client.getRoomById(_roomId!); + final room = client!.getRoomById(_roomId!); if (room == null) { - await client.joinRoomById(_roomId!); + await client!.joinRoomById(_roomId!); } - final eventId = await client.getRoomById(_roomId!)?.sendTextEvent( + final eventId = await client!.getRoomById(_roomId!)?.sendTextEvent( partialText.text, txid: messageId, ); @@ -153,7 +153,7 @@ class LiveChatCubit extends Cubit { ); emit(state.copyWith(messages: [message, ...state.messages])); await _checkIfRoomNotExistThenCreateIt(); - await client.getRoomById(_roomId!)?.sendFileEvent( + await client!.getRoomById(_roomId!)?.sendFileEvent( MatrixFile( bytes: File(result.files.single.path!).readAsBytesSync(), name: result.files.single.name, @@ -190,7 +190,7 @@ class LiveChatCubit extends Cubit { emit(state.copyWith(messages: [message, ...state.messages])); await _checkIfRoomNotExistThenCreateIt(); - await client.getRoomById(_roomId!)?.sendFileEvent( + await client!.getRoomById(_roomId!)?.sendFileEvent( MatrixFile( bytes: bytes, name: result.name, @@ -204,23 +204,9 @@ class LiveChatCubit extends Cubit { logger.i('init()'); try { final ssiKey = await secureStorageProvider.get(SecureStorageKeys.ssiKey); - if (ssiKey == null || ssiKey.isEmpty) { - emit( - state.copyWith( - status: AppStatus.error, - message: StateMessage.error( - messageHandler: ResponseMessage( - ResponseString - .RESPONSE_STRING_SOMETHING_WENT_WRONG_TRY_AGAIN_LATER, - ), - ), - ), - ); - return; - } final did = await secureStorageProvider.get(SecureStorageKeys.did) ?? ''; final username = did.replaceAll(':', '-'); - if (username.isEmpty) { + if (ssiKey == null || ssiKey.isEmpty || did.isEmpty || username.isEmpty) { emit( state.copyWith( status: AppStatus.error, @@ -234,6 +220,7 @@ class LiveChatCubit extends Cubit { ); return; } + emit(state.copyWith(status: AppStatus.loading)); await _initClient(); final isUserRegisteredMatrix = await secureStorageProvider @@ -302,7 +289,7 @@ class LiveChatCubit extends Cubit { Future _subscribeToEventsOfRoom() async { await _onEventSubscription?.cancel(); - _onEventSubscription = client.onRoomState.stream.listen((Event event) { + _onEventSubscription = client!.onRoomState.stream.listen((Event event) { if (event.roomId == _roomId && event.type == 'm.room.message') { final txId = event.unsigned?['transaction_id'] as String?; if (state.messages.any( @@ -401,7 +388,7 @@ class LiveChatCubit extends Cubit { } int get unreadMessageCount => - client.getRoomById(_roomId ?? '')?.notificationCount ?? 0; + client?.getRoomById(_roomId ?? '')?.notificationCount ?? 0; void _getUnreadMessageCount() { final unreadCount = unreadMessageCount; @@ -412,7 +399,7 @@ class LiveChatCubit extends Cubit { Future markMessageAsRead(List? eventIds) async { if (eventIds == null || eventIds.isEmpty) return; - final room = client.getRoomById(_roomId ?? ''); + final room = client?.getRoomById(_roomId ?? ''); if (room == null) return; try { for (final eventId in eventIds) { @@ -447,9 +434,9 @@ class LiveChatCubit extends Cubit { } Future> _retriveMessagesFromDB(String roomId) async { - final room = client.getRoomById(roomId); + final room = client?.getRoomById(roomId); if (room == null) return []; - final events = await client.database?.getEventList(room); + final events = await client!.database?.getEventList(room); if (events == null || events.isEmpty) return []; final messageEvents = events.where((event) => event.type == 'm.room.message').toList() @@ -487,7 +474,7 @@ class LiveChatCubit extends Cubit { /// room not exist before with this [name] and alias Future _createRoomAndInviteSupport(String name) async { try { - final roomId = await client.createRoom( + final roomId = await client!.createRoom( isDirect: true, name: name, invite: ['@support:matrix.talao.co'], @@ -513,7 +500,7 @@ class LiveChatCubit extends Cubit { '$name-updated-$millisecondsSinceEpoch', ); } else { - final roomId = await client.joinRoom(name); + final roomId = await client!.joinRoom(name); await _enableRoomEncyption(roomId); return roomId; } @@ -523,7 +510,7 @@ class LiveChatCubit extends Cubit { Future _enableRoomEncyption(String roomId) async { try { if (roomId.isEmpty) return; - final room = client.getRoomById(roomId); + final room = client?.getRoomById(roomId); if (room == null) return; if (room.encrypted) { logger.i('the room with id: ${room.id} encyrpted before!'); @@ -531,7 +518,7 @@ class LiveChatCubit extends Cubit { } await room.enableEncryption(); final verificationResponse = - await DeviceKeysList(client.userID!, client).startVerification(); + await DeviceKeysList(client!.userID!, client!).startVerification(); logger.i('verification response: $verificationResponse'); await verificationResponse.acceptVerification(); } catch (e, s) { @@ -550,12 +537,12 @@ class LiveChatCubit extends Cubit { return db; }, ); - client.homeserver = Uri.parse(Urls.matrixHomeServer); - await client.init(); + client!.homeserver = Uri.parse(Urls.matrixHomeServer); + await client!.init(); _notificationStreamController ??= StreamController.broadcast(); } catch (e, s) { logger.e('e: $e , s: $s'); - await client.init( + await client!.init( newHomeserver: Uri.parse(Urls.matrixHomeServer), ); } @@ -637,11 +624,11 @@ class LiveChatCubit extends Cubit { required String password, }) async { try { - final isLogged = client.isLogged(); - if (isLogged) return client.userID!; - client.homeserver = Uri.parse(Urls.matrixHomeServer); + final isLogged = client!.isLogged(); + if (isLogged) return client!.userID!; + client!.homeserver = Uri.parse(Urls.matrixHomeServer); final deviceId = await PlatformDeviceId.getDeviceId; - final loginResonse = await client.login( + final loginResonse = await client!.login( LoginType.mLoginPassword, password: password, deviceId: deviceId, @@ -656,15 +643,16 @@ class LiveChatCubit extends Cubit { Future dispose() async { try { - await client.logout().catchError((_) => null); - await client.dispose().catchError((_) => null); + await client?.logout().catchError((_) => null); + await client?.dispose().catchError((_) => null); await _notificationStreamController?.close().catchError((_) => null); _notificationStreamController = null; await _onEventSubscription?.cancel().catchError((_) => null); _onEventSubscription = null; _roomId = null; - } catch (e) { - logger.e('e: $e'); + client = null; + } catch (e, s) { + logger.e('e: $e, s: $s'); } } From 8c6960dfd3565509edcca6b96055f8c95f649ca3 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Mon, 6 Mar 2023 13:49:49 +0530 Subject: [PATCH 112/190] several gaming card issue fix #1432 --- .../list/cubit/credential_list_cubit.dart | 35 ++++++++++++------- pubspec.lock | 2 +- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/lib/dashboard/home/tab_bar/credentials/list/cubit/credential_list_cubit.dart b/lib/dashboard/home/tab_bar/credentials/list/cubit/credential_list_cubit.dart index dbf03c003..1b64e6c1c 100644 --- a/lib/dashboard/home/tab_bar/credentials/list/cubit/credential_list_cubit.dart +++ b/lib/dashboard/home/tab_bar/credentials/list/cubit/credential_list_cubit.dart @@ -221,6 +221,24 @@ class CredentialListCubit extends Cubit { final credentials = List.of(state.gamingCredentials) ..insert(0, HomeCredential.isNotDummy(credential)); emit(state.populate(gamingCredentials: credentials)); + + final credentialSubjectType = credential + .credentialPreview.credentialSubjectModel.credentialSubjectType; + + if (credentialSubjectType == + CredentialSubjectType.tezotopiaMembership || + credentialSubjectType == + CredentialSubjectType.chainbornMembership || + credentialSubjectType == CredentialSubjectType.bloometaPass || + credentialSubjectType == CredentialSubjectType.tezVoucher || + credentialSubjectType == CredentialSubjectType.voucher) { + _removeDummyIfCredentialExist( + credentials, + gamingCategories, + credentialSubjectType, + ); + } + break; case CredentialCategory.communityCards: @@ -240,18 +258,6 @@ class CredentialListCubit extends Cubit { final credentialSubjectType = credential .credentialPreview.credentialSubjectModel.credentialSubjectType; switch (credentialSubjectType) { - case CredentialSubjectType.tezotopiaMembership: - case CredentialSubjectType.bloometaPass: - case CredentialSubjectType.chainbornMembership: - case CredentialSubjectType.voucher: - case CredentialSubjectType.tezVoucher: - _removeDummyIfCredentialExist( - credentials, - gamingCategories, - credentialSubjectType, - ); - break; - case CredentialSubjectType.ageRange: case CredentialSubjectType.nationality: case CredentialSubjectType.identityPass: @@ -269,6 +275,11 @@ class CredentialListCubit extends Cubit { ); break; + case CredentialSubjectType.tezotopiaMembership: + case CredentialSubjectType.bloometaPass: + case CredentialSubjectType.chainbornMembership: + case CredentialSubjectType.voucher: + case CredentialSubjectType.tezVoucher: case CredentialSubjectType.linkedInCard: case CredentialSubjectType.tezosAssociatedWallet: case CredentialSubjectType.ethereumAssociatedWallet: diff --git a/pubspec.lock b/pubspec.lock index 8f4f97398..5e2029553 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2431,5 +2431,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.19.0 <3.7.0" + dart: ">=2.19.0 <3.0.0" flutter: ">=3.7.0-0" From 08cc222f9c4ee56fd258b9a15c6f0c8935bd193f Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Mon, 6 Mar 2023 12:20:15 +0330 Subject: [PATCH 113/190] color issue with popup #1429 --- lib/dashboard/home/home/widgets/kyc_dialog.dart | 5 ++++- lib/theme/app_theme/app_theme.dart | 14 ++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/lib/dashboard/home/home/widgets/kyc_dialog.dart b/lib/dashboard/home/home/widgets/kyc_dialog.dart index d769848d6..2d8279eb2 100644 --- a/lib/dashboard/home/home/widgets/kyc_dialog.dart +++ b/lib/dashboard/home/home/widgets/kyc_dialog.dart @@ -49,6 +49,7 @@ class KycDialog extends StatelessWidget { ), Row( mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ Row( @@ -66,6 +67,7 @@ class KycDialog extends StatelessWidget { ), const SizedBox(width: Sizes.spaceNormal), Row( + crossAxisAlignment: CrossAxisAlignment.center, children: [ Image.asset( IconStrings.checkCircleBlue, @@ -100,11 +102,12 @@ class KycDialog extends StatelessWidget { Row( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, children: [ Image.asset( IconStrings.lockCircle, width: Sizes.icon, + color: Theme.of(context).colorScheme.kycKeyIconColor, ), Expanded( child: Text( diff --git a/lib/theme/app_theme/app_theme.dart b/lib/theme/app_theme/app_theme.dart index 64de24684..81e84a3f8 100644 --- a/lib/theme/app_theme/app_theme.dart +++ b/lib/theme/app_theme/app_theme.dart @@ -113,6 +113,8 @@ extension CustomColorScheme on ColorScheme { Color get greyText => const Color(0xFFD1CCE3); + Color get kycKeyIconColor => const Color(0xFF86809D); + Color get popupBackground => const Color(0xff271C38); Color get cardHighlighted => const Color(0xFF251F38); @@ -687,19 +689,19 @@ extension CustomTextTheme on TextTheme { TextStyle get defaultDialogTitle => GoogleFonts.nunito( fontSize: 25, fontWeight: FontWeight.bold, - color: const Color(0xff180B2B), + color: const Color(0xffF5F5F5), ); TextStyle get defaultDialogBody => GoogleFonts.nunito( fontSize: 14, fontWeight: FontWeight.w400, - color: const Color(0xFF5F556F), + color: const Color(0xFF86809D), ); TextStyle get defaultDialogSubtitle => GoogleFonts.nunito( fontSize: 20, fontWeight: FontWeight.bold, - color: const Color(0xff180B2B), + color: const Color(0xff86809D), ); TextStyle get newVersionTitle => GoogleFonts.nunito( @@ -711,7 +713,7 @@ extension CustomTextTheme on TextTheme { TextStyle get kycDialogTitle => GoogleFonts.nunito( fontSize: 25, fontWeight: FontWeight.bold, - color: const Color(0xff180B2B), + color: const Color(0xffF5F5F5), ); TextStyle get kycDialogBodySmall => GoogleFonts.nunito( @@ -723,13 +725,13 @@ extension CustomTextTheme on TextTheme { TextStyle get kycDialogBody => GoogleFonts.nunito( fontSize: 14, fontWeight: FontWeight.w700, - color: const Color(0xFF180B2B), + color: const Color(0xff86809D), ); TextStyle get kycDialogFooter => GoogleFonts.nunito( fontSize: 10, fontWeight: FontWeight.w500, - color: const Color(0xFF180B2B), + color: const Color(0xff86809D), ); TextStyle get walletAltmeMessage => GoogleFonts.roboto( From 5dc3daa8782b41ab6500369aa129a6a535330cac Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Mon, 6 Mar 2023 14:51:33 +0530 Subject: [PATCH 114/190] fix crash issue if crypto account doesnot exist #1431 --- .../credential_subject_type_extension.dart | 28 ++++++ .../home_credential/home_credential.dart | 13 ++- .../binance_associated_address_widget.dart | 10 +- .../ethereum_associated_address_widget.dart | 10 +- .../fantom_associated_address_widget.dart | 10 +- .../polygon_associated_address_widget.dart | 10 +- .../tezos_associated_address_widget.dart | 10 +- .../view/missing_credentials_page.dart | 99 ++++++++++--------- 8 files changed, 115 insertions(+), 75 deletions(-) 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 a6e69ac6d..8d3f5520b 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 @@ -408,4 +408,32 @@ extension CredentialSubjectTypeExtension on CredentialSubjectType { } return false; } + + bool get isBlockchainAccount { + if (this == CredentialSubjectType.tezosAssociatedWallet || + this == CredentialSubjectType.ethereumAssociatedWallet || + this == CredentialSubjectType.binanceAssociatedWallet || + this == CredentialSubjectType.fantomAssociatedWallet || + this == CredentialSubjectType.polygonAssociatedWallet) { + return true; + } + return false; + } + + Widget? get blockchainWidget { + if (this == CredentialSubjectType.tezosAssociatedWallet) { + return const TezosAssociatedAddressWidget(); + } else if (this == CredentialSubjectType.ethereumAssociatedWallet) { + return const EthereumAssociatedAddressWidget(); + } else if (this == CredentialSubjectType.polygonAssociatedWallet) { + return const PolygonAssociatedAddressWidget(); + } else if (this == CredentialSubjectType.binanceAssociatedWallet) { + return const BinanceAssociatedAddressWidget(); + } else if (this == CredentialSubjectType.tezosAssociatedWallet) { + return const TezosAssociatedAddressWidget(); + } else if (this == CredentialSubjectType.fantomAssociatedWallet) { + return const FantomAssociatedAddressWidget(); + } + return null; + } } diff --git a/lib/dashboard/home/tab_bar/credentials/models/home_credential/home_credential.dart b/lib/dashboard/home/tab_bar/credentials/models/home_credential/home_credential.dart index 7052db98e..666e58fe5 100644 --- a/lib/dashboard/home/tab_bar/credentials/models/home_credential/home_credential.dart +++ b/lib/dashboard/home/tab_bar/credentials/models/home_credential/home_credential.dart @@ -251,6 +251,14 @@ class HomeCredential extends Equatable { link = Urls.bloometaCardUrl; break; + case CredentialSubjectType.ethereumAssociatedWallet: + case CredentialSubjectType.fantomAssociatedWallet: + case CredentialSubjectType.polygonAssociatedWallet: + case CredentialSubjectType.binanceAssociatedWallet: + case CredentialSubjectType.tezosAssociatedWallet: + image = ImageStrings.myAccountCard; + break; + case CredentialSubjectType.voucher: case CredentialSubjectType.selfIssued: case CredentialSubjectType.defaultCredential: @@ -262,7 +270,6 @@ class HomeCredential extends Equatable { case CredentialSubjectType.identityPass: case CredentialSubjectType.ecole42LearningAchievement: case CredentialSubjectType.studentCard: - case CredentialSubjectType.tezosAssociatedWallet: case CredentialSubjectType.learningAchievement: case CredentialSubjectType.certificateOfEmployment: case CredentialSubjectType.diplomaCard: @@ -271,11 +278,7 @@ class HomeCredential extends Equatable { case CredentialSubjectType.aragoLearningAchievement: case CredentialSubjectType.aragoOver18: case CredentialSubjectType.aragoPass: - case CredentialSubjectType.ethereumAssociatedWallet: case CredentialSubjectType.pcdsAgentCertificate: - case CredentialSubjectType.fantomAssociatedWallet: - case CredentialSubjectType.polygonAssociatedWallet: - case CredentialSubjectType.binanceAssociatedWallet: case CredentialSubjectType.tezosPooAddress: case CredentialSubjectType.ethereumPooAddress: case CredentialSubjectType.fantomPooAddress: diff --git a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/binance_associated_address_widget.dart b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/binance_associated_address_widget.dart index 58668c871..efe7bd5e8 100644 --- a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/binance_associated_address_widget.dart +++ b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/binance_associated_address_widget.dart @@ -6,21 +6,21 @@ import 'package:flutter/material.dart'; class BinanceAssociatedAddressWidget extends StatelessWidget { const BinanceAssociatedAddressWidget({ super.key, - required this.credentialModel, + this.credentialModel, }); - final CredentialModel credentialModel; + final CredentialModel? credentialModel; @override Widget build(BuildContext context) { final l10n = context.l10n; - final associatedAddress = credentialModel.credentialPreview - .credentialSubjectModel as BinanceAssociatedAddressModel; + final associatedAddress = credentialModel?.credentialPreview + .credentialSubjectModel as BinanceAssociatedAddressModel?; return MyBlockchainAccountBaseWidget( image: IconStrings.binance, name: l10n.binanceNetwork, - walletAddress: associatedAddress.associatedAddress ?? '', + walletAddress: associatedAddress?.associatedAddress ?? '', ); } } diff --git a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/ethereum_associated_address_widget.dart b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/ethereum_associated_address_widget.dart index 62942583e..3a3c1e2bc 100644 --- a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/ethereum_associated_address_widget.dart +++ b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/ethereum_associated_address_widget.dart @@ -6,21 +6,21 @@ import 'package:flutter/material.dart'; class EthereumAssociatedAddressWidget extends StatelessWidget { const EthereumAssociatedAddressWidget({ super.key, - required this.credentialModel, + this.credentialModel, }); - final CredentialModel credentialModel; + final CredentialModel? credentialModel; @override Widget build(BuildContext context) { final l10n = context.l10n; - final associatedAddress = credentialModel.credentialPreview - .credentialSubjectModel as EthereumAssociatedAddressModel; + final associatedAddress = credentialModel?.credentialPreview + .credentialSubjectModel as EthereumAssociatedAddressModel?; return MyBlockchainAccountBaseWidget( image: IconStrings.ethereum, name: l10n.ethereumNetwork, - walletAddress: associatedAddress.associatedAddress ?? '', + walletAddress: associatedAddress?.associatedAddress ?? '', ); } } diff --git a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/fantom_associated_address_widget.dart b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/fantom_associated_address_widget.dart index 103532cfd..8db65896a 100644 --- a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/fantom_associated_address_widget.dart +++ b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/fantom_associated_address_widget.dart @@ -6,21 +6,21 @@ import 'package:flutter/material.dart'; class FantomAssociatedAddressWidget extends StatelessWidget { const FantomAssociatedAddressWidget({ super.key, - required this.credentialModel, + this.credentialModel, }); - final CredentialModel credentialModel; + final CredentialModel? credentialModel; @override Widget build(BuildContext context) { final l10n = context.l10n; - final associatedAddress = credentialModel.credentialPreview - .credentialSubjectModel as FantomAssociatedAddressModel; + final associatedAddress = credentialModel?.credentialPreview + .credentialSubjectModel as FantomAssociatedAddressModel?; return MyBlockchainAccountBaseWidget( image: IconStrings.fantom, name: l10n.fantomNetwork, - walletAddress: associatedAddress.associatedAddress ?? '', + walletAddress: associatedAddress?.associatedAddress ?? '', ); } } diff --git a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/polygon_associated_address_widget.dart b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/polygon_associated_address_widget.dart index 47c478961..cfffb75c2 100644 --- a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/polygon_associated_address_widget.dart +++ b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/polygon_associated_address_widget.dart @@ -6,20 +6,20 @@ import 'package:flutter/material.dart'; class PolygonAssociatedAddressWidget extends StatelessWidget { const PolygonAssociatedAddressWidget({ super.key, - required this.credentialModel, + this.credentialModel, }); - final CredentialModel credentialModel; + final CredentialModel? credentialModel; @override Widget build(BuildContext context) { final l10n = context.l10n; - final associatedAddress = credentialModel.credentialPreview - .credentialSubjectModel as PolygonAssociatedAddressModel; + final associatedAddress = credentialModel?.credentialPreview + .credentialSubjectModel as PolygonAssociatedAddressModel?; return MyBlockchainAccountBaseWidget( image: IconStrings.polygon, name: l10n.polygonNetwork, - walletAddress: associatedAddress.associatedAddress ?? '', + walletAddress: associatedAddress?.associatedAddress ?? '', ); } } diff --git a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/tezos_associated_address_widget.dart b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/tezos_associated_address_widget.dart index cc3a7c02b..5f9fd17da 100644 --- a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/tezos_associated_address_widget.dart +++ b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/tezos_associated_address_widget.dart @@ -6,20 +6,20 @@ import 'package:flutter/material.dart'; class TezosAssociatedAddressWidget extends StatelessWidget { const TezosAssociatedAddressWidget({ super.key, - required this.credentialModel, + this.credentialModel, }); - final CredentialModel credentialModel; + final CredentialModel? credentialModel; @override Widget build(BuildContext context) { final l10n = context.l10n; - final associatedAddress = credentialModel.credentialPreview - .credentialSubjectModel as TezosAssociatedAddressModel; + final associatedAddress = credentialModel?.credentialPreview + .credentialSubjectModel as TezosAssociatedAddressModel?; return MyBlockchainAccountBaseWidget( image: IconStrings.tezos, name: l10n.tezosNetwork, - walletAddress: associatedAddress.associatedAddress ?? '', + walletAddress: associatedAddress?.associatedAddress ?? '', ); } } diff --git a/lib/dashboard/missing_creentials/view/missing_credentials_page.dart b/lib/dashboard/missing_creentials/view/missing_credentials_page.dart index 47b96b658..0bf14cfe2 100644 --- a/lib/dashboard/missing_creentials/view/missing_credentials_page.dart +++ b/lib/dashboard/missing_creentials/view/missing_credentials_page.dart @@ -95,45 +95,49 @@ class MissingCredentialsView extends StatelessWidget { shrinkWrap: true, itemBuilder: (context, i) { final homeCredential = state.dummyCredentials[i]; + final credentialType = homeCredential.credentialSubjectType; return Container( margin: const EdgeInsets.only( bottom: 15, right: 10, left: 10, ), - child: AspectRatio( - aspectRatio: Sizes.credentialAspectRatio, - child: CredentialImage( - image: homeCredential.image!, - child: homeCredential.dummyDescription == null - ? null - : CustomMultiChildLayout( - delegate: DummyCredentialItemDelegate( - position: Offset.zero, - ), - children: [ - LayoutId( - id: 'dummyDesc', - child: FractionallySizedBox( - widthFactor: 0.85, - heightFactor: 0.36, - child: MyText( - homeCredential.dummyDescription! - .getMessage( - context, - homeCredential.dummyDescription!, - ), - maxLines: 3, - style: Theme.of(context) - .textTheme - .bodySmall, + child: credentialType.isBlockchainAccount + ? credentialType.blockchainWidget + : AspectRatio( + aspectRatio: Sizes.credentialAspectRatio, + child: CredentialImage( + image: homeCredential.image!, + child: homeCredential.dummyDescription == null + ? null + : CustomMultiChildLayout( + delegate: DummyCredentialItemDelegate( + position: Offset.zero, ), + children: [ + LayoutId( + id: 'dummyDesc', + child: FractionallySizedBox( + widthFactor: 0.85, + heightFactor: 0.36, + child: MyText( + homeCredential.dummyDescription! + .getMessage( + context, + homeCredential + .dummyDescription!, + ), + maxLines: 3, + style: Theme.of(context) + .textTheme + .bodySmall, + ), + ), + ), + ], ), - ), - ], - ), - ), - ), + ), + ), ); }, ), @@ -142,20 +146,25 @@ class MissingCredentialsView extends StatelessWidget { MyGradientButton( onPressed: () async { for (final credentials in state.dummyCredentials) { - await Navigator.push( - context, - DiscoverDetailsPage.route( - homeCredential: credentials, - buttonText: l10n.getThisCard, - onCallBack: () async { - await discoverCredential( - homeCredential: credentials, - context: context, - ); - Navigator.pop(context); - }, - ), - ); + if (credentials.credentialSubjectType.isBlockchainAccount) { + await Navigator.of(context) + .push(ChooseAddAccountMethodPage.route()); + } else { + await Navigator.push( + context, + DiscoverDetailsPage.route( + homeCredential: credentials, + buttonText: l10n.getThisCard, + onCallBack: () async { + await discoverCredential( + homeCredential: credentials, + context: context, + ); + Navigator.pop(context); + }, + ), + ); + } } Navigator.pop(context); }, From dee7c36b184247cb5a7e4fb1a2bc56955a46723a Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Mon, 6 Mar 2023 16:37:46 +0330 Subject: [PATCH 115/190] chnage the border radius of buttons to be same as design in Figma --- lib/app/shared/widget/button/my_elevated_button.dart | 2 +- lib/app/shared/widget/button/my_outlined_button.dart | 2 +- .../credentials/detail/view/credentials_details_page.dart | 2 +- pubspec.lock | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/app/shared/widget/button/my_elevated_button.dart b/lib/app/shared/widget/button/my_elevated_button.dart index f6c3b54c7..6cfabe123 100644 --- a/lib/app/shared/widget/button/my_elevated_button.dart +++ b/lib/app/shared/widget/button/my_elevated_button.dart @@ -10,7 +10,7 @@ class MyElevatedButton extends StatelessWidget { this.icon, this.backgroundColor, this.textColor, - this.borderRadius = 20, + this.borderRadius = 18, this.verticalSpacing = 15, this.elevation = 2, this.fontSize = 18, diff --git a/lib/app/shared/widget/button/my_outlined_button.dart b/lib/app/shared/widget/button/my_outlined_button.dart index bc2bf48c7..a85dc4a39 100644 --- a/lib/app/shared/widget/button/my_outlined_button.dart +++ b/lib/app/shared/widget/button/my_outlined_button.dart @@ -10,7 +10,7 @@ class MyOutlinedButton extends StatelessWidget { this.backgroundColor, this.textColor, this.borderColor, - this.borderRadius = 50, + this.borderRadius = 18, this.verticalSpacing = 15, this.elevation = 2, this.fontSize = 18, 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 4eb97bd25..f69bee21b 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 @@ -256,7 +256,7 @@ class _CredentialsDetailsViewState extends State { : SafeArea( child: Container( padding: const EdgeInsets.symmetric( - horizontal: 24, + horizontal: 12, vertical: 5, ), child: Column( diff --git a/pubspec.lock b/pubspec.lock index 5e2029553..8f4f97398 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2431,5 +2431,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.19.0 <3.0.0" + dart: ">=2.19.0 <3.7.0" flutter: ">=3.7.0-0" From 9ec45d6482d660fcb593367d3aa82e2d98f5f785 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Mon, 6 Mar 2023 17:05:02 +0330 Subject: [PATCH 116/190] justify app bar title in discover details page --- lib/dashboard/discover/view/discover_details_page.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/dashboard/discover/view/discover_details_page.dart b/lib/dashboard/discover/view/discover_details_page.dart index e677e5b69..4d84ac04a 100644 --- a/lib/dashboard/discover/view/discover_details_page.dart +++ b/lib/dashboard/discover/view/discover_details_page.dart @@ -59,6 +59,7 @@ class DiscoverDetailsView extends StatelessWidget { return BasePage( title: l10n.cardDetails, scrollView: false, + titleAlignment: Alignment.topCenter, titleLeading: const BackLeadingButton(), body: BackgroundCard( child: Column( From 0620126135cbb730a5c3b936b421085bdbd03b00 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Mon, 6 Mar 2023 19:49:59 +0530 Subject: [PATCH 117/190] add eu verifiable id #1419 --- assets/image/eu_verifiable_id.png | Bin 0 -> 59059 bytes lib/app/shared/constants/image_strings.dart | 1 + .../credential_subject_type.dart | 1 + .../credential_subject_type_extension.dart | 14 ++++++ .../detail/view/credentials_details_page.dart | 7 ++- .../eu_verifiable_id_model.dart | 42 ++++++++++++++++++ .../tab_bar/credentials/models/model.dart | 1 + .../widgets/credential_display.dart | 3 ++ .../credential_widget/credential_widget.dart | 1 + .../eu_verifiable_id_widget.dart | 33 ++++++++++++++ 10 files changed, 99 insertions(+), 4 deletions(-) create mode 100644 assets/image/eu_verifiable_id.png create mode 100644 lib/dashboard/home/tab_bar/credentials/models/eu_verifiable_id/eu_verifiable_id_model.dart create mode 100644 lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/eu_verifiable_id_widget.dart diff --git a/assets/image/eu_verifiable_id.png b/assets/image/eu_verifiable_id.png new file mode 100644 index 0000000000000000000000000000000000000000..bf468caefba6700ffa74bfacb50c0250162645ed GIT binary patch literal 59059 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®O@MqJ3*UQ1-0Y6K#HknSwC-)9*&AMVMNyCDf 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 literal 0 HcmV?d00001 diff --git a/lib/app/shared/constants/image_strings.dart b/lib/app/shared/constants/image_strings.dart index 1d943e41f..987f3cb93 100644 --- a/lib/app/shared/constants/image_strings.dart +++ b/lib/app/shared/constants/image_strings.dart @@ -70,6 +70,7 @@ class ImageStrings { static const String linkedInCard = '$imagePath/linkedin_card.png'; static const String euDiplomaCard = '$imagePath/eu_diploma_card.png'; + static const String euVerifiableId = '$imagePath/eu_verifiable_id.png'; static const String myAccountCard = '$imagePath/my_account_card.png'; static const String pooAccountCard = '$imagePath/poo_account_card.png'; 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 0735444fc..dc4be4cf7 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 @@ -57,4 +57,5 @@ enum CredentialSubjectType { pcdsAgentCertificate, twitterCard, euDiplomaCard, + euVerifiableId, } 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 8d3f5520b..35dae3fd3 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 @@ -77,6 +77,7 @@ extension CredentialSubjectTypeExtension on CredentialSubjectType { case CredentialSubjectType.tezosPooAddress: case CredentialSubjectType.pcdsAgentCertificate: case CredentialSubjectType.euDiplomaCard: + case CredentialSubjectType.euVerifiableId: return Colors.white; } } @@ -145,6 +146,7 @@ extension CredentialSubjectTypeExtension on CredentialSubjectType { case CredentialSubjectType.tezosPooAddress: case CredentialSubjectType.pcdsAgentCertificate: case CredentialSubjectType.euDiplomaCard: + case CredentialSubjectType.euVerifiableId: return Icons.perm_identity; } } @@ -266,6 +268,8 @@ extension CredentialSubjectTypeExtension on CredentialSubjectType { return 'PCDSAgentCertificate'; case CredentialSubjectType.euDiplomaCard: return 'https://api.preprod.ebsi.eu/trusted-schemas-registry/v1/schemas/0xbf78fc08a7a9f28f5479f58dea269d3657f54f13ca37d380cd4e92237fb691dd'; + case CredentialSubjectType.euVerifiableId: + return 'https://api-conformance.ebsi.eu/trusted-schemas-registry/v2/schemas/z22ZAMdQtNLwi51T2vdZXGGZaYyjrsuP1yzWyXZirCAHv'; case CredentialSubjectType.defaultCredential: return ''; } @@ -379,6 +383,8 @@ extension CredentialSubjectTypeExtension on CredentialSubjectType { return TezosPooAddressModel.fromJson(json); case CredentialSubjectType.euDiplomaCard: return EUDiplomaCardModel.fromJson(json); + case CredentialSubjectType.euVerifiableId: + return EUVerifiableIdModel.fromJson(json); } } @@ -409,6 +415,14 @@ extension CredentialSubjectTypeExtension on CredentialSubjectType { return false; } + bool get isEbsiCard { + if (this == CredentialSubjectType.euDiplomaCard || + this == CredentialSubjectType.euVerifiableId) { + return true; + } + return false; + } + bool get isBlockchainAccount { if (this == CredentialSubjectType.tezosAssociatedWallet || this == CredentialSubjectType.ethereumAssociatedWallet || 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 f69bee21b..ccf27a150 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 @@ -132,9 +132,8 @@ class _CredentialsDetailsViewState extends State { .credentialSubjectModel.credentialSubjectType == CredentialSubjectType.linkedInCard; - final bool isEUDiplomaCard = widget.credentialModel.credentialPreview - .credentialSubjectModel.credentialSubjectType == - CredentialSubjectType.euDiplomaCard; + final bool isEbsiCard = widget.credentialModel.credentialPreview + .credentialSubjectModel.credentialSubjectType.isEbsiCard; final bool disAllowDelete = widget.credentialModel.credentialPreview .credentialSubjectModel.credentialSubjectType == @@ -279,7 +278,7 @@ class _CredentialsDetailsViewState extends State { ), ); } else { - if (isEUDiplomaCard) { + if (isEbsiCard) { /// removing type that was added in add_ebsi_credential.dart // ignore: lines_longer_than_80_chars widget.credentialModel.data['credentialSubject'] .remove('type'); diff --git a/lib/dashboard/home/tab_bar/credentials/models/eu_verifiable_id/eu_verifiable_id_model.dart b/lib/dashboard/home/tab_bar/credentials/models/eu_verifiable_id/eu_verifiable_id_model.dart new file mode 100644 index 000000000..c844382ab --- /dev/null +++ b/lib/dashboard/home/tab_bar/credentials/models/eu_verifiable_id/eu_verifiable_id_model.dart @@ -0,0 +1,42 @@ +import 'package:altme/app/app.dart'; +import 'package:altme/dashboard/dashboard.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'eu_verifiable_id_model.g.dart'; + +@JsonSerializable(explicitToJson: true) +class EUVerifiableIdModel extends CredentialSubjectModel { + EUVerifiableIdModel({ + this.expires = '', + this.awardingOpportunity, + this.dateOfBirth = '', + this.familyName = '', + this.givenNames = '', + this.gradingScheme, + this.identifier = '', + this.learningAchievement, + this.learningSpecification, + super.id, + super.type, + super.issuedBy, + }) : super( + credentialSubjectType: CredentialSubjectType.euDiplomaCard, + credentialCategory: CredentialCategory.educationCards, + ); + + factory EUVerifiableIdModel.fromJson(Map json) => + _$EUVerifiableIdModelFromJson(json); + + final String expires; + final AwardingOpportunity? awardingOpportunity; + final String dateOfBirth; + final String familyName; + final String givenNames; + final GradingScheme? gradingScheme; + final String identifier; + final LearningAchievement? learningAchievement; + final LearningSpecification? learningSpecification; + + @override + Map toJson() => _$EUVerifiableIdModelToJson(this); +} diff --git a/lib/dashboard/home/tab_bar/credentials/models/model.dart b/lib/dashboard/home/tab_bar/credentials/models/model.dart index 145960b7a..e19857db3 100644 --- a/lib/dashboard/home/tab_bar/credentials/models/model.dart +++ b/lib/dashboard/home/tab_bar/credentials/models/model.dart @@ -28,6 +28,7 @@ export 'ethereum_associated_address/ethereum_associated_address_credential.dart' 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 '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'; 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 3b2c8ce37..e6aaec643 100644 --- a/lib/dashboard/home/tab_bar/credentials/widgets/credential_display.dart +++ b/lib/dashboard/home/tab_bar/credentials/widgets/credential_display.dart @@ -116,6 +116,9 @@ class CredentialDisplay extends StatelessWidget { case CredentialSubjectType.euDiplomaCard: return EUDiplomaCardWidget(credentialModel: credentialModel); + case CredentialSubjectType.euVerifiableId: + return EUVerifiableIdWidget(credentialModel: credentialModel); + case CredentialSubjectType.learningAchievement: switch (credDisplayType) { case CredDisplayType.List: diff --git a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/credential_widget.dart b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/credential_widget.dart index fa2c82575..e530b93ed 100644 --- a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/credential_widget.dart +++ b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/credential_widget.dart @@ -21,6 +21,7 @@ export 'email_pass_widget.dart'; export 'ethereum_associated_address_widget.dart'; export 'ethereum_poo_address_widget.dart'; export 'eu_diploma_card_widget.dart'; +export 'eu_verifiable_id_widget.dart'; export 'fantom_associated_address_widget.dart'; export 'fantom_poo_address_widget.dart'; export 'gender_widget.dart'; diff --git a/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/eu_verifiable_id_widget.dart b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/eu_verifiable_id_widget.dart new file mode 100644 index 000000000..7820d4654 --- /dev/null +++ b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/eu_verifiable_id_widget.dart @@ -0,0 +1,33 @@ +import 'package:altme/app/app.dart'; +import 'package:altme/dashboard/dashboard.dart'; +import 'package:flutter/material.dart'; + +class EUVerifiableIdWidget extends StatelessWidget { + const EUVerifiableIdWidget({ + super.key, + required this.credentialModel, + }); + + final CredentialModel credentialModel; + + @override + Widget build(BuildContext context) { + final euVerifiableIdModel = credentialModel + .credentialPreview.credentialSubjectModel as EUVerifiableIdModel; + return CredentialBaseWidget( + cardBackgroundImagePath: ImageStrings.euVerifiableId, + // issuerName: credentialModel + // .credentialPreview.credentialSubjectModel.issuedBy?.name, + issuerName: euVerifiableIdModel + .awardingOpportunity?.awardingBody?.preferredName ?? + '', + value: euVerifiableIdModel.learningAchievement?.title ?? '', + issuanceDate: UiDate.formatDateForCredentialCard( + credentialModel.credentialPreview.issuanceDate, + ), + expirationDate: credentialModel.expirationDate == null + ? '--' + : UiDate.formatDateForCredentialCard(credentialModel.expirationDate!), + ); + } +} From 9e93320c9bd5b08909b47bc125d1828dfade32ed Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Mon, 6 Mar 2023 17:52:52 +0330 Subject: [PATCH 118/190] fix color of popups --- lib/app/shared/widget/add_account/add_account_popup.dart | 1 + lib/app/shared/widget/default_dialog.dart | 1 + lib/app/shared/widget/dialog/becareful_dialog.dart | 1 + lib/app/shared/widget/dialog/confirm_dialog.dart | 1 + lib/app/shared/widget/dialog/info_dialog.dart | 1 + lib/app/shared/widget/dialog/text_field_dialog.dart | 1 + lib/dashboard/dashboard/widgets/what_is_new_dialog.dart | 4 +++- lib/dashboard/home/home/widgets/finish_kyc_dialog.dart | 1 + lib/dashboard/home/home/widgets/kyc_dialog.dart | 1 + lib/dashboard/home/home/widgets/token_reward_dialog.dart | 1 + lib/dashboard/home/home/widgets/wallet_dialog.dart | 1 + .../home/tab_bar/credentials/widgets/descriptioni_dialog.dart | 1 + .../widgets/transaction_done_dialog.dart | 1 + lib/issuer_websites_page/widget/kyc_button.dart | 1 + 14 files changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/app/shared/widget/add_account/add_account_popup.dart b/lib/app/shared/widget/add_account/add_account_popup.dart index 4fe0515b0..108363582 100644 --- a/lib/app/shared/widget/add_account/add_account_popup.dart +++ b/lib/app/shared/widget/add_account/add_account_popup.dart @@ -29,6 +29,7 @@ class _AddAccountPopUpState extends State { return AlertDialog( backgroundColor: Theme.of(context).colorScheme.popupBackground, + surfaceTintColor: Colors.transparent, contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15), shape: RoundedRectangleBorder( side: BorderSide( diff --git a/lib/app/shared/widget/default_dialog.dart b/lib/app/shared/widget/default_dialog.dart index ee984a78e..f60e08e43 100644 --- a/lib/app/shared/widget/default_dialog.dart +++ b/lib/app/shared/widget/default_dialog.dart @@ -20,6 +20,7 @@ class DefaultDialog extends StatelessWidget { Widget build(BuildContext context) { return AlertDialog( backgroundColor: Theme.of(context).colorScheme.popupBackground, + surfaceTintColor: Colors.transparent, contentPadding: const EdgeInsets.symmetric( horizontal: Sizes.spaceNormal, vertical: Sizes.spaceSmall, diff --git a/lib/app/shared/widget/dialog/becareful_dialog.dart b/lib/app/shared/widget/dialog/becareful_dialog.dart index f7945ed04..7e1141faf 100644 --- a/lib/app/shared/widget/dialog/becareful_dialog.dart +++ b/lib/app/shared/widget/dialog/becareful_dialog.dart @@ -44,6 +44,7 @@ class BeCarefulDialog extends StatelessWidget { final l10n = context.l10n; return AlertDialog( backgroundColor: Theme.of(context).colorScheme.popupBackground, + surfaceTintColor: Colors.transparent, contentPadding: const EdgeInsets.symmetric( horizontal: Sizes.spaceNormal, vertical: Sizes.spaceSmall, diff --git a/lib/app/shared/widget/dialog/confirm_dialog.dart b/lib/app/shared/widget/dialog/confirm_dialog.dart index 5ec81608b..d9e77a3b3 100644 --- a/lib/app/shared/widget/dialog/confirm_dialog.dart +++ b/lib/app/shared/widget/dialog/confirm_dialog.dart @@ -34,6 +34,7 @@ class ConfirmDialog extends StatelessWidget { final l10n = context.l10n; return AlertDialog( backgroundColor: background, + surfaceTintColor: Colors.transparent, contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(25)), diff --git a/lib/app/shared/widget/dialog/info_dialog.dart b/lib/app/shared/widget/dialog/info_dialog.dart index fbb5e696a..d9e80f8ce 100644 --- a/lib/app/shared/widget/dialog/info_dialog.dart +++ b/lib/app/shared/widget/dialog/info_dialog.dart @@ -29,6 +29,7 @@ class InfoDialog extends StatelessWidget { final text = textColor ?? Theme.of(context).colorScheme.dialogText; return AlertDialog( backgroundColor: background, + surfaceTintColor: Colors.transparent, contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(25)), diff --git a/lib/app/shared/widget/dialog/text_field_dialog.dart b/lib/app/shared/widget/dialog/text_field_dialog.dart index 59a37f3f8..9fb27d1f2 100644 --- a/lib/app/shared/widget/dialog/text_field_dialog.dart +++ b/lib/app/shared/widget/dialog/text_field_dialog.dart @@ -59,6 +59,7 @@ class _TextFieldDialogState extends State { return AlertDialog( backgroundColor: background, + surfaceTintColor: Colors.transparent, contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15), shape: RoundedRectangleBorder( side: BorderSide( diff --git a/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart b/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart index 181579fb5..3ec5ae97d 100644 --- a/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart +++ b/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart @@ -31,6 +31,7 @@ class WhatIsNewDialog extends StatelessWidget { return SafeArea( child: AlertDialog( backgroundColor: Theme.of(context).colorScheme.popupBackground, + surfaceTintColor: Colors.transparent, contentPadding: const EdgeInsets.all(Sizes.spaceXSmall), insetPadding: const EdgeInsets.symmetric( horizontal: Sizes.spaceSmall, @@ -41,7 +42,8 @@ class WhatIsNewDialog extends StatelessWidget { Radius.circular(Sizes.normalRadius), ), ), - content: SizedBox( + content: Container( + //color: Theme.of(context).colorScheme.popupBackground, width: double.maxFinite, child: Stack( children: [ diff --git a/lib/dashboard/home/home/widgets/finish_kyc_dialog.dart b/lib/dashboard/home/home/widgets/finish_kyc_dialog.dart index add0ebcec..6dfdc26c2 100644 --- a/lib/dashboard/home/home/widgets/finish_kyc_dialog.dart +++ b/lib/dashboard/home/home/widgets/finish_kyc_dialog.dart @@ -13,6 +13,7 @@ class FinishKycDialog extends StatelessWidget { final l10n = context.l10n; return AlertDialog( backgroundColor: Theme.of(context).colorScheme.popupBackground, + surfaceTintColor: Colors.transparent, contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(25)), diff --git a/lib/dashboard/home/home/widgets/kyc_dialog.dart b/lib/dashboard/home/home/widgets/kyc_dialog.dart index 2d8279eb2..f8685393c 100644 --- a/lib/dashboard/home/home/widgets/kyc_dialog.dart +++ b/lib/dashboard/home/home/widgets/kyc_dialog.dart @@ -16,6 +16,7 @@ class KycDialog extends StatelessWidget { final l10n = context.l10n; return AlertDialog( backgroundColor: Theme.of(context).colorScheme.popupBackground, + surfaceTintColor: Colors.transparent, contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(25)), diff --git a/lib/dashboard/home/home/widgets/token_reward_dialog.dart b/lib/dashboard/home/home/widgets/token_reward_dialog.dart index 76dd24bae..08ce76f89 100644 --- a/lib/dashboard/home/home/widgets/token_reward_dialog.dart +++ b/lib/dashboard/home/home/widgets/token_reward_dialog.dart @@ -30,6 +30,7 @@ class TokenRewardDialog extends StatelessWidget { final l10n = context.l10n; return AlertDialog( backgroundColor: Theme.of(context).colorScheme.popupBackground, + surfaceTintColor: Colors.transparent, contentPadding: const EdgeInsets.symmetric( horizontal: Sizes.spaceNormal, vertical: Sizes.spaceSmall, diff --git a/lib/dashboard/home/home/widgets/wallet_dialog.dart b/lib/dashboard/home/home/widgets/wallet_dialog.dart index 46e8f58c4..599f8dfff 100644 --- a/lib/dashboard/home/home/widgets/wallet_dialog.dart +++ b/lib/dashboard/home/home/widgets/wallet_dialog.dart @@ -15,6 +15,7 @@ class WalletDialog extends StatelessWidget { final l10n = context.l10n; return AlertDialog( backgroundColor: Theme.of(context).colorScheme.popupBackground, + surfaceTintColor: Colors.transparent, contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(25)), diff --git a/lib/dashboard/home/tab_bar/credentials/widgets/descriptioni_dialog.dart b/lib/dashboard/home/tab_bar/credentials/widgets/descriptioni_dialog.dart index fe70d1c9d..d80ec6b30 100644 --- a/lib/dashboard/home/tab_bar/credentials/widgets/descriptioni_dialog.dart +++ b/lib/dashboard/home/tab_bar/credentials/widgets/descriptioni_dialog.dart @@ -19,6 +19,7 @@ class DescriptionDialog extends StatelessWidget { return AlertDialog( backgroundColor: background, + surfaceTintColor: Colors.transparent, contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(25)), diff --git a/lib/dashboard/home/tab_bar/tokens/confirm_token_transaction/widgets/transaction_done_dialog.dart b/lib/dashboard/home/tab_bar/tokens/confirm_token_transaction/widgets/transaction_done_dialog.dart index a8c6b4c3c..5d85bade6 100644 --- a/lib/dashboard/home/tab_bar/tokens/confirm_token_transaction/widgets/transaction_done_dialog.dart +++ b/lib/dashboard/home/tab_bar/tokens/confirm_token_transaction/widgets/transaction_done_dialog.dart @@ -41,6 +41,7 @@ class TransactionDoneDialog extends StatelessWidget { final l10n = context.l10n; return AlertDialog( backgroundColor: Theme.of(context).colorScheme.popupBackground, + surfaceTintColor: Colors.transparent, contentPadding: const EdgeInsets.symmetric( horizontal: Sizes.spaceNormal, vertical: Sizes.spaceSmall, diff --git a/lib/issuer_websites_page/widget/kyc_button.dart b/lib/issuer_websites_page/widget/kyc_button.dart index 1be0a0cc8..8300163b5 100644 --- a/lib/issuer_websites_page/widget/kyc_button.dart +++ b/lib/issuer_websites_page/widget/kyc_button.dart @@ -68,6 +68,7 @@ class KYCButton extends StatelessWidget { builder: (context) => AlertDialog( backgroundColor: Theme.of(context).colorScheme.popupBackground, + surfaceTintColor: Colors.transparent, contentPadding: const EdgeInsets.only( top: 24, bottom: 16, From 6f7e4073bdc7792b861fb46db7071a11b2560c33 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Mon, 6 Mar 2023 18:45:25 +0330 Subject: [PATCH 119/190] fix color for kyc popups --- .../home/home/widgets/finish_kyc_dialog.dart | 6 +++--- lib/theme/app_theme/app_theme.dart | 12 ------------ 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/lib/dashboard/home/home/widgets/finish_kyc_dialog.dart b/lib/dashboard/home/home/widgets/finish_kyc_dialog.dart index 6dfdc26c2..8f440d34f 100644 --- a/lib/dashboard/home/home/widgets/finish_kyc_dialog.dart +++ b/lib/dashboard/home/home/widgets/finish_kyc_dialog.dart @@ -27,18 +27,18 @@ class FinishKycDialog extends StatelessWidget { Icon( Icons.access_time_filled_rounded, size: Sizes.icon4x, - color: Theme.of(context).colorScheme.surface, + color: Theme.of(context).iconTheme.color, ), const SizedBox(height: Sizes.spaceSmall), Text( l10n.finishedVerificationTitle, - style: Theme.of(context).textTheme.finishVerificationDialogTitle, + style: Theme.of(context).textTheme.defaultDialogTitle, textAlign: TextAlign.center, ), const SizedBox(height: Sizes.spaceSmall), Text( l10n.finishedVerificationDescription, - style: Theme.of(context).textTheme.finishVerificationDialogBody, + style: Theme.of(context).textTheme.defaultDialogBody, textAlign: TextAlign.center, ), const SizedBox(height: Sizes.spaceSmall), diff --git a/lib/theme/app_theme/app_theme.dart b/lib/theme/app_theme/app_theme.dart index 81e84a3f8..9a2fbb209 100644 --- a/lib/theme/app_theme/app_theme.dart +++ b/lib/theme/app_theme/app_theme.dart @@ -674,18 +674,6 @@ extension CustomTextTheme on TextTheme { color: const Color(0xff180B2B), ); - TextStyle get finishVerificationDialogTitle => GoogleFonts.nunito( - fontSize: 25, - fontWeight: FontWeight.bold, - color: const Color(0xff180B2B), - ); - - TextStyle get finishVerificationDialogBody => GoogleFonts.nunito( - fontSize: 14, - fontWeight: FontWeight.w600, - color: const Color(0xFF5F556F), - ); - TextStyle get defaultDialogTitle => GoogleFonts.nunito( fontSize: 25, fontWeight: FontWeight.bold, From 2e4d30b1f739338c426ea81166f89e4df81ca9ea Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Tue, 7 Mar 2023 19:04:48 +0530 Subject: [PATCH 120/190] small flavor update --- .../cubit/onboarding_verify_phrase_cubit.dart | 8 +++++++- .../verify_phrase/view/onboarding_verify_phrase.dart | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/onboarding/verify_phrase/cubit/onboarding_verify_phrase_cubit.dart b/lib/onboarding/verify_phrase/cubit/onboarding_verify_phrase_cubit.dart index 6f3cb8b30..a49d51218 100644 --- a/lib/onboarding/verify_phrase/cubit/onboarding_verify_phrase_cubit.dart +++ b/lib/onboarding/verify_phrase/cubit/onboarding_verify_phrase_cubit.dart @@ -1,6 +1,7 @@ import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/did/did.dart'; +import 'package:altme/flavor/flavor.dart'; import 'package:altme/onboarding/helper_function/helper_function.dart'; import 'package:altme/splash/splash.dart'; import 'package:altme/wallet/wallet.dart'; @@ -25,6 +26,7 @@ class OnBoardingVerifyPhraseCubit extends Cubit { required this.homeCubit, required this.walletCubit, required this.splashCubit, + required this.flavorCubit, }) : super(OnBoardingVerifyPhraseState()); final SecureStorageProvider secureStorageProvider; @@ -33,6 +35,7 @@ class OnBoardingVerifyPhraseCubit extends Cubit { final DIDCubit didCubit; final HomeCubit homeCubit; final WalletCubit walletCubit; + final FlavorCubit flavorCubit; final SplashCubit splashCubit; final log = getLogger('OnBoardingVerifyPhraseCubit'); @@ -44,7 +47,10 @@ class OnBoardingVerifyPhraseCubit extends Cubit { for (var i = 1; i <= 12; i++) { oldState.add(MnemonicState(order: i)); } - oldState.shuffle(); + if (flavorCubit.state != FlavorMode.development) { + oldState.shuffle(); + } + emit(state.copyWith(mnemonicStates: oldState, status: AppStatus.idle)); } diff --git a/lib/onboarding/verify_phrase/view/onboarding_verify_phrase.dart b/lib/onboarding/verify_phrase/view/onboarding_verify_phrase.dart index a69af31f8..a8e6c61d0 100644 --- a/lib/onboarding/verify_phrase/view/onboarding_verify_phrase.dart +++ b/lib/onboarding/verify_phrase/view/onboarding_verify_phrase.dart @@ -1,6 +1,7 @@ import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/did/did.dart'; +import 'package:altme/flavor/flavor.dart'; import 'package:altme/l10n/l10n.dart'; import 'package:altme/onboarding/onboarding.dart'; import 'package:altme/splash/splash.dart'; @@ -34,6 +35,7 @@ class OnBoardingVerifyPhrasePage extends StatelessWidget { homeCubit: context.read(), walletCubit: context.read(), splashCubit: context.read(), + flavorCubit: context.read(), ), child: OnBoardingVerifyPhraseView(mnemonic: mnemonic), ); From a74c5d85bd96e563f44b4bd781edb63fa350f1b3 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Wed, 8 Mar 2023 10:59:54 +0530 Subject: [PATCH 121/190] bypass mnemonic verification #1438 --- .../get_multiple_credentials.dart | 10 +- lib/l10n/arb/app_en.arb | 4 +- lib/l10n/untranslated.json | 16 ++- .../cubit/onboarding_gen_phrase_cubit.dart | 52 ++++++++- .../view/onboarding_gen_phrase.dart | 100 +++++++----------- 5 files changed, 108 insertions(+), 74 deletions(-) diff --git a/lib/dashboard/home/tab_bar/credentials/helper_functions/get_multiple_credentials.dart b/lib/dashboard/home/tab_bar/credentials/helper_functions/get_multiple_credentials.dart index 3c61ec045..63a57ed76 100644 --- a/lib/dashboard/home/tab_bar/credentials/helper_functions/get_multiple_credentials.dart +++ b/lib/dashboard/home/tab_bar/credentials/helper_functions/get_multiple_credentials.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'dart:convert'; -import 'package:altme/app/shared/shared.dart'; +import 'package:altme/app/app.dart'; import 'package:altme/dashboard/home/tab_bar/credentials/models/activity/activity.dart'; import 'package:altme/dashboard/home/tab_bar/credentials/models/credential_model/credential_model.dart'; import 'package:altme/dashboard/home/tab_bar/tab_bar.dart'; @@ -37,7 +37,7 @@ Future getMutipleCredentials( secureStorageProvider, preAuthorizedCode, ); -// Wait 1 minute to let passbase verification time to happen + // Wait 1 minute to let passbase verification time to happen await multipleCredentialsTimer( preAuthorizedCode, client, @@ -250,10 +250,8 @@ Future registerMultipleCredentialsProcess( Future unregisterMultipleCredentialsProcess( secure_storage.SecureStorageProvider secureStorageProvider, ) async { - await secureStorageProvider.set( - SecureStorageKeys.passBaseVerificationDate, - '', - ); + await secureStorageProvider + .delete(SecureStorageKeys.passBaseVerificationDate); } Future isGettingMultipleCredentialsNeeded( diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 133ef1a7a..5cf74f55c 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -806,5 +806,7 @@ "contractAddress": "Contract address", "lastMetadataSync": "Last metadata sync", "e2eEncyptedChat": "Chat is encrypted from end to end.", - "pincodeAttemptMessage": "You have entered an incorrect PIN code three times. For security reasons, please wait for one minute before trying again." + "pincodeAttemptMessage": "You have entered an incorrect PIN code three times. For security reasons, please wait for one minute before trying again.", + "verifyNow": "Verify Now", + "verifyLater": "Verify Later" } \ No newline at end of file diff --git a/lib/l10n/untranslated.json b/lib/l10n/untranslated.json index 0552364dc..6ecab8269 100644 --- a/lib/l10n/untranslated.json +++ b/lib/l10n/untranslated.json @@ -742,7 +742,9 @@ "contractAddress", "lastMetadataSync", "e2eEncyptedChat", - "pincodeAttemptMessage" + "pincodeAttemptMessage", + "verifyNow", + "verifyLater" ], "es": [ @@ -1488,7 +1490,9 @@ "contractAddress", "lastMetadataSync", "e2eEncyptedChat", - "pincodeAttemptMessage" + "pincodeAttemptMessage", + "verifyNow", + "verifyLater" ], "fr": [ @@ -1517,7 +1521,9 @@ "contractAddress", "lastMetadataSync", "e2eEncyptedChat", - "pincodeAttemptMessage" + "pincodeAttemptMessage", + "verifyNow", + "verifyLater" ], "it": [ @@ -2263,6 +2269,8 @@ "contractAddress", "lastMetadataSync", "e2eEncyptedChat", - "pincodeAttemptMessage" + "pincodeAttemptMessage", + "verifyNow", + "verifyLater" ] } diff --git a/lib/onboarding/gen_phrase/cubit/onboarding_gen_phrase_cubit.dart b/lib/onboarding/gen_phrase/cubit/onboarding_gen_phrase_cubit.dart index 9ce8aa762..dc339d84b 100644 --- a/lib/onboarding/gen_phrase/cubit/onboarding_gen_phrase_cubit.dart +++ b/lib/onboarding/gen_phrase/cubit/onboarding_gen_phrase_cubit.dart @@ -1,18 +1,64 @@ import 'package:altme/app/app.dart'; +import 'package:altme/dashboard/dashboard.dart'; +import 'package:altme/did/did.dart'; +import 'package:altme/onboarding/onboarding.dart'; +import 'package:altme/splash/splash.dart'; +import 'package:altme/wallet/wallet.dart'; +import 'package:did_kit/did_kit.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:json_annotation/json_annotation.dart'; +import 'package:key_generator/key_generator.dart'; +import 'package:secure_storage/secure_storage.dart'; part 'onboarding_gen_phrase_cubit.g.dart'; part 'onboarding_gen_phrase_state.dart'; class OnBoardingGenPhraseCubit extends Cubit { - OnBoardingGenPhraseCubit() : super(const OnBoardingGenPhraseState()); + OnBoardingGenPhraseCubit({ + required this.secureStorageProvider, + required this.keyGenerator, + required this.didKitProvider, + required this.didCubit, + required this.homeCubit, + required this.walletCubit, + required this.splashCubit, + }) : super(const OnBoardingGenPhraseState()); + + final SecureStorageProvider secureStorageProvider; + final KeyGenerator keyGenerator; + final DIDKitProvider didKitProvider; + final DIDCubit didCubit; + final HomeCubit homeCubit; + final WalletCubit walletCubit; + final SplashCubit splashCubit; final log = getLogger('OnBoardingGenPhraseCubit'); - Future switchTick() async { - emit(state.copyWith(isTicked: !state.isTicked)); + Future generateSSIAndCryptoAccount(List mnemonic) async { + emit(state.loading()); + try { + await generateAccount( + mnemonic: mnemonic, + secureStorageProvider: secureStorageProvider, + keyGenerator: keyGenerator, + didKitProvider: didKitProvider, + didCubit: didCubit, + homeCubit: homeCubit, + walletCubit: walletCubit, + splashCubit: splashCubit, + ); + emit(state.success()); + } catch (error) { + log.e('something went wrong when generating a key', error); + emit( + state.error( + messageHandler: ResponseMessage( + ResponseString.RESPONSE_STRING_ERROR_GENERATING_KEY, + ), + ), + ); + } } } diff --git a/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart b/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart index 7e19cd1dc..f7816faac 100644 --- a/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart +++ b/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart @@ -1,11 +1,17 @@ import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; +import 'package:altme/did/did.dart'; import 'package:altme/l10n/l10n.dart'; import 'package:altme/onboarding/onboarding.dart'; +import 'package:altme/splash/cubit/splash_cubit.dart'; import 'package:altme/theme/theme.dart'; +import 'package:altme/wallet/wallet.dart'; import 'package:bip39/bip39.dart' as bip39; +import 'package:did_kit/did_kit.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:key_generator/key_generator.dart'; +import 'package:secure_storage/secure_storage.dart'; class OnBoardingGenPhrasePage extends StatelessWidget { const OnBoardingGenPhrasePage({super.key}); @@ -18,7 +24,15 @@ class OnBoardingGenPhrasePage extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => OnBoardingGenPhraseCubit(), + create: (context) => OnBoardingGenPhraseCubit( + secureStorageProvider: getSecureStorage, + didCubit: context.read(), + didKitProvider: DIDKitProvider(), + keyGenerator: KeyGenerator(), + homeCubit: context.read(), + walletCubit: context.read(), + splashCubit: context.read(), + ), child: const OnBoardingGenPhraseView(), ); } @@ -133,53 +147,6 @@ class _OnBoardingGenPhraseViewState extends State { ), ), ), - //const Spacer(), - Padding( - padding: const EdgeInsets.all( - Sizes.spaceNormal, - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.max, - children: [ - Transform.scale( - scale: 1.5, - child: Checkbox( - value: state.isTicked, - fillColor: MaterialStateProperty.all( - Theme.of(context).colorScheme.primary, - ), - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all( - Radius.circular(6), - ), - ), - onChanged: (newValue) => context - .read() - .switchTick(), - ), - ), - const SizedBox( - width: Sizes.spaceXSmall, - ), - Expanded( - child: InkWell( - onTap: () { - context.read().switchTick(); - }, - child: MyText( - l10n.onboardingWroteDownMessage, - style: Theme.of(context) - .textTheme - .onBoardingCheckMessage, - ), - ), - ), - ], - ), - ), ], ), navigation: SafeArea( @@ -188,18 +155,31 @@ class _OnBoardingGenPhraseViewState extends State { horizontal: Sizes.spaceSmall, vertical: Sizes.spaceSmall, ), - child: MyGradientButton( - text: l10n.onBoardingGenPhraseButton, - verticalSpacing: 18, - onPressed: state.isTicked - ? () { - Navigator.of(context).push( - OnBoardingVerifyPhrasePage.route( - mnemonic: mnemonic!, - ), - ); - } - : null, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + MyGradientButton( + text: l10n.verifyLater, + verticalSpacing: 18, + onPressed: () async { + await context + .read() + .generateSSIAndCryptoAccount(mnemonic!); + }, + ), + const SizedBox(height: 10), + MyGradientButton( + text: l10n.verifyNow, + verticalSpacing: 18, + onPressed: () { + Navigator.of(context).push( + OnBoardingVerifyPhrasePage.route( + mnemonic: mnemonic!, + ), + ); + }, + ), + ], ), ), ), From d621c0c9ff01ac0dfadaec122d9796c234295a23 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Wed, 8 Mar 2023 11:21:48 +0530 Subject: [PATCH 122/190] ebsi key curve changed to secp256k1 --- .../drawer/manage_did/view/did/manage_did_page.dart | 10 +++++----- .../manage_did/view/did_ebsi/manage_did_ebsi_page.dart | 10 +++++----- .../drawer/manage_did/widgets/did_private_key.dart | 8 ++------ packages/ebsi/lib/src/ebsi.dart | 6 +++--- 4 files changed, 15 insertions(+), 19 deletions(-) diff --git a/lib/dashboard/drawer/manage_did/view/did/manage_did_page.dart b/lib/dashboard/drawer/manage_did/view/did/manage_did_page.dart index d217d8ccd..62862af86 100644 --- a/lib/dashboard/drawer/manage_did/view/did/manage_did_page.dart +++ b/lib/dashboard/drawer/manage_did/view/did/manage_did_page.dart @@ -9,7 +9,10 @@ class ManageDIDPage extends StatelessWidget { const ManageDIDPage({super.key}); static Route route() { - return MaterialPageRoute(builder: (_) => const ManageDIDPage()); + return MaterialPageRoute( + builder: (_) => const ManageDIDPage(), + settings: const RouteSettings(name: '/ManageDIDPage'), + ); } @override @@ -35,10 +38,7 @@ class ManageDIDPage extends StatelessWidget { padding: EdgeInsets.symmetric(horizontal: Sizes.spaceNormal), child: Divider(), ), - DidPrivateKey( - l10n: l10n, - route: DIDPrivateKeyPage.route(), - ), + DidPrivateKey(route: DIDPrivateKeyPage.route()), ], ), ), diff --git a/lib/dashboard/drawer/manage_did/view/did_ebsi/manage_did_ebsi_page.dart b/lib/dashboard/drawer/manage_did/view/did_ebsi/manage_did_ebsi_page.dart index a016d8144..31427ff5a 100644 --- a/lib/dashboard/drawer/manage_did/view/did_ebsi/manage_did_ebsi_page.dart +++ b/lib/dashboard/drawer/manage_did/view/did_ebsi/manage_did_ebsi_page.dart @@ -10,7 +10,10 @@ class ManageDidEbsiPage extends StatefulWidget { const ManageDidEbsiPage({super.key}); static Route route() { - return MaterialPageRoute(builder: (_) => const ManageDidEbsiPage()); + return MaterialPageRoute( + builder: (_) => const ManageDidEbsiPage(), + settings: const RouteSettings(name: '/ManageDidEbsiPage'), + ); } @override @@ -55,10 +58,7 @@ class _ManageDidEbsiPageState extends State { padding: EdgeInsets.symmetric(horizontal: Sizes.spaceNormal), child: Divider(), ), - DidPrivateKey( - l10n: l10n, - route: DidEbsiPrivateKeyPage.route(), - ), + DidPrivateKey(route: DidEbsiPrivateKeyPage.route()), ], ), ), diff --git a/lib/dashboard/drawer/manage_did/widgets/did_private_key.dart b/lib/dashboard/drawer/manage_did/widgets/did_private_key.dart index cf5920367..1968aa71c 100644 --- a/lib/dashboard/drawer/manage_did/widgets/did_private_key.dart +++ b/lib/dashboard/drawer/manage_did/widgets/did_private_key.dart @@ -8,15 +8,14 @@ import 'package:flutter/material.dart'; class DidPrivateKey extends StatelessWidget { const DidPrivateKey({ super.key, - required this.l10n, required this.route, }); - final AppLocalizations l10n; final Route route; @override Widget build(BuildContext context) { + final l10n = context.l10n; return Column( children: [ const SizedBox( @@ -55,10 +54,7 @@ class DidPrivateKey extends StatelessWidget { PinCodePage.route( restrictToBack: false, isValidCallback: () { - Navigator.push( - context, - route, - ); + Navigator.push(context, route); }, ), ); diff --git a/packages/ebsi/lib/src/ebsi.dart b/packages/ebsi/lib/src/ebsi.dart index 98adf7e5f..280bbf2cb 100644 --- a/packages/ebsi/lib/src/ebsi.dart +++ b/packages/ebsi/lib/src/ebsi.dart @@ -64,10 +64,10 @@ class Ebsi { final ay = HEX.decode(my); final y = base64Url.encode(ay).substring(0, 43); // ATTENTION !!!!! - /// we are using P-256K for dart library conformance which is - /// the same as secp256k1 + /// we were using P-256K for dart library conformance which is + /// the same as secp256k1, but we are using secp256k1 now final jwk = { - 'crv': 'P-256K', + 'crv': 'secp256k1', 'd': d, 'kty': 'EC', 'x': x, From ac578200cfa9d69ca6885792ad2e3b7210607260 Mon Sep 17 00:00:00 2001 From: hawkbee1 Date: Wed, 8 Mar 2023 11:35:37 +0100 Subject: [PATCH 123/190] version: 1.11.1+159 --- pubspec.lock | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 2568f8ecd..a0bee4023 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2423,5 +2423,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.19.0 <3.7.0" + dart: ">=2.19.0 <3.0.0" flutter: ">=3.7.0-0" diff --git a/pubspec.yaml b/pubspec.yaml index 4e7a0a2d0..2dd95b2db 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: altme description: AltMe Flutter App -version: 1.11.0+157 +version: 1.11.1+159 publish_to: none environment: From 1df712e8dc2c9084d3691fd384f160efdb8ea9a0 Mon Sep 17 00:00:00 2001 From: hawkbee1 Date: Wed, 8 Mar 2023 11:59:38 +0100 Subject: [PATCH 124/190] version: 1.11.1+149 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 2a39806ac..7272fc6cb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: altme description: AltMe Flutter App -version: 1.11.0+148 +version: 1.11.1+149 publish_to: none environment: From c03d914e0ba591ab41cfadc2adcdd62f8c5aba2e Mon Sep 17 00:00:00 2001 From: hawkbee1 Date: Wed, 8 Mar 2023 12:44:12 +0100 Subject: [PATCH 125/190] change all roboto into poppins --- lib/theme/app_theme/app_theme.dart | 173 +++++++++++++++-------------- test/theme/app_theme_test.dart | 28 ++--- 2 files changed, 101 insertions(+), 100 deletions(-) diff --git a/lib/theme/app_theme/app_theme.dart b/lib/theme/app_theme/app_theme.dart index c625fbefe..fb0d8ef70 100644 --- a/lib/theme/app_theme/app_theme.dart +++ b/lib/theme/app_theme/app_theme.dart @@ -126,7 +126,7 @@ abstract class AppTheme { iconTheme: const IconThemeData(color: icon), snackBarTheme: SnackBarThemeData( backgroundColor: snackBarBackground, - contentTextStyle: GoogleFonts.roboto( + contentTextStyle: GoogleFonts.poppins( color: onPrimary, fontSize: 12, fontWeight: FontWeight.w400, @@ -284,18 +284,18 @@ extension CustomTextTheme on TextTheme { ); TextStyle get keyboardDigitTextStyle => - GoogleFonts.roboto(fontSize: 30, color: digitPrimary); + GoogleFonts.poppins(fontSize: 30, color: digitPrimary); - TextStyle get calculatorKeyboardDigitTextStyle => GoogleFonts.roboto( + TextStyle get calculatorKeyboardDigitTextStyle => GoogleFonts.poppins( fontSize: 30, color: digitPrimary, fontWeight: FontWeight.bold, ); TextStyle get keyboardDeleteButtonTextStyle => - GoogleFonts.roboto(fontSize: 16, color: digitPrimary); + GoogleFonts.poppins(fontSize: 16, color: digitPrimary); - TextStyle get loadingText => GoogleFonts.roboto( + TextStyle get loadingText => GoogleFonts.poppins( color: onPrimary, fontSize: 16, fontWeight: FontWeight.w600, @@ -337,38 +337,38 @@ extension CustomTextTheme on TextTheme { fontWeight: FontWeight.w500, ); - TextStyle get onBoardingTitleStyle => GoogleFonts.roboto( + TextStyle get onBoardingTitleStyle => GoogleFonts.poppins( color: onPrimary, fontSize: 22, fontWeight: FontWeight.w600, ); - TextStyle get onBoardingSubTitleStyle => GoogleFonts.roboto( + TextStyle get onBoardingSubTitleStyle => GoogleFonts.poppins( color: onTertiary, fontSize: 16, fontWeight: FontWeight.w400, ); - TextStyle get learnMoreTextStyle => GoogleFonts.roboto( + TextStyle get learnMoreTextStyle => GoogleFonts.poppins( color: onTertiary, fontSize: 16, fontWeight: FontWeight.w400, decoration: TextDecoration.underline, ); - TextStyle get infoTitle => GoogleFonts.roboto( + TextStyle get infoTitle => GoogleFonts.poppins( color: onSurface, fontSize: 20, fontWeight: FontWeight.w600, ); - TextStyle get infoSubtitle => GoogleFonts.roboto( + TextStyle get infoSubtitle => GoogleFonts.poppins( color: onTertiary, fontSize: 16, fontWeight: FontWeight.w400, ); - TextStyle get normal => GoogleFonts.roboto( + TextStyle get normal => GoogleFonts.poppins( color: onTertiary, fontSize: 16, fontWeight: FontWeight.w400, @@ -380,7 +380,7 @@ extension CustomTextTheme on TextTheme { fontWeight: FontWeight.w800, ); - TextStyle get bottomBar => GoogleFonts.roboto( + TextStyle get bottomBar => GoogleFonts.poppins( color: onPrimary, fontSize: 10, fontWeight: FontWeight.w600, @@ -392,19 +392,19 @@ extension CustomTextTheme on TextTheme { fontWeight: FontWeight.w800, ); - TextStyle get listTitle => GoogleFonts.roboto( + TextStyle get listTitle => GoogleFonts.poppins( color: onSurface, fontSize: 20, fontWeight: FontWeight.w600, ); - TextStyle get listSubtitle => GoogleFonts.roboto( + TextStyle get listSubtitle => GoogleFonts.poppins( color: onSurface, fontSize: 13, fontWeight: FontWeight.w500, ); - TextStyle get bodySmall2 => GoogleFonts.roboto( + TextStyle get bodySmall2 => GoogleFonts.poppins( color: const Color(0xFF8682A8), fontSize: 12, fontWeight: FontWeight.w400, @@ -416,31 +416,31 @@ extension CustomTextTheme on TextTheme { fontWeight: FontWeight.w400, ); - TextStyle get listTileTitle => GoogleFonts.roboto( + TextStyle get listTileTitle => GoogleFonts.poppins( color: onPrimary, fontSize: 14, fontWeight: FontWeight.w600, ); - TextStyle get listTileSubtitle => GoogleFonts.roboto( + TextStyle get listTileSubtitle => GoogleFonts.poppins( color: const Color(0xFF8682A8), fontSize: 12, fontWeight: FontWeight.w400, ); - TextStyle get close => GoogleFonts.roboto( + TextStyle get close => GoogleFonts.poppins( color: const Color(0xFFD6C3F2), fontSize: 13, fontWeight: FontWeight.w400, ); - TextStyle get dialogClose => GoogleFonts.roboto( + TextStyle get dialogClose => GoogleFonts.poppins( color: closeIcon, fontSize: 12, fontWeight: FontWeight.w400, ); - TextStyle get drawerMenu => GoogleFonts.roboto( + TextStyle get drawerMenu => GoogleFonts.poppins( color: onTertiary, fontSize: 15, fontWeight: FontWeight.w400, @@ -480,7 +480,7 @@ extension CustomTextTheme on TextTheme { fontWeight: FontWeight.w400, ); - TextStyle get biometricMessage => GoogleFonts.roboto( + TextStyle get biometricMessage => GoogleFonts.poppins( color: const Color(0xFFB1ADC3), fontSize: 12, fontWeight: FontWeight.w400, @@ -498,7 +498,7 @@ extension CustomTextTheme on TextTheme { fontWeight: FontWeight.w400, ); - TextStyle get getCardsButton => GoogleFonts.roboto( + TextStyle get getCardsButton => GoogleFonts.poppins( color: onPrimary, fontSize: 12, fontWeight: FontWeight.w600, @@ -510,123 +510,124 @@ extension CustomTextTheme on TextTheme { fontWeight: FontWeight.w500, ); - TextStyle get credentialTitle => GoogleFonts.roboto( + TextStyle get credentialTitle => GoogleFonts.poppins( color: const Color(0xFF424242), fontSize: 15, fontWeight: FontWeight.bold, ); - TextStyle get credentialDescription => GoogleFonts.roboto( + TextStyle get credentialDescription => GoogleFonts.poppins( color: const Color(0xFF757575), fontSize: 13, fontWeight: FontWeight.w400, ); - TextStyle get credentialFieldTitle => GoogleFonts.roboto( + TextStyle get credentialFieldTitle => GoogleFonts.poppins( color: const Color(0xff212121), fontSize: 14, fontWeight: FontWeight.w800, ); - TextStyle get credentialFieldDescription => GoogleFonts.roboto( + TextStyle get credentialFieldDescription => GoogleFonts.poppins( color: const Color(0xff212121), fontSize: 14, height: 1.5, fontWeight: FontWeight.w400, ); - TextStyle get discoverFieldTitle => GoogleFonts.roboto( + TextStyle get discoverFieldTitle => GoogleFonts.poppins( color: onTertiary, fontSize: 14, fontWeight: FontWeight.w800, ); - TextStyle get discoverFieldDescription => GoogleFonts.roboto( + TextStyle get discoverFieldDescription => GoogleFonts.poppins( color: onPrimary, fontSize: 14, height: 1.5, fontWeight: FontWeight.w400, ); - TextStyle get learningAchievementTitle => GoogleFonts.roboto( + TextStyle get learningAchievementTitle => GoogleFonts.poppins( color: const Color(0xff212121), fontSize: 12, fontWeight: FontWeight.w600, ); - TextStyle get learningAchievementDescription => GoogleFonts.roboto( + TextStyle get learningAchievementDescription => GoogleFonts.poppins( color: const Color(0xff212121), fontSize: 12, fontWeight: FontWeight.w400, ); - TextStyle get credentialIssuer => GoogleFonts.roboto( + TextStyle get credentialIssuer => GoogleFonts.poppins( color: const Color(0xff212121), fontSize: 13, fontWeight: FontWeight.w500, ); - TextStyle get imageCard => GoogleFonts.roboto( + TextStyle get imageCard => GoogleFonts.poppins( color: const Color(0xff212121), fontSize: 12, fontWeight: FontWeight.w500, ); - TextStyle get loyaltyCard => GoogleFonts.roboto( + TextStyle get loyaltyCard => GoogleFonts.poppins( color: onPrimary, fontSize: 13, fontWeight: FontWeight.w600, ); - TextStyle get professionalExperienceAssessmentRating => GoogleFonts.roboto( + TextStyle get professionalExperienceAssessmentRating => GoogleFonts.poppins( color: const Color(0xff212121), fontSize: 13, fontWeight: FontWeight.w500, ); - TextStyle get voucherOverlay => GoogleFonts.roboto( + TextStyle get voucherOverlay => GoogleFonts.poppins( color: onPrimary, fontSize: 13, fontWeight: FontWeight.w500, ); - TextStyle get ecole42LearningAchievementStudentIdentity => GoogleFonts.roboto( + TextStyle get ecole42LearningAchievementStudentIdentity => + GoogleFonts.poppins( color: const Color(0xff212121), fontSize: 6, fontWeight: FontWeight.w700, ); - TextStyle get ecole42LearningAchievementLevel => GoogleFonts.roboto( + TextStyle get ecole42LearningAchievementLevel => GoogleFonts.poppins( color: const Color(0xff212121), fontSize: 5, fontWeight: FontWeight.w700, ); - TextStyle get certificateOfEmploymentTitleCard => GoogleFonts.roboto( + TextStyle get certificateOfEmploymentTitleCard => GoogleFonts.poppins( color: const Color(0xFF0650C6), fontSize: 20, fontWeight: FontWeight.bold, ); - TextStyle get certificateOfEmploymentDescription => GoogleFonts.roboto( + TextStyle get certificateOfEmploymentDescription => GoogleFonts.poppins( color: const Color(0xFF757575), fontSize: 13, fontWeight: FontWeight.normal, ); - TextStyle get certificateOfEmploymentData => GoogleFonts.roboto( + TextStyle get certificateOfEmploymentData => GoogleFonts.poppins( color: const Color(0xFF434e62), fontSize: 12, fontWeight: FontWeight.normal, ); - TextStyle get identityCardData => GoogleFonts.roboto( + TextStyle get identityCardData => GoogleFonts.poppins( color: onPrimary, fontSize: 12, fontWeight: FontWeight.normal, ); - TextStyle get tezosAssociatedAddressData => GoogleFonts.roboto( + TextStyle get tezosAssociatedAddressData => GoogleFonts.poppins( color: divider, fontSize: 17, fontWeight: FontWeight.normal, @@ -638,67 +639,67 @@ extension CustomTextTheme on TextTheme { fontWeight: FontWeight.w700, ); - TextStyle get credentialStudentCardTextCard => GoogleFonts.roboto( + TextStyle get credentialStudentCardTextCard => GoogleFonts.poppins( color: onPrimary, fontSize: 14, fontWeight: FontWeight.normal, ); - TextStyle get over18 => GoogleFonts.roboto( + TextStyle get over18 => GoogleFonts.poppins( color: onPrimary, fontSize: 20, fontWeight: FontWeight.normal, ); - TextStyle get studentCardSchool => GoogleFonts.roboto( + TextStyle get studentCardSchool => GoogleFonts.poppins( color: const Color(0xff9dc5ff), fontSize: 15, fontWeight: FontWeight.bold, ); - TextStyle get studentCardData => GoogleFonts.roboto( + TextStyle get studentCardData => GoogleFonts.poppins( color: onPrimary, fontSize: 12, fontWeight: FontWeight.normal, ); - TextStyle get credentialTitleCard => GoogleFonts.roboto( + TextStyle get credentialTitleCard => GoogleFonts.poppins( color: onPrimary, fontSize: 20, fontWeight: FontWeight.bold, ); - TextStyle get voucherValueCard => GoogleFonts.roboto( + TextStyle get voucherValueCard => GoogleFonts.poppins( color: const Color(0xFFFEEA00), fontSize: 50, fontWeight: FontWeight.bold, ); - TextStyle get credentialTextCard => GoogleFonts.roboto( + TextStyle get credentialTextCard => GoogleFonts.poppins( color: const Color(0xff212121), fontSize: 14, fontWeight: FontWeight.normal, ); - TextStyle get illustrationPageDescription => GoogleFonts.roboto( + TextStyle get illustrationPageDescription => GoogleFonts.poppins( color: digitPrimary, fontSize: 16, fontWeight: FontWeight.w600, ); - TextStyle get dialogTitle => GoogleFonts.roboto( + TextStyle get dialogTitle => GoogleFonts.poppins( fontSize: 18, fontWeight: FontWeight.w600, color: digitPrimary, ); - TextStyle get dialogSubtitle => GoogleFonts.roboto( + TextStyle get dialogSubtitle => GoogleFonts.poppins( fontSize: 13, fontWeight: FontWeight.w400, color: digitPrimary.withOpacity(0.67), ); - TextStyle get walletAltme => GoogleFonts.roboto( + TextStyle get walletAltme => GoogleFonts.poppins( fontSize: 25, fontWeight: FontWeight.w600, color: const Color(0xff180B2B), @@ -764,43 +765,43 @@ extension CustomTextTheme on TextTheme { color: const Color(0xff86809D), ); - TextStyle get walletAltmeMessage => GoogleFonts.roboto( + TextStyle get walletAltmeMessage => GoogleFonts.poppins( fontSize: 16, fontWeight: FontWeight.w400, color: const Color(0xff9A8BB1), ); - TextStyle get credentialCategoryTitle => GoogleFonts.roboto( + TextStyle get credentialCategoryTitle => GoogleFonts.poppins( fontSize: 18, fontWeight: FontWeight.w600, color: onSurface, ); - TextStyle get credentialCategorySubTitle => GoogleFonts.roboto( + TextStyle get credentialCategorySubTitle => GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.normal, color: onTertiary, ); - TextStyle get credentialSurfaceText => GoogleFonts.roboto( + TextStyle get credentialSurfaceText => GoogleFonts.poppins( fontSize: 10, fontWeight: FontWeight.w400, color: applied, ); - TextStyle get errorMessage => GoogleFonts.roboto( + TextStyle get errorMessage => GoogleFonts.poppins( fontSize: 15, fontWeight: FontWeight.w400, color: onPrimary, ); - TextStyle get accountsText => GoogleFonts.roboto( + TextStyle get accountsText => GoogleFonts.poppins( fontSize: 22, fontWeight: FontWeight.w600, color: onPrimary, ); - TextStyle get accountsName => GoogleFonts.roboto( + TextStyle get accountsName => GoogleFonts.poppins( fontSize: 18, fontWeight: FontWeight.w500, color: onPrimary, @@ -812,19 +813,19 @@ extension CustomTextTheme on TextTheme { color: onPrimary, ); - TextStyle get walletAddress => GoogleFonts.roboto( + TextStyle get walletAddress => GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w400, color: const Color(0xFF757575), ); - TextStyle get textButton => GoogleFonts.roboto( + TextStyle get textButton => GoogleFonts.poppins( fontSize: 16, fontWeight: FontWeight.w700, color: primary, ); - TextStyle get scrollText => GoogleFonts.roboto( + TextStyle get scrollText => GoogleFonts.poppins( fontSize: 9, fontWeight: FontWeight.w500, color: onPrimary, @@ -860,12 +861,12 @@ extension CustomTextTheme on TextTheme { color: const Color(0xff86809D), ); - TextStyle get identitiyBaseLightText => GoogleFonts.roboto( + TextStyle get identitiyBaseLightText => GoogleFonts.poppins( fontSize: 16, fontWeight: FontWeight.w300, color: onPrimary, ); - TextStyle get identitiyBaseBoldText => GoogleFonts.roboto( + TextStyle get identitiyBaseBoldText => GoogleFonts.poppins( fontSize: 16, fontWeight: FontWeight.w600, color: onPrimary, @@ -884,13 +885,13 @@ extension CustomTextTheme on TextTheme { color: onPrimary, ); - TextStyle get messageTitle => GoogleFonts.roboto( + TextStyle get messageTitle => GoogleFonts.poppins( fontSize: 20, fontWeight: FontWeight.w600, color: onSurface, ); - TextStyle get messageSubtitle => GoogleFonts.roboto( + TextStyle get messageSubtitle => GoogleFonts.poppins( fontSize: 16, fontWeight: FontWeight.w400, color: onSurface, @@ -908,115 +909,115 @@ extension CustomTextTheme on TextTheme { fontWeight: FontWeight.w400, ); - TextStyle get credentialManifestTitle1 => GoogleFonts.roboto( + TextStyle get credentialManifestTitle1 => GoogleFonts.poppins( color: onPrimary, fontSize: 18, fontWeight: FontWeight.w700, ); - TextStyle get credentialManifestDescription => GoogleFonts.roboto( + TextStyle get credentialManifestDescription => GoogleFonts.poppins( color: onPrimary, fontSize: 16, fontWeight: FontWeight.w400, ); - TextStyle get credentialManifestTitle2 => GoogleFonts.roboto( + TextStyle get credentialManifestTitle2 => GoogleFonts.poppins( color: onPrimary, fontSize: 16, fontWeight: FontWeight.w700, ); - TextStyle get credentialSubtitle => GoogleFonts.roboto( + TextStyle get credentialSubtitle => GoogleFonts.poppins( color: onPrimary, fontSize: 14, fontWeight: FontWeight.w400, ); - TextStyle get credentialStatus => GoogleFonts.roboto( + TextStyle get credentialStatus => GoogleFonts.poppins( color: onPrimary, fontSize: 18, fontWeight: FontWeight.w800, ); - TextStyle get beaconRequestPermission => GoogleFonts.roboto( + TextStyle get beaconRequestPermission => GoogleFonts.poppins( color: onTertiary, fontSize: 16, fontWeight: FontWeight.w400, ); - TextStyle get beaconSelectAccont => GoogleFonts.roboto( + TextStyle get beaconSelectAccont => GoogleFonts.poppins( color: onPrimary, fontSize: 18, fontWeight: FontWeight.w800, ); - TextStyle get uploadFileTitle => GoogleFonts.roboto( + TextStyle get uploadFileTitle => GoogleFonts.poppins( color: onPrimary, fontSize: 18, fontWeight: FontWeight.w800, ); - TextStyle get beaconPermissionTitle => GoogleFonts.roboto( + TextStyle get beaconPermissionTitle => GoogleFonts.poppins( color: onPrimary, fontSize: 18, fontWeight: FontWeight.w800, ); - TextStyle get beaconPermissions => GoogleFonts.roboto( + TextStyle get beaconPermissions => GoogleFonts.poppins( color: onPrimary, fontSize: 16, fontWeight: FontWeight.w400, ); - TextStyle get beaconPayload => GoogleFonts.roboto( + TextStyle get beaconPayload => GoogleFonts.poppins( color: onPrimary, fontSize: 16, fontWeight: FontWeight.w400, ); - TextStyle get beaconWalletAddress => GoogleFonts.roboto( + TextStyle get beaconWalletAddress => GoogleFonts.poppins( fontSize: 16, fontWeight: FontWeight.w400, color: onPrimary, ); - TextStyle get dappName => GoogleFonts.roboto( + TextStyle get dappName => GoogleFonts.poppins( fontSize: 16, fontWeight: FontWeight.w400, color: onPrimary, ); - TextStyle get cacheErrorMessage => GoogleFonts.roboto( + TextStyle get cacheErrorMessage => GoogleFonts.poppins( fontSize: 16, fontWeight: FontWeight.w600, color: onPrimary, ); - TextStyle get credentialSteps => GoogleFonts.roboto( + TextStyle get credentialSteps => GoogleFonts.poppins( color: onPrimary, fontSize: 18, fontWeight: FontWeight.w600, ); - TextStyle get discoverOverlayDescription => GoogleFonts.roboto( + TextStyle get discoverOverlayDescription => GoogleFonts.poppins( color: onPrimary, fontSize: 11, fontWeight: FontWeight.w600, ); - TextStyle get faqQue => GoogleFonts.roboto( + TextStyle get faqQue => GoogleFonts.poppins( color: onPrimary, fontSize: 16, fontWeight: FontWeight.w700, ); - TextStyle get faqAns => GoogleFonts.roboto( + TextStyle get faqAns => GoogleFonts.poppins( color: const Color(0xFF757575), fontSize: 14, fontWeight: FontWeight.w400, ); - TextStyle get proofCardDetail => GoogleFonts.roboto( + TextStyle get proofCardDetail => GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w300, color: const Color(0xffFFFFFF), diff --git a/test/theme/app_theme_test.dart b/test/theme/app_theme_test.dart index bc8c8c3e1..1e534eb74 100644 --- a/test/theme/app_theme_test.dart +++ b/test/theme/app_theme_test.dart @@ -307,7 +307,7 @@ void main() { final brand = tester.widget(find.byKey(const Key('brand'))); expect( brand.style, - GoogleFonts.roboto( + GoogleFonts.poppins( color: const Color(0xFFFFFFFF), fontSize: 28, fontWeight: FontWeight.w400, @@ -318,7 +318,7 @@ void main() { tester.widget(find.byKey(const Key('credentialTitle'))); expect( credentialTitle.style, - GoogleFonts.roboto( + GoogleFonts.poppins( color: const Color(0xFF424242), fontSize: 14, fontWeight: FontWeight.bold, @@ -329,7 +329,7 @@ void main() { tester.widget(find.byKey(const Key('credentialDescription'))); expect( credentialDescription.style, - GoogleFonts.roboto( + GoogleFonts.poppins( color: const Color(0xFF757575), fontSize: 14, fontWeight: FontWeight.bold, @@ -340,7 +340,7 @@ void main() { tester.widget(find.byKey(const Key('credentialFieldTitle'))); expect( credentialFieldTitle.style, - GoogleFonts.roboto( + GoogleFonts.poppins( color: const Color(0xff212121), fontSize: 12, fontWeight: FontWeight.w400, @@ -351,7 +351,7 @@ void main() { .widget(find.byKey(const Key('credentialFieldDescription'))); expect( credentialFieldDescription.style, - GoogleFonts.roboto( + GoogleFonts.poppins( color: const Color(0xff212121), fontSize: 13, fontWeight: FontWeight.w600, @@ -362,7 +362,7 @@ void main() { .widget(find.byKey(const Key('learningAchievementTitle'))); expect( learningAchievementTitle.style, - GoogleFonts.roboto( + GoogleFonts.poppins( color: const Color(0xff212121), fontSize: 12, fontWeight: FontWeight.w600, @@ -374,7 +374,7 @@ void main() { ); expect( learningAchievementDescription.style, - GoogleFonts.roboto( + GoogleFonts.poppins( color: const Color(0xff212121), fontSize: 12, fontWeight: FontWeight.w400, @@ -385,7 +385,7 @@ void main() { tester.widget(find.byKey(const Key('credentialIssuer'))); expect( credentialIssuer.style, - GoogleFonts.roboto( + GoogleFonts.poppins( color: const Color(0xff212121), fontSize: 13, fontWeight: FontWeight.w500, @@ -395,7 +395,7 @@ void main() { final imageCard = tester.widget(find.byKey(const Key('imageCard'))); expect( imageCard.style, - GoogleFonts.roboto( + GoogleFonts.poppins( color: const Color(0xff212121), fontSize: 12, fontWeight: FontWeight.w500, @@ -406,7 +406,7 @@ void main() { tester.widget(find.byKey(const Key('loyaltyCard'))); expect( loyaltyCard.style, - GoogleFonts.roboto( + GoogleFonts.poppins( color: const Color(0xffffffff), fontSize: 13, fontWeight: FontWeight.w600, @@ -418,7 +418,7 @@ void main() { ); expect( professionalExperienceAssessmentRating.style, - GoogleFonts.roboto( + GoogleFonts.poppins( color: const Color(0xff212121), fontSize: 13, fontWeight: FontWeight.w500, @@ -429,7 +429,7 @@ void main() { tester.widget(find.byKey(const Key('voucherOverlay'))); expect( voucherOverlay.style, - GoogleFonts.roboto( + GoogleFonts.poppins( color: const Color(0xffFFFFFF), fontSize: 13, fontWeight: FontWeight.w500, @@ -441,7 +441,7 @@ void main() { ); expect( ecole42LearningAchievementStudentIdentity.style, - GoogleFonts.roboto( + GoogleFonts.poppins( color: const Color(0xff212121), fontSize: 6, fontWeight: FontWeight.w700, @@ -453,7 +453,7 @@ void main() { ); expect( ecole42LearningAchievementLevel.style, - GoogleFonts.roboto( + GoogleFonts.poppins( color: const Color(0xff212121), fontSize: 5, fontWeight: FontWeight.w700, From 9428558423698f1af1dcbe7094bb72fd16c4a01d Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Wed, 8 Mar 2023 16:35:44 +0530 Subject: [PATCH 126/190] attempt to createIdentity --- lib/bootstrap.dart | 4 + .../dashboard/view/dashboard_page.dart | 1 + .../helper_function/helper_function.dart | 3 + packages/polygonid/.gitignore | 39 ++++++++ packages/polygonid/README.md | 11 +++ packages/polygonid/analysis_options.yaml | 1 + packages/polygonid/lib/polygonid.dart | 3 + packages/polygonid/lib/src/polygonid.dart | 97 +++++++++++++++++++ packages/polygonid/pubspec.yaml | 22 +++++ .../polygonid/test/src/polygonid_test.dart | 11 +++ pubspec.lock | 86 +++++++++++++++- pubspec.yaml | 20 ++-- 12 files changed, 285 insertions(+), 13 deletions(-) create mode 100644 packages/polygonid/.gitignore create mode 100644 packages/polygonid/README.md create mode 100644 packages/polygonid/analysis_options.yaml create mode 100644 packages/polygonid/lib/polygonid.dart create mode 100644 packages/polygonid/lib/src/polygonid.dart create mode 100644 packages/polygonid/pubspec.yaml create mode 100644 packages/polygonid/test/src/polygonid_test.dart diff --git a/lib/bootstrap.dart b/lib/bootstrap.dart index 116605d17..2ea10f567 100644 --- a/lib/bootstrap.dart +++ b/lib/bootstrap.dart @@ -15,6 +15,7 @@ import 'package:dartez/dartez.dart'; import 'package:flutter/widgets.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:passbase_flutter/passbase_flutter.dart'; +import 'package:polygonid/polygonid.dart'; import 'package:secure_storage/secure_storage.dart' as secure_storage; import 'package:workmanager/workmanager.dart'; @@ -126,6 +127,9 @@ Future bootstrap(FutureOr Function() builder) async { await Dartez().init(); + /// PolygonId SDK initialization + await PolygonId().init(); + await runZonedGuarded( () async { Bloc.observer = AppBlocObserver(); diff --git a/lib/dashboard/dashboard/view/dashboard_page.dart b/lib/dashboard/dashboard/view/dashboard_page.dart index cb67a6128..9f4b01ece 100644 --- a/lib/dashboard/dashboard/view/dashboard_page.dart +++ b/lib/dashboard/dashboard/view/dashboard_page.dart @@ -7,6 +7,7 @@ import 'package:altme/splash/cubit/splash_cubit.dart'; import 'package:altme/wallet/cubit/wallet_cubit.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:polygonid/polygonid.dart'; import 'package:secure_storage/secure_storage.dart'; class DashboardPage extends StatelessWidget { diff --git a/lib/onboarding/helper_function/helper_function.dart b/lib/onboarding/helper_function/helper_function.dart index 6af04123a..b3993a1f3 100644 --- a/lib/onboarding/helper_function/helper_function.dart +++ b/lib/onboarding/helper_function/helper_function.dart @@ -25,6 +25,7 @@ Future generateAccount({ mnemonicFormatted, ); + /// did final ssiKey = await keyGenerator.jwkFromMnemonic( mnemonic: mnemonicFormatted, accountType: AccountType.ssi, @@ -44,6 +45,8 @@ Future generateAccount({ verificationMethod: verificationMethod, ); + ///polygon + /// what's new popup disabled splashCubit.disableWhatsNewPopUp(); diff --git a/packages/polygonid/.gitignore b/packages/polygonid/.gitignore new file mode 100644 index 000000000..d61303516 --- /dev/null +++ b/packages/polygonid/.gitignore @@ -0,0 +1,39 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# VSCode related +.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json diff --git a/packages/polygonid/README.md b/packages/polygonid/README.md new file mode 100644 index 000000000..20fe3abc0 --- /dev/null +++ b/packages/polygonid/README.md @@ -0,0 +1,11 @@ +# polygonid + +[![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link] +[![License: MIT][license_badge]][license_link] + +A Very Good Project created by Very Good CLI. + +[license_badge]: https://img.shields.io/badge/license-MIT-blue.svg +[license_link]: https://opensource.org/licenses/MIT +[very_good_analysis_badge]: https://img.shields.io/badge/style-very_good_analysis-B22C89.svg +[very_good_analysis_link]: https://pub.dev/packages/very_good_analysis diff --git a/packages/polygonid/analysis_options.yaml b/packages/polygonid/analysis_options.yaml new file mode 100644 index 000000000..9df80aa49 --- /dev/null +++ b/packages/polygonid/analysis_options.yaml @@ -0,0 +1 @@ +include: package:very_good_analysis/analysis_options.yaml diff --git a/packages/polygonid/lib/polygonid.dart b/packages/polygonid/lib/polygonid.dart new file mode 100644 index 000000000..d48ec3a40 --- /dev/null +++ b/packages/polygonid/lib/polygonid.dart @@ -0,0 +1,3 @@ +library polygonid; + +export 'src/polygonid.dart'; diff --git a/packages/polygonid/lib/src/polygonid.dart b/packages/polygonid/lib/src/polygonid.dart new file mode 100644 index 000000000..3d7c67234 --- /dev/null +++ b/packages/polygonid/lib/src/polygonid.dart @@ -0,0 +1,97 @@ +import 'dart:convert'; + +import 'package:bip32/bip32.dart' as bip32; +import 'package:bip39/bip39.dart' as bip393; +import 'package:flutter/foundation.dart'; +import 'package:hex/hex.dart'; +import 'package:polygonid_flutter_sdk/sdk/polygon_id_sdk.dart'; +import 'package:secp256k1/secp256k1.dart'; + +/// {@template polygonid} +/// A Very Good Project created by Very Good CLI. +/// {@endtemplate} +class PolygonId { + /// {@macro polygonid} + PolygonId(); + + /// PolygonId SDK initialization + /// Before you can start using the SDK, you need to initialise it, otherwise + /// a PolygonIsSdkNotInitializedException exception will be thrown. + + Future init() async { + await PolygonIdSdk.init(); + } + + /// Create Identity + /// + /// blockchain and network are not optional, they are used to associate the + /// identity to a specific blockchain network. + /// + /// it is recommended to securely save the privateKey generated with + /// createIdentity(), this will often be used within the sdk methods as a + /// security system, you can find the privateKey in the PrivateIdentityEntity + /// object. + /// + Future createIdentity() async { + //we get the sdk instance previously initialized + final sdk = PolygonIdSdk.I; + final identity = await sdk.identity.createIdentity( + blockchain: 'polygon', + network: 'main', + ); + print(identity.privateKey); + print(identity.did); + print(identity.publicKey); + print(identity.profiles); + } + + /// create JWK from mnemonic + Future privateKeyFromMnemonic({required String mnemonic}) async { + final seed = bip393.mnemonicToSeed(mnemonic); + + final rootKey = bip32.BIP32.fromSeed(seed); //Instance of 'BIP32' + final child = rootKey.derivePath( + "m/44'/5467'/0'/2'", + ); //Instance of 'BIP32' + final Iterable iterable = child.privateKey!; + final seedBytes = Uint8List.fromList(List.from(iterable)); + + final key = jwkFromSeed( + seedBytes: seedBytes, + ); + + return jsonEncode(key); + } + + /// create JWK from seed + Map jwkFromSeed({required Uint8List seedBytes}) { + // generate JWK for secp256k from bip39 mnemonic + // see https://iancoleman.io/bip39/ + final epk = HEX.encode(seedBytes); + final pk = PrivateKey.fromHex(epk); //Instance of 'PrivateKey' + final pub = pk.publicKey.toHex().substring(2); + final ad = HEX.decode(epk); + final d = base64Url.encode(ad).substring(0, 43); + // remove "=" padding 43/44 + final mx = pub.substring(0, 64); + // first 32 bytes + final ax = HEX.decode(mx); + final x = base64Url.encode(ax).substring(0, 43); + // remove "=" padding 43/44 + final my = pub.substring(64); + // last 32 bytes + final ay = HEX.decode(my); + final y = base64Url.encode(ay).substring(0, 43); + // ATTENTION !!!!! + /// we were using P-256K for dart library conformance which is + /// the same as secp256k1, but we are using secp256k1 now + final jwk = { + 'crv': 'secp256k1', + 'd': d, + 'kty': 'EC', + 'x': x, + 'y': y, + }; + return jwk; + } +} diff --git a/packages/polygonid/pubspec.yaml b/packages/polygonid/pubspec.yaml new file mode 100644 index 000000000..d40699a3e --- /dev/null +++ b/packages/polygonid/pubspec.yaml @@ -0,0 +1,22 @@ +name: polygonid +description: A Very Good Project created by Very Good CLI. +version: 1.0.0+1 +publish_to: none + +environment: + sdk: ">=2.17.0 <3.0.0" + +dependencies: + bip32: ^2.0.0 + bip39: ^1.0.6 + flutter: + sdk: flutter + hex: ^0.2.0 + polygonid_flutter_sdk: + path: ../../../polygonid-flutter-sdk + secp256k1: ^0.3.0 + +dev_dependencies: + flutter_test: + sdk: flutter + very_good_analysis: ^4.0.0+1 diff --git a/packages/polygonid/test/src/polygonid_test.dart b/packages/polygonid/test/src/polygonid_test.dart new file mode 100644 index 000000000..8978d590f --- /dev/null +++ b/packages/polygonid/test/src/polygonid_test.dart @@ -0,0 +1,11 @@ +// ignore_for_file: prefer_const_constructors +import 'package:flutter_test/flutter_test.dart'; +import 'package:polygonid/polygonid.dart'; + +void main() { + group('PolygonId', () { + test('can be instantiated', () { + expect(PolygonId(), isNotNull); + }); + }); +} diff --git a/pubspec.lock b/pubspec.lock index a0bee4023..bd9856abd 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -37,10 +37,10 @@ packages: dependency: transitive description: name: archive - sha256: ed7cc591a948744994714375caf9a2ce89e1d82e8243997c8a2994d57181c212 + sha256: "80e5141fafcb3361653ce308776cfd7d45e6e9fbb429e14eec571382c0c5fecb" url: "https://pub.dev" source: hosted - version: "3.3.5" + version: "3.3.2" args: dependency: transitive description: @@ -386,6 +386,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.1" + country_code: + dependency: transitive + description: + name: country_code + sha256: f69ccd5163b1ca43011be9632e33ebe7ffac65e49ce2afcd3e3e5228af5d91fc + url: "https://pub.dev" + source: hosted + version: "1.0.0" coverage: dependency: transitive description: @@ -638,6 +646,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.2" + encrypt: + dependency: transitive + description: + name: encrypt + sha256: "4fd4e4fdc21b9d7d4141823e1e6515cd94e7b8d84749504c232999fba25d9bbb" + url: "https://pub.dev" + source: hosted + version: "5.0.1" enhanced_enum: dependency: transitive description: @@ -646,6 +662,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.4" + envied: + dependency: transitive + description: + name: envied + sha256: "2545198df33f6ecb701af530bcc4f85385950d9c71a75882c16c85d0f95d0928" + url: "https://pub.dev" + source: hosted + version: "0.2.4" equatable: dependency: "direct main" description: @@ -678,6 +702,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.1" + ffigen: + dependency: transitive + description: + name: ffigen + sha256: "2f2f40265aae76d966164c3d53bfcbbeb73bc97f0ec2966032ac5e971bb3f20a" + url: "https://pub.dev" + source: hosted + version: "7.2.7" file: dependency: transitive description: @@ -970,6 +1002,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.0" + get_it: + dependency: transitive + description: + name: get_it + sha256: "290fde3a86072e4b37dbb03c07bec6126f0ecc28dad403c12ffe2e5a2d751ab7" + url: "https://pub.dev" + source: hosted + version: "7.2.0" glob: dependency: transitive description: @@ -1106,6 +1146,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.6.2" + injectable: + dependency: transitive + description: + name: injectable + sha256: f71eb879124ed286cbd2210337b91ff5f345f146187c1f1891c172e0ac06443a + url: "https://pub.dev" + source: hosted + version: "1.5.4" intl: dependency: "direct main" description: @@ -1473,7 +1521,7 @@ packages: source: hosted version: "1.0.1" path_provider: - dependency: "direct main" + dependency: "direct overridden" description: name: path_provider sha256: dcea5feb97d8abf90cab9e9030b497fb7c3cbf26b7a1fe9e3ef7dcb0a1ddec95 @@ -1664,6 +1712,20 @@ packages: url: "https://pub.dev" source: hosted version: "3.6.2" + polygonid: + dependency: "direct main" + description: + path: "packages/polygonid" + relative: true + source: path + version: "1.0.0+1" + polygonid_flutter_sdk: + dependency: transitive + description: + path: "../polygonid-flutter-sdk" + relative: true + source: path + version: "2.0.0" pool: dependency: transitive description: @@ -1831,6 +1893,14 @@ packages: relative: true source: path version: "1.0.0+1" + sembast: + dependency: transitive + description: + name: sembast + sha256: "4997717aa84f0622691815d7e2739988b7f7d3a463302fc878f7d5acfa748e96" + url: "https://pub.dev" + source: hosted + version: "3.4.0+6" share_plus: dependency: "direct main" description: @@ -2311,7 +2381,7 @@ packages: source: hosted version: "1.0.2" web3dart: - dependency: "direct main" + dependency: "direct overridden" description: name: web3dart sha256: "0b96223a6b284e3146e65dc842ded139eca68a85c4ab79c5ba1a73284927d3cd" @@ -2422,6 +2492,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.1" + yaml_edit: + dependency: transitive + description: + name: yaml_edit + sha256: "0b968021754d8fbd3e9c83563b538ee417d88b2cc587606da5615546b7ee033b" + url: "https://pub.dev" + source: hosted + version: "2.1.0" sdks: dart: ">=2.19.0 <3.0.0" flutter: ">=3.7.0-0" diff --git a/pubspec.yaml b/pubspec.yaml index 2dd95b2db..c02ce8cee 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: altme -description: AltMe Flutter App +description: AltMe Flutter App version: 1.11.1+159 publish_to: none @@ -7,12 +7,12 @@ environment: sdk: ">=2.19.0 <3.7.0" dependencies: - auto_size_text: ^3.0.0 + auto_size_text: ^3.0.0 badges: ^3.0.2 beacon_flutter: git: url: https://github.com/TalaoDAO/beacon.git - ref: 5afc170ff4f05c590508fed1c70e7e533021865f + ref: 5afc170ff4f05c590508fed1c70e7e533021865f # beacon_flutter: # path: ../beacon bip39: ^1.0.6 @@ -29,7 +29,7 @@ dependencies: dartez: git: url: https://github.com/TalebRafiepour/Dartez.git - ref: main + ref: main device_info_plus: ^8.1.0 device_preview: ^1.1.0 devicelocale: ^0.5.5 @@ -82,10 +82,11 @@ dependencies: package_info_plus: ^3.0.3 passbase_flutter: ^2.13.3 # new version available path: ^1.8.2 # flutter_test from sdk depends on path 1.8.2 - path_provider: ^2.0.12 permission_handler: ^10.2.0 platform_device_id: ^1.0.1 pointycastle: ^3.6.2 + polygonid: + path: packages/polygonid pretty_qr_code: ^2.0.2 qr_flutter: ^4.0.0 screenshot: ^1.3.0 @@ -99,7 +100,7 @@ dependencies: git: url: https://github.com/autonomy-system/tezart.git ref: bd4b8db6e3a352590a6e556d31df8aef60db3465 - timezone: ^0.9.1 + timezone: ^0.9.1 uni_links: ^0.5.1 url_launcher: ^6.1.9 uuid: ^3.0.7 @@ -109,18 +110,19 @@ dependencies: git: url: https://github.com/bibash28/wallet-connect-dart.git ref: fba9b209ee2b61b62ddd643f07f6a7f64426d7b2 - web3dart: ^2.6.1 webview_flutter: ^4.0.2 webview_flutter_android: ^3.2.4 webview_flutter_wkwebview: ^3.0.5 workmanager: ^0.5.1 dependency_overrides: -# Because flutter_sodium 0.2.0 depends on ffi ^1.0.0 and no versions of flutter_sodium match >0.2.0 <0.3.0, flutter_sodium ^0.2.0 requires ffi ^1.0.0 + # Because flutter_sodium 0.2.0 depends on ffi ^1.0.0 and no versions of flutter_sodium match >0.2.0 <0.3.0, flutter_sodium ^0.2.0 requires ffi ^1.0.0 async: ^2.9.0 ffi: ^2.0.1 markdown: ^4.0.0 + path_provider: ^2.0.12 stream_channel: ^2.1.0 + web3dart: ^2.6.1 dev_dependencies: bloc_test: ^9.1.1 @@ -155,4 +157,4 @@ flutter_icons: ios: true remove_alpha_ios: true image_path: "assets/launcher_icon.png" - min_sdk_android: 21 \ No newline at end of file + min_sdk_android: 21 From 808c8014a55bf0f933e051a173880717daaad94c Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Thu, 9 Mar 2023 12:25:59 +0530 Subject: [PATCH 127/190] android build fix by setting kotlin version 1.8.0 --- android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/build.gradle b/android/build.gradle index c4183abca..163e44d53 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.7.10' + ext.kotlin_version = '1.8.0' repositories { google() mavenCentral() From 7fb1e416c89f354fae13d65071a20af3fdb5c422 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Thu, 9 Mar 2023 13:29:12 +0530 Subject: [PATCH 128/190] create did functionality added from random secret --- packages/polygonid/lib/src/polygonid.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/polygonid/lib/src/polygonid.dart b/packages/polygonid/lib/src/polygonid.dart index 3d7c67234..565bd37a0 100644 --- a/packages/polygonid/lib/src/polygonid.dart +++ b/packages/polygonid/lib/src/polygonid.dart @@ -32,6 +32,7 @@ class PolygonId { /// security system, you can find the privateKey in the PrivateIdentityEntity /// object. /// + Future createIdentity() async { //we get the sdk instance previously initialized final sdk = PolygonIdSdk.I; @@ -39,10 +40,7 @@ class PolygonId { blockchain: 'polygon', network: 'main', ); - print(identity.privateKey); print(identity.did); - print(identity.publicKey); - print(identity.profiles); } /// create JWK from mnemonic From 22a07a8d107a3675abfea9390bac95fcbebaac39 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Thu, 9 Mar 2023 14:13:58 +0530 Subject: [PATCH 129/190] tezotopia description update #1446 --- lib/l10n/arb/app_en.arb | 2 +- pubspec.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 5cf74f55c..2629bbe9f 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -593,7 +593,7 @@ "linkedinCardHowToGetIt": "You can claim this card by following Altme’s KYC check. Only information related to your first name, last name, nationality and year of birth will be accessible from your LinkedIn profile.", "linkedinCardWhyGetThisCard": "This card is a proof of your identity for your LinkedIn profile. From this card, you can export a QR code and display it in the banner on your LinkedIn account. By scanning the QR code with his Altme wallet, anyone will be able to verify that your identity matches the URL of your LinkedIn profile, and will also be able to access 2 additional information: your nationality and your year of birth.", "linkedinCardExpirationDate": "This card will remain active and reusable for 1 YEAR.", - "tezotopiaMembershipHowToGetIt": "You need to present a Nationality Proof and an Age Range Proof. Claim them by following Altme’s KYC check.", + "tezotopiaMembershipHowToGetIt": "You need to present a proof that you are over 13 YO and a proof of your email.", "over18WhyGetThisCard": "This proof may be required by some Web 3 Apps / Websites to access their service or claim benefits : Membership card, Loyalty card, Rewards, etc.", "over18ExpirationDate": "This card will remain active and reusable for 1 YEAR.", "over18HowToGetIt": "You can claim this card by following Altme’s KYC check.", diff --git a/pubspec.lock b/pubspec.lock index bd9856abd..f8385abe6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2501,5 +2501,5 @@ packages: source: hosted version: "2.1.0" sdks: - dart: ">=2.19.0 <3.0.0" + dart: ">=2.19.0 <3.7.0" flutter: ">=3.7.0-0" From 42ce57cffe05e150cf09ce02207cf89112291826 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Thu, 9 Mar 2023 14:47:06 +0530 Subject: [PATCH 130/190] readme update - polygon id details added --- README.md | 225 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 143 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index d56c420d1..70710561b 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ [![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link] [![License: MIT][license_badge]][license_link] -`ALTME` – THE UNIVERSAL WALLET THAT WORKS FOR YOU -The Web 3 revolution is all about redistributing the power to the average consumer. +`ALTME` – THE UNIVERSAL WALLET THAT WORKS FOR YOU +The Web 3 revolution is all about redistributing the power to the average consumer. This is why we are building Altme, to help you get control over your data back. --- @@ -13,6 +13,7 @@ This is why we are building Altme, to help you get control over your data back. ## Getting Started 🚀 This project contains 3 flavors: + - development - staging - production @@ -43,6 +44,7 @@ the following dependencies: - Java 7 or higher - Flutter (`dev` channel) - [DIDKit](https://github.com/spruceid/didkit)/[SSI](https://github.com/spruceid/ssi) +- [PolygonID Flutter SDK](https://github.com/iden3/polygonid-flutter-sdk) ### Rust @@ -88,12 +90,16 @@ $ flutter doctor This project also depends on two other [`Spruce`](https://github.com/spruceid) projects, [`DIDKit`](https://github.com/spruceid/didkit) and -[`SSI`](https://github.com/spruceid/ssi). +[`SSI`](https://github.com/spruceid/ssi). These projects are all configured to work with relative paths by default, -so it is recommended to clone them all under the same root directory, for +so it is recommended to clone them all under the same root directory, for example `$HOME/$FOLDER_NAME/{didkit,ssi,altme}`. +### PolygonID Flutter SDK + +This is a flutter Plugin for PolygonID Mobile SDK (https://polygon.technology/polygon-id) This plugin provides a cross-platform tool (iOS, Android) to communicate with the PolygonID platform. + ## Target-Specific Dependencies ### Android Dependencies @@ -105,10 +111,11 @@ Studio](https://developer.android.com/studio/install), which install further dependencies upon first being opened after installation. Installing the appropriate Android NDK (often not the newest) in Android Studio can be accomplished by going to Settings > Appearance & Behavior > System Settings > -Android SDK and selecting to install the "NDK (Side by Side)". +Android SDK and selecting to install the "NDK (Side by Side)". -An alternative method of installing SDK and NDK without Android Studio can be +An alternative method of installing SDK and NDK without Android Studio can be found in the script below: + ``` cd $HOME wget https://dl.google.com/android/repository/sdk-tools-linux-4333796.zip @@ -137,18 +144,19 @@ $ export ANDROID_SDK_ROOT=/path/to/Android/Sdk Note: Some users have experienced difficulties with cross-compilation artefacts missing from the newest NDK, which is downloaded by default in the -installation process. If you experience errors of this kind, you may have to +installation process. If you experience errors of this kind, you may have to manually downgrade or install multiple NDK versions as [shown here])(img/ndk_downgrade.png) in the Android Studio installer (screengrabbed from an Ubuntu installation). -If your `build-tools` and/or `NDK` live in different locations than the default ones inside /SDK/, or if you want to specify a specific NDK or build-tools version, you can manually configure the following two environment variables: +If your `build-tools` and/or `NDK` live in different locations than the default ones inside /SDK/, or if you want to specify a specific NDK or build-tools version, you can manually configure the following two environment variables: ```bash $ export ANDROID_TOOLS=/path/to/SDK/build-tools/XX.X.X/ $ export ANDROID_NDK_HOME=/path/to/SDK/ndk/XX.X.XXXXX/ ``` -::: + +::: ### iOS Dependencies @@ -173,51 +181,70 @@ $ make -C lib ../target/test/android.stamp $ make -C lib ../target/test/flutter.stamp $ cargo build ``` -*This may take some time as it compiles the entire project for multiple targets* -### Android APK -```bash -# Development -$ flutter build apk --release --split-per-abi --flavor development -t lib/main_development.dart +_This may take some time as it compiles the entire project for multiple targets_ -# Staging -$ flutter build apk --release --split-per-abi --flavor staging -t lib/main_staging.dart +### iOS -# Production -$ flutter build apk --release --split-per-abi --flavor production -t lib/main_production.dart -``` +To build DIDKit for the iOS targets, you will go to the root of `DIDKit` and run: -### Android App Bundle ```bash -# Development -$ flutter build appbundle --flavor "development" --target "lib/main_development.dart" +$ make -C lib install-rustup-ios +$ make -C lib ../target/test/ios.stamp +$ cargo build +``` -# Staging -$ flutter build appbundle --flavor "staging" --target "lib/main_staging.dart" +## Setting Up PolygonID Flutter SDK -# Production -$ flutter build appbundle --flavor "production" --target "lib/main_production.dart" -``` +### Env variables -### iOS +#### Required: + +**NETWORK_NAME** - Blockchain name.
    +**NETWORK_ENV** - Network name.
    +**INFURA_URL** - Infura base url.
    +**INFURA_RDP_URL** - Infura base rdp url.
    +**INFURA_API_KEY** - Infura api key.
    +**ID_STATE_CONTRACT_ADDR** - Identity state smart contract address.
    + +#### Not required: -To build DIDKit for the iOS targets, you will go to the root of `DIDKit` and run: +**PUSH_URL** - Polygon push gateway server base url.
    + +### Deploy + +1. Clone this repository. +2. Generate `.env` and `.env.dev` files in the root folder of the project. +3. Add required env variables (example): + ```bash + NETWORK_NAME="polygon" + NETWORK_ENV="mumbai" + INFURA_URL="https://polygon-mumbai.infura.io/v3/" + INFURA_RDP_URL="wss://polygon-mumbai.infura.io/v3/" + INFURA_API_KEY="secret" + ID_STATE_CONTRACT_ADDR="sc_address" + PUSH_URL="push_url" + ``` +4. run `build_runner` to generate `.g.dart` files: ```bash -$ make -C lib install-rustup-ios -$ make -C lib ../target/test/ios.stamp -$ cargo build +flutter pub run build_runner build --delete-conflicting-outputs ``` +#### Note + +Using iOS simulator for testing wallet sdk is right now under maintenance and will be available soon. + ## Shortcut setup -In order to handle installation of didkit, ssi and altme, we can run shortcut script. We can also get the warnings if we have not configured the required things for building Altme. + +In order to handle installation of didkit, ssi, polygonid-flutter-sdk and altme, we can run shortcut script. We can also get the warnings if we have not configured the required things for building Altme. For consistent app builts we can use [`fvm`](https://fvm.app/docs/getting_started/installation). -You have to add the [`install_altme.sh`](https://github.com/TalaoDAO/mobile-install-deploy/blob/main/install_altme.sh) in the directory `$HOME/$FOLDER_NAME/`. Then run the following command to +You have to add the [`install_altme.sh`](https://github.com/TalaoDAO/mobile-install-deploy/blob/main/install_altme.sh) in the directory `$HOME/$FOLDER_NAME/`. Then run the following command to do the setup: -```bash +```bash # Android $ ./install_altme.sh -android @@ -225,37 +252,40 @@ $ ./install_altme.sh -android $ ./install_altme.sh -ios ``` +#### Note + +Please check the [Setting Up PolygonID Flutter SDK] section for complete setup of polygonid-flutter-sdk. ## Generate missing .g.dart file -In order to generate all *.g.dart files, run the following command: + +In order to generate all \*.g.dart files, run the following command: + ```bash $ flutter packages pub run build_runner build --delete-conflicting-outputs ``` ## Key Dependencies -For smooth running of the functionalities of Altme, you need to add the following -keys: +For smooth running of the functionalities of Altme, you need to add the following +keys: - 1. PASSBASE_WEBHOOK_AUTH_TOKEN
    -You can get this key from [`here`](https://passbase.com/). + You can get this key from [`here`](https://passbase.com/). 2. PASSBASE_CHECK_DID_AUTH_TOKEN
    -The key is available [`here`](https://passbase.com/). + The key is available [`here`](https://passbase.com/). 3. YOTI_AI_API_KEY
    -This key can be obtained from [`here`](https://developers.yoti.com/). + This key can be obtained from [`here`](https://developers.yoti.com/). 4. TALAO_ISSUER_API_KEY
    -The key is available [`here`](https://talao.io/). + The key is available [`here`](https://talao.io/). 5. INFURA_API_KEY
    -This key can be obtained from [`here`](https://docs.infura.io/infura/networks/ethereum/how-to/secure-a-project/project-id). + This key can be obtained from [`here`](https://docs.infura.io/infura/networks/ethereum/how-to/secure-a-project/project-id). 6. MORALIS_API_KEY
    -You can get this key from [`here`](https://docs.moralis.io/web3-data-api/get-your-api-key). - + You can get this key from [`here`](https://docs.moralis.io/web3-data-api/get-your-api-key). ## Building Altme @@ -276,7 +306,34 @@ $ flutter run --flavor staging --target lib/main_staging.dart $ flutter run --flavor production --target lib/main_production.dart ``` +### Android APK + +```bash +# Development +$ flutter build apk --release --split-per-abi --flavor development -t lib/main_development.dart + +# Staging +$ flutter build apk --release --split-per-abi --flavor staging -t lib/main_staging.dart + +# Production +$ flutter build apk --release --split-per-abi --flavor production -t lib/main_production.dart +``` + +### Android App Bundle + +```bash +# Development +$ flutter build appbundle --flavor "development" --target "lib/main_development.dart" + +# Staging +$ flutter build appbundle --flavor "staging" --target "lib/main_staging.dart" + +# Production +$ flutter build appbundle --flavor "production" --target "lib/main_production.dart" +``` + ### iOS .app for Simulator + ```bash # Development $ flutter build ios --simulator --flavor "development" --target "lib/main_development.dart" @@ -289,6 +346,7 @@ $ flutter build ios --simulator --flavor "production" --target "lib/main_product ``` ### iOS .app for Devices + ```bash # Development $ flutter build ios --no-codesign --flavor "development" --target "lib/main_development.dart" @@ -300,7 +358,8 @@ $ flutter build ios --no-codesign --flavor "staging" --target "lib/main_staging. $ flutter build ios --no-codesign --flavor "production" --target "lib/main_production.dart" ``` -### iOS IPA +### iOS IPA + ```bash # Development $ flutter build ipa --flavor "development" --target "lib/main_development.dart" @@ -313,13 +372,14 @@ $ flutter build ipa --flavor "production" --target "lib/main_production.dart" ``` ### iOS Continuous Delivery + If you have setup the [`fastlane`](https://docs.flutter.dev/deployment/cd) for continuous delivery, then you can run the following command to publish: ```bash $ flutter pub get $ flutter packages pub run build_runner build --delete-conflicting-outputs $ flutter build ios --release --flavor "production" --target "lib/main_production.dart" -$ $cd ios +$ $cd ios $ fastlane beta ``` @@ -333,8 +393,9 @@ For instance, on Flutter, you can delete build files to start over by running: ```bash $ flutter clean ``` + Also, reviewing the -[`install_altme.sh`](https://github.com/TalaoDAO/mobile-install-deploy/blob/main/install_altme.sh) +[`install_altme.sh`](https://github.com/TalaoDAO/mobile-install-deploy/blob/main/install_altme.sh) script may be helpful. ## Supported Protocols @@ -352,7 +413,7 @@ executed. After receiving a `CredentialOffer` from a trusted host, the app calls the API with `subject_id` in the form body, that value is the didKey obtained from the -private key stored in the `FlutterSecureStorage`, which is backed by KeyStore +private key stored in the `FlutterSecureStorage`, which is backed by KeyStore on Android and Keychain on iOS. The flow of events and actions is listed below: @@ -365,25 +426,25 @@ The flow of events and actions is listed below: And below is another version of the step-by-step: -| Wallet | 1 | | Server | -| ------------------------ | ------------ | :---: | --------------------------: | -| Scan QRCode 2 | | | -| Trust Host | ○ / × | | | -| HTTP GET | | → | https://domain.tld/endpoint | -| | | ← | CredentialOffer | -| Preview Credential | | | | -| Choose DID | ○ / × | | | -| HTTP POST 3 | | → | https://domain.tld/endpoint | -| | | ← | VerifiableCredential | -| Verify Credential | | | | -| Store Credential | | | | - -*1 Whether this action requires user confirmation, exiting the flow -early when the user denies.* -*2 The QRCode should contain the HTTP endpoint where the requests -will be made.* -*3 The body of the request contains a field `subject_id` set to the -chosen DID.* +| Wallet | 1 | | Server | +| ------------------------ | ------------ | :-: | --------------------------: | +| Scan QRCode 2 | | | +| Trust Host | ○ / × | | | +| HTTP GET | | → | https://domain.tld/endpoint | +| | | ← | CredentialOffer | +| Preview Credential | | | | +| Choose DID | ○ / × | | | +| HTTP POST 3 | | → | https://domain.tld/endpoint | +| | | ← | VerifiableCredential | +| Verify Credential | | | | +| Store Credential | | | | + +_1 Whether this action requires user confirmation, exiting the flow +early when the user denies._ +_2 The QRCode should contain the HTTP endpoint where the requests +will be made._ +_3 The body of the request contains a field `subject_id` set to the +chosen DID._ ### VerifiablePresentationRequest @@ -415,16 +476,16 @@ The flow of events and actions is listed below: And below is another version of the step-by-step: -| Wallet | 1 | | Server | -| ---------------------------- | ------------ | :---: | ----------------------------: | -| Scan QRCode 2 | | | -| Trust Host | ○ / × | | | -| HTTP GET | | → | https://domain.tld/endpoint | -| | | ← | VerifiablePresentationRequest | -| Preview Presentation | | | | -| Choose Verifiable Credential | ○ / × | | | -| HTTP POST 3 | | → | https://domain.tld/endpoint | -| | | ← | Result | +| Wallet | 1 | | Server | +| ---------------------------- | ------------ | :-: | ----------------------------: | +| Scan QRCode 2 | | | +| Trust Host | ○ / × | | | +| HTTP GET | | → | https://domain.tld/endpoint | +| | | ← | VerifiablePresentationRequest | +| Preview Presentation | | | | +| Choose Verifiable Credential | ○ / × | | | +| HTTP POST 3 | | → | https://domain.tld/endpoint | +| | | ← | Result | ## Running Tests 🧪 @@ -544,7 +605,9 @@ Update the `CFBundleLocalizations` array in the `Info.plist` at `ios/Runner/Info } } ``` + ## Run shortcut scripts + ```bash # generate .g.dart files $ ./script.sh -build_runner @@ -568,13 +631,11 @@ $ ./script.sh -build appbundle $ ./script.sh -deploy ios ``` - ```bash #For permission $ sudo chmod 777 script.sh ``` - [coverage_badge]: coverage_badge.svg [flutter_localizations_link]: https://api.flutter.dev/flutter/flutter_localizations/flutter_localizations-library.html [internationalization_link]: https://flutter.dev/docs/development/accessibility-and-localization/internationalization @@ -582,4 +643,4 @@ $ sudo chmod 777 script.sh [license_link]: https://opensource.org/licenses/MIT [very_good_analysis_badge]: https://img.shields.io/badge/style-very_good_analysis-B22C89.svg [very_good_analysis_link]: https://pub.dev/packages/very_good_analysis -[very_good_cli_link]: https://github.com/VeryGoodOpenSource/very_good_cli \ No newline at end of file +[very_good_cli_link]: https://github.com/VeryGoodOpenSource/very_good_cli From 0dc247b946bb27f5163ba4e921248a5de1e34912 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Thu, 9 Mar 2023 14:50:24 +0530 Subject: [PATCH 131/190] readme small formatting --- README.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 70710561b..915ad00d5 100644 --- a/README.md +++ b/README.md @@ -200,16 +200,20 @@ $ cargo build #### Required: -**NETWORK_NAME** - Blockchain name.
    -**NETWORK_ENV** - Network name.
    -**INFURA_URL** - Infura base url.
    -**INFURA_RDP_URL** - Infura base rdp url.
    -**INFURA_API_KEY** - Infura api key.
    -**ID_STATE_CONTRACT_ADDR** - Identity state smart contract address.
    +``` +NETWORK_NAME - Blockchain name. +NETWORK_ENV - Network name. +INFURA_URL - Infura base url. +INFURA_RDP_URL - Infura base rdp url. +INFURA_API_KEY - Infura api key. +ID_STATE_CONTRACT_ADDR - Identity state smart contract address. +``` #### Not required: -**PUSH_URL** - Polygon push gateway server base url.
    +``` +PUSH_URL - Polygon push gateway server base url. +``` ### Deploy From 1d18a3ae3b651b788180f0525c70ebabeaeb27f8 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Thu, 9 Mar 2023 15:47:05 +0530 Subject: [PATCH 132/190] readme update --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 915ad00d5..822f724f5 100644 --- a/README.md +++ b/README.md @@ -256,10 +256,6 @@ $ ./install_altme.sh -android $ ./install_altme.sh -ios ``` -#### Note - -Please check the [Setting Up PolygonID Flutter SDK] section for complete setup of polygonid-flutter-sdk. - ## Generate missing .g.dart file In order to generate all \*.g.dart files, run the following command: @@ -291,6 +287,10 @@ keys: 6. MORALIS_API_KEY
    You can get this key from [`here`](https://docs.moralis.io/web3-data-api/get-your-api-key). +#### Note + +Please check the [Setting Up PolygonID Flutter SDK] section for key-dependencies of polygonid-flutter-sdk. + ## Building Altme You are now ready to build or run Altme. From 10d366bda5b103a9bb95d8d9d2eea2b7115c1f8c Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Thu, 9 Mar 2023 18:24:17 +0530 Subject: [PATCH 133/190] mnemonics verify added in drawer --- .../shared/constants/secure_storage_keys.dart | 2 + .../helper_functions/helper_functions.dart | 6 +- .../shared/widget/custom_listtile_card.dart | 1 - .../dashboard/view/dashboard_page.dart | 1 - .../dashboard/widgets/what_is_new_dialog.dart | 2 +- .../cubit/backup_credential_cubit.dart | 2 +- .../cubit/recovery_key_cubit.dart | 57 +++++++ .../cubit/recovery_key_state.dart | 54 +++++++ .../drawer/recovery_key/recovery_key.dart | 2 + .../recovery_key/view/key_verified_page.dart | 74 +++++++++ .../recovery_key/view/recovery_key_page.dart | 144 ++++++++++++------ .../list/cubit/credential_list_cubit.dart | 3 + .../home_credential/home_credential.dart | 1 + lib/l10n/arb/app_en.arb | 5 +- lib/l10n/untranslated.json | 16 +- .../view/activate_biometrics_page.dart | 2 +- .../cubit/onboarding_gen_phrase_cubit.dart | 4 + .../view/onboarding_gen_phrase.dart | 3 +- .../cubit/onboarding_verify_phrase_cubit.dart | 30 ++-- .../view/onboarding_verify_phrase.dart | 65 +++++--- lib/pin_code/view/confirm_pin_code_page.dart | 2 +- .../view/enter_new_pin_code_page.dart | 2 +- packages/polygonid/lib/polygonid.dart | 1 + packages/polygonid/lib/src/polygonid.dart | 2 +- pubspec.lock | 2 +- 25 files changed, 389 insertions(+), 94 deletions(-) create mode 100644 lib/dashboard/drawer/recovery_key/cubit/recovery_key_cubit.dart create mode 100644 lib/dashboard/drawer/recovery_key/cubit/recovery_key_state.dart create mode 100644 lib/dashboard/drawer/recovery_key/view/key_verified_page.dart diff --git a/lib/app/shared/constants/secure_storage_keys.dart b/lib/app/shared/constants/secure_storage_keys.dart index dd1c944ab..100eef634 100644 --- a/lib/app/shared/constants/secure_storage_keys.dart +++ b/lib/app/shared/constants/secure_storage_keys.dart @@ -68,4 +68,6 @@ class SecureStorageKeys { static const String recoverCredentialMnemonics = 'recoverCredentialMnemonics'; static const String version = 'version'; static const String buildNumber = 'buildNumber'; + + static const String hasVerifiedMnemonics = 'hasVerifiedMnemonics'; } diff --git a/lib/app/shared/helper_functions/helper_functions.dart b/lib/app/shared/helper_functions/helper_functions.dart index cac3f995e..cbf536690 100644 --- a/lib/app/shared/helper_functions/helper_functions.dart +++ b/lib/app/shared/helper_functions/helper_functions.dart @@ -264,8 +264,10 @@ String timeFormatter({required int timeInSecond}) { return '$minute : $second'; } -Future> getssiMnemonicsInList() async { - final phrase = await getSecureStorage.get(SecureStorageKeys.ssiMnemonic); +Future> getssiMnemonicsInList( + SecureStorageProvider secureStorageProvider, +) async { + final phrase = await secureStorageProvider.get(SecureStorageKeys.ssiMnemonic); return phrase!.split(' '); } diff --git a/lib/app/shared/widget/custom_listtile_card.dart b/lib/app/shared/widget/custom_listtile_card.dart index fc1eecce1..111930abb 100644 --- a/lib/app/shared/widget/custom_listtile_card.dart +++ b/lib/app/shared/widget/custom_listtile_card.dart @@ -1,5 +1,4 @@ import 'package:altme/app/app.dart'; -import 'package:altme/app/shared/constants/sizes.dart'; import 'package:altme/theme/theme.dart'; import 'package:flutter/material.dart'; diff --git a/lib/dashboard/dashboard/view/dashboard_page.dart b/lib/dashboard/dashboard/view/dashboard_page.dart index 9f4b01ece..cb67a6128 100644 --- a/lib/dashboard/dashboard/view/dashboard_page.dart +++ b/lib/dashboard/dashboard/view/dashboard_page.dart @@ -7,7 +7,6 @@ import 'package:altme/splash/cubit/splash_cubit.dart'; import 'package:altme/wallet/cubit/wallet_cubit.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:polygonid/polygonid.dart'; import 'package:secure_storage/secure_storage.dart'; class DashboardPage extends StatelessWidget { diff --git a/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart b/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart index 3ec5ae97d..315fd2e68 100644 --- a/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart +++ b/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart @@ -42,7 +42,7 @@ class WhatIsNewDialog extends StatelessWidget { Radius.circular(Sizes.normalRadius), ), ), - content: Container( + content: SizedBox( //color: Theme.of(context).colorScheme.popupBackground, width: double.maxFinite, child: Stack( diff --git a/lib/dashboard/drawer/backup_credential/cubit/backup_credential_cubit.dart b/lib/dashboard/drawer/backup_credential/cubit/backup_credential_cubit.dart index 5da42ad86..c6c41540d 100644 --- a/lib/dashboard/drawer/backup_credential/cubit/backup_credential_cubit.dart +++ b/lib/dashboard/drawer/backup_credential/cubit/backup_credential_cubit.dart @@ -30,7 +30,7 @@ class BackupCredentialCubit extends Cubit { String? mnemonics; Future> loadMnemonic() async { - final mnemonicList = await getssiMnemonicsInList(); + final mnemonicList = await getssiMnemonicsInList(secureStorageProvider); mnemonics = mnemonicList.join(' '); return mnemonicList; } diff --git a/lib/dashboard/drawer/recovery_key/cubit/recovery_key_cubit.dart b/lib/dashboard/drawer/recovery_key/cubit/recovery_key_cubit.dart new file mode 100644 index 000000000..10a45ae6a --- /dev/null +++ b/lib/dashboard/drawer/recovery_key/cubit/recovery_key_cubit.dart @@ -0,0 +1,57 @@ +import 'package:altme/app/app.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:secure_storage/secure_storage.dart'; + +part 'recovery_key_cubit.g.dart'; +part 'recovery_key_state.dart'; + +class RecoveryKeyCubit extends Cubit { + RecoveryKeyCubit({ + required this.secureStorageProvider, + }) : super(const RecoveryKeyState()); + + final SecureStorageProvider secureStorageProvider; + + Future getMnemonics() async { + emit(state.loading()); + final mnemonics = await getssiMnemonicsInList(secureStorageProvider); + + bool isMnemonicsVerified = false; + + final hasVerifiedMnemonics = + await secureStorageProvider.get(SecureStorageKeys.hasVerifiedMnemonics); + + if (hasVerifiedMnemonics != null && hasVerifiedMnemonics == 'yes') { + isMnemonicsVerified = true; + } + + emit( + state.copyWith( + status: AppStatus.idle, + mnemonics: mnemonics, + hasVerifiedMnemonics: isMnemonicsVerified, + ), + ); + } + + Future getVerifiedStatus() async { + emit(state.loading()); + bool isMnemonicsVerified = false; + + final hasVerifiedMnemonics = + await secureStorageProvider.get(SecureStorageKeys.hasVerifiedMnemonics); + + if (hasVerifiedMnemonics != null && hasVerifiedMnemonics == 'yes') { + isMnemonicsVerified = true; + } + + emit( + state.copyWith( + status: AppStatus.idle, + hasVerifiedMnemonics: isMnemonicsVerified, + ), + ); + } +} diff --git a/lib/dashboard/drawer/recovery_key/cubit/recovery_key_state.dart b/lib/dashboard/drawer/recovery_key/cubit/recovery_key_state.dart new file mode 100644 index 000000000..ca1d895c6 --- /dev/null +++ b/lib/dashboard/drawer/recovery_key/cubit/recovery_key_state.dart @@ -0,0 +1,54 @@ +part of 'recovery_key_cubit.dart'; + +@JsonSerializable() +class RecoveryKeyState extends Equatable { + const RecoveryKeyState({ + this.status = AppStatus.init, + this.message, + this.mnemonics, + this.hasVerifiedMnemonics = false, + }); + + factory RecoveryKeyState.fromJson(Map json) => + _$RecoveryKeyStateFromJson(json); + + final AppStatus status; + final StateMessage? message; + final List? mnemonics; + final bool hasVerifiedMnemonics; + + RecoveryKeyState loading() { + return copyWith(status: AppStatus.loading); + } + + RecoveryKeyState error({required MessageHandler messageHandler}) { + return copyWith( + status: AppStatus.error, + message: StateMessage.error(messageHandler: messageHandler), + mnemonics: mnemonics, + ); + } + + RecoveryKeyState copyWith({ + AppStatus? status, + StateMessage? message, + List? mnemonics, + bool? hasVerifiedMnemonics, + }) { + return RecoveryKeyState( + status: status ?? this.status, + mnemonics: mnemonics ?? this.mnemonics, + hasVerifiedMnemonics: hasVerifiedMnemonics ?? this.hasVerifiedMnemonics, + ); + } + + Map toJson() => _$RecoveryKeyStateToJson(this); + + @override + List get props => [ + status, + message, + mnemonics, + hasVerifiedMnemonics, + ]; +} diff --git a/lib/dashboard/drawer/recovery_key/recovery_key.dart b/lib/dashboard/drawer/recovery_key/recovery_key.dart index cebce7cf4..955a45d74 100644 --- a/lib/dashboard/drawer/recovery_key/recovery_key.dart +++ b/lib/dashboard/drawer/recovery_key/recovery_key.dart @@ -1 +1,3 @@ +export 'cubit/recovery_key_cubit.dart'; +export 'view/key_verified_page.dart'; export 'view/recovery_key_page.dart'; diff --git a/lib/dashboard/drawer/recovery_key/view/key_verified_page.dart b/lib/dashboard/drawer/recovery_key/view/key_verified_page.dart new file mode 100644 index 000000000..abcb8d532 --- /dev/null +++ b/lib/dashboard/drawer/recovery_key/view/key_verified_page.dart @@ -0,0 +1,74 @@ +import 'package:altme/app/app.dart'; +import 'package:altme/l10n/l10n.dart'; +//import 'package:confetti/confetti.dart'; +import 'package:flutter/material.dart'; + +class KeyVerifiedPage extends StatelessWidget { + const KeyVerifiedPage({super.key}); + + static Route route() { + return MaterialPageRoute( + settings: const RouteSettings(name: '/KeyVerifiedPage'), + builder: (_) => const KeyVerifiedPage(), + ); + } + + @override + Widget build(BuildContext context) { + return const WalletReadyView(); + } +} + +class WalletReadyView extends StatelessWidget { + const WalletReadyView({super.key}); + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + return WillPopScope( + onWillPop: () async => false, + child: BasePage( + scrollView: false, + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const AltMeLogo(size: Sizes.logo2XLarge), + const SizedBox(height: Sizes.spaceNormal), + Text( + l10n.welDone, + style: Theme.of(context).textTheme.headlineMedium, + ), + const SizedBox(height: Sizes.spaceNormal), + Text( + l10n.mnemonicsVerifiedMessage, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.headlineSmall?.copyWith( + fontWeight: FontWeight.normal, + color: Theme.of(context).colorScheme.onTertiary, + ), + ), + const SizedBox(height: Sizes.space3XLarge), + ], + ), + ), + navigation: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: Sizes.spaceSmall, + vertical: Sizes.space2XSmall, + ), + child: MyGradientButton( + text: l10n.letsGo, + verticalSpacing: 18, + onPressed: () { + Navigator.pop(context); + }, + ), + ), + ), + ), + ); + } +} diff --git a/lib/dashboard/drawer/recovery_key/view/recovery_key_page.dart b/lib/dashboard/drawer/recovery_key/view/recovery_key_page.dart index 1f23228e3..75675c023 100644 --- a/lib/dashboard/drawer/recovery_key/view/recovery_key_page.dart +++ b/lib/dashboard/drawer/recovery_key/view/recovery_key_page.dart @@ -1,7 +1,11 @@ import 'package:altme/app/app.dart'; +import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/l10n/l10n.dart'; +import 'package:altme/onboarding/onboarding.dart'; import 'package:altme/theme/theme.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:secure_storage/secure_storage.dart'; class RecoveryKeyPage extends StatelessWidget { const RecoveryKeyPage({super.key}); @@ -13,7 +17,12 @@ class RecoveryKeyPage extends StatelessWidget { @override Widget build(BuildContext context) { - return const RecoveryKeyView(); + return BlocProvider( + create: (context) => RecoveryKeyCubit( + secureStorageProvider: getSecureStorage, + ), + child: const RecoveryKeyView(), + ); } } @@ -32,9 +41,16 @@ class _RecoveryKeyViewState extends State @override void initState() { super.initState(); + + WidgetsBinding.instance.addPostFrameCallback( + (_) async { + await context.read().getMnemonics(); + }, + ); + animationController = AnimationController( vsync: this, - duration: const Duration(seconds: 10), + duration: const Duration(seconds: 20), ); final Tween rotationTween = Tween(begin: 20, end: 0); @@ -58,58 +74,88 @@ class _RecoveryKeyViewState extends State Widget build(BuildContext context) { final l10n = context.l10n; - return BasePage( - title: l10n.recoveryKeyTitle, - titleAlignment: Alignment.topCenter, - titleLeading: const BackLeadingButton(), - titleTrailing: AnimatedBuilder( - animation: animation, - builder: (BuildContext context, Widget? child) { - return Text( - timeFormatter(timeInSecond: animation.value.toInt()), - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.titleLarge, + return BlocConsumer( + listener: (context, state) { + if (state.status == AppStatus.loading) { + LoadingView().show(context: context); + } else { + LoadingView().hide(); + } + + if (state.message != null) { + AlertMessage.showStateMessage( + context: context, + stateMessage: state.message!, ); - }, - ), - secureScreen: true, - scrollView: false, - body: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Column( - children: [ - Text( - l10n.genPhraseInstruction, - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.messageTitle, - ), - const SizedBox(height: 20), - Text( - l10n.genPhraseExplanation, + } + }, + builder: (context, state) { + return BasePage( + title: l10n.recoveryKeyTitle, + titleAlignment: Alignment.topCenter, + titleLeading: const BackLeadingButton(), + titleTrailing: AnimatedBuilder( + animation: animation, + builder: (BuildContext context, Widget? child) { + return Text( + timeFormatter(timeInSecond: animation.value.toInt()), textAlign: TextAlign.center, - style: Theme.of(context).textTheme.messageSubtitle, + style: Theme.of(context).textTheme.titleLarge, + ); + }, + ), + secureScreen: true, + scrollView: false, + body: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Column( + children: [ + Text( + l10n.genPhraseInstruction, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.messageTitle, + ), + const SizedBox(height: 20), + Text( + l10n.genPhraseExplanation, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.messageSubtitle, + ), + ], ), + const SizedBox(height: 32), + if (state.mnemonics != null) + MnemonicDisplay(mnemonic: state.mnemonics!) ], ), - const SizedBox(height: 32), - FutureBuilder>( - future: getssiMnemonicsInList(), - builder: (context, snapshot) { - switch (snapshot.connectionState) { - case ConnectionState.done: - final mnemonics = snapshot.data!; - - return MnemonicDisplay(mnemonic: mnemonics); - case ConnectionState.waiting: - case ConnectionState.none: - case ConnectionState.active: - return const SizedBox(); - } - }, - ) - ], - ), + navigation: state.mnemonics != null && !state.hasVerifiedMnemonics + ? SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: Sizes.spaceSmall, + vertical: Sizes.spaceSmall, + ), + child: MyGradientButton( + text: l10n.verifyNow, + verticalSpacing: 18, + onPressed: () async { + animationController.stop(); + await Navigator.of(context).push( + OnBoardingVerifyPhrasePage.route( + mnemonic: state.mnemonics!, + isFromOnboarding: false, + ), + ); + await context.read().getMnemonics(); + await animationController.forward(); + }, + ), + ), + ) + : null, + ); + }, ); } } diff --git a/lib/dashboard/home/tab_bar/credentials/list/cubit/credential_list_cubit.dart b/lib/dashboard/home/tab_bar/credentials/list/cubit/credential_list_cubit.dart index 1b64e6c1c..4d0590c77 100644 --- a/lib/dashboard/home/tab_bar/credentials/list/cubit/credential_list_cubit.dart +++ b/lib/dashboard/home/tab_bar/credentials/list/cubit/credential_list_cubit.dart @@ -104,6 +104,7 @@ class CredentialListCubit extends Cubit { case CredentialSubjectType.polygonPooAddress: case CredentialSubjectType.binancePooAddress: case CredentialSubjectType.euDiplomaCard: + case CredentialSubjectType.euVerifiableId: break; } @@ -318,6 +319,7 @@ class CredentialListCubit extends Cubit { case CredentialSubjectType.polygonPooAddress: case CredentialSubjectType.binancePooAddress: case CredentialSubjectType.euDiplomaCard: + case CredentialSubjectType.euVerifiableId: break; } @@ -684,6 +686,7 @@ class CredentialListCubit extends Cubit { case CredentialSubjectType.fantomPooAddress: case CredentialSubjectType.polygonPooAddress: case CredentialSubjectType.binancePooAddress: + case CredentialSubjectType.euVerifiableId: break; } diff --git a/lib/dashboard/home/tab_bar/credentials/models/home_credential/home_credential.dart b/lib/dashboard/home/tab_bar/credentials/models/home_credential/home_credential.dart index 666e58fe5..049877bec 100644 --- a/lib/dashboard/home/tab_bar/credentials/models/home_credential/home_credential.dart +++ b/lib/dashboard/home/tab_bar/credentials/models/home_credential/home_credential.dart @@ -285,6 +285,7 @@ class HomeCredential extends Equatable { case CredentialSubjectType.polygonPooAddress: case CredentialSubjectType.binancePooAddress: case CredentialSubjectType.euDiplomaCard: + case CredentialSubjectType.euVerifiableId: break; } diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 2629bbe9f..4fa1e7ddf 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -808,5 +808,8 @@ "e2eEncyptedChat": "Chat is encrypted from end to end.", "pincodeAttemptMessage": "You have entered an incorrect PIN code three times. For security reasons, please wait for one minute before trying again.", "verifyNow": "Verify Now", - "verifyLater": "Verify Later" + "verifyLater": "Verify Later", + "welDone": "Well done!", + "mnemonicsVerifiedMessage": "Your revovery phrase is saved correctly.", + "letsGo": "Let's Go" } \ No newline at end of file diff --git a/lib/l10n/untranslated.json b/lib/l10n/untranslated.json index 6ecab8269..4d13487a5 100644 --- a/lib/l10n/untranslated.json +++ b/lib/l10n/untranslated.json @@ -744,7 +744,9 @@ "e2eEncyptedChat", "pincodeAttemptMessage", "verifyNow", - "verifyLater" + "verifyLater", + "welDone", + "mnemonicsVerifiedMessage" ], "es": [ @@ -1492,7 +1494,9 @@ "e2eEncyptedChat", "pincodeAttemptMessage", "verifyNow", - "verifyLater" + "verifyLater", + "welDone", + "mnemonicsVerifiedMessage" ], "fr": [ @@ -1523,7 +1527,9 @@ "e2eEncyptedChat", "pincodeAttemptMessage", "verifyNow", - "verifyLater" + "verifyLater", + "welDone", + "mnemonicsVerifiedMessage" ], "it": [ @@ -2271,6 +2277,8 @@ "e2eEncyptedChat", "pincodeAttemptMessage", "verifyNow", - "verifyLater" + "verifyLater", + "welDone", + "mnemonicsVerifiedMessage" ] } diff --git a/lib/onboarding/activate_biometircs/view/activate_biometrics_page.dart b/lib/onboarding/activate_biometircs/view/activate_biometrics_page.dart index 4210afdd1..2982f25fc 100644 --- a/lib/onboarding/activate_biometircs/view/activate_biometrics_page.dart +++ b/lib/onboarding/activate_biometircs/view/activate_biometrics_page.dart @@ -103,7 +103,7 @@ class ActivateBiometricsView extends StatelessWidget { children: [ MStepper( step: 2, - totalStep: byPassScreen ? 2 : 4, + totalStep: byPassScreen ? 2 : 3, ), const Spacer(), Text( diff --git a/lib/onboarding/gen_phrase/cubit/onboarding_gen_phrase_cubit.dart b/lib/onboarding/gen_phrase/cubit/onboarding_gen_phrase_cubit.dart index dc339d84b..aa35d53a7 100644 --- a/lib/onboarding/gen_phrase/cubit/onboarding_gen_phrase_cubit.dart +++ b/lib/onboarding/gen_phrase/cubit/onboarding_gen_phrase_cubit.dart @@ -49,6 +49,10 @@ class OnBoardingGenPhraseCubit extends Cubit { walletCubit: walletCubit, splashCubit: splashCubit, ); + await secureStorageProvider.set( + SecureStorageKeys.hasVerifiedMnemonics, + 'no', + ); emit(state.success()); } catch (error) { log.e('something went wrong when generating a key', error); diff --git a/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart b/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart index f7816faac..13dd45d85 100644 --- a/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart +++ b/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart @@ -103,7 +103,7 @@ class _OnBoardingGenPhraseViewState extends State { children: [ const MStepper( step: 3, - totalStep: 4, + totalStep: 3, ), const SizedBox( height: Sizes.spaceNormal, @@ -175,6 +175,7 @@ class _OnBoardingGenPhraseViewState extends State { Navigator.of(context).push( OnBoardingVerifyPhrasePage.route( mnemonic: mnemonic!, + isFromOnboarding: true, ), ); }, diff --git a/lib/onboarding/verify_phrase/cubit/onboarding_verify_phrase_cubit.dart b/lib/onboarding/verify_phrase/cubit/onboarding_verify_phrase_cubit.dart index a49d51218..4575f309d 100644 --- a/lib/onboarding/verify_phrase/cubit/onboarding_verify_phrase_cubit.dart +++ b/lib/onboarding/verify_phrase/cubit/onboarding_verify_phrase_cubit.dart @@ -105,19 +105,27 @@ class OnBoardingVerifyPhraseCubit extends Cubit { } } - Future generateSSIAndCryptoAccount(List mnemonic) async { + Future generateSSIAndCryptoAccount({ + required List mnemonic, + required bool isFromOnboarding, + }) async { emit(state.loading()); - await Future.delayed(const Duration(milliseconds: 500)); try { - await generateAccount( - mnemonic: mnemonic, - secureStorageProvider: secureStorageProvider, - keyGenerator: keyGenerator, - didKitProvider: didKitProvider, - didCubit: didCubit, - homeCubit: homeCubit, - walletCubit: walletCubit, - splashCubit: splashCubit, + if (isFromOnboarding) { + await generateAccount( + mnemonic: mnemonic, + secureStorageProvider: secureStorageProvider, + keyGenerator: keyGenerator, + didKitProvider: didKitProvider, + didCubit: didCubit, + homeCubit: homeCubit, + walletCubit: walletCubit, + splashCubit: splashCubit, + ); + } + await secureStorageProvider.set( + SecureStorageKeys.hasVerifiedMnemonics, + 'yes', ); emit(state.success()); } catch (error) { diff --git a/lib/onboarding/verify_phrase/view/onboarding_verify_phrase.dart b/lib/onboarding/verify_phrase/view/onboarding_verify_phrase.dart index a8e6c61d0..aa74de446 100644 --- a/lib/onboarding/verify_phrase/view/onboarding_verify_phrase.dart +++ b/lib/onboarding/verify_phrase/view/onboarding_verify_phrase.dart @@ -14,13 +14,24 @@ import 'package:key_generator/key_generator.dart'; import 'package:secure_storage/secure_storage.dart'; class OnBoardingVerifyPhrasePage extends StatelessWidget { - const OnBoardingVerifyPhrasePage({required this.mnemonic, super.key}); + const OnBoardingVerifyPhrasePage({ + required this.mnemonic, + required this.isFromOnboarding, + super.key, + }); final List mnemonic; + final bool isFromOnboarding; - static Route route({required List mnemonic}) => + static Route route({ + required List mnemonic, + required bool isFromOnboarding, + }) => MaterialPageRoute( - builder: (context) => OnBoardingVerifyPhrasePage(mnemonic: mnemonic), + builder: (context) => OnBoardingVerifyPhrasePage( + mnemonic: mnemonic, + isFromOnboarding: isFromOnboarding, + ), settings: const RouteSettings(name: '/OnBoardingVerifyPhrasePage'), ); @@ -37,15 +48,23 @@ class OnBoardingVerifyPhrasePage extends StatelessWidget { splashCubit: context.read(), flavorCubit: context.read(), ), - child: OnBoardingVerifyPhraseView(mnemonic: mnemonic), + child: OnBoardingVerifyPhraseView( + mnemonic: mnemonic, + isFromOnboarding: isFromOnboarding, + ), ); } } class OnBoardingVerifyPhraseView extends StatefulWidget { - const OnBoardingVerifyPhraseView({required this.mnemonic, super.key}); + const OnBoardingVerifyPhraseView({ + required this.mnemonic, + required this.isFromOnboarding, + super.key, + }); final List mnemonic; + final bool isFromOnboarding; @override State createState() => @@ -83,12 +102,19 @@ class _OnBoardingVerifyPhraseViewState } if (state.status == AppStatus.success) { - context.read().init(); - Navigator.pushAndRemoveUntil( - context, - WalletReadyPage.route(), - (Route route) => route.isFirst, - ); + if (widget.isFromOnboarding) { + context.read().init(); + Navigator.pushAndRemoveUntil( + context, + WalletReadyPage.route(), + (Route route) => route.isFirst, + ); + } else { + Navigator.pushReplacement( + context, + KeyVerifiedPage.route(), + ); + } } }, builder: (context, state) { @@ -102,11 +128,13 @@ class _OnBoardingVerifyPhraseViewState ? Container() : Column( children: [ - const MStepper( - step: 4, - totalStep: 4, - ), - const SizedBox(height: Sizes.spaceNormal), + if (widget.isFromOnboarding) ...[ + const MStepper( + step: 3, + totalStep: 3, + ), + const SizedBox(height: Sizes.spaceNormal), + ], Padding( padding: const EdgeInsets.symmetric( horizontal: Sizes.spaceNormal, @@ -215,7 +243,10 @@ class _OnBoardingVerifyPhraseViewState ? () async { await context .read() - .generateSSIAndCryptoAccount(widget.mnemonic); + .generateSSIAndCryptoAccount( + mnemonic: widget.mnemonic, + isFromOnboarding: widget.isFromOnboarding, + ); } : null, ), diff --git a/lib/pin_code/view/confirm_pin_code_page.dart b/lib/pin_code/view/confirm_pin_code_page.dart index b7d6a5a81..52dc7077d 100644 --- a/lib/pin_code/view/confirm_pin_code_page.dart +++ b/lib/pin_code/view/confirm_pin_code_page.dart @@ -97,7 +97,7 @@ class _ConfirmPinCodeViewState extends State { header: widget.isFromOnboarding ? MStepper( step: 1, - totalStep: byPassScreen ? 2 : 4, + totalStep: byPassScreen ? 2 : 3, ) : null, deleteButton: Text( diff --git a/lib/pin_code/view/enter_new_pin_code_page.dart b/lib/pin_code/view/enter_new_pin_code_page.dart index 5609609b9..529946920 100644 --- a/lib/pin_code/view/enter_new_pin_code_page.dart +++ b/lib/pin_code/view/enter_new_pin_code_page.dart @@ -83,7 +83,7 @@ class _EnterNewPinCodeViewState extends State { header: widget.isFromOnboarding ? MStepper( step: 1, - totalStep: byPassScreen ? 2 : 4, + totalStep: byPassScreen ? 2 : 3, ) : null, deleteButton: Text( diff --git a/packages/polygonid/lib/polygonid.dart b/packages/polygonid/lib/polygonid.dart index d48ec3a40..e178082f4 100644 --- a/packages/polygonid/lib/polygonid.dart +++ b/packages/polygonid/lib/polygonid.dart @@ -1,3 +1,4 @@ +/// polygonId Flutter SDK library polygonid; export 'src/polygonid.dart'; diff --git a/packages/polygonid/lib/src/polygonid.dart b/packages/polygonid/lib/src/polygonid.dart index 565bd37a0..c58aa5ce7 100644 --- a/packages/polygonid/lib/src/polygonid.dart +++ b/packages/polygonid/lib/src/polygonid.dart @@ -40,7 +40,7 @@ class PolygonId { blockchain: 'polygon', network: 'main', ); - print(identity.did); + //print(identity.did); } /// create JWK from mnemonic diff --git a/pubspec.lock b/pubspec.lock index f8385abe6..bd9856abd 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2501,5 +2501,5 @@ packages: source: hosted version: "2.1.0" sdks: - dart: ">=2.19.0 <3.7.0" + dart: ">=2.19.0 <3.0.0" flutter: ">=3.7.0-0" From 47795c644904070fd66fd36886c806436204d543 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Thu, 9 Mar 2023 19:26:22 +0330 Subject: [PATCH 134/190] fix popup text styles --- .../widget/add_account/add_account.dart | 1 - .../widget/add_account/add_account_popup.dart | 122 ------------------ .../shared/widget/dialog/confirm_dialog.dart | 8 +- lib/app/shared/widget/dialog/info_dialog.dart | 8 +- .../widget/dialog/text_field_dialog.dart | 8 +- .../dashboard/widgets/what_is_new_dialog.dart | 11 +- .../home/home/widgets/wallet_dialog.dart | 6 +- .../widgets/transaction_done_dialog.dart | 1 + lib/theme/app_theme/app_theme.dart | 30 +---- pubspec.lock | 2 +- 10 files changed, 27 insertions(+), 170 deletions(-) delete mode 100644 lib/app/shared/widget/add_account/add_account_popup.dart diff --git a/lib/app/shared/widget/add_account/add_account.dart b/lib/app/shared/widget/add_account/add_account.dart index 1e9867b9b..adfa2d93e 100644 --- a/lib/app/shared/widget/add_account/add_account.dart +++ b/lib/app/shared/widget/add_account/add_account.dart @@ -1,2 +1 @@ export 'add_account_button.dart'; -export 'add_account_popup.dart'; diff --git a/lib/app/shared/widget/add_account/add_account_popup.dart b/lib/app/shared/widget/add_account/add_account_popup.dart deleted file mode 100644 index 108363582..000000000 --- a/lib/app/shared/widget/add_account/add_account_popup.dart +++ /dev/null @@ -1,122 +0,0 @@ -import 'package:altme/app/app.dart'; -import 'package:altme/l10n/l10n.dart'; -import 'package:altme/theme/theme.dart'; -import 'package:flutter/material.dart'; - -class AddAccountPopUp extends StatefulWidget { - const AddAccountPopUp({ - super.key, - this.defaultAccountName, - required this.onCreateAccount, - required this.onImportAccount, - }); - - final String? defaultAccountName; - final dynamic Function(String) onImportAccount; - final dynamic Function(String) onCreateAccount; - - @override - State createState() => _AddAccountPopUpState(); -} - -class _AddAccountPopUpState extends State { - late TextEditingController controller = - TextEditingController(text: widget.defaultAccountName); - - @override - Widget build(BuildContext context) { - final l10n = context.l10n; - - return AlertDialog( - backgroundColor: Theme.of(context).colorScheme.popupBackground, - surfaceTintColor: Colors.transparent, - contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15), - shape: RoundedRectangleBorder( - side: BorderSide( - color: Theme.of(context).colorScheme.inversePrimary, - width: 0.3, - ), - borderRadius: const BorderRadius.all(Radius.circular(25)), - ), - content: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - l10n.cryptoAddAccount, - style: Theme.of(context).textTheme.dialogTitle, - textAlign: TextAlign.center, - ), - const SizedBox( - height: Sizes.spaceSmall, - ), - Text( - l10n.enterNameForYourNewAccount, - style: Theme.of(context).textTheme.dialogSubtitle, - textAlign: TextAlign.center, - ), - ], - ), - RawMaterialButton( - onPressed: () => Navigator.of(context).pop(), - fillColor: Theme.of(context).colorScheme.primary, - padding: const EdgeInsets.all(2), - shape: const CircleBorder(), - elevation: 5, - constraints: const BoxConstraints( - maxWidth: Sizes.icon2x, - maxHeight: Sizes.icon2x, - ), - child: const Icon( - Icons.close, - size: Sizes.icon, - color: Colors.white, - ), - ) - ], - ), - const SizedBox(height: Sizes.spaceNormal), - BaseTextField( - controller: controller, - borderRadius: Sizes.smallRadius, - textCapitalization: TextCapitalization.sentences, - fillColor: Theme.of(context).highlightColor, - contentPadding: const EdgeInsets.symmetric( - vertical: Sizes.spaceSmall, - horizontal: Sizes.spaceSmall, - ), - borderColor: Theme.of(context).colorScheme.onInverseSurface, - ), - const SizedBox(height: 24), - MyGradientButton( - text: l10n.create, - verticalSpacing: 10, - borderRadius: 10, - elevation: 10, - onPressed: () => widget.onCreateAccount.call(controller.text), - ), - const SizedBox(height: Sizes.spaceXSmall), - MyOutlinedButton( - text: l10n.import, - verticalSpacing: Sizes.smallRadius, - fontSize: 17, - elevation: 0, - borderColor: Colors.transparent, - backgroundColor: Colors.transparent, - textColor: Theme.of(context).colorScheme.label, - onPressed: () => widget.onImportAccount.call(controller.text), - ), - ], - ), - ); - } -} diff --git a/lib/app/shared/widget/dialog/confirm_dialog.dart b/lib/app/shared/widget/dialog/confirm_dialog.dart index d9e77a3b3..dc8176ecf 100644 --- a/lib/app/shared/widget/dialog/confirm_dialog.dart +++ b/lib/app/shared/widget/dialog/confirm_dialog.dart @@ -50,8 +50,10 @@ class ConfirmDialog extends StatelessWidget { ), Text( title, - style: - Theme.of(context).textTheme.dialogTitle.copyWith(color: text), + style: Theme.of(context) + .textTheme + .defaultDialogTitle + .copyWith(color: text), textAlign: TextAlign.center, ), const SizedBox(height: Sizes.spaceXSmall), @@ -60,7 +62,7 @@ class ConfirmDialog extends StatelessWidget { subtitle!, style: Theme.of(context) .textTheme - .dialogSubtitle + .defaultDialogSubtitle .copyWith(color: text), textAlign: TextAlign.center, ), diff --git a/lib/app/shared/widget/dialog/info_dialog.dart b/lib/app/shared/widget/dialog/info_dialog.dart index d9e80f8ce..bb5ccef0c 100644 --- a/lib/app/shared/widget/dialog/info_dialog.dart +++ b/lib/app/shared/widget/dialog/info_dialog.dart @@ -45,8 +45,10 @@ class InfoDialog extends StatelessWidget { const SizedBox(height: 24), Text( title, - style: - Theme.of(context).textTheme.dialogTitle.copyWith(color: text), + style: Theme.of(context) + .textTheme + .defaultDialogTitle + .copyWith(color: text), textAlign: TextAlign.center, ), if (subtitle != null) @@ -54,7 +56,7 @@ class InfoDialog extends StatelessWidget { subtitle!, style: Theme.of(context) .textTheme - .dialogSubtitle + .defaultDialogSubtitle .copyWith(color: text), textAlign: TextAlign.center, ), diff --git a/lib/app/shared/widget/dialog/text_field_dialog.dart b/lib/app/shared/widget/dialog/text_field_dialog.dart index 9fb27d1f2..380edb186 100644 --- a/lib/app/shared/widget/dialog/text_field_dialog.dart +++ b/lib/app/shared/widget/dialog/text_field_dialog.dart @@ -73,8 +73,10 @@ class _TextFieldDialogState extends State { children: [ Text( widget.title, - style: - Theme.of(context).textTheme.dialogTitle.copyWith(color: text), + style: Theme.of(context) + .textTheme + .defaultDialogTitle + .copyWith(color: text), textAlign: TextAlign.center, ), if (widget.subtitle != null) @@ -82,7 +84,7 @@ class _TextFieldDialogState extends State { widget.subtitle!, style: Theme.of(context) .textTheme - .dialogSubtitle + .defaultDialogSubtitle .copyWith(color: text), textAlign: TextAlign.center, ), diff --git a/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart b/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart index 315fd2e68..6ee8148cf 100644 --- a/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart +++ b/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart @@ -43,14 +43,9 @@ class WhatIsNewDialog extends StatelessWidget { ), ), content: SizedBox( - //color: Theme.of(context).colorScheme.popupBackground, width: double.maxFinite, child: Stack( children: [ - const Align( - alignment: Alignment.topRight, - child: WhiteCloseButton(), - ), Column( mainAxisSize: MainAxisSize.min, children: [ @@ -63,7 +58,6 @@ class WhatIsNewDialog extends StatelessWidget { right: Sizes.spaceXLarge, ), child: Column( - //mainAxisSize: MainAxisSize.min, children: [ const AltMeLogo( color: Colors.white, @@ -221,7 +215,10 @@ class WhatIsNewDialog extends StatelessWidget { ) ], ), - ], + const Align( + alignment: Alignment.topRight, + child: WhiteCloseButton(), + ),], ), ), ), diff --git a/lib/dashboard/home/home/widgets/wallet_dialog.dart b/lib/dashboard/home/home/widgets/wallet_dialog.dart index 599f8dfff..ddaa315ac 100644 --- a/lib/dashboard/home/home/widgets/wallet_dialog.dart +++ b/lib/dashboard/home/home/widgets/wallet_dialog.dart @@ -28,13 +28,13 @@ class WalletDialog extends StatelessWidget { const SizedBox(height: 15), Text( l10n.walletAltme, - style: Theme.of(context).textTheme.walletAltme, + style: Theme.of(context).textTheme.defaultDialogTitle, textAlign: TextAlign.center, ), const SizedBox(height: 5), Text( l10n.createTitle, - style: Theme.of(context).textTheme.walletAltmeMessage, + style: Theme.of(context).textTheme.defaultDialogSubtitle, textAlign: TextAlign.center, ), Image.asset( @@ -44,7 +44,7 @@ class WalletDialog extends StatelessWidget { ), Text( l10n.createSubtitle, - style: Theme.of(context).textTheme.walletAltmeMessage, + style: Theme.of(context).textTheme.defaultDialogSubtitle, textAlign: TextAlign.center, ), const SizedBox(height: 15), diff --git a/lib/dashboard/home/tab_bar/tokens/confirm_token_transaction/widgets/transaction_done_dialog.dart b/lib/dashboard/home/tab_bar/tokens/confirm_token_transaction/widgets/transaction_done_dialog.dart index 5d85bade6..4b409e268 100644 --- a/lib/dashboard/home/tab_bar/tokens/confirm_token_transaction/widgets/transaction_done_dialog.dart +++ b/lib/dashboard/home/tab_bar/tokens/confirm_token_transaction/widgets/transaction_done_dialog.dart @@ -58,6 +58,7 @@ class TransactionDoneDialog extends StatelessWidget { IconStrings.bigCheckCircle, height: Sizes.icon4x, width: Sizes.icon4x, + color: Theme.of(context).iconTheme.color, ), const SizedBox(height: Sizes.spaceNormal), Text( diff --git a/lib/theme/app_theme/app_theme.dart b/lib/theme/app_theme/app_theme.dart index 9a2fbb209..facdad540 100644 --- a/lib/theme/app_theme/app_theme.dart +++ b/lib/theme/app_theme/app_theme.dart @@ -211,7 +211,7 @@ extension CustomColorScheme on ColorScheme { Color get qrScanOuterShadow => const Color(0xff430F91); - Color get dialogText => const Color(0xFF180B2B); + Color get dialogText => const Color(0xffF5F5F5); Color get tabBarNotSelected => const Color(0xFF280164); @@ -656,26 +656,8 @@ extension CustomTextTheme on TextTheme { fontWeight: FontWeight.w600, ); - TextStyle get dialogTitle => GoogleFonts.roboto( - fontSize: 18, - fontWeight: FontWeight.w600, - color: Colors.white, - ); - - TextStyle get dialogSubtitle => GoogleFonts.roboto( - fontSize: 13, - fontWeight: FontWeight.w400, - color: Colors.white.withOpacity(0.67), - ); - - TextStyle get walletAltme => GoogleFonts.roboto( - fontSize: 25, - fontWeight: FontWeight.w600, - color: const Color(0xff180B2B), - ); - TextStyle get defaultDialogTitle => GoogleFonts.nunito( - fontSize: 25, + fontSize: 24, fontWeight: FontWeight.bold, color: const Color(0xffF5F5F5), ); @@ -687,7 +669,7 @@ extension CustomTextTheme on TextTheme { ); TextStyle get defaultDialogSubtitle => GoogleFonts.nunito( - fontSize: 20, + fontSize: 18, fontWeight: FontWeight.bold, color: const Color(0xff86809D), ); @@ -722,12 +704,6 @@ extension CustomTextTheme on TextTheme { color: const Color(0xff86809D), ); - TextStyle get walletAltmeMessage => GoogleFonts.roboto( - fontSize: 16, - fontWeight: FontWeight.w400, - color: const Color(0xff9A8BB1), - ); - TextStyle get credentialCategoryTitle => GoogleFonts.roboto( fontSize: 18, fontWeight: FontWeight.w600, diff --git a/pubspec.lock b/pubspec.lock index bd9856abd..f243ce09c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1725,7 +1725,7 @@ packages: path: "../polygonid-flutter-sdk" relative: true source: path - version: "2.0.0" + version: "2.1.1" pool: dependency: transitive description: From bfd49848bbc588619c57b6596c6070879df162d6 Mon Sep 17 00:00:00 2001 From: hawkbee1 Date: Thu, 9 Mar 2023 21:38:25 +0100 Subject: [PATCH 135/190] Add fvm_config.json to git --- .fvm/fvm_config.json | 4 ++++ .gitignore | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 .fvm/fvm_config.json diff --git a/.fvm/fvm_config.json b/.fvm/fvm_config.json new file mode 100644 index 000000000..6e3d57e67 --- /dev/null +++ b/.fvm/fvm_config.json @@ -0,0 +1,4 @@ +{ + "flutterSdkVersion": "3.7.6", + "flavors": {} +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 24a4a85ed..8be0fd9dc 100644 --- a/.gitignore +++ b/.gitignore @@ -54,7 +54,7 @@ flutter_*.png linked_*.ds unlinked.ds unlinked_spec.ds -.fvm/ +.fvm/flutter_sdk # Android related **/android/**/gradle-wrapper.jar From eeff6079afa7bc8aaaff339b20bd4e9adeeca398 Mon Sep 17 00:00:00 2001 From: hawkbee1 Date: Thu, 9 Mar 2023 22:52:45 +0100 Subject: [PATCH 136/190] remove polygon id from release candidate --- lib/bootstrap.dart | 3 +- packages/polygonid/.gitignore | 39 -------- packages/polygonid/README.md | 11 --- packages/polygonid/analysis_options.yaml | 1 - packages/polygonid/lib/polygonid.dart | 4 - packages/polygonid/lib/src/polygonid.dart | 95 ------------------- packages/polygonid/pubspec.yaml | 22 ----- .../polygonid/test/src/polygonid_test.dart | 11 --- pubspec.lock | 78 --------------- pubspec.yaml | 2 - 10 files changed, 1 insertion(+), 265 deletions(-) delete mode 100644 packages/polygonid/.gitignore delete mode 100644 packages/polygonid/README.md delete mode 100644 packages/polygonid/analysis_options.yaml delete mode 100644 packages/polygonid/lib/polygonid.dart delete mode 100644 packages/polygonid/lib/src/polygonid.dart delete mode 100644 packages/polygonid/pubspec.yaml delete mode 100644 packages/polygonid/test/src/polygonid_test.dart diff --git a/lib/bootstrap.dart b/lib/bootstrap.dart index 2ea10f567..c951e8c78 100644 --- a/lib/bootstrap.dart +++ b/lib/bootstrap.dart @@ -15,7 +15,6 @@ import 'package:dartez/dartez.dart'; import 'package:flutter/widgets.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:passbase_flutter/passbase_flutter.dart'; -import 'package:polygonid/polygonid.dart'; import 'package:secure_storage/secure_storage.dart' as secure_storage; import 'package:workmanager/workmanager.dart'; @@ -128,7 +127,7 @@ Future bootstrap(FutureOr Function() builder) async { await Dartez().init(); /// PolygonId SDK initialization - await PolygonId().init(); + // await PolygonId().init(); await runZonedGuarded( () async { diff --git a/packages/polygonid/.gitignore b/packages/polygonid/.gitignore deleted file mode 100644 index d61303516..000000000 --- a/packages/polygonid/.gitignore +++ /dev/null @@ -1,39 +0,0 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.buildlog/ -.history -.svn/ - -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ - -# VSCode related -.vscode/ - -# Flutter/Dart/Pub related -**/doc/api/ -**/ios/Flutter/.last_build_id -.dart_tool/ -.flutter-plugins -.flutter-plugins-dependencies -.packages -.pub-cache/ -.pub/ -/build/ - -# Web related -lib/generated_plugin_registrant.dart - -# Symbolication related -app.*.symbols - -# Obfuscation related -app.*.map.json diff --git a/packages/polygonid/README.md b/packages/polygonid/README.md deleted file mode 100644 index 20fe3abc0..000000000 --- a/packages/polygonid/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# polygonid - -[![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link] -[![License: MIT][license_badge]][license_link] - -A Very Good Project created by Very Good CLI. - -[license_badge]: https://img.shields.io/badge/license-MIT-blue.svg -[license_link]: https://opensource.org/licenses/MIT -[very_good_analysis_badge]: https://img.shields.io/badge/style-very_good_analysis-B22C89.svg -[very_good_analysis_link]: https://pub.dev/packages/very_good_analysis diff --git a/packages/polygonid/analysis_options.yaml b/packages/polygonid/analysis_options.yaml deleted file mode 100644 index 9df80aa49..000000000 --- a/packages/polygonid/analysis_options.yaml +++ /dev/null @@ -1 +0,0 @@ -include: package:very_good_analysis/analysis_options.yaml diff --git a/packages/polygonid/lib/polygonid.dart b/packages/polygonid/lib/polygonid.dart deleted file mode 100644 index e178082f4..000000000 --- a/packages/polygonid/lib/polygonid.dart +++ /dev/null @@ -1,4 +0,0 @@ -/// polygonId Flutter SDK -library polygonid; - -export 'src/polygonid.dart'; diff --git a/packages/polygonid/lib/src/polygonid.dart b/packages/polygonid/lib/src/polygonid.dart deleted file mode 100644 index c58aa5ce7..000000000 --- a/packages/polygonid/lib/src/polygonid.dart +++ /dev/null @@ -1,95 +0,0 @@ -import 'dart:convert'; - -import 'package:bip32/bip32.dart' as bip32; -import 'package:bip39/bip39.dart' as bip393; -import 'package:flutter/foundation.dart'; -import 'package:hex/hex.dart'; -import 'package:polygonid_flutter_sdk/sdk/polygon_id_sdk.dart'; -import 'package:secp256k1/secp256k1.dart'; - -/// {@template polygonid} -/// A Very Good Project created by Very Good CLI. -/// {@endtemplate} -class PolygonId { - /// {@macro polygonid} - PolygonId(); - - /// PolygonId SDK initialization - /// Before you can start using the SDK, you need to initialise it, otherwise - /// a PolygonIsSdkNotInitializedException exception will be thrown. - - Future init() async { - await PolygonIdSdk.init(); - } - - /// Create Identity - /// - /// blockchain and network are not optional, they are used to associate the - /// identity to a specific blockchain network. - /// - /// it is recommended to securely save the privateKey generated with - /// createIdentity(), this will often be used within the sdk methods as a - /// security system, you can find the privateKey in the PrivateIdentityEntity - /// object. - /// - - Future createIdentity() async { - //we get the sdk instance previously initialized - final sdk = PolygonIdSdk.I; - final identity = await sdk.identity.createIdentity( - blockchain: 'polygon', - network: 'main', - ); - //print(identity.did); - } - - /// create JWK from mnemonic - Future privateKeyFromMnemonic({required String mnemonic}) async { - final seed = bip393.mnemonicToSeed(mnemonic); - - final rootKey = bip32.BIP32.fromSeed(seed); //Instance of 'BIP32' - final child = rootKey.derivePath( - "m/44'/5467'/0'/2'", - ); //Instance of 'BIP32' - final Iterable iterable = child.privateKey!; - final seedBytes = Uint8List.fromList(List.from(iterable)); - - final key = jwkFromSeed( - seedBytes: seedBytes, - ); - - return jsonEncode(key); - } - - /// create JWK from seed - Map jwkFromSeed({required Uint8List seedBytes}) { - // generate JWK for secp256k from bip39 mnemonic - // see https://iancoleman.io/bip39/ - final epk = HEX.encode(seedBytes); - final pk = PrivateKey.fromHex(epk); //Instance of 'PrivateKey' - final pub = pk.publicKey.toHex().substring(2); - final ad = HEX.decode(epk); - final d = base64Url.encode(ad).substring(0, 43); - // remove "=" padding 43/44 - final mx = pub.substring(0, 64); - // first 32 bytes - final ax = HEX.decode(mx); - final x = base64Url.encode(ax).substring(0, 43); - // remove "=" padding 43/44 - final my = pub.substring(64); - // last 32 bytes - final ay = HEX.decode(my); - final y = base64Url.encode(ay).substring(0, 43); - // ATTENTION !!!!! - /// we were using P-256K for dart library conformance which is - /// the same as secp256k1, but we are using secp256k1 now - final jwk = { - 'crv': 'secp256k1', - 'd': d, - 'kty': 'EC', - 'x': x, - 'y': y, - }; - return jwk; - } -} diff --git a/packages/polygonid/pubspec.yaml b/packages/polygonid/pubspec.yaml deleted file mode 100644 index d40699a3e..000000000 --- a/packages/polygonid/pubspec.yaml +++ /dev/null @@ -1,22 +0,0 @@ -name: polygonid -description: A Very Good Project created by Very Good CLI. -version: 1.0.0+1 -publish_to: none - -environment: - sdk: ">=2.17.0 <3.0.0" - -dependencies: - bip32: ^2.0.0 - bip39: ^1.0.6 - flutter: - sdk: flutter - hex: ^0.2.0 - polygonid_flutter_sdk: - path: ../../../polygonid-flutter-sdk - secp256k1: ^0.3.0 - -dev_dependencies: - flutter_test: - sdk: flutter - very_good_analysis: ^4.0.0+1 diff --git a/packages/polygonid/test/src/polygonid_test.dart b/packages/polygonid/test/src/polygonid_test.dart deleted file mode 100644 index 8978d590f..000000000 --- a/packages/polygonid/test/src/polygonid_test.dart +++ /dev/null @@ -1,11 +0,0 @@ -// ignore_for_file: prefer_const_constructors -import 'package:flutter_test/flutter_test.dart'; -import 'package:polygonid/polygonid.dart'; - -void main() { - group('PolygonId', () { - test('can be instantiated', () { - expect(PolygonId(), isNotNull); - }); - }); -} diff --git a/pubspec.lock b/pubspec.lock index f243ce09c..45d70aef8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -386,14 +386,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.1" - country_code: - dependency: transitive - description: - name: country_code - sha256: f69ccd5163b1ca43011be9632e33ebe7ffac65e49ce2afcd3e3e5228af5d91fc - url: "https://pub.dev" - source: hosted - version: "1.0.0" coverage: dependency: transitive description: @@ -646,14 +638,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.2" - encrypt: - dependency: transitive - description: - name: encrypt - sha256: "4fd4e4fdc21b9d7d4141823e1e6515cd94e7b8d84749504c232999fba25d9bbb" - url: "https://pub.dev" - source: hosted - version: "5.0.1" enhanced_enum: dependency: transitive description: @@ -662,14 +646,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.4" - envied: - dependency: transitive - description: - name: envied - sha256: "2545198df33f6ecb701af530bcc4f85385950d9c71a75882c16c85d0f95d0928" - url: "https://pub.dev" - source: hosted - version: "0.2.4" equatable: dependency: "direct main" description: @@ -702,14 +678,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.1" - ffigen: - dependency: transitive - description: - name: ffigen - sha256: "2f2f40265aae76d966164c3d53bfcbbeb73bc97f0ec2966032ac5e971bb3f20a" - url: "https://pub.dev" - source: hosted - version: "7.2.7" file: dependency: transitive description: @@ -1002,14 +970,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.0" - get_it: - dependency: transitive - description: - name: get_it - sha256: "290fde3a86072e4b37dbb03c07bec6126f0ecc28dad403c12ffe2e5a2d751ab7" - url: "https://pub.dev" - source: hosted - version: "7.2.0" glob: dependency: transitive description: @@ -1146,14 +1106,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.6.2" - injectable: - dependency: transitive - description: - name: injectable - sha256: f71eb879124ed286cbd2210337b91ff5f345f146187c1f1891c172e0ac06443a - url: "https://pub.dev" - source: hosted - version: "1.5.4" intl: dependency: "direct main" description: @@ -1712,20 +1664,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.6.2" - polygonid: - dependency: "direct main" - description: - path: "packages/polygonid" - relative: true - source: path - version: "1.0.0+1" - polygonid_flutter_sdk: - dependency: transitive - description: - path: "../polygonid-flutter-sdk" - relative: true - source: path - version: "2.1.1" pool: dependency: transitive description: @@ -1893,14 +1831,6 @@ packages: relative: true source: path version: "1.0.0+1" - sembast: - dependency: transitive - description: - name: sembast - sha256: "4997717aa84f0622691815d7e2739988b7f7d3a463302fc878f7d5acfa748e96" - url: "https://pub.dev" - source: hosted - version: "3.4.0+6" share_plus: dependency: "direct main" description: @@ -2492,14 +2422,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.1" - yaml_edit: - dependency: transitive - description: - name: yaml_edit - sha256: "0b968021754d8fbd3e9c83563b538ee417d88b2cc587606da5615546b7ee033b" - url: "https://pub.dev" - source: hosted - version: "2.1.0" sdks: dart: ">=2.19.0 <3.0.0" flutter: ">=3.7.0-0" diff --git a/pubspec.yaml b/pubspec.yaml index c02ce8cee..725fc4b9a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -85,8 +85,6 @@ dependencies: permission_handler: ^10.2.0 platform_device_id: ^1.0.1 pointycastle: ^3.6.2 - polygonid: - path: packages/polygonid pretty_qr_code: ^2.0.2 qr_flutter: ^4.0.0 screenshot: ^1.3.0 From 53851cfe63e30c7f153c1c135ddb8d8c9dfa9ff2 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Fri, 10 Mar 2023 11:07:19 +0530 Subject: [PATCH 137/190] add depend_on_referenced_packages on linter --- analysis_options.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index df35d6a43..869116156 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,7 +1,7 @@ include: package:very_good_analysis/analysis_options.yaml analyzer: exclude: - - '**/*.g.dart' + - "**/*.g.dart" linter: rules: public_member_api_docs: false @@ -15,4 +15,5 @@ linter: avoid_dynamic_calls: false use_build_context_synchronously: false use_string_buffers: false - always_put_required_named_parameters_first: false \ No newline at end of file + always_put_required_named_parameters_first: false + depend_on_referenced_packages: false From 628b5cc1f11f9b84eb29dcfef1db01251ab8770f Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Fri, 10 Mar 2023 16:31:40 +0530 Subject: [PATCH 138/190] extract the correct public key with teh kid #1437 --- packages/ebsi/lib/src/ebsi.dart | 14 +++++++++----- packages/ebsi/test/src/ebsi_test.dart | 24 ++++++++++++++++++------ 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/packages/ebsi/lib/src/ebsi.dart b/packages/ebsi/lib/src/ebsi.dart index 280bbf2cb..df355d86f 100644 --- a/packages/ebsi/lib/src/ebsi.dart +++ b/packages/ebsi/lib/src/ebsi.dart @@ -296,13 +296,13 @@ class Ebsi { } Map readPublicKeyJwk( - String issuerDid, + String holderKid, Response> didDocumentResponse, ) { final jsonPath = JsonPath(r'$..verificationMethod'); final data = jsonPath.read(didDocumentResponse.data).first.value ..where( - (dynamic e) => e['controller'].toString() == issuerDid, + (dynamic e) => e['id'].toString() == holderKid, ).toList(); final value = data.first['publicKeyJwk']; @@ -337,8 +337,11 @@ class Ebsi { final issuerDid = readIssuerDid(openidConfigurationResponse); - final isVerified = - await verifyCredential(issuerDid: issuerDid, vcJwt: vcJwt); + final isVerified = await verifyCredential( + issuerDid: issuerDid, + vcJwt: vcJwt, + holderKid: issuerTokenParameters.kid, + ); if (isVerified == VerificationType.notVerified) { throw Exception('VERIFICATION_ISSUE'); @@ -356,12 +359,13 @@ class Ebsi { Future verifyCredential({ required String issuerDid, + required String holderKid, required String vcJwt, }) async { try { final didDocument = await getDidDocument(issuerDid); - final publicKeyJwk = readPublicKeyJwk(issuerDid, didDocument); + final publicKeyJwk = readPublicKeyJwk(holderKid, didDocument); // create a JsonWebSignature from the encoded string final jws = JsonWebSignature.fromCompactSerialization(vcJwt); diff --git a/packages/ebsi/test/src/ebsi_test.dart b/packages/ebsi/test/src/ebsi_test.dart index 05bcacaba..913edb1f3 100644 --- a/packages/ebsi/test/src/ebsi_test.dart +++ b/packages/ebsi/test/src/ebsi_test.dart @@ -427,6 +427,9 @@ void main() { const issuerDid1 = 'did:ebsi:zeFCExU2XAAshYkPCpjuahA'; const issuerDid2 = 'did:ebsi:zhSw5rPXkcHjvquwnVcTzzC'; + const holderKid = + 'did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53'; + const didDocumentUrl = 'https://api-pilot.ebsi.eu/did-registry/v3/identifiers/$issuerDid1'; @@ -457,8 +460,11 @@ void main() { 'wqT2SI-KGDKB34XO0aw_7XdtAG8GaSwFKdCAPZgoXD2YBJZCPEX3xKpRwcdOO8Kp' 'EHwJjyqOgzDO7iKvU8vcnwNrmxYbSW9ERBXukOXolLzeO_Jn'; - final isVerified = - await ebsi.verifyCredential(issuerDid: issuerDid1, vcJwt: vcJwt); + final isVerified = await ebsi.verifyCredential( + issuerDid: issuerDid1, + vcJwt: vcJwt, + holderKid: holderKid, + ); expect(isVerified, VerificationType.verified); }); @@ -467,8 +473,11 @@ void main() { const vcJwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksiLCJqd2siOnsiY3J2IjoiUC0yNTZLIiwia3R5IjoiRUMiLCJ4IjoiSjR2UXRMVXlyVlVpRklYUnJ0RXE0eHVybUJacDJlcTl3Sm1Ya0lBX3N0SSIsInkiOiJFVVU2dlhvRzNCR1gyenp3alhyR0RjcjRFeUREMFZmazNfNWZnNWtTZ0tFIn0sImtpZCI6ImRpZDplYnNpOnpvOUZSMVlmQUtGUDNRNmR2cWh4Y1h4bmZlRGlKRFA5N2ttbnFoeUFVU0FDaiNDZ2NnMXk5eGo5dVdGdzU2UE1jMjlYQmQ5RVJlaXh6dm5mdEJ6OEp3UUZpQiJ9.eyJpc3MiOiJkaWQ6ZWJzaTp6bzlGUjFZZkFLRlAzUTZkdnFoeGNYeG5mZURpSkRQOTdrbW5xaHlBVVNBQ2oiLCJub25jZSI6IjdhMDdkZTBmLWE4NzktMTFlZC04MjJiLTBhMTYyODk1ODU2MCIsImlhdCI6MTY3NzA1MDc0MDEyMzIzNSwiYXVkIjoiaHR0cHM6Ly90YWxhby5jby9zYW5kYm94L2Vic2kvaXNzdWVyL3ZndmdoeWxvemwifQ.htjRCpFWbRwanAyQcAq9XZ4vxCXyFbzaaN3yPbPxWIcKFFzDDcA4QCHTUl-L4vzWq0R3LSgQFXQ9bo5D9uCm4w'; // ignore: lines_longer_than_80_chars - final isVerified = - await ebsi.verifyCredential(issuerDid: issuerDid1, vcJwt: vcJwt); + final isVerified = await ebsi.verifyCredential( + issuerDid: issuerDid1, + vcJwt: vcJwt, + holderKid: holderKid, + ); expect(isVerified, VerificationType.notVerified); }); @@ -477,8 +486,11 @@ void main() { const vcJwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksiLCJqd2siOnsiY3J2IjoiUC0yNTZLIiwia3R5IjoiRUMiLCJ4IjoiSjR2UXRMVXlyVlVpRklYUnJ0RXE0eHVybUJacDJlcTl3Sm1Ya0lBX3N0SSIsInkiOiJFVVU2dlhvRzNCR1gyenp3alhyR0RjcjRFeUREMFZmazNfNWZnNWtTZ0tFIn0sImtpZCI6ImRpZDplYnNpOnpvOUZSMVlmQUtGUDNRNmR2cWh4Y1h4bmZlRGlKRFA5N2ttbnFoeUFVU0FDaiNDZ2NnMXk5eGo5dVdGdzU2UE1jMjlYQmQ5RVJlaXh6dm5mdEJ6OEp3UUZpQiJ9.eyJpc3MiOiJkaWQ6ZWJzaTp6bzlGUjFZZkFLRlAzUTZkdnFoeGNYeG5mZURpSkRQOTdrbW5xaHlBVVNBQ2oiLCJub25jZSI6IjdhMDdkZTBmLWE4NzktMTFlZC04MjJiLTBhMTYyODk1ODU2MCIsImlhdCI6MTY3NzA1MDc0MDEyMzIzNSwiYXVkIjoiaHR0cHM6Ly90YWxhby5jby9zYW5kYm94L2Vic2kvaXNzdWVyL3ZndmdoeWxvemwifQ.htjRCpFWbRwanAyQcAq9XZ4vxCXyFbzaaN3yPbPxWIcKFFzDDcA4QCHTUl-L4vzWq0R3LSgQFXQ9bo5D9uCm4w'; // ignore: lines_longer_than_80_chars - final isVerified = - await ebsi.verifyCredential(issuerDid: issuerDid2, vcJwt: vcJwt); + final isVerified = await ebsi.verifyCredential( + issuerDid: issuerDid2, + vcJwt: vcJwt, + holderKid: holderKid, + ); expect(isVerified, VerificationType.unKnown); }); }); From ba09d2ffac949eb4b56441e28632898019a3e0db Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Fri, 10 Mar 2023 16:54:03 +0530 Subject: [PATCH 139/190] extract the correct public key with teh kid #1437 --- .../detail/cubit/credential_details_cubit.dart | 4 ++++ .../detail/view/credentials_details_page.dart | 2 ++ lib/ebsi/verify_ebsi_credential.dart | 11 ++++++++++- packages/ebsi/lib/src/ebsi.dart | 10 ++++++++++ packages/ebsi/lib/src/token_parameters.dart | 2 +- 5 files changed, 27 insertions(+), 2 deletions(-) diff --git a/lib/dashboard/home/tab_bar/credentials/detail/cubit/credential_details_cubit.dart b/lib/dashboard/home/tab_bar/credentials/detail/cubit/credential_details_cubit.dart index 9455452f9..23e074402 100644 --- a/lib/dashboard/home/tab_bar/credentials/detail/cubit/credential_details_cubit.dart +++ b/lib/dashboard/home/tab_bar/credentials/detail/cubit/credential_details_cubit.dart @@ -8,6 +8,7 @@ import 'package:ebsi/ebsi.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:json_annotation/json_annotation.dart'; +import 'package:secure_storage/secure_storage.dart'; part 'credential_details_cubit.g.dart'; @@ -16,10 +17,12 @@ part 'credential_details_state.dart'; class CredentialDetailsCubit extends Cubit { CredentialDetailsCubit({ required this.didKitProvider, + required this.secureStorageProvider, required this.client, }) : super(const CredentialDetailsState()); final DIDKitProvider didKitProvider; + final SecureStorageProvider secureStorageProvider; final DioClient client; void changeTabStatus(CredentialDetailTabStatus credentialDetailTabStatus) { @@ -51,6 +54,7 @@ class CredentialDetailsCubit extends Cubit { final VerificationType isVerified = await isEbsiCredentialVerified( issuerDid, client, + secureStorageProvider, item.jwt!, ); 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 ccf27a150..1579036a1 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 @@ -10,6 +10,7 @@ import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:secure_storage/secure_storage.dart'; import 'package:share_plus/share_plus.dart'; class CredentialsDetailsPage extends StatelessWidget { @@ -40,6 +41,7 @@ class CredentialsDetailsPage extends StatelessWidget { return BlocProvider( create: (context) => CredentialDetailsCubit( didKitProvider: DIDKitProvider(), + secureStorageProvider: context.read(), client: DioClient('', Dio()), ), child: CredentialsDetailsView( diff --git a/lib/ebsi/verify_ebsi_credential.dart b/lib/ebsi/verify_ebsi_credential.dart index be6b28b64..c1df70892 100644 --- a/lib/ebsi/verify_ebsi_credential.dart +++ b/lib/ebsi/verify_ebsi_credential.dart @@ -1,16 +1,25 @@ -import 'package:altme/app/shared/dio_client/dio_client.dart'; +import 'package:altme/app/app.dart'; import 'package:dio/dio.dart'; import 'package:ebsi/ebsi.dart'; +import 'package:secure_storage/secure_storage.dart'; Future isEbsiCredentialVerified( String issuerDid, DioClient client, + SecureStorageProvider secureStorageProvider, String vcJwt, ) async { final Ebsi ebsi = Ebsi(Dio()); + + final String p256PrivateKey = + await getRandomP256PrivateKey(secureStorageProvider); + + final holderKid = await ebsi.getKid(null, p256PrivateKey); + final VerificationType verificationType = await ebsi.verifyCredential( issuerDid: issuerDid, vcJwt: vcJwt, + holderKid: holderKid, ); return verificationType; } diff --git a/packages/ebsi/lib/src/ebsi.dart b/packages/ebsi/lib/src/ebsi.dart index df355d86f..0b6ca7ad3 100644 --- a/packages/ebsi/lib/src/ebsi.dart +++ b/packages/ebsi/lib/src/ebsi.dart @@ -585,4 +585,14 @@ class Ebsi { final tokenParameters = TokenParameters(private); return tokenParameters.didKey; } + + Future getKid( + String? mnemonic, + String? privateKey, + ) async { + final private = await getPrivateKey(mnemonic, privateKey); + + final tokenParameters = TokenParameters(private); + return tokenParameters.kid; + } } diff --git a/packages/ebsi/lib/src/token_parameters.dart b/packages/ebsi/lib/src/token_parameters.dart index c19587512..fe66ed895 100644 --- a/packages/ebsi/lib/src/token_parameters.dart +++ b/packages/ebsi/lib/src/token_parameters.dart @@ -6,7 +6,7 @@ import 'package:fast_base58/fast_base58.dart'; /// Most of the parameters used to get or present EBSI credentials /// are computed from private key of the user wallet. -/// [TokenParameters] regroup those computed parameters +/// [TokenParameters] regroup those computed parameters` class TokenParameters { /// TokenParameters(this.privateKey); From cb64dea41e3c0066c63df75f910b4d636649a37b Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Fri, 10 Mar 2023 19:31:56 +0530 Subject: [PATCH 140/190] show do you trust dialog for siopv2 #1437 --- .../cubit/qr_code_scan_cubit.dart | 92 ++++++++++--------- lib/splash/bloclisteners/blocklisteners.dart | 9 ++ packages/ebsi/lib/src/ebsi.dart | 46 ++++++---- 3 files changed, 88 insertions(+), 59 deletions(-) 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 73fca70d2..102fabbb6 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 @@ -197,47 +197,7 @@ class QRCodeScanCubit extends Cubit { ), ); } else { - var claims = uri.queryParameters['claims'] ?? ''; - - // TODO(hawkbee): change when correction is done on verifier - claims = claims.replaceAll("'email': None", "'email': 'None'"); - - claims = claims.replaceAll("'", '"'); - final jsonPath = JsonPath(r'$..input_descriptors'); - final outputDescriptors = - jsonPath.readValues(jsonDecode(claims)).first as List; - final inputDescriptorList = outputDescriptors - .map((e) => InputDescriptor.fromJson(e as Map)) - .toList(); - - final PresentationDefinition presentationDefinition = - PresentationDefinition(inputDescriptorList); - final CredentialModel credentialPreview = CredentialModel( - id: 'id', - image: 'image', - credentialPreview: Credential.dummy(), - shareLink: 'shareLink', - display: Display.emptyDisplay(), - data: const {}, - credentialManifest: CredentialManifest( - 'id', - IssuedBy('', ''), - null, - presentationDefinition, - ), - ); - emit( - state.copyWith( - qrScanStatus: QrScanStatus.success, - route: CredentialManifestOfferPickPage.route( - uri: uri, - credential: credentialPreview, - issuer: Issuer.emptyIssuer('domain'), - inputDescriptorIndex: 0, - credentialsToBePresented: [], - ), - ), - ); + emit(state.acceptHost(uri: uri)); } // final openIdCredential = getCredentialName(sIOPV2Param.claims!); @@ -327,6 +287,8 @@ class QRCodeScanCubit extends Cubit { late final dynamic data; try { + /// ebsi credential + /// oidc4vci if (state.uri.toString().startsWith('openid://initiate_issuance?')) { await initiateEbsiCredentialIssuance( state.uri.toString(), @@ -339,6 +301,54 @@ class QRCodeScanCubit extends Cubit { return; } + /// ebsi presentation + /// siopv2 + if (state.uri?.queryParameters['scope'] == 'openid') { + var claims = uri.queryParameters['claims'] ?? ''; + + // TODO(hawkbee): change when correction is done on verifier + claims = claims.replaceAll("'email': None", "'email': 'None'"); + + claims = claims.replaceAll("'", '"'); + final jsonPath = JsonPath(r'$..input_descriptors'); + final outputDescriptors = + jsonPath.readValues(jsonDecode(claims)).first as List; + final inputDescriptorList = outputDescriptors + .map((e) => InputDescriptor.fromJson(e as Map)) + .toList(); + + final PresentationDefinition presentationDefinition = + PresentationDefinition(inputDescriptorList); + final CredentialModel credentialPreview = CredentialModel( + id: 'id', + image: 'image', + credentialPreview: Credential.dummy(), + shareLink: 'shareLink', + display: Display.emptyDisplay(), + data: const {}, + credentialManifest: CredentialManifest( + 'id', + IssuedBy('', ''), + null, + presentationDefinition, + ), + ); + emit( + state.copyWith( + qrScanStatus: QrScanStatus.success, + route: CredentialManifestOfferPickPage.route( + uri: uri, + credential: credentialPreview, + issuer: Issuer.emptyIssuer('domain'), + inputDescriptorIndex: 0, + credentialsToBePresented: [], + ), + ), + ); + return; + } + + /// did credential addition and presentation final dynamic response = await client.get(uri.toString()); data = response is String ? jsonDecode(response) : response; diff --git a/lib/splash/bloclisteners/blocklisteners.dart b/lib/splash/bloclisteners/blocklisteners.dart index f954cefa7..ae5b094b3 100644 --- a/lib/splash/bloclisteners/blocklisteners.dart +++ b/lib/splash/bloclisteners/blocklisteners.dart @@ -174,6 +174,11 @@ final qrCodeBlocListener = BlocListener( isIssuerVerificationSettingTrue = true; } + if (state.uri?.queryParameters['scope'] == 'openid') { + isIssuerVerificationSettingTrue = + state.uri!.queryParameters['request_uri'] != null; + } + log.i('checking issuer - $isIssuerVerificationSettingTrue'); if (isIssuerVerificationSettingTrue) { @@ -210,6 +215,10 @@ final qrCodeBlocListener = BlocListener( subtitle = state.uri!.queryParameters['issuer'].toString(); } + if (state.uri?.queryParameters['scope'] == 'openid') { + subtitle = state.uri!.queryParameters['request_uri'].toString(); + } + acceptHost = await showDialog( context: context, builder: (BuildContext context) { diff --git a/packages/ebsi/lib/src/ebsi.dart b/packages/ebsi/lib/src/ebsi.dart index 0b6ca7ad3..649aa8538 100644 --- a/packages/ebsi/lib/src/ebsi.dart +++ b/packages/ebsi/lib/src/ebsi.dart @@ -452,30 +452,40 @@ class Ebsi { String? mnemonic, String? privateKey, ) async { - final private = await getPrivateKey(mnemonic, privateKey); + //TODO(bibash): if the "request_uri" attribute exists, + //wallet must do a GET to endpoint to get the request value as a + //json. The wallet receives a JWT which must be verified wit the public key + // of the verifier. It means that wallety must call the API to get teh DID + //document from EBSI and extract the correct public key with teh kid. - final tokenParameters = VerifierTokenParameters( - private, - uri, - credentialsToBePresented, - ); + try { + // final dynamic response = + // await client.get(uri.queryParameters['request_uri']!); - // structures - final verifierIdToken = await getIdToken(tokenParameters); + final private = await getPrivateKey(mnemonic, privateKey); - /// build vp token + final tokenParameters = VerifierTokenParameters( + private, + uri, + credentialsToBePresented, + ); - final vpToken = await getVpToken(tokenParameters); + // structures + final verifierIdToken = await getIdToken(tokenParameters); - final responseHeaders = { - 'Content-Type': 'application/x-www-form-urlencoded', - }; + /// build vp token + + final vpToken = await getVpToken(tokenParameters); + + final responseHeaders = { + 'Content-Type': 'application/x-www-form-urlencoded', + }; + + final responseData = { + 'id_token': verifierIdToken, + 'vp_token': vpToken + }; - final responseData = { - 'id_token': verifierIdToken, - 'vp_token': vpToken - }; - try { await client.post( uri.queryParameters['redirect_uri']!, options: Options(headers: responseHeaders), From 57b4daef47ca5dd55ac44b33d7e2c187ade64ec1 Mon Sep 17 00:00:00 2001 From: hawkbee1 Date: Fri, 10 Mar 2023 17:17:28 +0100 Subject: [PATCH 141/190] realease candidate 1.11.2+160 whith whatsnew, ebsi key from mnemonic and bloometa hidden --- lib/app/shared/constants/constant_list.dart | 2 +- .../dashboard/widgets/what_is_new_dialog.dart | 15 +++++++++++++-- .../view/did_ebsi/manage_did_ebsi_page.dart | 9 ++++----- lib/ebsi/initiate_ebsi_credential_issuance.dart | 6 ++++-- lib/scan/cubit/scan_cubit.dart | 11 ++++++----- lib/splash/view/splash_page.dart | 12 ++++++------ packages/ebsi/lib/src/ebsi.dart | 6 +++++- packages/ebsi/test/src/ebsi_test.dart | 1 - pubspec.yaml | 2 +- 9 files changed, 40 insertions(+), 24 deletions(-) diff --git a/lib/app/shared/constants/constant_list.dart b/lib/app/shared/constants/constant_list.dart index 81fb78c07..e11d0fd01 100644 --- a/lib/app/shared/constants/constant_list.dart +++ b/lib/app/shared/constants/constant_list.dart @@ -4,7 +4,7 @@ class DiscoverList { static final List gamingCategories = [ CredentialSubjectType.tezotopiaMembership, CredentialSubjectType.chainbornMembership, - CredentialSubjectType.bloometaPass, + // CredentialSubjectType.bloometaPass, CredentialSubjectType.troopezPass, CredentialSubjectType.pigsPass, CredentialSubjectType.matterlightPass, diff --git a/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart b/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart index 6ee8148cf..320441031 100644 --- a/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart +++ b/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart @@ -76,6 +76,16 @@ class WhatIsNewDialog extends StatelessWidget { NewContent( version: versionNumber, features: const [ + 'Improve onboarding experience', + 'Add confirmation of recovery phrase', + 'Improve popup design', + 'Update SIOPV2 flow', + 'Add deeplink for EBSI credentials', + ], + ), + const NewContent( + version: '1.10.5', + features: [ 'End to end encryption of decentralized chat in Altme', 'Specific design for EBSI diploma card', ], @@ -215,10 +225,11 @@ class WhatIsNewDialog extends StatelessWidget { ) ], ), - const Align( + const Align( alignment: Alignment.topRight, child: WhiteCloseButton(), - ),], + ), + ], ), ), ), diff --git a/lib/dashboard/drawer/manage_did/view/did_ebsi/manage_did_ebsi_page.dart b/lib/dashboard/drawer/manage_did/view/did_ebsi/manage_did_ebsi_page.dart index 31427ff5a..0d0fc9a87 100644 --- a/lib/dashboard/drawer/manage_did/view/did_ebsi/manage_did_ebsi_page.dart +++ b/lib/dashboard/drawer/manage_did/view/did_ebsi/manage_did_ebsi_page.dart @@ -26,11 +26,10 @@ class _ManageDidEbsiPageState extends State { void initState() { Future.delayed(Duration.zero, () async { final ebsi = Ebsi(Dio()); - //final mnemonic = - // await getSecureStorage.get(SecureStorageKeys.ssiMnemonic); - final String p256PrivateKey = - await getRandomP256PrivateKey(getSecureStorage); - did = await ebsi.getDidFromMnemonic(null, p256PrivateKey); + final mnemonic = + await getSecureStorage.get(SecureStorageKeys.ssiMnemonic); + final privateKey = await ebsi.privateKeyFromMnemonic(mnemonic: mnemonic!); + did = await ebsi.getDidFromMnemonic(null, privateKey); setState(() {}); }); super.initState(); diff --git a/lib/ebsi/initiate_ebsi_credential_issuance.dart b/lib/ebsi/initiate_ebsi_credential_issuance.dart index eec0c5bc3..50aaca8b9 100644 --- a/lib/ebsi/initiate_ebsi_credential_issuance.dart +++ b/lib/ebsi/initiate_ebsi_credential_issuance.dart @@ -1,4 +1,5 @@ import 'package:altme/app/shared/constants/parameters.dart'; +import 'package:altme/app/shared/constants/secure_storage_keys.dart'; import 'package:altme/app/shared/dio_client/dio_client.dart'; import 'package:altme/app/shared/helper_functions/helper_functions.dart'; import 'package:altme/app/shared/launch_url/launch_url.dart'; @@ -17,7 +18,8 @@ Future initiateEbsiCredentialIssuance( final Ebsi ebsi = Ebsi(Dio()); final Uri uriFromScannedResponse = Uri.parse(scannedResponse); if (uriFromScannedResponse.queryParameters['pre-authorized_code'] != null) { - final String p256PrivateKey = await getRandomP256PrivateKey(secureStorage); + final mnemonic = await getSecureStorage.get(SecureStorageKeys.ssiMnemonic); + final privateKey = await ebsi.privateKeyFromMnemonic(mnemonic: mnemonic!); // final mnemonic = await secureStorage.get( // SecureStorageKeys.ssiMnemonic, @@ -26,7 +28,7 @@ Future initiateEbsiCredentialIssuance( final dynamic encodedCredentialFromEbsi = await ebsi.getCredential( uriFromScannedResponse, null, - p256PrivateKey, + privateKey, ); await addEbsiCredential( diff --git a/lib/scan/cubit/scan_cubit.dart b/lib/scan/cubit/scan_cubit.dart index 105fd69e8..8d4b090bf 100644 --- a/lib/scan/cubit/scan_cubit.dart +++ b/lib/scan/cubit/scan_cubit.dart @@ -57,16 +57,17 @@ class ScanCubit extends Cubit { try { if (uri.queryParameters['scope'] == 'openid') { - final String p256PrivateKey = - await getRandomP256PrivateKey(secureStorageProvider); - // final mnemonic = - // await secureStorageProvider.get(SecureStorageKeys.ssiMnemonic); + final ebsi = Ebsi(Dio()); + final mnemonic = + await getSecureStorage.get(SecureStorageKeys.ssiMnemonic); + final privateKey = + await ebsi.privateKeyFromMnemonic(mnemonic: mnemonic!); final credentialList = credentialsToBePresented! .map((e) => jsonEncode(e.toJson())) .toList(); - await ebsi.sendPresentation(uri, credentialList, null, p256PrivateKey); + await ebsi.sendPresentation(uri, credentialList, null, privateKey); await presentationActivity( credentialModels: credentialsToBePresented, diff --git a/lib/splash/view/splash_page.dart b/lib/splash/view/splash_page.dart index b2b78524f..d1ba40464 100644 --- a/lib/splash/view/splash_page.dart +++ b/lib/splash/view/splash_page.dart @@ -71,20 +71,20 @@ class _SplashViewState extends State { if (url == Parameters.ebsiUniversalLink) { final client = Dio(); final ebsi = Ebsi(client); - // final mnemonic = await secure_storage.getSecureStorage.get( - // SecureStorageKeys.ssiMnemonic, - // ); + final mnemonic = + await getSecureStorage.get(SecureStorageKeys.ssiMnemonic); + final privateKey = + await ebsi.privateKeyFromMnemonic(mnemonic: mnemonic!); var credentialUri = uri; if (uri.queryParameters['uri'] != null) { final credentialUrl = uri.queryParameters['uri']; credentialUri = Uri.parse(credentialUrl!); } - final String p256PrivateKey = - await getRandomP256PrivateKey(getSecureStorage); + final dynamic encodedCredentialFromEbsi = await ebsi.getCredential( credentialUri, null, - p256PrivateKey, + privateKey, ); await addEbsiCredential( encodedCredentialFromEbsi, diff --git a/packages/ebsi/lib/src/ebsi.dart b/packages/ebsi/lib/src/ebsi.dart index 280bbf2cb..450298aca 100644 --- a/packages/ebsi/lib/src/ebsi.dart +++ b/packages/ebsi/lib/src/ebsi.dart @@ -67,7 +67,7 @@ class Ebsi { /// we were using P-256K for dart library conformance which is /// the same as secp256k1, but we are using secp256k1 now final jwk = { - 'crv': 'secp256k1', + 'crv': 'P-256K', 'd': d, 'kty': 'EC', 'x': x, @@ -517,6 +517,10 @@ class Ebsi { final vpVerifierClaims = JsonWebTokenClaims.fromJson(vpTokenPayload); // create a builder, decoding the JWT in a JWS, so using a // JsonWebSignatureBuilder + final privateKey = tokenParameters.privateKey; + if (tokenParameters.privateKey['crv'] == 'secp256k1') { + privateKey['crv'] = 'P-256K'; + } final key = JsonWebKey.fromJson(tokenParameters.privateKey); diff --git a/packages/ebsi/test/src/ebsi_test.dart b/packages/ebsi/test/src/ebsi_test.dart index 05bcacaba..bd0215e79 100644 --- a/packages/ebsi/test/src/ebsi_test.dart +++ b/packages/ebsi/test/src/ebsi_test.dart @@ -404,7 +404,6 @@ void main() { test('get publicKey did with didDocumentResponse', () { const didDocumentResponse = '{"assertionMethod":["did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53"],"authentication":["did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53"],"@context":"https://www.w3.org/ns/did/v1","id":"did:ebsi:zeFCExU2XAAshYkPCpjuahA","verificationMethod":[{"controller":"did:ebsi:zeFCExU2XAAshYkPCpjuahA","id":"did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53","publicKeyJwk":{"alg":"EdDSA","crv":"Ed25519","kid":"3623b877bbb24b08ba390f3585418f53","kty":"OKP","use":"sig","x":"pWgA8M3etXlLaqcRmgjEQkz7waseg3FKzMCzfm9Yeow"},"type":"Ed25519VerificationKey2019"}]}'; - const issuerDid = 'did:ebsi:zeFCExU2XAAshYkPCpjuahA'; const expectedPublicKey = diff --git a/pubspec.yaml b/pubspec.yaml index 725fc4b9a..3c64f69a6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: altme description: AltMe Flutter App -version: 1.11.1+159 +version: 1.11.2+160 publish_to: none environment: From ee9663136a63a68dcc13662f62932f5716a4fee8 Mon Sep 17 00:00:00 2001 From: hawkbee1 Date: Fri, 10 Mar 2023 18:36:22 +0100 Subject: [PATCH 142/190] ensure same functionalities for EBSI from QRCode or Deeplink --- .../cubit/qr_code_scan_cubit.dart | 107 +++++++++-------- lib/splash/view/splash_page.dart | 112 +++++++++--------- 2 files changed, 114 insertions(+), 105 deletions(-) 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 73fca70d2..e21c1d9de 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 @@ -189,56 +189,7 @@ class QRCodeScanCubit extends Cubit { // Check if we can respond to presentation request: // having credentials? // having correct crv in ebsi key - if (!await isSiopV2RequestValid(uri!)) { - emit( - state.copyWith( - qrScanStatus: QrScanStatus.success, - route: IssuerWebsitesPage.route(''), - ), - ); - } else { - var claims = uri.queryParameters['claims'] ?? ''; - - // TODO(hawkbee): change when correction is done on verifier - claims = claims.replaceAll("'email': None", "'email': 'None'"); - - claims = claims.replaceAll("'", '"'); - final jsonPath = JsonPath(r'$..input_descriptors'); - final outputDescriptors = - jsonPath.readValues(jsonDecode(claims)).first as List; - final inputDescriptorList = outputDescriptors - .map((e) => InputDescriptor.fromJson(e as Map)) - .toList(); - - final PresentationDefinition presentationDefinition = - PresentationDefinition(inputDescriptorList); - final CredentialModel credentialPreview = CredentialModel( - id: 'id', - image: 'image', - credentialPreview: Credential.dummy(), - shareLink: 'shareLink', - display: Display.emptyDisplay(), - data: const {}, - credentialManifest: CredentialManifest( - 'id', - IssuedBy('', ''), - null, - presentationDefinition, - ), - ); - emit( - state.copyWith( - qrScanStatus: QrScanStatus.success, - route: CredentialManifestOfferPickPage.route( - uri: uri, - credential: credentialPreview, - issuer: Issuer.emptyIssuer('domain'), - inputDescriptorIndex: 0, - credentialsToBePresented: [], - ), - ), - ); - } + await launchSiopV2Flow(uri); // final openIdCredential = getCredentialName(sIOPV2Param.claims!); // final openIdIssuer = getIssuersName(sIOPV2Param.claims!); @@ -316,6 +267,62 @@ class QRCodeScanCubit extends Cubit { } } + Future launchSiopV2Flow(Uri? uri) async { + // Check if we can respond to presentation request: + // having credentials? + // having correct crv in ebsi key + if (!await isSiopV2RequestValid(uri!)) { + emit( + state.copyWith( + qrScanStatus: QrScanStatus.success, + route: IssuerWebsitesPage.route(''), + ), + ); + } else { + var claims = uri.queryParameters['claims'] ?? ''; + + // TODO(hawkbee): change when correction is done on verifier + claims = claims.replaceAll("'email': None", "'email': 'None'"); + + claims = claims.replaceAll("'", '"'); + final jsonPath = JsonPath(r'$..input_descriptors'); + final outputDescriptors = + jsonPath.readValues(jsonDecode(claims)).first as List; + final inputDescriptorList = outputDescriptors + .map((e) => InputDescriptor.fromJson(e as Map)) + .toList(); + + final PresentationDefinition presentationDefinition = + PresentationDefinition(inputDescriptorList); + final CredentialModel credentialPreview = CredentialModel( + id: 'id', + image: 'image', + credentialPreview: Credential.dummy(), + shareLink: 'shareLink', + display: Display.emptyDisplay(), + data: const {}, + credentialManifest: CredentialManifest( + 'id', + IssuedBy('', ''), + null, + presentationDefinition, + ), + ); + emit( + state.copyWith( + qrScanStatus: QrScanStatus.success, + route: CredentialManifestOfferPickPage.route( + uri: uri, + credential: credentialPreview, + issuer: Issuer.emptyIssuer('domain'), + inputDescriptorIndex: 0, + credentialsToBePresented: [], + ), + ), + ); + } + } + Future accept({ required Uri uri, required Issuer issuer, diff --git a/lib/splash/view/splash_page.dart b/lib/splash/view/splash_page.dart index d1ba40464..6b6ed923d 100644 --- a/lib/splash/view/splash_page.dart +++ b/lib/splash/view/splash_page.dart @@ -67,66 +67,68 @@ class _SplashViewState extends State { if (!mounted) return; log.i('got uri: $uri'); final url = '${uri!.scheme}://${uri.authority}${uri.path}'; + // if (url == Parameters.ebsiUniversalLink) { + // final client = Dio(); + // final ebsi = Ebsi(client); + // final mnemonic = + // await getSecureStorage.get(SecureStorageKeys.ssiMnemonic); + // final privateKey = + // await ebsi.privateKeyFromMnemonic(mnemonic: mnemonic!); + // var credentialUri = uri; + // if (uri.queryParameters['uri'] != null) { + // final credentialUrl = uri.queryParameters['uri']; + // credentialUri = Uri.parse(credentialUrl!); + // if (credentialUri.queryParameters['scope'] == 'openid') { + // print('we should launch SIOPV2'); + // } + // } - if (url == Parameters.ebsiUniversalLink) { - final client = Dio(); - final ebsi = Ebsi(client); - final mnemonic = - await getSecureStorage.get(SecureStorageKeys.ssiMnemonic); - final privateKey = - await ebsi.privateKeyFromMnemonic(mnemonic: mnemonic!); - var credentialUri = uri; - if (uri.queryParameters['uri'] != null) { - final credentialUrl = uri.queryParameters['uri']; - credentialUri = Uri.parse(credentialUrl!); - } - - final dynamic encodedCredentialFromEbsi = await ebsi.getCredential( - credentialUri, - null, - privateKey, - ); - await addEbsiCredential( - encodedCredentialFromEbsi, - credentialUri, - context.read(), - ); - } else { - String beaconData = ''; - bool isBeaconRequest = false; - uri.queryParameters.forEach((key, value) async { - if (key == 'uri') { - final url = value.replaceAll(RegExp(r'ß^\"|\"$'), ''); - context.read().addDeepLink(url); - final ssiKey = await secure_storage.getSecureStorage - .get(SecureStorageKeys.ssiKey); - if (ssiKey != null) { - await context.read().deepLink(); - } - } - if (key == 'type' && value == 'tzip10') { - isBeaconRequest = true; - } - if (key == 'data') { - beaconData = value; - } - if (uri.scheme == 'openid' && - uri.authority == 'initiate_issuance') { - final DioClient client = DioClient('', Dio()); - await initiateEbsiCredentialIssuance( - uri.toString(), - client, - context.read(), - secure_storage.getSecureStorage, - ); + // final dynamic encodedCredentialFromEbsi = await ebsi.getCredential( + // credentialUri, + // null, + // privateKey, + // ); + // await addEbsiCredential( + // encodedCredentialFromEbsi, + // credentialUri, + // context.read(), + // ); + // } else { + String beaconData = ''; + bool isBeaconRequest = false; + uri.queryParameters.forEach((key, value) async { + if (key == 'uri') { + final url = value.replaceAll(RegExp(r'ß^\"|\"$'), ''); + context.read().addDeepLink(url); + final ssiKey = await secure_storage.getSecureStorage + .get(SecureStorageKeys.ssiKey); + if (ssiKey != null) { + await context.read().deepLink(); } - }); - if (isBeaconRequest && beaconData != '') { - unawaited( - context.read().peerFromDeepLink(beaconData), + } + if (key == 'type' && value == 'tzip10') { + isBeaconRequest = true; + } + if (key == 'data') { + beaconData = value; + } + if (uri.scheme == 'openid' && + uri.authority == 'initiate_issuance') { + final DioClient client = DioClient('', Dio()); + await initiateEbsiCredentialIssuance( + uri.toString(), + client, + context.read(), + secure_storage.getSecureStorage, ); } + }); + if (isBeaconRequest && beaconData != '') { + unawaited( + context.read().peerFromDeepLink(beaconData), + ); } + // } }, onError: (Object err) { if (!mounted) return; From 0c659c11253b1fd12454a39f38e713cda175864c Mon Sep 17 00:00:00 2001 From: hawkbee1 Date: Mon, 13 Mar 2023 07:51:54 +0100 Subject: [PATCH 143/190] EBSI: secp256k1 key when using mnemonic --- packages/ebsi/lib/src/ebsi.dart | 2 +- packages/ebsi/test/src/ebsi_test.dart | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/ebsi/lib/src/ebsi.dart b/packages/ebsi/lib/src/ebsi.dart index 450298aca..3ea6ad792 100644 --- a/packages/ebsi/lib/src/ebsi.dart +++ b/packages/ebsi/lib/src/ebsi.dart @@ -67,7 +67,7 @@ class Ebsi { /// we were using P-256K for dart library conformance which is /// the same as secp256k1, but we are using secp256k1 now final jwk = { - 'crv': 'P-256K', + 'crv': 'secp256k1', 'd': d, 'kty': 'EC', 'x': x, diff --git a/packages/ebsi/test/src/ebsi_test.dart b/packages/ebsi/test/src/ebsi_test.dart index bd0215e79..75b111943 100644 --- a/packages/ebsi/test/src/ebsi_test.dart +++ b/packages/ebsi/test/src/ebsi_test.dart @@ -81,7 +81,7 @@ void main() { ]; const expectedJwk = { - 'crv': 'P-256K', + 'crv': 'secp256k1', 'd': 'ccWWNSjGiv1iWlNh4kfhWvwG3yyQMe8o31Du0uKRzrs', 'kty': 'EC', 'x': 'J4vQtLUyrVUiFIXRrtEq4xurmBZp2eq9wJmXkIA_stI', @@ -100,7 +100,7 @@ void main() { test('privateKey from mnemonic', () async { const key = { - 'crv': 'P-256K', + 'crv': 'secp256k1', 'd': 'ccWWNSjGiv1iWlNh4kfhWvwG3yyQMe8o31Du0uKRzrs', 'kty': 'EC', 'x': 'J4vQtLUyrVUiFIXRrtEq4xurmBZp2eq9wJmXkIA_stI', From e8b48b7c03bf5681725f3d5a29fced4c7cc24fb0 Mon Sep 17 00:00:00 2001 From: hawkbee1 Date: Mon, 13 Mar 2023 07:52:26 +0100 Subject: [PATCH 144/190] version: 1.11.3+161 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 3c64f69a6..264000e24 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: altme description: AltMe Flutter App -version: 1.11.2+160 +version: 1.11.3+161 publish_to: none environment: From a2b0f14f288a419c8b50ddb66902f41a08607fb3 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Mon, 13 Mar 2023 13:54:02 +0530 Subject: [PATCH 145/190] comments for siopv2 and oidc4VCI --- .../qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart | 10 +++++----- lib/splash/bloclisteners/blocklisteners.dart | 4 ++++ 2 files changed, 9 insertions(+), 5 deletions(-) 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 102fabbb6..4cf642d7f 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 @@ -184,12 +184,12 @@ class QRCodeScanCubit extends Cubit { }) async { emit(state.loading(isScan: isScan)); try { - ///Check if SIOPV2 request + /// verifier side (siopv2) without request_uri if (uri?.queryParameters['scope'] == 'openid') { // Check if we can respond to presentation request: // having credentials? // having correct crv in ebsi key - if (!await isSiopV2RequestValid(uri!)) { + if (!await isSiopV2WithRequestURIValid(uri!)) { emit( state.copyWith( qrScanStatus: QrScanStatus.success, @@ -288,7 +288,7 @@ class QRCodeScanCubit extends Cubit { try { /// ebsi credential - /// oidc4vci + /// issuer side (oidc4VCI) if (state.uri.toString().startsWith('openid://initiate_issuance?')) { await initiateEbsiCredentialIssuance( state.uri.toString(), @@ -302,7 +302,7 @@ class QRCodeScanCubit extends Cubit { } /// ebsi presentation - /// siopv2 + /// verifier side (siopv2) without request_uri if (state.uri?.queryParameters['scope'] == 'openid') { var claims = uri.queryParameters['claims'] ?? ''; @@ -601,7 +601,7 @@ class QRCodeScanCubit extends Cubit { return data; } - Future isSiopV2RequestValid(Uri uri) async { + Future isSiopV2WithRequestURIValid(Uri uri) async { bool isValid = true; ///credential should not be empty since we have to present diff --git a/lib/splash/bloclisteners/blocklisteners.dart b/lib/splash/bloclisteners/blocklisteners.dart index ae5b094b3..4ccea1db0 100644 --- a/lib/splash/bloclisteners/blocklisteners.dart +++ b/lib/splash/bloclisteners/blocklisteners.dart @@ -170,10 +170,12 @@ final qrCodeBlocListener = BlocListener( isIssuerVerificationSettingTrue = profileCubit.state.model.issuerVerificationUrl != ''; + /// issuer side (oidc4VCI) if (state.uri!.toString().startsWith('openid://initiate_issuance?')) { isIssuerVerificationSettingTrue = true; } + /// verifier side (siopv2) without request_uri if (state.uri?.queryParameters['scope'] == 'openid') { isIssuerVerificationSettingTrue = state.uri!.queryParameters['request_uri'] != null; @@ -211,10 +213,12 @@ final qrCodeBlocListener = BlocListener( ? state.uri!.host : '''${approvedIssuer.organizationInfo.legalName}\n${approvedIssuer.organizationInfo.currentAddress}'''; + /// issuer side (oidc4VCI) if (state.uri!.toString().startsWith('openid://initiate_issuance?')) { subtitle = state.uri!.queryParameters['issuer'].toString(); } + /// verifier side (siopv2) without request_uri if (state.uri?.queryParameters['scope'] == 'openid') { subtitle = state.uri!.queryParameters['request_uri'].toString(); } From e671e60d45180182b5c2e34738fca5351e559de2 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Mon, 13 Mar 2023 13:57:17 +0530 Subject: [PATCH 146/190] comments for siopv2 and oidc4VCI --- lib/app/shared/constants/urls.dart | 3 +-- .../credential_subject_type_extension.dart | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/app/shared/constants/urls.dart b/lib/app/shared/constants/urls.dart index 701ce6839..b4a5b6387 100644 --- a/lib/app/shared/constants/urls.dart +++ b/lib/app/shared/constants/urls.dart @@ -29,8 +29,7 @@ class Urls { static const String tezotopiaMembershipCardUrl = 'https://issuer.talao.co/tezotopia/membershipcard/'; - static const String bloometaCardUrl = - 'https://issuer.talao.co/bloometa/membershipcard/'; + static const String bloometaCardUrl = ' https://issuer.talao.co/bloometa'; static const String chainbornMembershipCardUrl = 'https://issuer.talao.co/chainborn/membershipcard/'; 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 35dae3fd3..235823689 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 @@ -399,7 +399,6 @@ extension CredentialSubjectTypeExtension on CredentialSubjectType { bool get byPassDeepLink { if (this == CredentialSubjectType.tezotopiaMembership || - this == CredentialSubjectType.bloometaPass || this == CredentialSubjectType.chainbornMembership || this == CredentialSubjectType.twitterCard || this == CredentialSubjectType.over13 || From 88142e62357991a7e62275ca271b2876094967c3 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Mon, 13 Mar 2023 13:58:42 +0530 Subject: [PATCH 147/190] Revert "comments for siopv2 and oidc4VCI" This reverts commit e671e60d45180182b5c2e34738fca5351e559de2. --- lib/app/shared/constants/urls.dart | 3 ++- .../credential_subject_type_extension.dart | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/app/shared/constants/urls.dart b/lib/app/shared/constants/urls.dart index b4a5b6387..701ce6839 100644 --- a/lib/app/shared/constants/urls.dart +++ b/lib/app/shared/constants/urls.dart @@ -29,7 +29,8 @@ class Urls { static const String tezotopiaMembershipCardUrl = 'https://issuer.talao.co/tezotopia/membershipcard/'; - static const String bloometaCardUrl = ' https://issuer.talao.co/bloometa'; + static const String bloometaCardUrl = + 'https://issuer.talao.co/bloometa/membershipcard/'; static const String chainbornMembershipCardUrl = 'https://issuer.talao.co/chainborn/membershipcard/'; 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 235823689..35dae3fd3 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 @@ -399,6 +399,7 @@ extension CredentialSubjectTypeExtension on CredentialSubjectType { bool get byPassDeepLink { if (this == CredentialSubjectType.tezotopiaMembership || + this == CredentialSubjectType.bloometaPass || this == CredentialSubjectType.chainbornMembership || this == CredentialSubjectType.twitterCard || this == CredentialSubjectType.over13 || From 37a93ac0f34264df6f808d389c7d4e40baf62edf Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Mon, 13 Mar 2023 13:59:33 +0530 Subject: [PATCH 148/190] Revert "comments for siopv2 and oidc4VCI" This reverts commit a2b0f14f288a419c8b50ddb66902f41a08607fb3. --- .../qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart | 10 +++++----- lib/splash/bloclisteners/blocklisteners.dart | 4 ---- 2 files changed, 5 insertions(+), 9 deletions(-) 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 4cf642d7f..102fabbb6 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 @@ -184,12 +184,12 @@ class QRCodeScanCubit extends Cubit { }) async { emit(state.loading(isScan: isScan)); try { - /// verifier side (siopv2) without request_uri + ///Check if SIOPV2 request if (uri?.queryParameters['scope'] == 'openid') { // Check if we can respond to presentation request: // having credentials? // having correct crv in ebsi key - if (!await isSiopV2WithRequestURIValid(uri!)) { + if (!await isSiopV2RequestValid(uri!)) { emit( state.copyWith( qrScanStatus: QrScanStatus.success, @@ -288,7 +288,7 @@ class QRCodeScanCubit extends Cubit { try { /// ebsi credential - /// issuer side (oidc4VCI) + /// oidc4vci if (state.uri.toString().startsWith('openid://initiate_issuance?')) { await initiateEbsiCredentialIssuance( state.uri.toString(), @@ -302,7 +302,7 @@ class QRCodeScanCubit extends Cubit { } /// ebsi presentation - /// verifier side (siopv2) without request_uri + /// siopv2 if (state.uri?.queryParameters['scope'] == 'openid') { var claims = uri.queryParameters['claims'] ?? ''; @@ -601,7 +601,7 @@ class QRCodeScanCubit extends Cubit { return data; } - Future isSiopV2WithRequestURIValid(Uri uri) async { + Future isSiopV2RequestValid(Uri uri) async { bool isValid = true; ///credential should not be empty since we have to present diff --git a/lib/splash/bloclisteners/blocklisteners.dart b/lib/splash/bloclisteners/blocklisteners.dart index 4ccea1db0..ae5b094b3 100644 --- a/lib/splash/bloclisteners/blocklisteners.dart +++ b/lib/splash/bloclisteners/blocklisteners.dart @@ -170,12 +170,10 @@ final qrCodeBlocListener = BlocListener( isIssuerVerificationSettingTrue = profileCubit.state.model.issuerVerificationUrl != ''; - /// issuer side (oidc4VCI) if (state.uri!.toString().startsWith('openid://initiate_issuance?')) { isIssuerVerificationSettingTrue = true; } - /// verifier side (siopv2) without request_uri if (state.uri?.queryParameters['scope'] == 'openid') { isIssuerVerificationSettingTrue = state.uri!.queryParameters['request_uri'] != null; @@ -213,12 +211,10 @@ final qrCodeBlocListener = BlocListener( ? state.uri!.host : '''${approvedIssuer.organizationInfo.legalName}\n${approvedIssuer.organizationInfo.currentAddress}'''; - /// issuer side (oidc4VCI) if (state.uri!.toString().startsWith('openid://initiate_issuance?')) { subtitle = state.uri!.queryParameters['issuer'].toString(); } - /// verifier side (siopv2) without request_uri if (state.uri?.queryParameters['scope'] == 'openid') { subtitle = state.uri!.queryParameters['request_uri'].toString(); } From 865bd004d0c283b9c0c25e000313bb83b0eab16f Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Mon, 13 Mar 2023 14:07:07 +0530 Subject: [PATCH 149/190] comments for siopv2 and oidc4VCI --- .../qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart | 10 +++++----- lib/splash/bloclisteners/blocklisteners.dart | 4 ++++ 2 files changed, 9 insertions(+), 5 deletions(-) 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 102fabbb6..4cf642d7f 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 @@ -184,12 +184,12 @@ class QRCodeScanCubit extends Cubit { }) async { emit(state.loading(isScan: isScan)); try { - ///Check if SIOPV2 request + /// verifier side (siopv2) without request_uri if (uri?.queryParameters['scope'] == 'openid') { // Check if we can respond to presentation request: // having credentials? // having correct crv in ebsi key - if (!await isSiopV2RequestValid(uri!)) { + if (!await isSiopV2WithRequestURIValid(uri!)) { emit( state.copyWith( qrScanStatus: QrScanStatus.success, @@ -288,7 +288,7 @@ class QRCodeScanCubit extends Cubit { try { /// ebsi credential - /// oidc4vci + /// issuer side (oidc4VCI) if (state.uri.toString().startsWith('openid://initiate_issuance?')) { await initiateEbsiCredentialIssuance( state.uri.toString(), @@ -302,7 +302,7 @@ class QRCodeScanCubit extends Cubit { } /// ebsi presentation - /// siopv2 + /// verifier side (siopv2) without request_uri if (state.uri?.queryParameters['scope'] == 'openid') { var claims = uri.queryParameters['claims'] ?? ''; @@ -601,7 +601,7 @@ class QRCodeScanCubit extends Cubit { return data; } - Future isSiopV2RequestValid(Uri uri) async { + Future isSiopV2WithRequestURIValid(Uri uri) async { bool isValid = true; ///credential should not be empty since we have to present diff --git a/lib/splash/bloclisteners/blocklisteners.dart b/lib/splash/bloclisteners/blocklisteners.dart index ae5b094b3..4ccea1db0 100644 --- a/lib/splash/bloclisteners/blocklisteners.dart +++ b/lib/splash/bloclisteners/blocklisteners.dart @@ -170,10 +170,12 @@ final qrCodeBlocListener = BlocListener( isIssuerVerificationSettingTrue = profileCubit.state.model.issuerVerificationUrl != ''; + /// issuer side (oidc4VCI) if (state.uri!.toString().startsWith('openid://initiate_issuance?')) { isIssuerVerificationSettingTrue = true; } + /// verifier side (siopv2) without request_uri if (state.uri?.queryParameters['scope'] == 'openid') { isIssuerVerificationSettingTrue = state.uri!.queryParameters['request_uri'] != null; @@ -211,10 +213,12 @@ final qrCodeBlocListener = BlocListener( ? state.uri!.host : '''${approvedIssuer.organizationInfo.legalName}\n${approvedIssuer.organizationInfo.currentAddress}'''; + /// issuer side (oidc4VCI) if (state.uri!.toString().startsWith('openid://initiate_issuance?')) { subtitle = state.uri!.queryParameters['issuer'].toString(); } + /// verifier side (siopv2) without request_uri if (state.uri?.queryParameters['scope'] == 'openid') { subtitle = state.uri!.queryParameters['request_uri'].toString(); } From 1ac5c2803e4b84d9701c42b1c9343e953b7937de Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Mon, 13 Mar 2023 14:11:30 +0530 Subject: [PATCH 150/190] bloometa card call update #1451 --- lib/app/shared/constants/urls.dart | 3 +-- .../credential_subject_type_extension.dart | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/app/shared/constants/urls.dart b/lib/app/shared/constants/urls.dart index 701ce6839..dcafb945b 100644 --- a/lib/app/shared/constants/urls.dart +++ b/lib/app/shared/constants/urls.dart @@ -29,8 +29,7 @@ class Urls { static const String tezotopiaMembershipCardUrl = 'https://issuer.talao.co/tezotopia/membershipcard/'; - static const String bloometaCardUrl = - 'https://issuer.talao.co/bloometa/membershipcard/'; + static const String bloometaCardUrl = 'https://issuer.talao.co/bloometa'; static const String chainbornMembershipCardUrl = 'https://issuer.talao.co/chainborn/membershipcard/'; 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 35dae3fd3..235823689 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 @@ -399,7 +399,6 @@ extension CredentialSubjectTypeExtension on CredentialSubjectType { bool get byPassDeepLink { if (this == CredentialSubjectType.tezotopiaMembership || - this == CredentialSubjectType.bloometaPass || this == CredentialSubjectType.chainbornMembership || this == CredentialSubjectType.twitterCard || this == CredentialSubjectType.over13 || From 301db294e76c9b52b7d2b7042658d6cbd82a8994 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Mon, 13 Mar 2023 14:23:26 +0530 Subject: [PATCH 151/190] remove - Select your credentials --- ...l_manifest_credential_offer_pick_page.dart | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) 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 28b0931a7..4d80d6798 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 @@ -123,21 +123,14 @@ class CredentialManifestOfferPickView extends StatelessWidget { style: Theme.of(context).textTheme.credentialSteps, ), const SizedBox(height: 10), - if (purpose != null) - Padding( - padding: const EdgeInsets.all(8), - child: Text( - purpose, - style: Theme.of(context) - .textTheme - .credentialSubtitle, - ), - ) - else - const SizedBox.shrink(), - Text( - l10n.credentialPickSelect, - style: Theme.of(context).textTheme.bodyLarge, + Padding( + padding: const EdgeInsets.all(8), + child: Text( + purpose ?? l10n.credentialPickSelect, + style: Theme.of(context) + .textTheme + .credentialSubtitle, + ), ), const SizedBox(height: 12), ...List.generate( From a2e90f468d844add4fe6be0fd11b4f8b9eb49645 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Mon, 13 Mar 2023 15:08:02 +0530 Subject: [PATCH 152/190] secure storage bug fix in credential details page --- .../credentials/detail/view/credentials_details_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1579036a1..00525bdfd 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 @@ -41,7 +41,7 @@ class CredentialsDetailsPage extends StatelessWidget { return BlocProvider( create: (context) => CredentialDetailsCubit( didKitProvider: DIDKitProvider(), - secureStorageProvider: context.read(), + secureStorageProvider: getSecureStorage, client: DioClient('', Dio()), ), child: CredentialsDetailsView( From 5a1ee92eb9fab8242e66b3283be1ec4754722df5 Mon Sep 17 00:00:00 2001 From: hawkbee1 Date: Fri, 10 Mar 2023 17:17:28 +0100 Subject: [PATCH 153/190] realease candidate 1.11.2+160 whith whatsnew, ebsi key from mnemonic and bloometa hidden --- lib/app/shared/constants/constant_list.dart | 2 +- .../dashboard/widgets/what_is_new_dialog.dart | 15 +++++++++++++-- .../view/did_ebsi/manage_did_ebsi_page.dart | 9 ++++----- lib/ebsi/initiate_ebsi_credential_issuance.dart | 6 ++++-- lib/scan/cubit/scan_cubit.dart | 11 ++++++----- lib/splash/view/splash_page.dart | 12 ++++++------ packages/ebsi/lib/src/ebsi.dart | 6 +++++- packages/ebsi/test/src/ebsi_test.dart | 1 - pubspec.yaml | 2 +- 9 files changed, 40 insertions(+), 24 deletions(-) diff --git a/lib/app/shared/constants/constant_list.dart b/lib/app/shared/constants/constant_list.dart index 81fb78c07..e11d0fd01 100644 --- a/lib/app/shared/constants/constant_list.dart +++ b/lib/app/shared/constants/constant_list.dart @@ -4,7 +4,7 @@ class DiscoverList { static final List gamingCategories = [ CredentialSubjectType.tezotopiaMembership, CredentialSubjectType.chainbornMembership, - CredentialSubjectType.bloometaPass, + // CredentialSubjectType.bloometaPass, CredentialSubjectType.troopezPass, CredentialSubjectType.pigsPass, CredentialSubjectType.matterlightPass, diff --git a/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart b/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart index 6ee8148cf..320441031 100644 --- a/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart +++ b/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart @@ -76,6 +76,16 @@ class WhatIsNewDialog extends StatelessWidget { NewContent( version: versionNumber, features: const [ + 'Improve onboarding experience', + 'Add confirmation of recovery phrase', + 'Improve popup design', + 'Update SIOPV2 flow', + 'Add deeplink for EBSI credentials', + ], + ), + const NewContent( + version: '1.10.5', + features: [ 'End to end encryption of decentralized chat in Altme', 'Specific design for EBSI diploma card', ], @@ -215,10 +225,11 @@ class WhatIsNewDialog extends StatelessWidget { ) ], ), - const Align( + const Align( alignment: Alignment.topRight, child: WhiteCloseButton(), - ),], + ), + ], ), ), ), diff --git a/lib/dashboard/drawer/manage_did/view/did_ebsi/manage_did_ebsi_page.dart b/lib/dashboard/drawer/manage_did/view/did_ebsi/manage_did_ebsi_page.dart index 31427ff5a..0d0fc9a87 100644 --- a/lib/dashboard/drawer/manage_did/view/did_ebsi/manage_did_ebsi_page.dart +++ b/lib/dashboard/drawer/manage_did/view/did_ebsi/manage_did_ebsi_page.dart @@ -26,11 +26,10 @@ class _ManageDidEbsiPageState extends State { void initState() { Future.delayed(Duration.zero, () async { final ebsi = Ebsi(Dio()); - //final mnemonic = - // await getSecureStorage.get(SecureStorageKeys.ssiMnemonic); - final String p256PrivateKey = - await getRandomP256PrivateKey(getSecureStorage); - did = await ebsi.getDidFromMnemonic(null, p256PrivateKey); + final mnemonic = + await getSecureStorage.get(SecureStorageKeys.ssiMnemonic); + final privateKey = await ebsi.privateKeyFromMnemonic(mnemonic: mnemonic!); + did = await ebsi.getDidFromMnemonic(null, privateKey); setState(() {}); }); super.initState(); diff --git a/lib/ebsi/initiate_ebsi_credential_issuance.dart b/lib/ebsi/initiate_ebsi_credential_issuance.dart index eec0c5bc3..50aaca8b9 100644 --- a/lib/ebsi/initiate_ebsi_credential_issuance.dart +++ b/lib/ebsi/initiate_ebsi_credential_issuance.dart @@ -1,4 +1,5 @@ import 'package:altme/app/shared/constants/parameters.dart'; +import 'package:altme/app/shared/constants/secure_storage_keys.dart'; import 'package:altme/app/shared/dio_client/dio_client.dart'; import 'package:altme/app/shared/helper_functions/helper_functions.dart'; import 'package:altme/app/shared/launch_url/launch_url.dart'; @@ -17,7 +18,8 @@ Future initiateEbsiCredentialIssuance( final Ebsi ebsi = Ebsi(Dio()); final Uri uriFromScannedResponse = Uri.parse(scannedResponse); if (uriFromScannedResponse.queryParameters['pre-authorized_code'] != null) { - final String p256PrivateKey = await getRandomP256PrivateKey(secureStorage); + final mnemonic = await getSecureStorage.get(SecureStorageKeys.ssiMnemonic); + final privateKey = await ebsi.privateKeyFromMnemonic(mnemonic: mnemonic!); // final mnemonic = await secureStorage.get( // SecureStorageKeys.ssiMnemonic, @@ -26,7 +28,7 @@ Future initiateEbsiCredentialIssuance( final dynamic encodedCredentialFromEbsi = await ebsi.getCredential( uriFromScannedResponse, null, - p256PrivateKey, + privateKey, ); await addEbsiCredential( diff --git a/lib/scan/cubit/scan_cubit.dart b/lib/scan/cubit/scan_cubit.dart index 105fd69e8..8d4b090bf 100644 --- a/lib/scan/cubit/scan_cubit.dart +++ b/lib/scan/cubit/scan_cubit.dart @@ -57,16 +57,17 @@ class ScanCubit extends Cubit { try { if (uri.queryParameters['scope'] == 'openid') { - final String p256PrivateKey = - await getRandomP256PrivateKey(secureStorageProvider); - // final mnemonic = - // await secureStorageProvider.get(SecureStorageKeys.ssiMnemonic); + final ebsi = Ebsi(Dio()); + final mnemonic = + await getSecureStorage.get(SecureStorageKeys.ssiMnemonic); + final privateKey = + await ebsi.privateKeyFromMnemonic(mnemonic: mnemonic!); final credentialList = credentialsToBePresented! .map((e) => jsonEncode(e.toJson())) .toList(); - await ebsi.sendPresentation(uri, credentialList, null, p256PrivateKey); + await ebsi.sendPresentation(uri, credentialList, null, privateKey); await presentationActivity( credentialModels: credentialsToBePresented, diff --git a/lib/splash/view/splash_page.dart b/lib/splash/view/splash_page.dart index b2b78524f..d1ba40464 100644 --- a/lib/splash/view/splash_page.dart +++ b/lib/splash/view/splash_page.dart @@ -71,20 +71,20 @@ class _SplashViewState extends State { if (url == Parameters.ebsiUniversalLink) { final client = Dio(); final ebsi = Ebsi(client); - // final mnemonic = await secure_storage.getSecureStorage.get( - // SecureStorageKeys.ssiMnemonic, - // ); + final mnemonic = + await getSecureStorage.get(SecureStorageKeys.ssiMnemonic); + final privateKey = + await ebsi.privateKeyFromMnemonic(mnemonic: mnemonic!); var credentialUri = uri; if (uri.queryParameters['uri'] != null) { final credentialUrl = uri.queryParameters['uri']; credentialUri = Uri.parse(credentialUrl!); } - final String p256PrivateKey = - await getRandomP256PrivateKey(getSecureStorage); + final dynamic encodedCredentialFromEbsi = await ebsi.getCredential( credentialUri, null, - p256PrivateKey, + privateKey, ); await addEbsiCredential( encodedCredentialFromEbsi, diff --git a/packages/ebsi/lib/src/ebsi.dart b/packages/ebsi/lib/src/ebsi.dart index 649aa8538..407f1af24 100644 --- a/packages/ebsi/lib/src/ebsi.dart +++ b/packages/ebsi/lib/src/ebsi.dart @@ -67,7 +67,7 @@ class Ebsi { /// we were using P-256K for dart library conformance which is /// the same as secp256k1, but we are using secp256k1 now final jwk = { - 'crv': 'secp256k1', + 'crv': 'P-256K', 'd': d, 'kty': 'EC', 'x': x, @@ -531,6 +531,10 @@ class Ebsi { final vpVerifierClaims = JsonWebTokenClaims.fromJson(vpTokenPayload); // create a builder, decoding the JWT in a JWS, so using a // JsonWebSignatureBuilder + final privateKey = tokenParameters.privateKey; + if (tokenParameters.privateKey['crv'] == 'secp256k1') { + privateKey['crv'] = 'P-256K'; + } final key = JsonWebKey.fromJson(tokenParameters.privateKey); diff --git a/packages/ebsi/test/src/ebsi_test.dart b/packages/ebsi/test/src/ebsi_test.dart index 913edb1f3..dba81bf57 100644 --- a/packages/ebsi/test/src/ebsi_test.dart +++ b/packages/ebsi/test/src/ebsi_test.dart @@ -404,7 +404,6 @@ void main() { test('get publicKey did with didDocumentResponse', () { const didDocumentResponse = '{"assertionMethod":["did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53"],"authentication":["did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53"],"@context":"https://www.w3.org/ns/did/v1","id":"did:ebsi:zeFCExU2XAAshYkPCpjuahA","verificationMethod":[{"controller":"did:ebsi:zeFCExU2XAAshYkPCpjuahA","id":"did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53","publicKeyJwk":{"alg":"EdDSA","crv":"Ed25519","kid":"3623b877bbb24b08ba390f3585418f53","kty":"OKP","use":"sig","x":"pWgA8M3etXlLaqcRmgjEQkz7waseg3FKzMCzfm9Yeow"},"type":"Ed25519VerificationKey2019"}]}'; - const issuerDid = 'did:ebsi:zeFCExU2XAAshYkPCpjuahA'; const expectedPublicKey = diff --git a/pubspec.yaml b/pubspec.yaml index 725fc4b9a..3c64f69a6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: altme description: AltMe Flutter App -version: 1.11.1+159 +version: 1.11.2+160 publish_to: none environment: From fe19a042fe0e75bffc9cf658439c2f9d78652158 Mon Sep 17 00:00:00 2001 From: hawkbee1 Date: Fri, 10 Mar 2023 18:36:22 +0100 Subject: [PATCH 154/190] ensure same functionalities for EBSI from QRCode or Deeplink --- .../cubit/qr_code_scan_cubit.dart | 67 +++++++++-- lib/splash/view/splash_page.dart | 112 +++++++++--------- 2 files changed, 114 insertions(+), 65 deletions(-) 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 4cf642d7f..546ad10c4 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 @@ -189,16 +189,7 @@ class QRCodeScanCubit extends Cubit { // Check if we can respond to presentation request: // having credentials? // having correct crv in ebsi key - if (!await isSiopV2WithRequestURIValid(uri!)) { - emit( - state.copyWith( - qrScanStatus: QrScanStatus.success, - route: IssuerWebsitesPage.route(''), - ), - ); - } else { - emit(state.acceptHost(uri: uri)); - } + await launchSiopV2Flow(uri); // final openIdCredential = getCredentialName(sIOPV2Param.claims!); // final openIdIssuer = getIssuersName(sIOPV2Param.claims!); @@ -276,6 +267,62 @@ class QRCodeScanCubit extends Cubit { } } + Future launchSiopV2Flow(Uri? uri) async { + // Check if we can respond to presentation request: + // having credentials? + // having correct crv in ebsi key + if (!await isSiopV2WithRequestURIValid(uri!)) { + emit( + state.copyWith( + qrScanStatus: QrScanStatus.success, + route: IssuerWebsitesPage.route(''), + ), + ); + } else { + var claims = uri.queryParameters['claims'] ?? ''; + + // TODO(hawkbee): change when correction is done on verifier + claims = claims.replaceAll("'email': None", "'email': 'None'"); + + claims = claims.replaceAll("'", '"'); + final jsonPath = JsonPath(r'$..input_descriptors'); + final outputDescriptors = + jsonPath.readValues(jsonDecode(claims)).first as List; + final inputDescriptorList = outputDescriptors + .map((e) => InputDescriptor.fromJson(e as Map)) + .toList(); + + final PresentationDefinition presentationDefinition = + PresentationDefinition(inputDescriptorList); + final CredentialModel credentialPreview = CredentialModel( + id: 'id', + image: 'image', + credentialPreview: Credential.dummy(), + shareLink: 'shareLink', + display: Display.emptyDisplay(), + data: const {}, + credentialManifest: CredentialManifest( + 'id', + IssuedBy('', ''), + null, + presentationDefinition, + ), + ); + emit( + state.copyWith( + qrScanStatus: QrScanStatus.success, + route: CredentialManifestOfferPickPage.route( + uri: uri, + credential: credentialPreview, + issuer: Issuer.emptyIssuer('domain'), + inputDescriptorIndex: 0, + credentialsToBePresented: [], + ), + ), + ); + } + } + Future accept({ required Uri uri, required Issuer issuer, diff --git a/lib/splash/view/splash_page.dart b/lib/splash/view/splash_page.dart index d1ba40464..6b6ed923d 100644 --- a/lib/splash/view/splash_page.dart +++ b/lib/splash/view/splash_page.dart @@ -67,66 +67,68 @@ class _SplashViewState extends State { if (!mounted) return; log.i('got uri: $uri'); final url = '${uri!.scheme}://${uri.authority}${uri.path}'; + // if (url == Parameters.ebsiUniversalLink) { + // final client = Dio(); + // final ebsi = Ebsi(client); + // final mnemonic = + // await getSecureStorage.get(SecureStorageKeys.ssiMnemonic); + // final privateKey = + // await ebsi.privateKeyFromMnemonic(mnemonic: mnemonic!); + // var credentialUri = uri; + // if (uri.queryParameters['uri'] != null) { + // final credentialUrl = uri.queryParameters['uri']; + // credentialUri = Uri.parse(credentialUrl!); + // if (credentialUri.queryParameters['scope'] == 'openid') { + // print('we should launch SIOPV2'); + // } + // } - if (url == Parameters.ebsiUniversalLink) { - final client = Dio(); - final ebsi = Ebsi(client); - final mnemonic = - await getSecureStorage.get(SecureStorageKeys.ssiMnemonic); - final privateKey = - await ebsi.privateKeyFromMnemonic(mnemonic: mnemonic!); - var credentialUri = uri; - if (uri.queryParameters['uri'] != null) { - final credentialUrl = uri.queryParameters['uri']; - credentialUri = Uri.parse(credentialUrl!); - } - - final dynamic encodedCredentialFromEbsi = await ebsi.getCredential( - credentialUri, - null, - privateKey, - ); - await addEbsiCredential( - encodedCredentialFromEbsi, - credentialUri, - context.read(), - ); - } else { - String beaconData = ''; - bool isBeaconRequest = false; - uri.queryParameters.forEach((key, value) async { - if (key == 'uri') { - final url = value.replaceAll(RegExp(r'ß^\"|\"$'), ''); - context.read().addDeepLink(url); - final ssiKey = await secure_storage.getSecureStorage - .get(SecureStorageKeys.ssiKey); - if (ssiKey != null) { - await context.read().deepLink(); - } - } - if (key == 'type' && value == 'tzip10') { - isBeaconRequest = true; - } - if (key == 'data') { - beaconData = value; - } - if (uri.scheme == 'openid' && - uri.authority == 'initiate_issuance') { - final DioClient client = DioClient('', Dio()); - await initiateEbsiCredentialIssuance( - uri.toString(), - client, - context.read(), - secure_storage.getSecureStorage, - ); + // final dynamic encodedCredentialFromEbsi = await ebsi.getCredential( + // credentialUri, + // null, + // privateKey, + // ); + // await addEbsiCredential( + // encodedCredentialFromEbsi, + // credentialUri, + // context.read(), + // ); + // } else { + String beaconData = ''; + bool isBeaconRequest = false; + uri.queryParameters.forEach((key, value) async { + if (key == 'uri') { + final url = value.replaceAll(RegExp(r'ß^\"|\"$'), ''); + context.read().addDeepLink(url); + final ssiKey = await secure_storage.getSecureStorage + .get(SecureStorageKeys.ssiKey); + if (ssiKey != null) { + await context.read().deepLink(); } - }); - if (isBeaconRequest && beaconData != '') { - unawaited( - context.read().peerFromDeepLink(beaconData), + } + if (key == 'type' && value == 'tzip10') { + isBeaconRequest = true; + } + if (key == 'data') { + beaconData = value; + } + if (uri.scheme == 'openid' && + uri.authority == 'initiate_issuance') { + final DioClient client = DioClient('', Dio()); + await initiateEbsiCredentialIssuance( + uri.toString(), + client, + context.read(), + secure_storage.getSecureStorage, ); } + }); + if (isBeaconRequest && beaconData != '') { + unawaited( + context.read().peerFromDeepLink(beaconData), + ); } + // } }, onError: (Object err) { if (!mounted) return; From 6c4503ff9c064a1e6b3bf97d27b5736395bb703f Mon Sep 17 00:00:00 2001 From: hawkbee1 Date: Mon, 13 Mar 2023 07:51:54 +0100 Subject: [PATCH 155/190] EBSI: secp256k1 key when using mnemonic --- packages/ebsi/lib/src/ebsi.dart | 2 +- packages/ebsi/test/src/ebsi_test.dart | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/ebsi/lib/src/ebsi.dart b/packages/ebsi/lib/src/ebsi.dart index 407f1af24..a5f2b6baf 100644 --- a/packages/ebsi/lib/src/ebsi.dart +++ b/packages/ebsi/lib/src/ebsi.dart @@ -67,7 +67,7 @@ class Ebsi { /// we were using P-256K for dart library conformance which is /// the same as secp256k1, but we are using secp256k1 now final jwk = { - 'crv': 'P-256K', + 'crv': 'secp256k1', 'd': d, 'kty': 'EC', 'x': x, diff --git a/packages/ebsi/test/src/ebsi_test.dart b/packages/ebsi/test/src/ebsi_test.dart index dba81bf57..141a3d42a 100644 --- a/packages/ebsi/test/src/ebsi_test.dart +++ b/packages/ebsi/test/src/ebsi_test.dart @@ -81,7 +81,7 @@ void main() { ]; const expectedJwk = { - 'crv': 'P-256K', + 'crv': 'secp256k1', 'd': 'ccWWNSjGiv1iWlNh4kfhWvwG3yyQMe8o31Du0uKRzrs', 'kty': 'EC', 'x': 'J4vQtLUyrVUiFIXRrtEq4xurmBZp2eq9wJmXkIA_stI', @@ -100,7 +100,7 @@ void main() { test('privateKey from mnemonic', () async { const key = { - 'crv': 'P-256K', + 'crv': 'secp256k1', 'd': 'ccWWNSjGiv1iWlNh4kfhWvwG3yyQMe8o31Du0uKRzrs', 'kty': 'EC', 'x': 'J4vQtLUyrVUiFIXRrtEq4xurmBZp2eq9wJmXkIA_stI', From 380a43c6775a946a67a56b30875cfe79891a711b Mon Sep 17 00:00:00 2001 From: hawkbee1 Date: Mon, 13 Mar 2023 07:52:26 +0100 Subject: [PATCH 156/190] version: 1.11.3+161 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 3c64f69a6..264000e24 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: altme description: AltMe Flutter App -version: 1.11.2+160 +version: 1.11.3+161 publish_to: none environment: From 4b0ffe6edd68c9b378bc9bbc7aa0494911b26291 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Mon, 13 Mar 2023 13:59:33 +0530 Subject: [PATCH 157/190] Revert "comments for siopv2 and oidc4VCI" This reverts commit a2b0f14f288a419c8b50ddb66902f41a08607fb3. --- .../qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart | 10 +++++----- lib/splash/bloclisteners/blocklisteners.dart | 4 ---- 2 files changed, 5 insertions(+), 9 deletions(-) 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 546ad10c4..9e3b5d567 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 @@ -184,7 +184,7 @@ class QRCodeScanCubit extends Cubit { }) async { emit(state.loading(isScan: isScan)); try { - /// verifier side (siopv2) without request_uri + ///Check if SIOPV2 request if (uri?.queryParameters['scope'] == 'openid') { // Check if we can respond to presentation request: // having credentials? @@ -271,7 +271,7 @@ class QRCodeScanCubit extends Cubit { // Check if we can respond to presentation request: // having credentials? // having correct crv in ebsi key - if (!await isSiopV2WithRequestURIValid(uri!)) { + if (!await isSiopV2RequestValid(uri!)) { emit( state.copyWith( qrScanStatus: QrScanStatus.success, @@ -335,7 +335,7 @@ class QRCodeScanCubit extends Cubit { try { /// ebsi credential - /// issuer side (oidc4VCI) + /// oidc4vci if (state.uri.toString().startsWith('openid://initiate_issuance?')) { await initiateEbsiCredentialIssuance( state.uri.toString(), @@ -349,7 +349,7 @@ class QRCodeScanCubit extends Cubit { } /// ebsi presentation - /// verifier side (siopv2) without request_uri + /// siopv2 if (state.uri?.queryParameters['scope'] == 'openid') { var claims = uri.queryParameters['claims'] ?? ''; @@ -648,7 +648,7 @@ class QRCodeScanCubit extends Cubit { return data; } - Future isSiopV2WithRequestURIValid(Uri uri) async { + Future isSiopV2RequestValid(Uri uri) async { bool isValid = true; ///credential should not be empty since we have to present diff --git a/lib/splash/bloclisteners/blocklisteners.dart b/lib/splash/bloclisteners/blocklisteners.dart index 4ccea1db0..ae5b094b3 100644 --- a/lib/splash/bloclisteners/blocklisteners.dart +++ b/lib/splash/bloclisteners/blocklisteners.dart @@ -170,12 +170,10 @@ final qrCodeBlocListener = BlocListener( isIssuerVerificationSettingTrue = profileCubit.state.model.issuerVerificationUrl != ''; - /// issuer side (oidc4VCI) if (state.uri!.toString().startsWith('openid://initiate_issuance?')) { isIssuerVerificationSettingTrue = true; } - /// verifier side (siopv2) without request_uri if (state.uri?.queryParameters['scope'] == 'openid') { isIssuerVerificationSettingTrue = state.uri!.queryParameters['request_uri'] != null; @@ -213,12 +211,10 @@ final qrCodeBlocListener = BlocListener( ? state.uri!.host : '''${approvedIssuer.organizationInfo.legalName}\n${approvedIssuer.organizationInfo.currentAddress}'''; - /// issuer side (oidc4VCI) if (state.uri!.toString().startsWith('openid://initiate_issuance?')) { subtitle = state.uri!.queryParameters['issuer'].toString(); } - /// verifier side (siopv2) without request_uri if (state.uri?.queryParameters['scope'] == 'openid') { subtitle = state.uri!.queryParameters['request_uri'].toString(); } From 2d0e9efd3f12ffe7176594c2a7aa00cb0d369e24 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Mon, 13 Mar 2023 14:07:07 +0530 Subject: [PATCH 158/190] comments for siopv2 and oidc4VCI --- .../qr_code/qr_code_scan/cubit/qr_code_scan_cubit.dart | 10 +++++----- lib/splash/bloclisteners/blocklisteners.dart | 4 ++++ 2 files changed, 9 insertions(+), 5 deletions(-) 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 9e3b5d567..546ad10c4 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 @@ -184,7 +184,7 @@ class QRCodeScanCubit extends Cubit { }) async { emit(state.loading(isScan: isScan)); try { - ///Check if SIOPV2 request + /// verifier side (siopv2) without request_uri if (uri?.queryParameters['scope'] == 'openid') { // Check if we can respond to presentation request: // having credentials? @@ -271,7 +271,7 @@ class QRCodeScanCubit extends Cubit { // Check if we can respond to presentation request: // having credentials? // having correct crv in ebsi key - if (!await isSiopV2RequestValid(uri!)) { + if (!await isSiopV2WithRequestURIValid(uri!)) { emit( state.copyWith( qrScanStatus: QrScanStatus.success, @@ -335,7 +335,7 @@ class QRCodeScanCubit extends Cubit { try { /// ebsi credential - /// oidc4vci + /// issuer side (oidc4VCI) if (state.uri.toString().startsWith('openid://initiate_issuance?')) { await initiateEbsiCredentialIssuance( state.uri.toString(), @@ -349,7 +349,7 @@ class QRCodeScanCubit extends Cubit { } /// ebsi presentation - /// siopv2 + /// verifier side (siopv2) without request_uri if (state.uri?.queryParameters['scope'] == 'openid') { var claims = uri.queryParameters['claims'] ?? ''; @@ -648,7 +648,7 @@ class QRCodeScanCubit extends Cubit { return data; } - Future isSiopV2RequestValid(Uri uri) async { + Future isSiopV2WithRequestURIValid(Uri uri) async { bool isValid = true; ///credential should not be empty since we have to present diff --git a/lib/splash/bloclisteners/blocklisteners.dart b/lib/splash/bloclisteners/blocklisteners.dart index ae5b094b3..4ccea1db0 100644 --- a/lib/splash/bloclisteners/blocklisteners.dart +++ b/lib/splash/bloclisteners/blocklisteners.dart @@ -170,10 +170,12 @@ final qrCodeBlocListener = BlocListener( isIssuerVerificationSettingTrue = profileCubit.state.model.issuerVerificationUrl != ''; + /// issuer side (oidc4VCI) if (state.uri!.toString().startsWith('openid://initiate_issuance?')) { isIssuerVerificationSettingTrue = true; } + /// verifier side (siopv2) without request_uri if (state.uri?.queryParameters['scope'] == 'openid') { isIssuerVerificationSettingTrue = state.uri!.queryParameters['request_uri'] != null; @@ -211,10 +213,12 @@ final qrCodeBlocListener = BlocListener( ? state.uri!.host : '''${approvedIssuer.organizationInfo.legalName}\n${approvedIssuer.organizationInfo.currentAddress}'''; + /// issuer side (oidc4VCI) if (state.uri!.toString().startsWith('openid://initiate_issuance?')) { subtitle = state.uri!.queryParameters['issuer'].toString(); } + /// verifier side (siopv2) without request_uri if (state.uri?.queryParameters['scope'] == 'openid') { subtitle = state.uri!.queryParameters['request_uri'].toString(); } From 63a3a68701b24ab29c4c0235c11afb5564707caa Mon Sep 17 00:00:00 2001 From: hawkbee1 Date: Tue, 14 Mar 2023 09:47:11 +0100 Subject: [PATCH 159/190] Talao build 1.11.1 : text color issue on pop up #1457 --- lib/dashboard/dashboard/widgets/new_content.dart | 4 ++-- lib/dashboard/dashboard/widgets/what_is_new_dialog.dart | 4 ++-- lib/theme/app_theme/app_theme.dart | 8 ++++---- pubspec.lock | 2 +- pubspec.yaml | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/dashboard/dashboard/widgets/new_content.dart b/lib/dashboard/dashboard/widgets/new_content.dart index c6a261e51..90a775364 100644 --- a/lib/dashboard/dashboard/widgets/new_content.dart +++ b/lib/dashboard/dashboard/widgets/new_content.dart @@ -38,7 +38,7 @@ class NewContent extends StatelessWidget { padding: EdgeInsets.only(top: 3), child: Icon( Icons.check_circle, - color: Colors.white, + color: background, size: 18, ), ), @@ -51,7 +51,7 @@ class NewContent extends StatelessWidget { .textTheme .defaultDialogBody .copyWith( - color: Colors.white, + color: background, ), ), ), diff --git a/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart b/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart index 7bc6e37f5..640a15a10 100644 --- a/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart +++ b/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart @@ -60,7 +60,7 @@ class WhatIsNewDialog extends StatelessWidget { child: Column( children: [ const AltMeLogo( - color: Colors.white, + color: background, size: Sizes.logoLarge * 1.05, ), Text( @@ -69,7 +69,7 @@ class WhatIsNewDialog extends StatelessWidget { .textTheme .defaultDialogTitle .copyWith( - color: Colors.white, + color: background, ), textAlign: TextAlign.center, ), diff --git a/lib/theme/app_theme/app_theme.dart b/lib/theme/app_theme/app_theme.dart index 4a48de283..7ef1261a3 100644 --- a/lib/theme/app_theme/app_theme.dart +++ b/lib/theme/app_theme/app_theme.dart @@ -146,7 +146,7 @@ extension CustomColorScheme on ColorScheme { Color get kycKeyIconColor => const Color(0xFF86809D); - Color get popupBackground => const Color(0xff271C38); + Color get popupBackground => onPrimary; Color get cardHighlighted => cardHighlight; @@ -737,13 +737,13 @@ extension CustomTextTheme on TextTheme { color: const Color(0xff86809D), ); - TextStyle get newVersionTitle => GoogleFonts.nunito( + TextStyle get newVersionTitle => GoogleFonts.poppins( fontSize: 18, fontWeight: FontWeight.w800, - color: const Color(0xFFFFFFFF), + color: background, ); - TextStyle get kycDialogTitle => GoogleFonts.nunito( + TextStyle get kycDialogTitle => GoogleFonts.poppins( fontSize: 25, fontWeight: FontWeight.bold, color: const Color(0xffF5F5F5), diff --git a/pubspec.lock b/pubspec.lock index 72f3132c0..9da12cc7b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -37,7 +37,7 @@ packages: dependency: transitive description: name: archive - sha256: "80e5141fafcb3361653ce308776cfd7d45e6e9fbb429e14eec571382c0c5fecb" + sha256: ed7cc591a948744994714375caf9a2ce89e1d82e8243997c8a2994d57181c212 url: "https://pub.dev" source: hosted version: "3.3.5" diff --git a/pubspec.yaml b/pubspec.yaml index 14f32a8bd..f760d21d4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: altme description: AltMe Flutter App -version: 1.11.2+150 +version: 1.11.3+151 publish_to: none environment: From ab24d8cab09b3c953d0a4ef69cfaf8a90824a882 Mon Sep 17 00:00:00 2001 From: hawkbee1 Date: Tue, 14 Mar 2023 14:48:45 +0100 Subject: [PATCH 160/190] add .env.dev to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 8be0fd9dc..fdae26f28 100644 --- a/.gitignore +++ b/.gitignore @@ -133,3 +133,4 @@ app.*.map.json #private key related *.env +*.env.dev From 1141cadfa7f3a0ad9d54a475f4064749f26233b0 Mon Sep 17 00:00:00 2001 From: hawkbee <49282360+hawkbee1@users.noreply.github.com> Date: Tue, 14 Mar 2023 20:39:13 +0100 Subject: [PATCH 161/190] 1.11.2 (#1459) * realease candidate 1.11.2+160 whith whatsnew, ebsi key from mnemonic and bloometa hidden * ensure same functionalities for EBSI from QRCode or Deeplink * EBSI: secp256k1 key when using mnemonic * version: 1.11.3+161 * code refactring --- analysis_options.yaml | 2 +- lib/ebsi/initiate_ebsi_credential_issuance.dart | 1 - lib/splash/view/splash_page.dart | 3 --- pubspec.lock | 4 ++-- pubspec.yaml | 2 ++ 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index 869116156..6ec9b67c0 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -16,4 +16,4 @@ linter: use_build_context_synchronously: false use_string_buffers: false always_put_required_named_parameters_first: false - depend_on_referenced_packages: false + depend_on_referenced_packages: true diff --git a/lib/ebsi/initiate_ebsi_credential_issuance.dart b/lib/ebsi/initiate_ebsi_credential_issuance.dart index 50aaca8b9..b83fd899a 100644 --- a/lib/ebsi/initiate_ebsi_credential_issuance.dart +++ b/lib/ebsi/initiate_ebsi_credential_issuance.dart @@ -1,7 +1,6 @@ import 'package:altme/app/shared/constants/parameters.dart'; import 'package:altme/app/shared/constants/secure_storage_keys.dart'; import 'package:altme/app/shared/dio_client/dio_client.dart'; -import 'package:altme/app/shared/helper_functions/helper_functions.dart'; import 'package:altme/app/shared/launch_url/launch_url.dart'; import 'package:altme/ebsi/add_ebsi_credential.dart'; import 'package:altme/wallet/wallet.dart'; diff --git a/lib/splash/view/splash_page.dart b/lib/splash/view/splash_page.dart index 6b6ed923d..9af4eef5f 100644 --- a/lib/splash/view/splash_page.dart +++ b/lib/splash/view/splash_page.dart @@ -4,19 +4,16 @@ import 'package:altme/app/app.dart'; import 'package:altme/connection_bridge/connection_bridge.dart'; import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/deep_link/deep_link.dart'; -import 'package:altme/ebsi/add_ebsi_credential.dart'; import 'package:altme/ebsi/initiate_ebsi_credential_issuance.dart'; import 'package:altme/splash/splash.dart'; import 'package:altme/theme/app_theme/app_theme.dart'; import 'package:altme/wallet/wallet.dart'; import 'package:dio/dio.dart'; -import 'package:ebsi/ebsi.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart' as services; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:secure_storage/secure_storage.dart' as secure_storage; -import 'package:secure_storage/secure_storage.dart'; import 'package:uni_links/uni_links.dart'; bool _initialUriIsHandled = false; diff --git a/pubspec.lock b/pubspec.lock index 45d70aef8..9558188a6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1473,7 +1473,7 @@ packages: source: hosted version: "1.0.1" path_provider: - dependency: "direct overridden" + dependency: "direct main" description: name: path_provider sha256: dcea5feb97d8abf90cab9e9030b497fb7c3cbf26b7a1fe9e3ef7dcb0a1ddec95 @@ -2311,7 +2311,7 @@ packages: source: hosted version: "1.0.2" web3dart: - dependency: "direct overridden" + dependency: "direct main" description: name: web3dart sha256: "0b96223a6b284e3146e65dc842ded139eca68a85c4ab79c5ba1a73284927d3cd" diff --git a/pubspec.yaml b/pubspec.yaml index 264000e24..9675c390d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -82,6 +82,7 @@ dependencies: package_info_plus: ^3.0.3 passbase_flutter: ^2.13.3 # new version available path: ^1.8.2 # flutter_test from sdk depends on path 1.8.2 + path_provider: ^2.0.13 permission_handler: ^10.2.0 platform_device_id: ^1.0.1 pointycastle: ^3.6.2 @@ -108,6 +109,7 @@ dependencies: git: url: https://github.com/bibash28/wallet-connect-dart.git ref: fba9b209ee2b61b62ddd643f07f6a7f64426d7b2 + web3dart: ^2.6.1 webview_flutter: ^4.0.2 webview_flutter_android: ^3.2.4 webview_flutter_wkwebview: ^3.0.5 From 235a21b14677703d40df6e4694d839c457cf2ad6 Mon Sep 17 00:00:00 2001 From: Taleb <40627325+TalebRafiepour@users.noreply.github.com> Date: Tue, 14 Mar 2023 23:29:17 +0330 Subject: [PATCH 162/190] Live chat external company#1394 (#1455) * move reuseable stuf of Matrix to new objects * increase the abstraction level and do refactoring * enable chat for cards * enable chat for cards --- lib/app/shared/constants/altme_strings.dart | 3 + .../shared/constants/secure_storage_keys.dart | 2 + lib/app/view/app.dart | 10 +- .../dashboard/view/dashboard_page.dart | 6 +- .../altme_support_chat.dart | 2 + .../cubit/altme_support_chat_cubit.dart | 11 + .../view/altme_support_chat_page.dart | 27 + lib/dashboard/drawer/chat_room/chat_room.dart | 3 + .../chat_room/cubit/chat_room_cubit.dart | 285 ++++++++ .../cubit/chat_room_state.dart} | 16 +- .../chat_room/matrix_chat/matrix_chat.dart | 2 + .../matrix_chat/matrix_chat_impl.dart | 535 ++++++++++++++ .../matrix_chat/matrix_chat_interface.dart | 52 ++ .../view/chat_room_view.dart} | 53 +- lib/dashboard/drawer/drawer.dart | 4 +- .../drawer/drawer/view/help_center_menu.dart | 6 +- .../drawer/drawer/view/reset_wallet_menu.dart | 4 +- .../live_chat/cubit/live_chat_cubit.dart | 679 ------------------ lib/dashboard/drawer/live_chat/live_chat.dart | 2 - .../loyalty_card_support_chat_cubit.dart | 11 + .../loyalty_card_support_chat.dart | 2 + .../view/loyalty_card_support_chat_page.dart | 50 ++ .../detail/view/credentials_details_page.dart | 32 +- .../view/import_from_wallet_page.dart | 2 +- .../view/import_wallet_page.dart | 2 +- lib/l10n/arb/app_en.arb | 3 +- lib/l10n/untranslated.json | 12 +- .../view/activate_biometrics_page.dart | 2 +- .../view/onboarding_gen_phrase.dart | 2 +- .../view/onboarding_verify_phrase.dart | 2 +- pubspec.lock | 2 +- 31 files changed, 1064 insertions(+), 760 deletions(-) create mode 100644 lib/dashboard/drawer/altme_support_chat/altme_support_chat.dart create mode 100644 lib/dashboard/drawer/altme_support_chat/cubit/altme_support_chat_cubit.dart create mode 100644 lib/dashboard/drawer/altme_support_chat/view/altme_support_chat_page.dart create mode 100644 lib/dashboard/drawer/chat_room/chat_room.dart create mode 100644 lib/dashboard/drawer/chat_room/cubit/chat_room_cubit.dart rename lib/dashboard/drawer/{live_chat/cubit/live_chat_state.dart => chat_room/cubit/chat_room_state.dart} (66%) create mode 100644 lib/dashboard/drawer/chat_room/matrix_chat/matrix_chat.dart create mode 100644 lib/dashboard/drawer/chat_room/matrix_chat/matrix_chat_impl.dart create mode 100644 lib/dashboard/drawer/chat_room/matrix_chat/matrix_chat_interface.dart rename lib/dashboard/drawer/{live_chat/view/live_chat_page.dart => chat_room/view/chat_room_view.dart} (83%) delete mode 100644 lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart delete mode 100644 lib/dashboard/drawer/live_chat/live_chat.dart create mode 100644 lib/dashboard/drawer/loyalty_card_support_chat/cubit/loyalty_card_support_chat_cubit.dart create mode 100644 lib/dashboard/drawer/loyalty_card_support_chat/loyalty_card_support_chat.dart create mode 100644 lib/dashboard/drawer/loyalty_card_support_chat/view/loyalty_card_support_chat_page.dart diff --git a/lib/app/shared/constants/altme_strings.dart b/lib/app/shared/constants/altme_strings.dart index b0181a940..64f37ac19 100644 --- a/lib/app/shared/constants/altme_strings.dart +++ b/lib/app/shared/constants/altme_strings.dart @@ -18,4 +18,7 @@ class AltMeStrings { static const String uri = 'uri'; static const String email = 'email'; static const String time = 'time'; + + //Chat + static const String matrixSupportId = '@support:matrix.talao.co'; } diff --git a/lib/app/shared/constants/secure_storage_keys.dart b/lib/app/shared/constants/secure_storage_keys.dart index 100eef634..3e8e076c4 100644 --- a/lib/app/shared/constants/secure_storage_keys.dart +++ b/lib/app/shared/constants/secure_storage_keys.dart @@ -1,6 +1,8 @@ class SecureStorageKeys { static const String isUserRegisteredMatrix = 'isUserRegisteredMatrix'; static const String supportRoomId = 'MatrixSupportRoomId'; + static const String loyaltyCardsupportRoomId = + 'MatrixLoyaltyCardsupportRoomId'; static const String isFirstSelectedTokenContracts = 'isFirstSelectedTokenContracts'; static const String selectedContracts = 'selectedContracts'; diff --git a/lib/app/view/app.dart b/lib/app/view/app.dart index 15a7afaf5..4d8dfb215 100644 --- a/lib/app/view/app.dart +++ b/lib/app/view/app.dart @@ -171,12 +171,14 @@ class App extends StatelessWidget { manageNetworkCubit: context.read(), ), ), - BlocProvider( + BlocProvider( lazy: false, - create: (context) => LiveChatCubit( - dioClient: DioClient('', Dio()), - didKit: DIDKitProvider(), + create: (context) => AltmeChatSupportCubit( secureStorageProvider: getSecureStorage, + matrixChat: MatrixChatImpl(), + invites: [AltMeStrings.matrixSupportId], + storageKey: SecureStorageKeys.supportRoomId, + roomNamePrefix: 'Altme', ), ), ], diff --git a/lib/dashboard/dashboard/view/dashboard_page.dart b/lib/dashboard/dashboard/view/dashboard_page.dart index cb67a6128..2bf4af53b 100644 --- a/lib/dashboard/dashboard/view/dashboard_page.dart +++ b/lib/dashboard/dashboard/view/dashboard_page.dart @@ -183,7 +183,7 @@ class _DashboardViewState extends State { child: BlocBuilder( builder: (context, state) { if (state.selectedIndex == 3) { - context.read().setMessagesAsRead(); + context.read().setMessagesAsRead(); } return WillPopScope( onWillPop: () async { @@ -265,7 +265,7 @@ class _DashboardViewState extends State { WertPage() else SearchPage(), - LiveChatPage(hideAppBar: true), + AltmeSupportChatPage(), ], ), ), @@ -303,7 +303,7 @@ class _DashboardViewState extends State { ), StreamBuilder( stream: context - .read() + .read() .unreadMessageCountStream, builder: (_, snapShot) { return BottomBarItem( diff --git a/lib/dashboard/drawer/altme_support_chat/altme_support_chat.dart b/lib/dashboard/drawer/altme_support_chat/altme_support_chat.dart new file mode 100644 index 000000000..0d1020ccd --- /dev/null +++ b/lib/dashboard/drawer/altme_support_chat/altme_support_chat.dart @@ -0,0 +1,2 @@ +export 'cubit/altme_support_chat_cubit.dart'; +export 'view/altme_support_chat_page.dart'; diff --git a/lib/dashboard/drawer/altme_support_chat/cubit/altme_support_chat_cubit.dart b/lib/dashboard/drawer/altme_support_chat/cubit/altme_support_chat_cubit.dart new file mode 100644 index 000000000..02c027c70 --- /dev/null +++ b/lib/dashboard/drawer/altme_support_chat/cubit/altme_support_chat_cubit.dart @@ -0,0 +1,11 @@ +import 'package:altme/dashboard/dashboard.dart'; + +class AltmeChatSupportCubit extends ChatRoomCubit { + AltmeChatSupportCubit({ + required super.secureStorageProvider, + required super.matrixChat, + required super.invites, + required super.storageKey, + required super.roomNamePrefix, + }); +} diff --git a/lib/dashboard/drawer/altme_support_chat/view/altme_support_chat_page.dart b/lib/dashboard/drawer/altme_support_chat/view/altme_support_chat_page.dart new file mode 100644 index 000000000..4e5d2cce5 --- /dev/null +++ b/lib/dashboard/drawer/altme_support_chat/view/altme_support_chat_page.dart @@ -0,0 +1,27 @@ +import 'package:altme/dashboard/dashboard.dart'; +import 'package:flutter/material.dart'; + +class AltmeSupportChatPage extends StatelessWidget { + const AltmeSupportChatPage({ + super.key, + this.appBarTitle, + }); + + final String? appBarTitle; + + static Route route({String? appBarTitle}) { + return MaterialPageRoute( + builder: (_) => AltmeSupportChatPage( + appBarTitle: appBarTitle, + ), + settings: const RouteSettings(name: '/altmeSupportChatPage'), + ); + } + + @override + Widget build(BuildContext context) { + return ChatRoomView( + appBarTitle: appBarTitle, + ); + } +} diff --git a/lib/dashboard/drawer/chat_room/chat_room.dart b/lib/dashboard/drawer/chat_room/chat_room.dart new file mode 100644 index 000000000..92c32cfb9 --- /dev/null +++ b/lib/dashboard/drawer/chat_room/chat_room.dart @@ -0,0 +1,3 @@ +export 'cubit/chat_room_cubit.dart'; +export 'matrix_chat/matrix_chat.dart'; +export 'view/chat_room_view.dart'; diff --git a/lib/dashboard/drawer/chat_room/cubit/chat_room_cubit.dart b/lib/dashboard/drawer/chat_room/cubit/chat_room_cubit.dart new file mode 100644 index 000000000..bf8d2ed97 --- /dev/null +++ b/lib/dashboard/drawer/chat_room/cubit/chat_room_cubit.dart @@ -0,0 +1,285 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:altme/app/app.dart'; +import 'package:altme/dashboard/dashboard.dart'; +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter_chat_types/flutter_chat_types.dart'; +import 'package:http/http.dart' as http; +import 'package:json_annotation/json_annotation.dart'; +import 'package:matrix/matrix.dart' hide User; +import 'package:open_filex/open_filex.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:secure_storage/secure_storage.dart'; + +part 'chat_room_cubit.g.dart'; +part 'chat_room_state.dart'; + +abstract class ChatRoomCubit extends Cubit { + ChatRoomCubit({ + required this.secureStorageProvider, + required this.matrixChat, + required this.invites, + required this.storageKey, + required this.roomNamePrefix, + }) : super( + const ChatRoomState(), + ) { + init(); + } + + final SecureStorageProvider secureStorageProvider; + final logger = getLogger('ChatRoomCubit'); + String? _roomId; + StreamSubscription? _onEventSubscription; + StreamController? _notificationStreamController; + final List? invites; + final String storageKey; + final String roomNamePrefix; + + // + final MatrixChatImpl matrixChat; + + Stream get unreadMessageCountStream { + _notificationStreamController ??= StreamController.broadcast(); + return _notificationStreamController!.stream; + } + + Future onSendPressed(PartialText partialText) async { + return matrixChat.onSendPressed( + partialText: partialText, + onMessageCreated: (message) async { + emit(state.copyWith(messages: [message, ...state.messages])); + // + await _checkIfRoomNotExistThenCreateIt(); + return _roomId!; + }, + ); + } + + Future handleMessageTap(Message message) async { + if (message is FileMessage) { + var localPath = message.uri; + + if (message.uri.startsWith('http')) { + try { + final index = + state.messages.indexWhere((element) => element.id == message.id); + final updatedMessage = + (state.messages[index] as FileMessage).copyWith( + isLoading: true, + ); + + state.messages[index] = updatedMessage; + emit(state.copyWith(messages: state.messages)); + + final httpClient = http.Client(); + final request = await httpClient.get(Uri.parse(message.uri)); + final bytes = request.bodyBytes; + final documentsDir = (await getApplicationDocumentsDirectory()).path; + localPath = '$documentsDir/${message.name}'; + + if (!File(localPath).existsSync()) { + final file = File(localPath); + await file.writeAsBytes(bytes); + } + } finally { + final index = + state.messages.indexWhere((element) => element.id == message.id); + final updatedMessage = + (state.messages[index] as FileMessage).copyWith( + isLoading: null, + ); + + state.messages[index] = updatedMessage; + emit(state.copyWith(messages: state.messages)); + } + } + + await OpenFilex.open(localPath); + } + } + + void handlePreviewDataFetched( + TextMessage message, + PreviewData previewData, + ) { + final index = state.messages.indexWhere( + (element) => element.id == message.id, + ); + final updatedMessage = (state.messages[index] as TextMessage).copyWith( + previewData: previewData, + ); + state.messages[index] = updatedMessage; + emit(state.copyWith(messages: state.messages)); + } + + Future handleFileSelection() { + return matrixChat.handleFileSelection( + onMessageCreated: (message) async { + emit(state.copyWith(messages: [message, ...state.messages])); + await _checkIfRoomNotExistThenCreateIt(); + return _roomId!; + }, + ); + } + + Future handleImageSelection() { + return matrixChat.handleImageSelection( + onMessageCreated: (message) async { + emit(state.copyWith(messages: [message, ...state.messages])); + + await _checkIfRoomNotExistThenCreateIt(); + return _roomId!; + }, + ); + } + + Future init() async { + try { + emit(state.copyWith(status: AppStatus.loading)); + final user = await matrixChat.init(); + _notificationStreamController ??= StreamController.broadcast(); + + List retrivedMessageFromDB = []; + final savedRoomId = await matrixChat.getRoomIdFromStorage(storageKey); + if (savedRoomId != null) { + _roomId = savedRoomId; + await matrixChat.enableRoomEncyption(savedRoomId); + _getUnreadMessageCount(); + await _subscribeToEventsOfRoom(); + retrivedMessageFromDB = + await matrixChat.retriveMessagesFromDB(_roomId!); + } + logger.i('roomId : $_roomId'); + emit( + state.copyWith( + status: AppStatus.init, + user: user, + messages: retrivedMessageFromDB, + ), + ); + } catch (e, s) { + logger.e('error: $e, stack: $s'); + if (e is MatrixException) { + emit( + state.copyWith( + status: AppStatus.error, + message: StateMessage.error( + stringMessage: e.errorMessage, + ), + ), + ); + } else { + emit( + state.copyWith( + status: AppStatus.error, + message: StateMessage( + messageHandler: ResponseMessage( + ResponseString + .RESPONSE_STRING_SOMETHING_WENT_WRONG_TRY_AGAIN_LATER, + ), + ), + ), + ); + } + } + } + + Future _subscribeToEventsOfRoom() async { + await _onEventSubscription?.cancel(); + + _onEventSubscription = + matrixChat.client!.onRoomState.stream.listen((Event event) { + if (event.roomId == _roomId && event.type == 'm.room.message') { + final txId = event.unsigned?['transaction_id'] as String?; + if (state.messages.any( + (element) => element.id == txId, + )) { + final index = state.messages.indexWhere( + (element) => element.id == txId, + ); + final updatedMessage = state.messages[index].copyWith( + status: matrixChat.mapEventStatusToMessageStatus(event.status), + ); + final newMessages = List.of(state.messages); + newMessages[index] = updatedMessage; + emit(state.copyWith(messages: newMessages)); + } else { + final Message message = matrixChat.mapEventToMessage(event); + _getUnreadMessageCount(); + emit( + state.copyWith( + messages: [message, ...state.messages], + ), + ); + } + } + }); + } + + int get unreadMessageCount => matrixChat.getUnreadMessageCount(_roomId); + + void _getUnreadMessageCount() { + final unreadCount = unreadMessageCount; + logger.i('unread message count: $unreadCount'); + _notificationStreamController?.sink.add(unreadCount); + } + + Future markMessageAsRead(List? eventIds) async { + if (eventIds == null || eventIds.isEmpty) return; + await matrixChat.markMessageAsRead(eventIds, _roomId); + _getUnreadMessageCount(); + } + + // this function called when state emited in UI and needs + // to set messages as read + void setMessagesAsRead() { + try { + logger.i('setMessagesAsRead'); + if (unreadMessageCount > 0) { + final unreadMessageEventIds = state.messages + .take(unreadMessageCount) + .map((e) => e.remoteId!) + .toList(); + logger.i( + 'unread message event ids lenght: ${unreadMessageEventIds.length}', + ); + markMessageAsRead(unreadMessageEventIds); + } + } catch (e, s) { + logger.e('e: $e, s: $s'); + } + } + + Future _checkIfRoomNotExistThenCreateIt() async { + if (_roomId == null || _roomId!.isEmpty) { + final did = await secureStorageProvider.get(SecureStorageKeys.did) ?? ''; + final username = did.replaceAll(':', '-'); + _roomId = await matrixChat.createRoomAndInviteSupport( + '$roomNamePrefix-$username', + invites, + ); + await matrixChat.setRoomIdInStorage( + storageKey, + _roomId!, + ); + _getUnreadMessageCount(); + await _subscribeToEventsOfRoom(); + } + } + + Future dispose() async { + try { + await matrixChat.dispose(); + await _notificationStreamController?.close().catchError((_) => null); + _notificationStreamController = null; + await _onEventSubscription?.cancel().catchError((_) => null); + _onEventSubscription = null; + _roomId = null; + } catch (e, s) { + logger.e('e: $e, s: $s'); + } + } +} diff --git a/lib/dashboard/drawer/live_chat/cubit/live_chat_state.dart b/lib/dashboard/drawer/chat_room/cubit/chat_room_state.dart similarity index 66% rename from lib/dashboard/drawer/live_chat/cubit/live_chat_state.dart rename to lib/dashboard/drawer/chat_room/cubit/chat_room_state.dart index 2f2741959..3d326d7fa 100644 --- a/lib/dashboard/drawer/live_chat/cubit/live_chat_state.dart +++ b/lib/dashboard/drawer/chat_room/cubit/chat_room_state.dart @@ -1,31 +1,31 @@ -part of 'live_chat_cubit.dart'; +part of 'chat_room_cubit.dart'; @JsonSerializable() -class LiveChatState extends Equatable { - const LiveChatState({ +class ChatRoomState extends Equatable { + const ChatRoomState({ this.status = AppStatus.idle, this.messages = const [], this.user, this.message, }); - factory LiveChatState.fromJson(Map json) => - _$LiveChatStateFromJson(json); + factory ChatRoomState.fromJson(Map json) => + _$ChatRoomStateFromJson(json); final AppStatus status; final List messages; final StateMessage? message; final User? user; - Map toJson() => _$LiveChatStateToJson(this); + Map toJson() => _$ChatRoomStateToJson(this); - LiveChatState copyWith({ + ChatRoomState copyWith({ AppStatus? status, List? messages, User? user, StateMessage? message, }) { - return LiveChatState( + return ChatRoomState( status: status ?? this.status, messages: messages ?? this.messages, user: user ?? this.user, diff --git a/lib/dashboard/drawer/chat_room/matrix_chat/matrix_chat.dart b/lib/dashboard/drawer/chat_room/matrix_chat/matrix_chat.dart new file mode 100644 index 000000000..cdac45803 --- /dev/null +++ b/lib/dashboard/drawer/chat_room/matrix_chat/matrix_chat.dart @@ -0,0 +1,2 @@ +export 'matrix_chat_impl.dart'; +export 'matrix_chat_interface.dart'; diff --git a/lib/dashboard/drawer/chat_room/matrix_chat/matrix_chat_impl.dart b/lib/dashboard/drawer/chat_room/matrix_chat/matrix_chat_impl.dart new file mode 100644 index 000000000..665b09359 --- /dev/null +++ b/lib/dashboard/drawer/chat_room/matrix_chat/matrix_chat_impl.dart @@ -0,0 +1,535 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:altme/app/app.dart'; +import 'package:altme/dashboard/dashboard.dart'; +import 'package:crypto/crypto.dart'; +import 'package:did_kit/did_kit.dart'; +import 'package:dio/dio.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_chat_types/flutter_chat_types.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:matrix/matrix.dart' hide User; +import 'package:mime/mime.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:platform_device_id/platform_device_id.dart'; +import 'package:secure_storage/secure_storage.dart'; +import 'package:uuid/uuid.dart'; + +typedef OnMessageCreated = Future Function(Message); + +class MatrixChatImpl extends MatrixChatInterface { + factory MatrixChatImpl() { + _instance ??= MatrixChatImpl._( + didKit: DIDKitProvider(), + dioClient: DioClient('', Dio()), + secureStorageProvider: getSecureStorage, + ); + return _instance!; + } + + MatrixChatImpl._({ + required this.didKit, + required this.dioClient, + required this.secureStorageProvider, + }) { + init(); + } + + static MatrixChatImpl? _instance; + + final DIDKitProvider didKit; + final DioClient dioClient; + final SecureStorageProvider secureStorageProvider; + + @override + Future init() async { + logger.i('init()'); + if (user != null) { + return user!; + } + final ssiKey = await secureStorageProvider.get(SecureStorageKeys.ssiKey); + final did = await secureStorageProvider.get(SecureStorageKeys.did) ?? ''; + final username = did.replaceAll(':', '-'); + if (ssiKey == null || ssiKey.isEmpty || did.isEmpty || username.isEmpty) { + throw Exception( + 'ssiKey == null || ssiKey.isEmpty || did.isEmpty || username.isEmpty', + ); + } + + await _initClient(); + final isUserRegisteredMatrix = await secureStorageProvider + .get(SecureStorageKeys.isUserRegisteredMatrix); + late String userId; + if (isUserRegisteredMatrix != 'true') { + await register(did: did); + await secureStorageProvider.set( + SecureStorageKeys.isUserRegisteredMatrix, + true.toString(), + ); + userId = await login( + username: username, + password: await _getPasswordForDID(), + ); + } else { + userId = await login( + username: username, + password: await _getPasswordForDID(), + ); + } + user = User(id: userId); + return user!; + } + + Future _initClient() async { + try { + client = Client( + 'AltMeUser', + databaseBuilder: (_) async { + final dir = await getApplicationSupportDirectory(); + final db = HiveCollectionsDatabase('matrix_support_chat', dir.path); + await db.open(); + return db; + }, + ); + client!.homeserver = Uri.parse(Urls.matrixHomeServer); + await client!.init(); + } catch (e, s) { + logger.e('e: $e , s: $s'); + await client!.init( + newHomeserver: Uri.parse(Urls.matrixHomeServer), + ); + } + } + + @override + Future getRoomIdFromStorage(String key) async { + return secureStorageProvider.get(key); + } + + @override + Future setRoomIdInStorage(String key, String roomId) async { + await secureStorageProvider.set( + key, + roomId, + ); + } + + @override + int getUnreadMessageCount(String? roomId) => + client?.getRoomById(roomId ?? '')?.notificationCount ?? 0; + + @override + Future markMessageAsRead( + List? eventIds, + String? roomId, + ) async { + if (eventIds == null || eventIds.isEmpty) return; + + final room = client?.getRoomById(roomId ?? ''); + if (room == null) return; + try { + for (final eventId in eventIds) { + if (eventId != null) { + await room.postReceipt(eventId); + } + } + } catch (e, s) { + logger.e('e: $e , s: $s'); + } + } + + @override + Future> retriveMessagesFromDB(String roomId) async { + final room = client?.getRoomById(roomId); + if (room == null) return []; + final events = await client!.database?.getEventList(room); + if (events == null || events.isEmpty) return []; + final messageEvents = + events.where((event) => event.type == 'm.room.message').toList() + ..sort( + (e1, e2) => e2.originServerTs.compareTo(e1.originServerTs), + ); + return messageEvents.map(mapEventToMessage).toList(); + } + + @override + Message mapEventToMessage(Event event) { + late final Message message; + if (event.messageType == 'm.text') { + message = TextMessage( + id: event.unsigned?['transaction_id'] as String? ?? const Uuid().v4(), + remoteId: event.eventId, + text: event.text, + createdAt: event.originServerTs.millisecondsSinceEpoch, + status: mapEventStatusToMessageStatus(event.status), + author: User( + id: event.senderId, + ), + ); + } else if (event.messageType == 'm.image') { + message = ImageMessage( + id: const Uuid().v4(), + remoteId: event.eventId, + name: event.body, + size: event.content['info']['size'] as num, + uri: getUrlFromUri(uri: event.content['url'] as String), + status: mapEventStatusToMessageStatus(event.status), + createdAt: event.originServerTs.millisecondsSinceEpoch, + author: User( + id: event.senderId, + ), + ); + } else if (event.messageType == 'm.file') { + message = FileMessage( + id: const Uuid().v4(), + remoteId: event.eventId, + name: event.body, + size: event.content['info']['size'] as num, + uri: getUrlFromUri(uri: event.content['url'] as String), + status: mapEventStatusToMessageStatus(event.status), + createdAt: event.originServerTs.millisecondsSinceEpoch, + author: User( + id: event.senderId, + ), + ); + } else if (event.messageType == 'm.audio') { + message = AudioMessage( + id: const Uuid().v4(), + remoteId: event.eventId, + duration: Duration( + milliseconds: event.content['info']['duration'] as int, + ), + name: event.body, + size: event.content['info']['size'] as num, + uri: getUrlFromUri(uri: event.content['url'] as String), + status: mapEventStatusToMessageStatus(event.status), + createdAt: event.originServerTs.millisecondsSinceEpoch, + author: User( + id: event.senderId, + ), + ); + } else { + message = TextMessage( + id: const Uuid().v4(), + remoteId: event.eventId, + text: event.text, + createdAt: event.originServerTs.millisecondsSinceEpoch, + status: mapEventStatusToMessageStatus(event.status), + author: User( + id: event.senderId, + ), + ); + } + return message; + } + + @override + Status mapEventStatusToMessageStatus(EventStatus status) { + switch (status) { + case EventStatus.error: + return Status.error; + case EventStatus.removed: + return Status.error; + case EventStatus.roomState: + return Status.delivered; + case EventStatus.sending: + return Status.sending; + case EventStatus.sent: + return Status.sent; + case EventStatus.synced: + return Status.seen; + } + } + + @override + Future onSendPressed({ + required PartialText partialText, + required OnMessageCreated onMessageCreated, + }) async { + try { + final messageId = const Uuid().v4(); + final message = TextMessage( + author: user!, + createdAt: DateTime.now().millisecondsSinceEpoch, + id: messageId, + text: partialText.text, + status: Status.sending, + ); + final roomId = await onMessageCreated.call(message); + final room = client!.getRoomById(roomId); + if (room == null) { + await client!.joinRoomById(roomId); + } + final eventId = await client!.getRoomById(roomId)?.sendTextEvent( + partialText.text, + txid: messageId, + ); + logger.i('send text event: $eventId'); + } catch (e, s) { + logger.e('e: $e , s: $s'); + } + } + + @override + Future handleFileSelection({ + required OnMessageCreated onMessageCreated, + }) async { + final result = await FilePicker.platform.pickFiles( + type: FileType.any, + ); + + if (result != null && result.files.single.path != null) { + final messageId = const Uuid().v4(); + final message = FileMessage( + author: user!, + createdAt: DateTime.now().millisecondsSinceEpoch, + id: messageId, + mimeType: lookupMimeType(result.files.single.path!), + name: result.files.single.name, + size: result.files.single.size, + uri: result.files.single.path!, + status: Status.sending, + ); + final roomId = await onMessageCreated.call(message); + final room = client!.getRoomById(roomId); + if (room == null) { + await client!.joinRoomById(roomId); + } + await client!.getRoomById(roomId)?.sendFileEvent( + MatrixFile( + bytes: File(result.files.single.path!).readAsBytesSync(), + name: result.files.single.name, + ), + txid: messageId, + ); + } + } + + @override + Future handleImageSelection({ + required OnMessageCreated onMessageCreated, + }) async { + try { + final result = await ImagePicker().pickImage( + imageQuality: 70, + maxWidth: 1440, + source: ImageSource.gallery, + ); + + if (result != null) { + final bytes = await result.readAsBytes(); + final image = await decodeImageFromList(bytes); + final messageId = const Uuid().v4(); + + final message = ImageMessage( + author: user!, + createdAt: DateTime.now().millisecondsSinceEpoch, + height: image.height.toDouble(), + id: messageId, + name: result.name, + size: bytes.length, + uri: result.path, + width: image.width.toDouble(), + status: Status.sending, + ); + + final roomId = await onMessageCreated.call(message); + final room = client!.getRoomById(roomId); + if (room == null) { + await client!.joinRoomById(roomId); + } + await client!.getRoomById(roomId)?.sendFileEvent( + MatrixFile( + bytes: bytes, + name: result.name, + ), + txid: messageId, + ); + } + } catch (e, s) { + logger.e('e: $e, s: $s'); + } + } + + /// before calling this function you need to check if + /// room not exist before with this [roomName] and alias + @override + Future createRoomAndInviteSupport( + String roomName, + List? invites, + ) async { + try { + final roomId = await client!.createRoom( + isDirect: true, + name: roomName, + invite: invites, + roomAliasName: roomName, + initialState: [ + StateEvent( + type: EventTypes.Encryption, + stateKey: '', + content: { + 'algorithm': 'm.megolm.v1.aes-sha2', + }, + ), + ], + ); + await enableRoomEncyption(roomId); + logger.i('room created! => id: $roomId'); + return roomId; + } catch (e, s) { + logger.e('e: $e, s: $s'); + if (e is MatrixException && e.errcode == 'M_ROOM_IN_USE') { + final millisecondsSinceEpoch = DateTime.now().millisecondsSinceEpoch; + return createRoomAndInviteSupport( + '$roomName-updated-$millisecondsSinceEpoch', + invites, + ); + } else { + final roomId = await client!.joinRoom(roomName); + await enableRoomEncyption(roomId); + return roomId; + } + } + } + + @override + String getUrlFromUri({ + required String uri, + int width = 500, + int height = 500, + }) { + return '${Urls.matrixHomeServer}/_matrix/media/v3/thumbnail/${Urls.matrixHomeServer.replaceAll('https://', '')}/${uri.split('/').last}?width=$width&height=$height'; + } + + @override + Future register({ + required String did, + }) async { + final nonce = await _getNonce(did); + final didAuth = await _getDidAuth(did, nonce); + await dotenv.load(); + final apiKey = dotenv.get('TALAO_MATRIX_API_KEY'); + final password = await _getPasswordForDID(); + + final data = { + 'username': did, + 'password': password, + 'didAuth': didAuth, + }; + final response = await dioClient.post( + Urls.registerToMatrix, + headers: { + 'X-API-KEY': apiKey, + 'Content-Type': 'application/json', + }, + data: data, + ); + + logger.i('regester response: $response'); + } + + Future _getDidAuth(String did, String nonce) async { + final verificationMethod = + await secureStorageProvider.get(SecureStorageKeys.verificationMethod); + + final options = { + 'verificationMethod': verificationMethod, + 'proofPurpose': 'authentication', + 'challenge': nonce, + 'domain': 'issuer.talao.co', + }; + + final key = (await secureStorageProvider.get(SecureStorageKeys.ssiKey))!; + + final String didAuth = await didKit.didAuth( + did, + jsonEncode(options), + key, + ); + + return didAuth; + } + + Future _getNonce(String did) async { + await dotenv.load(); + final apiKey = dotenv.get('TALAO_MATRIX_API_KEY'); + final nonce = await dioClient.get( + Urls.getNonce, + queryParameters: { + 'did': did, + }, + headers: { + 'X-API-KEY': apiKey, + }, + ) as String; + return nonce; + } + + Future _getPasswordForDID() async { + final ssiKey = (await secureStorageProvider.get(SecureStorageKeys.ssiKey))!; + final bytesToHash = utf8.encode(ssiKey); + final sha256Digest = sha256.convert(bytesToHash); + final password = sha256Digest.toString(); + return password; + } + + @override + Future login({ + required String username, + required String password, + }) async { + try { + final isLogged = client!.isLogged(); + if (isLogged) return client!.userID!; + client!.homeserver = Uri.parse(Urls.matrixHomeServer); + final deviceId = await PlatformDeviceId.getDeviceId; + final loginResonse = await client!.login( + LoginType.mLoginPassword, + password: password, + deviceId: deviceId, + identifier: AuthenticationUserIdentifier(user: username), + ); + return loginResonse.userId!; + } catch (e, s) { + logger.i('e: $e, s: $s'); + return '@$username:${Urls.matrixHomeServer.replaceAll('https://', '')}'; + } + } + + @override + Future dispose() async { + try { + await client?.logout().catchError((_) => null); + await client?.dispose().catchError((_) => null); + user = null; + client = null; + } catch (e, s) { + logger.e('e: $e, s: $s'); + } + } + + @override + Future enableRoomEncyption(String roomId) async { + try { + if (roomId.isEmpty) return; + final room = client?.getRoomById(roomId); + if (room == null) return; + if (room.encrypted) { + logger.i('the room with id: ${room.id} encyrpted before!'); + return; + } + await room.enableEncryption(); + final verificationResponse = + await DeviceKeysList(client!.userID!, client!).startVerification(); + logger.i('verification response: $verificationResponse'); + await verificationResponse.acceptVerification(); + } catch (e, s) { + logger.e('error in enabling room e2e encryption, e: $e, s: $s'); + } + } +} diff --git a/lib/dashboard/drawer/chat_room/matrix_chat/matrix_chat_interface.dart b/lib/dashboard/drawer/chat_room/matrix_chat/matrix_chat_interface.dart new file mode 100644 index 000000000..386a4f575 --- /dev/null +++ b/lib/dashboard/drawer/chat_room/matrix_chat/matrix_chat_interface.dart @@ -0,0 +1,52 @@ +import 'dart:async'; + +import 'package:altme/app/app.dart'; +import 'package:altme/dashboard/dashboard.dart'; +import 'package:flutter_chat_types/flutter_chat_types.dart'; +import 'package:logger/logger.dart'; +import 'package:matrix/matrix.dart' hide User; + +abstract class MatrixChatInterface { + Client? client; + User? user; + final Logger logger = getLogger('MatrixChatInterface'); + Future onSendPressed({ + required PartialText partialText, + required OnMessageCreated onMessageCreated, + }); + Future createRoomAndInviteSupport( + String roomName, + List? invites, + ); + Future enableRoomEncyption(String roomId); + Future handleImageSelection({ + required OnMessageCreated onMessageCreated, + }); + Future handleFileSelection({ + required OnMessageCreated onMessageCreated, + }); + Message mapEventToMessage(Event event); + Status mapEventStatusToMessageStatus(EventStatus status); + String getUrlFromUri({ + required String uri, + int width = 500, + int height = 500, + }); + Future login({ + required String username, + required String password, + }); + Future register({ + required String did, + }); + Future dispose(); + Future init(); + Future getRoomIdFromStorage(String key); + Future setRoomIdInStorage(String key, String roomId); + int getUnreadMessageCount(String? roomId); + Future> retriveMessagesFromDB(String roomId); + Future markMessageAsRead( + List? eventIds, + String? roomId, + ); +} diff --git a/lib/dashboard/drawer/live_chat/view/live_chat_page.dart b/lib/dashboard/drawer/chat_room/view/chat_room_view.dart similarity index 83% rename from lib/dashboard/drawer/live_chat/view/live_chat_page.dart rename to lib/dashboard/drawer/chat_room/view/chat_room_view.dart index 230128ee3..f5b918ab0 100644 --- a/lib/dashboard/drawer/live_chat/view/live_chat_page.dart +++ b/lib/dashboard/drawer/chat_room/view/chat_room_view.dart @@ -1,57 +1,33 @@ import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/l10n/l10n.dart'; -import 'package:altme/theme/app_theme/app_theme.dart'; +import 'package:altme/theme/theme.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_chat_types/flutter_chat_types.dart'; -import 'package:flutter_chat_ui/flutter_chat_ui.dart' hide FileMessage, Message; +import 'package:flutter_chat_ui/flutter_chat_ui.dart' hide Message; import 'package:visibility_detector/visibility_detector.dart'; -class LiveChatPage extends StatelessWidget { - const LiveChatPage({ +class ChatRoomView extends StatefulWidget { + const ChatRoomView({ super.key, - this.hideAppBar = false, + this.appBarTitle, }); - static Route route() { - return MaterialPageRoute( - builder: (_) => const LiveChatPage( - hideAppBar: false, - ), - settings: const RouteSettings(name: '/liveChatPage'), - ); - } - - final bool hideAppBar; - - @override - Widget build(BuildContext context) { - return LiveChatView( - hideAppBar: hideAppBar, - ); - } -} - -class LiveChatView extends StatefulWidget { - const LiveChatView({ - super.key, - this.hideAppBar = false, - }); - final bool hideAppBar; + final String? appBarTitle; @override - State createState() => _ContactUsViewState(); + _ChatRoomViewState createState() => _ChatRoomViewState(); } -class _ContactUsViewState extends State { - late final LiveChatCubit liveChatCubit; +class _ChatRoomViewState extends State { + late final B liveChatCubit; bool pageIsVisible = false; @override void initState() { - liveChatCubit = context.read(); + liveChatCubit = context.read(); super.initState(); } @@ -64,13 +40,14 @@ class _ContactUsViewState extends State { Widget build(BuildContext context) { final l10n = context.l10n; return BasePage( - title: widget.hideAppBar ? null : l10n.altmeSupport, + title: widget.appBarTitle, scrollView: false, - titleLeading: widget.hideAppBar ? null : const BackLeadingButton(), + titleLeading: + widget.appBarTitle == null ? null : const BackLeadingButton(), titleAlignment: Alignment.topCenter, padding: const EdgeInsets.all(Sizes.spaceSmall), - body: BlocBuilder( - builder: (context, state) { + body: BlocBuilder( + builder: (context, ChatRoomState state) { if (state.status == AppStatus.loading) { return const Center( child: CircularProgressIndicator(), diff --git a/lib/dashboard/drawer/drawer.dart b/lib/dashboard/drawer/drawer.dart index 6d3ba728e..55fc59526 100644 --- a/lib/dashboard/drawer/drawer.dart +++ b/lib/dashboard/drawer/drawer.dart @@ -1,10 +1,12 @@ export 'advance_settings/advance_settings.dart'; +export 'altme_support_chat/altme_support_chat.dart'; export 'backup_credential/backup_credential.dart'; +export 'chat_room/chat_room.dart'; export 'contact_us/contact_us.dart'; export 'drawer/drawer.dart'; export 'faqs/faqs.dart'; export 'import_talao_community_card/import_talao_community_card.dart'; -export 'live_chat/live_chat.dart'; +export 'loyalty_card_support_chat/loyalty_card_support_chat.dart'; export 'manage_accounts/manage_accounts.dart'; export 'manage_did/manage_did.dart'; export 'manage_issuers_registry/manage_issuers_registry.dart'; diff --git a/lib/dashboard/drawer/drawer/view/help_center_menu.dart b/lib/dashboard/drawer/drawer/view/help_center_menu.dart index d428c4d04..37e929368 100644 --- a/lib/dashboard/drawer/drawer/view/help_center_menu.dart +++ b/lib/dashboard/drawer/drawer/view/help_center_menu.dart @@ -86,7 +86,11 @@ class HelpCenterView extends StatelessWidget { DrawerItem( title: l10n.altmeSupport, onTap: () { - Navigator.of(context).push(LiveChatPage.route()); + Navigator.of(context).push( + AltmeSupportChatPage.route( + appBarTitle: l10n.altmeSupport, + ), + ); }, ), ], diff --git a/lib/dashboard/drawer/drawer/view/reset_wallet_menu.dart b/lib/dashboard/drawer/drawer/view/reset_wallet_menu.dart index 049a7c21a..1ed8a40d5 100644 --- a/lib/dashboard/drawer/drawer/view/reset_wallet_menu.dart +++ b/lib/dashboard/drawer/drawer/view/reset_wallet_menu.dart @@ -109,13 +109,13 @@ class ResetWalletView extends StatelessWidget { await getSecureStorage.get(SecureStorageKeys.pinCode); if (pinCode?.isEmpty ?? true) { await context.read().resetWallet(); - await context.read().dispose(); + await context.read().dispose(); } else { await Navigator.of(context).push( PinCodePage.route( isValidCallback: () { context.read().resetWallet(); - context.read().dispose(); + context.read().dispose(); }, restrictToBack: false, ), diff --git a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart deleted file mode 100644 index 843fd6010..000000000 --- a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart +++ /dev/null @@ -1,679 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; - -import 'package:altme/app/app.dart'; -import 'package:bloc/bloc.dart'; -import 'package:crypto/crypto.dart'; -import 'package:did_kit/did_kit.dart'; -import 'package:equatable/equatable.dart'; -import 'package:file_picker/file_picker.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_chat_types/flutter_chat_types.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; -import 'package:http/http.dart' as http; -import 'package:image_picker/image_picker.dart'; -import 'package:json_annotation/json_annotation.dart'; -import 'package:matrix/matrix.dart' hide User; -import 'package:mime/mime.dart'; -import 'package:open_filex/open_filex.dart'; -import 'package:path_provider/path_provider.dart'; -import 'package:platform_device_id/platform_device_id.dart'; -import 'package:secure_storage/secure_storage.dart'; -import 'package:uuid/uuid.dart'; - -part 'live_chat_cubit.g.dart'; -part 'live_chat_state.dart'; - -class LiveChatCubit extends Cubit { - LiveChatCubit({ - required this.didKit, - required this.secureStorageProvider, - required this.dioClient, - }) : super( - const LiveChatState(), - ) { - init(); - } - - final SecureStorageProvider secureStorageProvider; - Client? client; - final DioClient dioClient; - final logger = getLogger('LiveChatCubit'); - String? _roomId; - final DIDKitProvider didKit; - StreamSubscription? _onEventSubscription; - StreamController? _notificationStreamController; - - Stream get unreadMessageCountStream { - _notificationStreamController ??= StreamController.broadcast(); - return _notificationStreamController!.stream; - } - - Future onSendPressed(PartialText partialText) async { - try { - final messageId = const Uuid().v4(); - final message = TextMessage( - author: state.user!, - createdAt: DateTime.now().millisecondsSinceEpoch, - id: messageId, - text: partialText.text, - status: Status.sending, - ); - emit(state.copyWith(messages: [message, ...state.messages])); - // - await _checkIfRoomNotExistThenCreateIt(); - final room = client!.getRoomById(_roomId!); - if (room == null) { - await client!.joinRoomById(_roomId!); - } - final eventId = await client!.getRoomById(_roomId!)?.sendTextEvent( - partialText.text, - txid: messageId, - ); - logger.i('send text event: $eventId'); - } catch (e, s) { - logger.e('e: $e , s: $s'); - } - } - - Future handleMessageTap(Message message) async { - if (message is FileMessage) { - var localPath = message.uri; - - if (message.uri.startsWith('http')) { - try { - final index = - state.messages.indexWhere((element) => element.id == message.id); - final updatedMessage = - (state.messages[index] as FileMessage).copyWith( - isLoading: true, - ); - - state.messages[index] = updatedMessage; - emit(state.copyWith(messages: state.messages)); - - final httpClient = http.Client(); - final request = await httpClient.get(Uri.parse(message.uri)); - final bytes = request.bodyBytes; - final documentsDir = (await getApplicationDocumentsDirectory()).path; - localPath = '$documentsDir/${message.name}'; - - if (!File(localPath).existsSync()) { - final file = File(localPath); - await file.writeAsBytes(bytes); - } - } finally { - final index = - state.messages.indexWhere((element) => element.id == message.id); - final updatedMessage = - (state.messages[index] as FileMessage).copyWith( - isLoading: null, - ); - - state.messages[index] = updatedMessage; - emit(state.copyWith(messages: state.messages)); - } - } - - await OpenFilex.open(localPath); - } - } - - void handlePreviewDataFetched( - TextMessage message, - PreviewData previewData, - ) { - final index = state.messages.indexWhere( - (element) => element.id == message.id, - ); - final updatedMessage = (state.messages[index] as TextMessage).copyWith( - previewData: previewData, - ); - state.messages[index] = updatedMessage; - emit(state.copyWith(messages: state.messages)); - } - - Future handleFileSelection() async { - final result = await FilePicker.platform.pickFiles( - type: FileType.any, - ); - - if (result != null && result.files.single.path != null) { - final messageId = const Uuid().v4(); - final message = FileMessage( - author: state.user!, - createdAt: DateTime.now().millisecondsSinceEpoch, - id: messageId, - mimeType: lookupMimeType(result.files.single.path!), - name: result.files.single.name, - size: result.files.single.size, - uri: result.files.single.path!, - status: Status.sending, - ); - emit(state.copyWith(messages: [message, ...state.messages])); - await _checkIfRoomNotExistThenCreateIt(); - await client!.getRoomById(_roomId!)?.sendFileEvent( - MatrixFile( - bytes: File(result.files.single.path!).readAsBytesSync(), - name: result.files.single.name, - ), - txid: messageId, - ); - } - } - - Future handleImageSelection() async { - final result = await ImagePicker().pickImage( - imageQuality: 70, - maxWidth: 1440, - source: ImageSource.gallery, - ); - - if (result != null) { - final bytes = await result.readAsBytes(); - final image = await decodeImageFromList(bytes); - final messageId = const Uuid().v4(); - - final message = ImageMessage( - author: state.user!, - createdAt: DateTime.now().millisecondsSinceEpoch, - height: image.height.toDouble(), - id: messageId, - name: result.name, - size: bytes.length, - uri: result.path, - width: image.width.toDouble(), - status: Status.sending, - ); - - emit(state.copyWith(messages: [message, ...state.messages])); - - await _checkIfRoomNotExistThenCreateIt(); - await client!.getRoomById(_roomId!)?.sendFileEvent( - MatrixFile( - bytes: bytes, - name: result.name, - ), - txid: messageId, - ); - } - } - - Future init() async { - logger.i('init()'); - try { - final ssiKey = await secureStorageProvider.get(SecureStorageKeys.ssiKey); - final did = await secureStorageProvider.get(SecureStorageKeys.did) ?? ''; - final username = did.replaceAll(':', '-'); - if (ssiKey == null || ssiKey.isEmpty || did.isEmpty || username.isEmpty) { - emit( - state.copyWith( - status: AppStatus.error, - message: StateMessage.error( - messageHandler: ResponseMessage( - ResponseString - .RESPONSE_STRING_SOMETHING_WENT_WRONG_TRY_AGAIN_LATER, - ), - ), - ), - ); - return; - } - - emit(state.copyWith(status: AppStatus.loading)); - await _initClient(); - final isUserRegisteredMatrix = await secureStorageProvider - .get(SecureStorageKeys.isUserRegisteredMatrix); - late String userId; - if (isUserRegisteredMatrix != 'true') { - await _register(did: did); - await secureStorageProvider.set( - SecureStorageKeys.isUserRegisteredMatrix, - true.toString(), - ); - userId = await _login( - username: username, - password: await _getPasswordForDID(), - ); - } else { - userId = await _login( - username: username, - password: await _getPasswordForDID(), - ); - } - List retrivedMessageFromDB = []; - final savedRoomId = await _getRoomIdFromStorage(); - if (savedRoomId != null) { - _roomId = savedRoomId; - await _enableRoomEncyption(savedRoomId); - _getUnreadMessageCount(); - await _subscribeToEventsOfRoom(); - retrivedMessageFromDB = await _retriveMessagesFromDB(_roomId!); - } - logger.i('roomId : $_roomId'); - emit( - state.copyWith( - status: AppStatus.init, - user: User(id: userId), - messages: retrivedMessageFromDB, - ), - ); - } catch (e, s) { - logger.e('error: $e, stack: $s'); - if (e is MatrixException) { - emit( - state.copyWith( - status: AppStatus.error, - message: StateMessage.error( - stringMessage: e.errorMessage, - ), - ), - ); - } else { - emit( - state.copyWith( - status: AppStatus.error, - message: StateMessage( - messageHandler: ResponseMessage( - ResponseString - .RESPONSE_STRING_SOMETHING_WENT_WRONG_TRY_AGAIN_LATER, - ), - ), - ), - ); - } - } - } - - Future _subscribeToEventsOfRoom() async { - await _onEventSubscription?.cancel(); - - _onEventSubscription = client!.onRoomState.stream.listen((Event event) { - if (event.roomId == _roomId && event.type == 'm.room.message') { - final txId = event.unsigned?['transaction_id'] as String?; - if (state.messages.any( - (element) => element.id == txId, - )) { - final index = state.messages.indexWhere( - (element) => element.id == txId, - ); - final updatedMessage = state.messages[index].copyWith( - status: _mapEventStatusToMessageStatus(event.status), - ); - final newMessages = List.of(state.messages); - newMessages[index] = updatedMessage; - emit(state.copyWith(messages: newMessages)); - } else { - final Message message = _mapEventToMessage(event); - _getUnreadMessageCount(); - emit( - state.copyWith( - messages: [message, ...state.messages], - ), - ); - } - } - }); - } - - Message _mapEventToMessage(Event event) { - late final Message message; - if (event.messageType == 'm.text') { - message = TextMessage( - id: event.unsigned?['transaction_id'] as String? ?? const Uuid().v4(), - remoteId: event.eventId, - text: event.text, - createdAt: event.originServerTs.millisecondsSinceEpoch, - status: _mapEventStatusToMessageStatus(event.status), - author: User( - id: event.senderId, - ), - ); - } else if (event.messageType == 'm.image') { - message = ImageMessage( - id: const Uuid().v4(), - remoteId: event.eventId, - name: event.body, - size: event.content['info']['size'] as num, - uri: _getUrlFromUri(event.content['url'] as String), - status: _mapEventStatusToMessageStatus(event.status), - createdAt: event.originServerTs.millisecondsSinceEpoch, - author: User( - id: event.senderId, - ), - ); - } else if (event.messageType == 'm.file') { - message = FileMessage( - id: const Uuid().v4(), - remoteId: event.eventId, - name: event.body, - size: event.content['info']['size'] as num, - uri: _getUrlFromUri(event.content['url'] as String), - status: _mapEventStatusToMessageStatus(event.status), - createdAt: event.originServerTs.millisecondsSinceEpoch, - author: User( - id: event.senderId, - ), - ); - } else if (event.messageType == 'm.audio') { - message = AudioMessage( - id: const Uuid().v4(), - remoteId: event.eventId, - duration: Duration( - milliseconds: event.content['info']['duration'] as int, - ), - name: event.body, - size: event.content['info']['size'] as num, - uri: _getUrlFromUri(event.content['url'] as String), - status: _mapEventStatusToMessageStatus(event.status), - createdAt: event.originServerTs.millisecondsSinceEpoch, - author: User( - id: event.senderId, - ), - ); - } else { - message = TextMessage( - id: const Uuid().v4(), - remoteId: event.eventId, - text: event.text, - createdAt: event.originServerTs.millisecondsSinceEpoch, - status: _mapEventStatusToMessageStatus(event.status), - author: User( - id: event.senderId, - ), - ); - } - return message; - } - - int get unreadMessageCount => - client?.getRoomById(_roomId ?? '')?.notificationCount ?? 0; - - void _getUnreadMessageCount() { - final unreadCount = unreadMessageCount; - logger.i('unread message count: $unreadCount'); - _notificationStreamController?.sink.add(unreadCount); - } - - Future markMessageAsRead(List? eventIds) async { - if (eventIds == null || eventIds.isEmpty) return; - - final room = client?.getRoomById(_roomId ?? ''); - if (room == null) return; - try { - for (final eventId in eventIds) { - if (eventId != null) { - await room.postReceipt(eventId); - } - } - } catch (e, s) { - logger.e('e: $e , s: $s'); - } - _getUnreadMessageCount(); - } - - // this function called when state emited in UI and needs - // to set messages as read - void setMessagesAsRead() { - try { - logger.i('setMessagesAsRead'); - if (unreadMessageCount > 0) { - final unreadMessageEventIds = state.messages - .take(unreadMessageCount) - .map((e) => e.remoteId!) - .toList(); - logger.i( - 'unread message event ids lenght: ${unreadMessageEventIds.length}', - ); - markMessageAsRead(unreadMessageEventIds); - } - } catch (e, s) { - logger.e('e: $e, s: $s'); - } - } - - Future> _retriveMessagesFromDB(String roomId) async { - final room = client?.getRoomById(roomId); - if (room == null) return []; - final events = await client!.database?.getEventList(room); - if (events == null || events.isEmpty) return []; - final messageEvents = - events.where((event) => event.type == 'm.room.message').toList() - ..sort( - (e1, e2) => e2.originServerTs.compareTo(e1.originServerTs), - ); - return messageEvents.map(_mapEventToMessage).toList(); - } - - Future _checkIfRoomNotExistThenCreateIt() async { - if (_roomId == null || _roomId!.isEmpty) { - final did = await secureStorageProvider.get(SecureStorageKeys.did) ?? ''; - final username = did.replaceAll(':', '-'); - _roomId = await _createRoomAndInviteSupport( - username, - ); - await _setRoomIdInStorage(_roomId!); - _getUnreadMessageCount(); - await _subscribeToEventsOfRoom(); - } - } - - Future _getRoomIdFromStorage() async { - return secureStorageProvider.get(SecureStorageKeys.supportRoomId); - } - - Future _setRoomIdInStorage(String roomId) async { - await secureStorageProvider.set( - SecureStorageKeys.supportRoomId, - roomId, - ); - } - - /// before calling this function you need to check if - /// room not exist before with this [name] and alias - Future _createRoomAndInviteSupport(String name) async { - try { - final roomId = await client!.createRoom( - isDirect: true, - name: name, - invite: ['@support:matrix.talao.co'], - roomAliasName: name, - initialState: [ - StateEvent( - type: EventTypes.Encryption, - stateKey: '', - content: { - 'algorithm': 'm.megolm.v1.aes-sha2', - }, - ), - ], - ); - await _enableRoomEncyption(roomId); - logger.i('room created! => id: $roomId'); - return roomId; - } catch (e, s) { - logger.e('e: $e, s: $s'); - if (e is MatrixException && e.errcode == 'M_ROOM_IN_USE') { - final millisecondsSinceEpoch = DateTime.now().millisecondsSinceEpoch; - return _createRoomAndInviteSupport( - '$name-updated-$millisecondsSinceEpoch', - ); - } else { - final roomId = await client!.joinRoom(name); - await _enableRoomEncyption(roomId); - return roomId; - } - } - } - - Future _enableRoomEncyption(String roomId) async { - try { - if (roomId.isEmpty) return; - final room = client?.getRoomById(roomId); - if (room == null) return; - if (room.encrypted) { - logger.i('the room with id: ${room.id} encyrpted before!'); - return; - } - await room.enableEncryption(); - final verificationResponse = - await DeviceKeysList(client!.userID!, client!).startVerification(); - logger.i('verification response: $verificationResponse'); - await verificationResponse.acceptVerification(); - } catch (e, s) { - logger.e('error in enabling room e2e encryption, e: $e, s: $s'); - } - } - - Future _initClient() async { - try { - client = Client( - 'AltMeUser', - databaseBuilder: (_) async { - final dir = await getApplicationSupportDirectory(); - final db = HiveCollectionsDatabase('matrix_support_chat', dir.path); - await db.open(); - return db; - }, - ); - client!.homeserver = Uri.parse(Urls.matrixHomeServer); - await client!.init(); - _notificationStreamController ??= StreamController.broadcast(); - } catch (e, s) { - logger.e('e: $e , s: $s'); - await client!.init( - newHomeserver: Uri.parse(Urls.matrixHomeServer), - ); - } - } - - Future _getDidAuth(String did, String nonce) async { - final verificationMethod = - await secureStorageProvider.get(SecureStorageKeys.verificationMethod); - - final options = { - 'verificationMethod': verificationMethod, - 'proofPurpose': 'authentication', - 'challenge': nonce, - 'domain': 'issuer.talao.co', - }; - - final key = (await secureStorageProvider.get(SecureStorageKeys.ssiKey))!; - - final String didAuth = await didKit.didAuth( - did, - jsonEncode(options), - key, - ); - - return didAuth; - } - - Future _getNonce(String did) async { - await dotenv.load(); - final apiKey = dotenv.get('TALAO_MATRIX_API_KEY'); - final nonce = await dioClient.get( - Urls.getNonce, - queryParameters: { - 'did': did, - }, - headers: { - 'X-API-KEY': apiKey, - }, - ) as String; - return nonce; - } - - Future _getPasswordForDID() async { - final ssiKey = (await secureStorageProvider.get(SecureStorageKeys.ssiKey))!; - final bytesToHash = utf8.encode(ssiKey); - final sha256Digest = sha256.convert(bytesToHash); - final password = sha256Digest.toString(); - return password; - } - - Future _register({ - required String did, - }) async { - final nonce = await _getNonce(did); - final didAuth = await _getDidAuth(did, nonce); - await dotenv.load(); - final apiKey = dotenv.get('TALAO_MATRIX_API_KEY'); - final password = await _getPasswordForDID(); - - final data = { - 'username': did, - 'password': password, - 'didAuth': didAuth, - }; - final response = await dioClient.post( - Urls.registerToMatrix, - headers: { - 'X-API-KEY': apiKey, - 'Content-Type': 'application/json', - }, - data: data, - ); - - logger.i('regester response: $response'); - } - - Future _login({ - required String username, - required String password, - }) async { - try { - final isLogged = client!.isLogged(); - if (isLogged) return client!.userID!; - client!.homeserver = Uri.parse(Urls.matrixHomeServer); - final deviceId = await PlatformDeviceId.getDeviceId; - final loginResonse = await client!.login( - LoginType.mLoginPassword, - password: password, - deviceId: deviceId, - identifier: AuthenticationUserIdentifier(user: username), - ); - return loginResonse.userId!; - } catch (e, s) { - logger.i('e: $e, s: $s'); - return '@$username:${Urls.matrixHomeServer.replaceAll('https://', '')}'; - } - } - - Future dispose() async { - try { - await client?.logout().catchError((_) => null); - await client?.dispose().catchError((_) => null); - await _notificationStreamController?.close().catchError((_) => null); - _notificationStreamController = null; - await _onEventSubscription?.cancel().catchError((_) => null); - _onEventSubscription = null; - _roomId = null; - client = null; - } catch (e, s) { - logger.e('e: $e, s: $s'); - } - } - - String _getUrlFromUri(String uri) { - return '${Urls.matrixHomeServer}/_matrix/media/v3/thumbnail/${Urls.matrixHomeServer.replaceAll('https://', '')}/${uri.split('/').last}?width=500&height=500'; - } - - Status _mapEventStatusToMessageStatus(EventStatus status) { - switch (status) { - case EventStatus.error: - return Status.error; - case EventStatus.removed: - return Status.error; - case EventStatus.roomState: - return Status.delivered; - case EventStatus.sending: - return Status.sending; - case EventStatus.sent: - return Status.sent; - case EventStatus.synced: - return Status.seen; - } - } -} diff --git a/lib/dashboard/drawer/live_chat/live_chat.dart b/lib/dashboard/drawer/live_chat/live_chat.dart deleted file mode 100644 index 284049675..000000000 --- a/lib/dashboard/drawer/live_chat/live_chat.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'cubit/live_chat_cubit.dart'; -export 'view/live_chat_page.dart'; diff --git a/lib/dashboard/drawer/loyalty_card_support_chat/cubit/loyalty_card_support_chat_cubit.dart b/lib/dashboard/drawer/loyalty_card_support_chat/cubit/loyalty_card_support_chat_cubit.dart new file mode 100644 index 000000000..8614c4484 --- /dev/null +++ b/lib/dashboard/drawer/loyalty_card_support_chat/cubit/loyalty_card_support_chat_cubit.dart @@ -0,0 +1,11 @@ +import 'package:altme/dashboard/dashboard.dart'; + +class LoyaltyCardSupportChatCubit extends ChatRoomCubit { + LoyaltyCardSupportChatCubit({ + required super.secureStorageProvider, + required super.matrixChat, + required super.invites, + required super.storageKey, + required super.roomNamePrefix, + }); +} diff --git a/lib/dashboard/drawer/loyalty_card_support_chat/loyalty_card_support_chat.dart b/lib/dashboard/drawer/loyalty_card_support_chat/loyalty_card_support_chat.dart new file mode 100644 index 000000000..6b2fcfc49 --- /dev/null +++ b/lib/dashboard/drawer/loyalty_card_support_chat/loyalty_card_support_chat.dart @@ -0,0 +1,2 @@ +export 'cubit/loyalty_card_support_chat_cubit.dart'; +export 'view/loyalty_card_support_chat_page.dart'; diff --git a/lib/dashboard/drawer/loyalty_card_support_chat/view/loyalty_card_support_chat_page.dart b/lib/dashboard/drawer/loyalty_card_support_chat/view/loyalty_card_support_chat_page.dart new file mode 100644 index 000000000..fae840c0a --- /dev/null +++ b/lib/dashboard/drawer/loyalty_card_support_chat/view/loyalty_card_support_chat_page.dart @@ -0,0 +1,50 @@ +import 'package:altme/app/app.dart'; +import 'package:altme/dashboard/dashboard.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:secure_storage/secure_storage.dart'; + +class LoyaltyCardSupportChatPage extends StatelessWidget { + const LoyaltyCardSupportChatPage({ + super.key, + this.appBarTitle, + required this.companySupportId, + required this.loyaltyCardType, + }); + + final String? appBarTitle; + final String companySupportId; + final String loyaltyCardType; + + static Route route({ + String? appBarTitle, + required String companySupportId, + required String loyaltyCardType, + }) { + return MaterialPageRoute( + builder: (_) => LoyaltyCardSupportChatPage( + appBarTitle: appBarTitle, + companySupportId: companySupportId, + loyaltyCardType: loyaltyCardType, + ), + settings: const RouteSettings(name: '/loyaltyCardSupportChatPage'), + ); + } + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (_) => LoyaltyCardSupportChatCubit( + secureStorageProvider: getSecureStorage, + matrixChat: MatrixChatImpl(), + invites: [companySupportId], + storageKey: + '$loyaltyCardType-${SecureStorageKeys.loyaltyCardsupportRoomId}', + roomNamePrefix: loyaltyCardType, + ), + child: ChatRoomView( + appBarTitle: appBarTitle, + ), + ); + } +} 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 00525bdfd..b192b63b9 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 @@ -148,16 +148,27 @@ class _CredentialsDetailsViewState extends State { title: widget.readOnly ? l10n.linkedInProfile : l10n.cardDetails, titleAlignment: Alignment.topCenter, titleLeading: const BackLeadingButton(), - // titleTrailing: IconButton( - // onPressed: () { - // Navigator.of(context) - // .push(CredentialQrPage.route(widget.credentialModel)); // ignore: lines_longer_than_80_chars - // }, - // icon: Icon( - // Icons.qr_code, - // color: Theme.of(context).colorScheme.onBackground, - // ), - // ), + + /// TODO(Taleb): check if json contains the ChatSupport then enable chat button + titleTrailing: IconButton( + onPressed: () { + Navigator.push( + context, + LoyaltyCardSupportChatPage.route( + /// TODO(Taleb): read the ChatSupport property from credential + /// json and replace by this '@bloometa:matrix.talao.co' + companySupportId: '@bloometa:matrix.talao.co', + appBarTitle: l10n.companySupport, + loyaltyCardType: widget.credentialModel.credentialPreview + .credentialSubjectModel.credentialSubjectType.name, + ), + ); + }, + icon: Icon( + Icons.support_agent_rounded, + color: Theme.of(context).colorScheme.onBackground, + ), + ), padding: const EdgeInsets.symmetric(horizontal: 10), scrollView: false, body: Column( @@ -251,7 +262,6 @@ class _CredentialsDetailsViewState extends State { ), ], ), - navigation: widget.readOnly ? null : SafeArea( diff --git a/lib/import_wallet/view/import_from_wallet_page.dart b/lib/import_wallet/view/import_from_wallet_page.dart index adff7b847..96d486d51 100644 --- a/lib/import_wallet/view/import_from_wallet_page.dart +++ b/lib/import_wallet/view/import_from_wallet_page.dart @@ -113,7 +113,7 @@ class _ImportFromOtherWalletViewState extends State { if (state.status == AppStatus.success) { /// Removes every stack except first route (splashPage) if (widget.isFromOnboard) { - context.read().init(); + context.read().init(); Navigator.pushAndRemoveUntil( context, WalletReadyPage.route(), diff --git a/lib/import_wallet/view/import_wallet_page.dart b/lib/import_wallet/view/import_wallet_page.dart index bd7d26ad4..38820c065 100644 --- a/lib/import_wallet/view/import_wallet_page.dart +++ b/lib/import_wallet/view/import_wallet_page.dart @@ -111,7 +111,7 @@ class _ImportWalletViewState extends State { if (state.status == AppStatus.success) { /// Removes every stack except first route (splashPage) if (widget.isFromOnboarding) { - context.read().init(); + context.read().init(); Navigator.pushAndRemoveUntil( context, WalletReadyPage.route(), diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 4fa1e7ddf..8175be518 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -811,5 +811,6 @@ "verifyLater": "Verify Later", "welDone": "Well done!", "mnemonicsVerifiedMessage": "Your revovery phrase is saved correctly.", - "letsGo": "Let's Go" + "letsGo": "Let's Go", + "companySupport": "Company support" } \ No newline at end of file diff --git a/lib/l10n/untranslated.json b/lib/l10n/untranslated.json index 4d13487a5..da05d3a5e 100644 --- a/lib/l10n/untranslated.json +++ b/lib/l10n/untranslated.json @@ -746,7 +746,8 @@ "verifyNow", "verifyLater", "welDone", - "mnemonicsVerifiedMessage" + "mnemonicsVerifiedMessage", + "companySupport" ], "es": [ @@ -1496,7 +1497,8 @@ "verifyNow", "verifyLater", "welDone", - "mnemonicsVerifiedMessage" + "mnemonicsVerifiedMessage", + "companySupport" ], "fr": [ @@ -1529,7 +1531,8 @@ "verifyNow", "verifyLater", "welDone", - "mnemonicsVerifiedMessage" + "mnemonicsVerifiedMessage", + "companySupport" ], "it": [ @@ -2279,6 +2282,7 @@ "verifyNow", "verifyLater", "welDone", - "mnemonicsVerifiedMessage" + "mnemonicsVerifiedMessage", + "companySupport" ] } diff --git a/lib/onboarding/activate_biometircs/view/activate_biometrics_page.dart b/lib/onboarding/activate_biometircs/view/activate_biometrics_page.dart index 2982f25fc..bb807c582 100644 --- a/lib/onboarding/activate_biometircs/view/activate_biometrics_page.dart +++ b/lib/onboarding/activate_biometircs/view/activate_biometrics_page.dart @@ -84,7 +84,7 @@ class ActivateBiometricsView extends StatelessWidget { } if (state.status == AppStatus.success) { - context.read().init(); + context.read().init(); Navigator.pushAndRemoveUntil( context, WalletReadyPage.route(), diff --git a/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart b/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart index 13dd45d85..2bf72c80a 100644 --- a/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart +++ b/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart @@ -77,7 +77,7 @@ class _OnBoardingGenPhraseViewState extends State { } if (state.status == AppStatus.success) { - context.read().init(); + context.read().init(); Navigator.pushAndRemoveUntil( context, WalletReadyPage.route(), diff --git a/lib/onboarding/verify_phrase/view/onboarding_verify_phrase.dart b/lib/onboarding/verify_phrase/view/onboarding_verify_phrase.dart index aa74de446..cef108096 100644 --- a/lib/onboarding/verify_phrase/view/onboarding_verify_phrase.dart +++ b/lib/onboarding/verify_phrase/view/onboarding_verify_phrase.dart @@ -103,7 +103,7 @@ class _OnBoardingVerifyPhraseViewState if (state.status == AppStatus.success) { if (widget.isFromOnboarding) { - context.read().init(); + context.read().init(); Navigator.pushAndRemoveUntil( context, WalletReadyPage.route(), diff --git a/pubspec.lock b/pubspec.lock index 9558188a6..94d91e6aa 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2423,5 +2423,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.19.0 <3.0.0" + dart: ">=2.19.0 <3.7.0" flutter: ">=3.7.0-0" From bb87387139c29e21c77b7310b11858c277b01a91 Mon Sep 17 00:00:00 2001 From: hawkbee1 Date: Tue, 14 Mar 2023 21:04:06 +0100 Subject: [PATCH 163/190] version: 1.12.0+162 --- ios/fastlane/report.xml | 18 ++++++++++++++++++ pubspec.yaml | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 ios/fastlane/report.xml diff --git a/ios/fastlane/report.xml b/ios/fastlane/report.xml new file mode 100644 index 000000000..083df4c87 --- /dev/null +++ b/ios/fastlane/report.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/pubspec.yaml b/pubspec.yaml index 9675c390d..8c4aa493b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: altme description: AltMe Flutter App -version: 1.11.3+161 +version: 1.12.0+162 publish_to: none environment: From ded4be63dc0db4f4ef9771ea07e0883aaed0ce90 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Wed, 15 Mar 2023 10:57:44 +0330 Subject: [PATCH 164/190] some wording in help center menu #1442 --- .../drawer/drawer/view/help_center_menu.dart | 26 +++++++++---------- lib/l10n/arb/app_en.arb | 7 ++--- lib/l10n/untranslated.json | 12 ++++++--- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/lib/dashboard/drawer/drawer/view/help_center_menu.dart b/lib/dashboard/drawer/drawer/view/help_center_menu.dart index 37e929368..a44fa83b8 100644 --- a/lib/dashboard/drawer/drawer/view/help_center_menu.dart +++ b/lib/dashboard/drawer/drawer/view/help_center_menu.dart @@ -46,17 +46,27 @@ class HelpCenterView extends StatelessWidget { height: Sizes.spaceSmall, ), DrawerItem( - title: l10n.faqs, + title: l10n.altmeSupport, onTap: () { - Navigator.of(context).push(FAQsPage.route()); + Navigator.of(context).push( + AltmeSupportChatPage.route( + appBarTitle: l10n.altmeSupport, + ), + ); }, ), DrawerItem( - title: '${l10n.contactUs} : ${AltMeStrings.appSupportMail}', + title: '${l10n.sendAnEmail} : ${AltMeStrings.appSupportMail}', onTap: () { Navigator.of(context).push(ContactUsPage.route()); }, ), + DrawerItem( + title: l10n.faqs, + onTap: () { + Navigator.of(context).push(FAQsPage.route()); + }, + ), DrawerItem( onTap: () { LaunchUrl.launch( @@ -83,16 +93,6 @@ class HelpCenterView extends StatelessWidget { ), ), ), - DrawerItem( - title: l10n.altmeSupport, - onTap: () { - Navigator.of(context).push( - AltmeSupportChatPage.route( - appBarTitle: l10n.altmeSupport, - ), - ); - }, - ), ], ), ), diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 8175be518..b56103b2d 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -772,7 +772,7 @@ "buy": "Buy", "thisFeatureIsNotSupportedYetForFantom": "This feature is not supported yet for Fantom.", "thisFeatureIsNotSupportedYetForBinance": "This feature is not supported yet for Binance.", - "faqs": "FAQs", + "faqs": "Frequently Asked Questions (FAQs)", "softwareLicenses": "Software Licenses", "notAValidWalletAddress": "Not a valid wallet address!", "otherAccount": "Other account", @@ -789,7 +789,7 @@ "scanAndDisplay": "Scan and Display", "whatsNew": "What's new", "okGotIt": "OK, GOT IT!", - "altmeSupport": "AltMe support", + "altmeSupport": "Chat with Altme’s support", "transactionDoneDialogDescription": "It can take a few minutes for the transfer to complete", "withdrawalFailedMessage": "The withdrawal from the account was unsuccessful", "credentialRequiredMessage": "You need to get those credentials in your wallet to acquire this card:", @@ -812,5 +812,6 @@ "welDone": "Well done!", "mnemonicsVerifiedMessage": "Your revovery phrase is saved correctly.", "letsGo": "Let's Go", - "companySupport": "Company support" + "companySupport": "Company support", + "sendAnEmail": "Send an email" } \ No newline at end of file diff --git a/lib/l10n/untranslated.json b/lib/l10n/untranslated.json index da05d3a5e..86f34d277 100644 --- a/lib/l10n/untranslated.json +++ b/lib/l10n/untranslated.json @@ -747,7 +747,8 @@ "verifyLater", "welDone", "mnemonicsVerifiedMessage", - "companySupport" + "companySupport", + "sendAnEmail" ], "es": [ @@ -1498,7 +1499,8 @@ "verifyLater", "welDone", "mnemonicsVerifiedMessage", - "companySupport" + "companySupport", + "sendAnEmail" ], "fr": [ @@ -1532,7 +1534,8 @@ "verifyLater", "welDone", "mnemonicsVerifiedMessage", - "companySupport" + "companySupport", + "sendAnEmail" ], "it": [ @@ -2283,6 +2286,7 @@ "verifyLater", "welDone", "mnemonicsVerifiedMessage", - "companySupport" + "companySupport", + "sendAnEmail" ] } From d851830bc42e78eb1c41c1e1cfa3931600d6e703 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Wed, 15 Mar 2023 11:06:53 +0330 Subject: [PATCH 165/190] replace 'Tokens' by 'Coins' #1443 --- .../tab_bar/tab_controller/view/tab_controller_page.dart | 2 +- lib/l10n/arb/app_en.arb | 2 +- lib/l10n/untranslated.json | 7 ++++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/dashboard/home/tab_bar/tab_controller/view/tab_controller_page.dart b/lib/dashboard/home/tab_bar/tab_controller/view/tab_controller_page.dart index 00a20834f..1e55933a4 100644 --- a/lib/dashboard/home/tab_bar/tab_controller/view/tab_controller_page.dart +++ b/lib/dashboard/home/tab_bar/tab_controller/view/tab_controller_page.dart @@ -116,7 +116,7 @@ class _TabControllerViewState extends State }, ), MyTab( - text: l10n.tokens, + text: l10n.coins, icon: state == 2 ? IconStrings.health : IconStrings.healthBlur, diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index b56103b2d..54d62e897 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -294,7 +294,7 @@ "version": "Version", "cards": "Cards", "nfts": "NFT's", - "tokens": "Tokens", + "coins": "Coins", "getCards": "Get Credentials", "close": "Close", "profile": "Profile", diff --git a/lib/l10n/untranslated.json b/lib/l10n/untranslated.json index 86f34d277..c36d76721 100644 --- a/lib/l10n/untranslated.json +++ b/lib/l10n/untranslated.json @@ -244,7 +244,7 @@ "version", "cards", "nfts", - "tokens", + "coins", "getCards", "close", "profile", @@ -996,7 +996,7 @@ "version", "cards", "nfts", - "tokens", + "coins", "getCards", "close", "profile", @@ -1504,6 +1504,7 @@ ], "fr": [ + "coins", "educationCredentials", "onboardingVerifyPhraseMessage", "onboardingVerifyPhraseMessageDetails", @@ -1783,7 +1784,7 @@ "version", "cards", "nfts", - "tokens", + "coins", "getCards", "close", "profile", From 196846643f116976ab129e3d013325baf70b4955 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Wed, 15 Mar 2023 16:34:16 +0530 Subject: [PATCH 166/190] siopv2 presentation --- .../shared/widget/dialog/confirm_dialog.dart | 7 +- lib/app/view/app.dart | 1 + .../cubit/credential_details_cubit.dart | 4 +- .../cubit/qr_code_scan_cubit.dart | 289 ++++++++---------- .../cubit/qr_code_scan_state.dart | 21 +- ...edential.dart => verify_encoded_data.dart} | 8 +- lib/l10n/arb/app_en.arb | 1 + lib/l10n/untranslated.json | 12 +- lib/scan/cubit/scan_cubit.dart | 12 +- lib/splash/bloclisteners/blocklisteners.dart | 30 +- packages/ebsi/lib/src/ebsi.dart | 85 ++++-- packages/ebsi/pubspec.yaml | 1 + packages/ebsi/test/src/ebsi_test.dart | 38 ++- pubspec.lock | 4 +- 14 files changed, 297 insertions(+), 216 deletions(-) rename lib/ebsi/{verify_ebsi_credential.dart => verify_encoded_data.dart} (76%) diff --git a/lib/app/shared/widget/dialog/confirm_dialog.dart b/lib/app/shared/widget/dialog/confirm_dialog.dart index dc8176ecf..8c90022b4 100644 --- a/lib/app/shared/widget/dialog/confirm_dialog.dart +++ b/lib/app/shared/widget/dialog/confirm_dialog.dart @@ -29,7 +29,8 @@ class ConfirmDialog extends StatelessWidget { Widget build(BuildContext context) { final color = dialogColor ?? Theme.of(context).colorScheme.primary; final background = bgColor ?? Theme.of(context).colorScheme.popupBackground; - final text = textColor ?? Theme.of(context).colorScheme.dialogText; + final textColor = + this.textColor ?? Theme.of(context).colorScheme.dialogText; final l10n = context.l10n; return AlertDialog( @@ -53,7 +54,7 @@ class ConfirmDialog extends StatelessWidget { style: Theme.of(context) .textTheme .defaultDialogTitle - .copyWith(color: text), + .copyWith(color: textColor), textAlign: TextAlign.center, ), const SizedBox(height: Sizes.spaceXSmall), @@ -63,7 +64,7 @@ class ConfirmDialog extends StatelessWidget { style: Theme.of(context) .textTheme .defaultDialogSubtitle - .copyWith(color: text), + .copyWith(color: textColor), textAlign: TextAlign.center, ), const SizedBox(height: 24), diff --git a/lib/app/view/app.dart b/lib/app/view/app.dart index 4d8dfb215..7d3de1fd5 100644 --- a/lib/app/view/app.dart +++ b/lib/app/view/app.dart @@ -129,6 +129,7 @@ class App extends StatelessWidget { walletCubit: context.read(), beacon: Beacon(), walletConnectCubit: context.read(), + secureStorageProvider: secure_storage.getSecureStorage, ), ), BlocProvider( diff --git a/lib/dashboard/home/tab_bar/credentials/detail/cubit/credential_details_cubit.dart b/lib/dashboard/home/tab_bar/credentials/detail/cubit/credential_details_cubit.dart index 23e074402..4fd07cfb9 100644 --- a/lib/dashboard/home/tab_bar/credentials/detail/cubit/credential_details_cubit.dart +++ b/lib/dashboard/home/tab_bar/credentials/detail/cubit/credential_details_cubit.dart @@ -2,7 +2,7 @@ import 'dart:convert'; import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; -import 'package:altme/ebsi/verify_ebsi_credential.dart'; +import 'package:altme/ebsi/verify_encoded_data.dart'; import 'package:did_kit/did_kit.dart'; import 'package:ebsi/ebsi.dart'; import 'package:equatable/equatable.dart'; @@ -51,7 +51,7 @@ class CredentialDetailsCubit extends Cubit { final issuerDid = item.data['issuer']! as String; //const issuerDid = 'did:ebsi:zeFCExU2XAAshYkPCpjuahA'; - final VerificationType isVerified = await isEbsiCredentialVerified( + final VerificationType isVerified = await verifyEncodedData( issuerDid, client, secureStorageProvider, 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 546ad10c4..1dd46dcc7 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 @@ -5,6 +5,7 @@ import 'package:altme/connection_bridge/connection_bridge.dart'; import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/deep_link/deep_link.dart'; import 'package:altme/ebsi/initiate_ebsi_credential_issuance.dart'; +import 'package:altme/ebsi/verify_encoded_data.dart'; import 'package:altme/issuer_websites_page/issuer_websites.dart'; import 'package:altme/query_by_example/query_by_example.dart'; import 'package:altme/scan/scan.dart'; @@ -12,6 +13,7 @@ import 'package:altme/wallet/wallet.dart'; import 'package:beacon_flutter/beacon_flutter.dart'; import 'package:bloc/bloc.dart'; import 'package:credential_manifest/credential_manifest.dart'; +import 'package:ebsi/ebsi.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:json_annotation/json_annotation.dart'; @@ -34,6 +36,7 @@ class QRCodeScanCubit extends Cubit { required this.jwtDecode, required this.beacon, required this.walletConnectCubit, + required this.secureStorageProvider, }) : super(const QRCodeScanState()); final DioClient client; @@ -46,6 +49,7 @@ class QRCodeScanCubit extends Cubit { final JWTDecode jwtDecode; final Beacon beacon; final WalletConnectCubit walletConnectCubit; + final SecureStorageProvider secureStorageProvider; final log = getLogger('QRCodeScanCubit'); @@ -82,7 +86,8 @@ class QRCodeScanCubit extends Cubit { emit(state.copyWith(qrScanStatus: QrScanStatus.goBack)); } else { - await host(url: scannedResponse); + final uri = Uri.parse(scannedResponse); + await verify(uri: uri); } } on FormatException { log.i('Format Exception'); @@ -112,55 +117,13 @@ class QRCodeScanCubit extends Cubit { } } - Future host({required String? url}) async { - emit(state.loading(isScan: true)); - try { - final isInternetAvailable = await isConnected(); - if (!isInternetAvailable) { - throw NetworkException( - message: NetworkError.NETWORK_ERROR_NO_INTERNET_CONNECTION, - ); - } - if (url == null || url.isEmpty) { - throw ResponseMessage( - ResponseString.RESPONSE_STRING_THIS_QR_CODE_IS_NOT_SUPPORTED, - ); - } else { - final uri = Uri.parse(url); - await verify(uri: uri, isScan: true); - } - } on FormatException { - emit( - state.error( - messageHandler: ResponseMessage( - ResponseString.RESPONSE_STRING_THIS_QR_CODE_IS_NOT_SUPPORTED, - ), - ), - ); - } catch (e) { - if (e is MessageHandler) { - emit(state.error(messageHandler: e)); - } else { - emit( - state.error( - messageHandler: ResponseMessage( - ResponseString - .RESPONSE_STRING_SOMETHING_WENT_WRONG_TRY_AGAIN_LATER, - ), - ), - ); - } - } - } - Future deepLink() async { - emit(state.loading(isScan: false)); final deepLinkUrl = deepLinkCubit.state; + emit(state.loading(isScan: false)); if (deepLinkUrl != '') { deepLinkCubit.resetDeepLink(); try { - final uri = Uri.parse(deepLinkUrl); - await verify(uri: uri, isScan: false); + await verify(uri: Uri.parse(deepLinkUrl)); } on FormatException { emit( state.error( @@ -178,18 +141,22 @@ class QRCodeScanCubit extends Cubit { emit(state.error(messageHandler: messageHandler)); } - Future verify({ - required Uri? uri, - required bool isScan, - }) async { - emit(state.loading(isScan: isScan)); + Future verify({required Uri uri, bool? isScan}) async { + emit( + state.copyWith( + uri: uri, + qrScanStatus: QrScanStatus.loading, + isScan: isScan, + ), + ); + try { /// verifier side (siopv2) without request_uri - if (uri?.queryParameters['scope'] == 'openid') { + if (state.uri?.queryParameters['scope'] == 'openid') { // Check if we can respond to presentation request: // having credentials? // having correct crv in ebsi key - await launchSiopV2Flow(uri); + await launchSiopV2RequestFlow(); // final openIdCredential = getCredentialName(sIOPV2Param.claims!); // final openIdIssuer = getIssuersName(sIOPV2Param.claims!); @@ -247,8 +214,12 @@ class QRCodeScanCubit extends Cubit { // ), // ), // ); + } else if (state.uri.toString().startsWith('openid://?client_id')) { + /// ebsi presentation + /// verifier side (siopv2) with request_uri + await verifySiopv2Jwt(state.uri); } else { - emit(state.acceptHost(uri: uri!)); + emit(state.acceptHost(isRequestVerified: true)); } } catch (e) { log.e(e); @@ -267,11 +238,12 @@ class QRCodeScanCubit extends Cubit { } } - Future launchSiopV2Flow(Uri? uri) async { + Future launchSiopV2RequestFlow() async { // Check if we can respond to presentation request: // having credentials? // having correct crv in ebsi key - if (!await isSiopV2WithRequestURIValid(uri!)) { + + if (!await isSiopV2WithRequestURIValid(state.uri!)) { emit( state.copyWith( qrScanStatus: QrScanStatus.success, @@ -279,56 +251,43 @@ class QRCodeScanCubit extends Cubit { ), ); } else { - var claims = uri.queryParameters['claims'] ?? ''; - - // TODO(hawkbee): change when correction is done on verifier - claims = claims.replaceAll("'email': None", "'email': 'None'"); - - claims = claims.replaceAll("'", '"'); - final jsonPath = JsonPath(r'$..input_descriptors'); - final outputDescriptors = - jsonPath.readValues(jsonDecode(claims)).first as List; - final inputDescriptorList = outputDescriptors - .map((e) => InputDescriptor.fromJson(e as Map)) - .toList(); - - final PresentationDefinition presentationDefinition = - PresentationDefinition(inputDescriptorList); - final CredentialModel credentialPreview = CredentialModel( - id: 'id', - image: 'image', - credentialPreview: Credential.dummy(), - shareLink: 'shareLink', - display: Display.emptyDisplay(), - data: const {}, - credentialManifest: CredentialManifest( - 'id', - IssuedBy('', ''), - null, - presentationDefinition, - ), - ); - emit( - state.copyWith( - qrScanStatus: QrScanStatus.success, - route: CredentialManifestOfferPickPage.route( - uri: uri, - credential: credentialPreview, - issuer: Issuer.emptyIssuer('domain'), - inputDescriptorIndex: 0, - credentialsToBePresented: [], - ), - ), + final claims = state.uri?.queryParameters['claims'] ?? ''; + await completeSiopV2WithClaim(claims: claims); + } + } + + late dynamic encodedData; + + Future verifySiopv2Jwt(Uri? uri) async { + final requestUri = state.uri!.queryParameters['request_uri'].toString(); + + encodedData = await fetchRequestUriPayload(url: requestUri); + + final Map response = decoder(token: encodedData as String); + + final String issuerDid = jsonEncode(response['client_id']); + + //check Signature + try { + final VerificationType isVerified = await verifyEncodedData( + issuerDid, + client, + secureStorageProvider, + encodedData.toString(), ); + + if (isVerified == VerificationType.verified) { + emit(state.acceptHost(isRequestVerified: true)); + } else { + emit(state.acceptHost(isRequestVerified: false)); + } + } catch (e) { + emit(state.acceptHost(isRequestVerified: false)); } } - Future accept({ - required Uri uri, - required Issuer issuer, - required bool isScan, - }) async { - emit(state.loading(isScan: isScan)); + Future accept({required Issuer issuer}) async { + emit(state.loading()); final log = getLogger('QRCodeScanCubit - accept'); late final dynamic data; @@ -348,55 +307,15 @@ class QRCodeScanCubit extends Cubit { return; } - /// ebsi presentation - /// verifier side (siopv2) without request_uri - if (state.uri?.queryParameters['scope'] == 'openid') { - var claims = uri.queryParameters['claims'] ?? ''; - - // TODO(hawkbee): change when correction is done on verifier - claims = claims.replaceAll("'email': None", "'email': 'None'"); - - claims = claims.replaceAll("'", '"'); - final jsonPath = JsonPath(r'$..input_descriptors'); - final outputDescriptors = - jsonPath.readValues(jsonDecode(claims)).first as List; - final inputDescriptorList = outputDescriptors - .map((e) => InputDescriptor.fromJson(e as Map)) - .toList(); - - final PresentationDefinition presentationDefinition = - PresentationDefinition(inputDescriptorList); - final CredentialModel credentialPreview = CredentialModel( - id: 'id', - image: 'image', - credentialPreview: Credential.dummy(), - shareLink: 'shareLink', - display: Display.emptyDisplay(), - data: const {}, - credentialManifest: CredentialManifest( - 'id', - IssuedBy('', ''), - null, - presentationDefinition, - ), - ); - emit( - state.copyWith( - qrScanStatus: QrScanStatus.success, - route: CredentialManifestOfferPickPage.route( - uri: uri, - credential: credentialPreview, - issuer: Issuer.emptyIssuer('domain'), - inputDescriptorIndex: 0, - credentialsToBePresented: [], - ), - ), - ); + if (state.uri.toString().startsWith('openid://?client_id')) { + /// ebsi presentation + /// verifier side (siopv2) with request_uri + await launchSiopV2WithRequestUriFlow(state.uri); return; } /// did credential addition and presentation - final dynamic response = await client.get(uri.toString()); + final dynamic response = await client.get(state.uri!.toString()); data = response is String ? jsonDecode(response) : response; log.i('data - $data'); @@ -447,7 +366,7 @@ class QRCodeScanCubit extends Cubit { state.copyWith( qrScanStatus: QrScanStatus.success, route: CredentialsReceivePage.route( - uri: uri, + uri: state.uri!, preview: data as Map, issuer: issuer, ), @@ -491,7 +410,7 @@ class QRCodeScanCubit extends Cubit { done: (done) { log.i('done'); }, - uri: uri, + uri: state.uri!, challenge: data['challenge'] as String, domain: data['domain'] as String, ); @@ -502,7 +421,7 @@ class QRCodeScanCubit extends Cubit { state.copyWith( qrScanStatus: QrScanStatus.success, route: QueryByExamplePresentPage.route( - uri: uri, + uri: state.uri!, preview: data as Map, issuer: issuer, ), @@ -518,7 +437,7 @@ class QRCodeScanCubit extends Cubit { state.copyWith( qrScanStatus: QrScanStatus.success, route: QueryByExamplePresentPage.route( - uri: uri, + uri: state.uri!, preview: data as Map, issuer: issuer, ), @@ -532,7 +451,7 @@ class QRCodeScanCubit extends Cubit { state.copyWith( qrScanStatus: QrScanStatus.success, route: CredentialsReceivePage.route( - uri: uri, + uri: state.uri!, preview: data as Map, issuer: issuer, ), @@ -564,6 +483,66 @@ class QRCodeScanCubit extends Cubit { } } + Future launchSiopV2WithRequestUriFlow(Uri? uri) async { + final Map response = decoder(token: encodedData as String); + + final String claims = jsonEncode(response['claims']); + + //update uri + + final redirectUri = response['redirect_uri'] ?? ''; + final nonce = response['nonce'] ?? ''; + + final updatedUri = Uri.parse( + '${state.uri}&redirect_uri=$redirectUri&nonce=$nonce', + ); + + emit(state.copyWith(uri: updatedUri)); + await completeSiopV2WithClaim(claims: claims); + } + + Future completeSiopV2WithClaim({required String claims}) async { + // TODO(hawkbee): change when correction is done on verifier + claims = claims.replaceAll("'email': None", "'email': 'None'"); + + claims = claims.replaceAll("'", '"'); + final jsonPath = JsonPath(r'$..input_descriptors'); + final outputDescriptors = + jsonPath.readValues(jsonDecode(claims)).first as List; + final inputDescriptorList = outputDescriptors + .map((e) => InputDescriptor.fromJson(e as Map)) + .toList(); + + final PresentationDefinition presentationDefinition = + PresentationDefinition(inputDescriptorList); + final CredentialModel credentialPreview = CredentialModel( + id: 'id', + image: 'image', + credentialPreview: Credential.dummy(), + shareLink: 'shareLink', + display: Display.emptyDisplay(), + data: const {}, + credentialManifest: CredentialManifest( + 'id', + IssuedBy('', ''), + null, + presentationDefinition, + ), + ); + emit( + state.copyWith( + qrScanStatus: QrScanStatus.success, + route: CredentialManifestOfferPickPage.route( + uri: state.uri!, + credential: credentialPreview, + issuer: Issuer.emptyIssuer('domain'), + inputDescriptorIndex: 0, + credentialsToBePresented: [], + ), + ), + ); + } + bool requestAttributeExists(Uri uri) { var condition = false; uri.queryParameters.forEach((key, value) { @@ -610,7 +589,7 @@ class QRCodeScanCubit extends Cubit { final dynamic encodedData = await fetchRequestUriPayload(url: request_uri!); if (encodedData != null) { - requestUriPayload = decoder(token: encodedData as String); + requestUriPayload = decoder(token: encodedData as String).toString(); } } return SIOPV2Param( @@ -635,13 +614,13 @@ class QRCodeScanCubit extends Cubit { return data; } - String decoder({required String token}) { + Map decoder({required String token}) { final log = getLogger('QRCodeScanCubit - jwtDecode'); - late final String data; + late final Map data; try { final payload = jwtDecode.parseJwt(token); - data = payload.toString(); + data = payload; } catch (e) { log.e('An error occurred while decoding.', e); } diff --git a/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_state.dart b/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_state.dart index 83f926fe9..4628476c1 100644 --- a/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_state.dart +++ b/lib/dashboard/qr_code/qr_code_scan/cubit/qr_code_scan_state.dart @@ -7,6 +7,7 @@ class QRCodeScanState extends Equatable { this.uri, this.route, this.isScan = false, + this.isRequestVerified = true, this.message, }); @@ -18,23 +19,26 @@ class QRCodeScanState extends Equatable { @JsonKey(includeFromJson: false, includeToJson: false) final Route? route; final bool isScan; + final bool isRequestVerified; final StateMessage? message; Map toJson() => _$QRCodeScanStateToJson(this); - QRCodeScanState loading({required bool isScan}) { + QRCodeScanState loading({bool? isScan}) { return QRCodeScanState( status: QrScanStatus.loading, - isScan: isScan, + isScan: isScan ?? this.isScan, uri: uri, + isRequestVerified: isRequestVerified, ); } - QRCodeScanState acceptHost({required Uri uri}) { + QRCodeScanState acceptHost({required bool isRequestVerified}) { return QRCodeScanState( status: QrScanStatus.acceptHost, isScan: isScan, uri: uri, + isRequestVerified: isRequestVerified, ); } @@ -44,6 +48,7 @@ class QRCodeScanState extends Equatable { message: StateMessage.error(messageHandler: messageHandler), isScan: isScan, uri: uri, + isRequestVerified: isRequestVerified, ); } @@ -51,16 +56,20 @@ class QRCodeScanState extends Equatable { QrScanStatus qrScanStatus = QrScanStatus.idle, StateMessage? message, Route? route, + Uri? uri, + bool? isScan, }) { return QRCodeScanState( status: qrScanStatus, message: message, - isScan: isScan, - uri: uri, + isScan: isScan ?? this.isScan, + uri: uri ?? this.uri, route: route ?? this.route, + isRequestVerified: isRequestVerified, ); } @override - List get props => [status, uri, route, isScan, message]; + List get props => + [status, uri, route, isScan, message, isRequestVerified]; } diff --git a/lib/ebsi/verify_ebsi_credential.dart b/lib/ebsi/verify_encoded_data.dart similarity index 76% rename from lib/ebsi/verify_ebsi_credential.dart rename to lib/ebsi/verify_encoded_data.dart index c1df70892..2af69541e 100644 --- a/lib/ebsi/verify_ebsi_credential.dart +++ b/lib/ebsi/verify_encoded_data.dart @@ -3,11 +3,11 @@ import 'package:dio/dio.dart'; import 'package:ebsi/ebsi.dart'; import 'package:secure_storage/secure_storage.dart'; -Future isEbsiCredentialVerified( +Future verifyEncodedData( String issuerDid, DioClient client, SecureStorageProvider secureStorageProvider, - String vcJwt, + String jwt, ) async { final Ebsi ebsi = Ebsi(Dio()); @@ -16,9 +16,9 @@ Future isEbsiCredentialVerified( final holderKid = await ebsi.getKid(null, p256PrivateKey); - final VerificationType verificationType = await ebsi.verifyCredential( + final VerificationType verificationType = await ebsi.verifyEncodedData( issuerDid: issuerDid, - vcJwt: vcJwt, + jwt: jwt, holderKid: holderKid, ); return verificationType; diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 54d62e897..136abe0bd 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -814,4 +814,5 @@ "letsGo": "Let's Go", "companySupport": "Company support", "sendAnEmail": "Send an email" + "service_not_registered_message": "This is services is not registered." } \ No newline at end of file diff --git a/lib/l10n/untranslated.json b/lib/l10n/untranslated.json index c36d76721..4327bd6bd 100644 --- a/lib/l10n/untranslated.json +++ b/lib/l10n/untranslated.json @@ -748,7 +748,8 @@ "welDone", "mnemonicsVerifiedMessage", "companySupport", - "sendAnEmail" + "sendAnEmail", + "service_not_registered_message" ], "es": [ @@ -1500,7 +1501,8 @@ "welDone", "mnemonicsVerifiedMessage", "companySupport", - "sendAnEmail" + "sendAnEmail", + "service_not_registered_message" ], "fr": [ @@ -1536,7 +1538,8 @@ "welDone", "mnemonicsVerifiedMessage", "companySupport", - "sendAnEmail" + "sendAnEmail", + "service_not_registered_message" ], "it": [ @@ -2288,6 +2291,7 @@ "welDone", "mnemonicsVerifiedMessage", "companySupport", - "sendAnEmail" + "sendAnEmail", + "service_not_registered_message" ] } diff --git a/lib/scan/cubit/scan_cubit.dart b/lib/scan/cubit/scan_cubit.dart index 8d4b090bf..5b9464c0f 100644 --- a/lib/scan/cubit/scan_cubit.dart +++ b/lib/scan/cubit/scan_cubit.dart @@ -56,18 +56,18 @@ class ScanCubit extends Cubit { final log = getLogger('ScanCubit - credentialOffer'); try { - if (uri.queryParameters['scope'] == 'openid') { + if (uri.queryParameters['scope'] == 'openid' || + uri.toString().startsWith('openid://?client_id')) { final ebsi = Ebsi(Dio()); - final mnemonic = - await getSecureStorage.get(SecureStorageKeys.ssiMnemonic); - final privateKey = - await ebsi.privateKeyFromMnemonic(mnemonic: mnemonic!); + + final String p256PrivateKey = + await getRandomP256PrivateKey(secureStorageProvider); final credentialList = credentialsToBePresented! .map((e) => jsonEncode(e.toJson())) .toList(); - await ebsi.sendPresentation(uri, credentialList, null, privateKey); + await ebsi.sendPresentation(uri, credentialList, null, p256PrivateKey); await presentationActivity( credentialModels: credentialsToBePresented, diff --git a/lib/splash/bloclisteners/blocklisteners.dart b/lib/splash/bloclisteners/blocklisteners.dart index 4ccea1db0..37a6c94e4 100644 --- a/lib/splash/bloclisteners/blocklisteners.dart +++ b/lib/splash/bloclisteners/blocklisteners.dart @@ -176,7 +176,13 @@ final qrCodeBlocListener = BlocListener( } /// verifier side (siopv2) without request_uri - if (state.uri?.queryParameters['scope'] == 'openid') { + // if (state.uri?.queryParameters['scope'] == 'openid') { + // isIssuerVerificationSettingTrue = + // state.uri!.queryParameters['request_uri'] != null; + // } + + /// verifier side (siopv2) with request_uri + if (state.uri.toString().startsWith('openid://?client_id')) { isIssuerVerificationSettingTrue = state.uri!.queryParameters['request_uri'] != null; } @@ -219,15 +225,27 @@ final qrCodeBlocListener = BlocListener( } /// verifier side (siopv2) without request_uri - if (state.uri?.queryParameters['scope'] == 'openid') { + // if (state.uri?.queryParameters['scope'] == 'openid') { + // subtitle = state.uri!.queryParameters['request_uri'].toString(); + // } + + /// verifier side (siopv2) with request_uri + if (state.uri.toString().startsWith('openid://?client_id')) { subtitle = state.uri!.queryParameters['request_uri'].toString(); } + String title = l10n.scanPromptHost; + + if (!state.isRequestVerified) { + title = '${l10n.service_not_registered_message} ' + '${l10n.scanPromptHost}'; + } + acceptHost = await showDialog( context: context, builder: (BuildContext context) { return ConfirmDialog( - title: l10n.scanPromptHost, + title: title, subtitle: subtitle, yes: l10n.communicationHostAllow, no: l10n.communicationHostDeny, @@ -239,11 +257,7 @@ final qrCodeBlocListener = BlocListener( } if (acceptHost) { - await context.read().accept( - uri: state.uri!, - issuer: approvedIssuer, - isScan: state.isScan, - ); + await context.read().accept(issuer: approvedIssuer); } else { await context.read().emitError( ResponseMessage( diff --git a/packages/ebsi/lib/src/ebsi.dart b/packages/ebsi/lib/src/ebsi.dart index a5f2b6baf..2af6efd9f 100644 --- a/packages/ebsi/lib/src/ebsi.dart +++ b/packages/ebsi/lib/src/ebsi.dart @@ -4,6 +4,7 @@ import 'dart:convert'; import 'package:bip32/bip32.dart' as bip32; import 'package:bip39/bip39.dart' as bip393; +import 'package:cryptography/cryptography.dart' as cryptography; import 'package:dio/dio.dart'; import 'package:ebsi/src/issuer_token_parameters.dart'; import 'package:ebsi/src/token_parameters.dart'; @@ -335,17 +336,17 @@ class Ebsi { final vcJwt = await getIssuerJwt(issuerTokenParameters, nonce); - final issuerDid = readIssuerDid(openidConfigurationResponse); + //final issuerDid = readIssuerDid(openidConfigurationResponse); - final isVerified = await verifyCredential( - issuerDid: issuerDid, - vcJwt: vcJwt, - holderKid: issuerTokenParameters.kid, - ); + // final isVerified = await verifyEncodedData( + // issuerDid: issuerDid, + // jwt: vcJwt, + // holderKid: issuerTokenParameters.kid, + // ); - if (isVerified == VerificationType.notVerified) { - throw Exception('VERIFICATION_ISSUE'); - } + // if (isVerified == VerificationType.notVerified) { + // throw Exception('VERIFICATION_ISSUE'); + // } final credentialType = credentialRequestUri.queryParameters['credential_type']; @@ -357,26 +358,43 @@ class Ebsi { return credentialData; } - Future verifyCredential({ + Future verifyEncodedData({ required String issuerDid, required String holderKid, - required String vcJwt, + required String jwt, }) async { try { final didDocument = await getDidDocument(issuerDid); final publicKeyJwk = readPublicKeyJwk(holderKid, didDocument); - // create a JsonWebSignature from the encoded string - final jws = JsonWebSignature.fromCompactSerialization(vcJwt); + final kty = publicKeyJwk['kty'].toString(); + + late final bool isVerified; + if (kty == 'OKP') { + var xString = publicKeyJwk['x'].toString(); + final paddingLength = 4 - (xString.length % 4); + xString += '=' * paddingLength; - // create a JsonWebKey for verifying the signature - final keyStore = JsonWebKeyStore() - ..addKey( - JsonWebKey.fromJson(publicKeyJwk), + final publicKeyBytes = base64Url.decode(xString); + + final publicKey = cryptography.SimplePublicKey( + publicKeyBytes, + type: cryptography.KeyPairType.ed25519, ); - final isVerified = await jws.verify(keyStore); + isVerified = await verifyJwt(jwt, publicKey); + } else { + // create a JsonWebSignature from the encoded string + final jws = JsonWebSignature.fromCompactSerialization(jwt); + + // create a JsonWebKey for verifying the signature + final keyStore = JsonWebKeyStore() + ..addKey( + JsonWebKey.fromJson(publicKeyJwk), + ); + isVerified = await jws.verify(keyStore); + } if (isVerified) { return VerificationType.verified; @@ -388,6 +406,33 @@ class Ebsi { } } + Future verifyJwt( + String vcJwt, + cryptography.SimplePublicKey publicKey, + ) async { + final parts = vcJwt.split('.'); + + final header = parts[0]; + final payload = parts[1]; + + final message = utf8.encode('$header.$payload'); + + // Get the signature + var signatureString = parts[2]; + final paddingLength = 4 - (signatureString.length % 4); + signatureString += '=' * paddingLength; + final signatureBytes = base64Url.decode(signatureString); + + final signature = + cryptography.Signature(signatureBytes, publicKey: publicKey); + + //verify signature + final result = + await cryptography.Ed25519().verify(message, signature: signature); + + return result; + } + String readCredentialEndpoint( Response> openidConfigurationResponse, ) { @@ -519,6 +564,8 @@ class Ebsi { 'nonce': tokenParameters.nonce }; + print(vpTokenPayload); + final verifierVpJwt = generateToken(vpTokenPayload, tokenParameters); return verifierVpJwt; @@ -536,6 +583,8 @@ class Ebsi { privateKey['crv'] = 'P-256K'; } + print(tokenParameters.publicJWK); + final key = JsonWebKey.fromJson(tokenParameters.privateKey); final vpBuilder = JsonWebSignatureBuilder() diff --git a/packages/ebsi/pubspec.yaml b/packages/ebsi/pubspec.yaml index 12bc58229..0784d5fd7 100644 --- a/packages/ebsi/pubspec.yaml +++ b/packages/ebsi/pubspec.yaml @@ -11,6 +11,7 @@ dependencies: bip32: ^2.0.0 bip39: ^1.0.6 crypto: ^3.0.2 + cryptography: ^2.5.0 dart_bip32_bip44: ^0.2.0 dart_jsonwebtoken: ^2.7.1 dart_web3: ^0.0.3 diff --git a/packages/ebsi/test/src/ebsi_test.dart b/packages/ebsi/test/src/ebsi_test.dart index 141a3d42a..8155e293a 100644 --- a/packages/ebsi/test/src/ebsi_test.dart +++ b/packages/ebsi/test/src/ebsi_test.dart @@ -422,9 +422,10 @@ void main() { }); }); - group('verify credential', () { + group('verify encoded data', () { const issuerDid1 = 'did:ebsi:zeFCExU2XAAshYkPCpjuahA'; const issuerDid2 = 'did:ebsi:zhSw5rPXkcHjvquwnVcTzzC'; + const issuerDid3 = 'did:ebsi:zhSw5rPXkcHjvquwnVcTzzC'; const holderKid = 'did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53'; @@ -441,6 +442,11 @@ void main() { const didDocumentResponse2 = '{"assertionMethod":["did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53"],"authentication":["did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53"],"@context":"https://www.w3.org/ns/did/v1","id":"did:ebsi:zeFCExU2XAAshYkPCpjuahA","verificationMethod":[{"controller":"did:ebsi:zeFCExU2XAAshYkPCpjuahA","id":"did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53","publicKeyJwk":{"crv":"Ed25519","kty":"OKP","x":"AekpBQ8ST8a8VcfVOTNl353vSrDCLLJXmPk06wTjxrrjcBpXp5EOnYG_NjFZ6OvLFV1jSfS9tsz4qUxcWceqwQGk","y":"ADSmRA43Z1DSNx_RvcLI87cdL07l6jQyyBXMoxVg_l2Th-x3S1WDhjDly79ajL4Kkd0AZMaZmh9ubmf63e3kyMj2"},"type":"Ed25519VerificationKey2019"}]}'; + const didDocumentUrl3 = + 'https://api-pilot.ebsi.eu/did-registry/v3/identifiers/$issuerDid3'; + + const didDocumentResponse3 = + '{"assertionMethod":["did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53"],"authentication":["did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53"],"@context":"https://www.w3.org/ns/did/v1","id":"did:ebsi:zeFCExU2XAAshYkPCpjuahA","verificationMethod":[{"controller":"did:ebsi:zeFCExU2XAAshYkPCpjuahA","id":"did:ebsi:zeFCExU2XAAshYkPCpjuahA#3623b877bbb24b08ba390f3585418f53","publicKeyJwk":{"alg":"ES256K-R","crv":"secp256k1","kid":"3623b877bbb24b08ba390f3585418f53","kty":"EC","use":"sig","x":"9GVoP1wJSgenjKaxA16LzkzeAKY8-wZX1ZmDr1Oe0s4"},"type":"Ed25519VerificationKey2019"}]}'; dioAdapter ..onGet( didDocumentUrl, @@ -449,6 +455,10 @@ void main() { ..onGet( didDocumentUrl2, (request) => request.reply(200, jsonDecode(didDocumentResponse2)), + ) + ..onGet( + didDocumentUrl3, + (request) => request.reply(200, jsonDecode(didDocumentResponse3)), ); final ebsi = Ebsi(client); @@ -459,9 +469,9 @@ void main() { 'wqT2SI-KGDKB34XO0aw_7XdtAG8GaSwFKdCAPZgoXD2YBJZCPEX3xKpRwcdOO8Kp' 'EHwJjyqOgzDO7iKvU8vcnwNrmxYbSW9ERBXukOXolLzeO_Jn'; - final isVerified = await ebsi.verifyCredential( - issuerDid: issuerDid1, - vcJwt: vcJwt, + final isVerified = await ebsi.verifyEncodedData( + issuerDid: issuerDid2, + jwt: vcJwt, holderKid: holderKid, ); @@ -472,22 +482,34 @@ void main() { const vcJwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksiLCJqd2siOnsiY3J2IjoiUC0yNTZLIiwia3R5IjoiRUMiLCJ4IjoiSjR2UXRMVXlyVlVpRklYUnJ0RXE0eHVybUJacDJlcTl3Sm1Ya0lBX3N0SSIsInkiOiJFVVU2dlhvRzNCR1gyenp3alhyR0RjcjRFeUREMFZmazNfNWZnNWtTZ0tFIn0sImtpZCI6ImRpZDplYnNpOnpvOUZSMVlmQUtGUDNRNmR2cWh4Y1h4bmZlRGlKRFA5N2ttbnFoeUFVU0FDaiNDZ2NnMXk5eGo5dVdGdzU2UE1jMjlYQmQ5RVJlaXh6dm5mdEJ6OEp3UUZpQiJ9.eyJpc3MiOiJkaWQ6ZWJzaTp6bzlGUjFZZkFLRlAzUTZkdnFoeGNYeG5mZURpSkRQOTdrbW5xaHlBVVNBQ2oiLCJub25jZSI6IjdhMDdkZTBmLWE4NzktMTFlZC04MjJiLTBhMTYyODk1ODU2MCIsImlhdCI6MTY3NzA1MDc0MDEyMzIzNSwiYXVkIjoiaHR0cHM6Ly90YWxhby5jby9zYW5kYm94L2Vic2kvaXNzdWVyL3ZndmdoeWxvemwifQ.htjRCpFWbRwanAyQcAq9XZ4vxCXyFbzaaN3yPbPxWIcKFFzDDcA4QCHTUl-L4vzWq0R3LSgQFXQ9bo5D9uCm4w'; // ignore: lines_longer_than_80_chars - final isVerified = await ebsi.verifyCredential( + final isVerified = await ebsi.verifyEncodedData( issuerDid: issuerDid1, - vcJwt: vcJwt, + jwt: vcJwt, holderKid: holderKid, ); expect(isVerified, VerificationType.notVerified); }); + test('returns VerificationType.verified for OKP', () async { + const vcJwt = + 'eyJhbGciOiJFUzI1NksiLCJraWQiOiJkaWQ6ZWJzaTp6amhvb0thNVk1RDhBVVhzeVZYeUdvayNCdURBSTg1SEVLeEpWY2dDMnVGM2YyRmFjcExNalZHOThjVDZJdERTU1I4IiwidHlwIjoiSldUIn0.eyJjbGFpbXMiOnsiaWRfdG9rZW4iOnsiZW1haWwiOm51bGx9LCJ2cF90b2tlbiI6eyJwcmVzZW50YXRpb25fZGVmaW5pdGlvbiI6eyJmb3JtYXQiOnsiand0X3ZwIjp7ImFsZyI6WyJFUzI1NksiLCJFUzI1NiIsIkVTMzg0IiwiRVM1MTIiLCJSUzI1NiJdfX0sImlkIjoiNDE1MjAyY2YtYzI1ZS0xMWVkLTg3ZDAtMGExNjI4OTU4NTYwIiwiaW5wdXRfZGVzY3JpcHRvcnMiOlt7ImNvbnN0cmFpbnRzIjp7ImZpZWxkcyI6W3siZmlsdGVyIjp7InBhdHRlcm4iOiJodHRwczovL2FwaS5wcmVwcm9kLmVic2kuZXUvdHJ1c3RlZC1zY2hlbWFzLXJlZ2lzdHJ5L3YxL3NjaGVtYXMvMHhiZjc4ZmMwOGE3YTlmMjhmNTQ3OWY1OGRlYTI2OWQzNjU3ZjU0ZjEzY2EzN2QzODBjZDRlOTIyMzdmYjY5MWRkIiwidHlwZSI6InN0cmluZyJ9LCJwYXRoIjpbIiQuY3JlZGVudGlhbFNjaGVtYS5pZCJdfV19LCJpZCI6IjQxNTIwOTk2LWMyNWUtMTFlZC05ZDk5LTBhMTYyODk1ODU2MCIsIm5hbWUiOiJJbnB1dCBkZXNjcmlwdG9yIDEiLCJwdXJwb3NlIjoiICJ9XX19fSwiY2xpZW50X2lkIjoiZGlkOmVic2k6empob29LYTVZNUQ4QVVYc3lWWHlHb2siLCJub25jZSI6IjQxNTIwYWIwLWMyNWUtMTFlZC04ODlhLTBhMTYyODk1ODU2MCIsInJlZGlyZWN0X3VyaSI6Imh0dHBzOi8vdGFsYW8uY28vc2FuZGJveC9lYnNpL2xvZ2luL2VuZHBvaW50LzQxNTFlNTRhLWMyNWUtMTFlZC1hNGMzLTBhMTYyODk1ODU2MCIsInJlc3BvbnNlX3R5cGUiOiJpZF90b2tlbiIsInNjb3BlIjoib3BlbmlkIn0.LbmFZbDyWE1jRufvZFP-oczmQVutUGGtB5VtJ4RuzzUPQ97K9676JvfhYiENDnl5Ej5R7jstXNxqc61Ue671iA'; // ignore: lines_longer_than_80_chars + + final isVerified = await ebsi.verifyEncodedData( + issuerDid: issuerDid3, + jwt: vcJwt, + holderKid: holderKid, + ); + expect(isVerified, VerificationType.verified); + }); + test('returns VerificationType.unKnown', () async { const vcJwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksiLCJqd2siOnsiY3J2IjoiUC0yNTZLIiwia3R5IjoiRUMiLCJ4IjoiSjR2UXRMVXlyVlVpRklYUnJ0RXE0eHVybUJacDJlcTl3Sm1Ya0lBX3N0SSIsInkiOiJFVVU2dlhvRzNCR1gyenp3alhyR0RjcjRFeUREMFZmazNfNWZnNWtTZ0tFIn0sImtpZCI6ImRpZDplYnNpOnpvOUZSMVlmQUtGUDNRNmR2cWh4Y1h4bmZlRGlKRFA5N2ttbnFoeUFVU0FDaiNDZ2NnMXk5eGo5dVdGdzU2UE1jMjlYQmQ5RVJlaXh6dm5mdEJ6OEp3UUZpQiJ9.eyJpc3MiOiJkaWQ6ZWJzaTp6bzlGUjFZZkFLRlAzUTZkdnFoeGNYeG5mZURpSkRQOTdrbW5xaHlBVVNBQ2oiLCJub25jZSI6IjdhMDdkZTBmLWE4NzktMTFlZC04MjJiLTBhMTYyODk1ODU2MCIsImlhdCI6MTY3NzA1MDc0MDEyMzIzNSwiYXVkIjoiaHR0cHM6Ly90YWxhby5jby9zYW5kYm94L2Vic2kvaXNzdWVyL3ZndmdoeWxvemwifQ.htjRCpFWbRwanAyQcAq9XZ4vxCXyFbzaaN3yPbPxWIcKFFzDDcA4QCHTUl-L4vzWq0R3LSgQFXQ9bo5D9uCm4w'; // ignore: lines_longer_than_80_chars - final isVerified = await ebsi.verifyCredential( + final isVerified = await ebsi.verifyEncodedData( issuerDid: issuerDid2, - vcJwt: vcJwt, + jwt: vcJwt, holderKid: holderKid, ); expect(isVerified, VerificationType.unKnown); diff --git a/pubspec.lock b/pubspec.lock index 94d91e6aa..92e883a23 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -436,10 +436,10 @@ packages: dependency: transitive description: name: cryptography - sha256: e0e37f79665cd5c86e8897f9abe1accfe813c0cc5299dab22256e22fddc1fef8 + sha256: df156c5109286340817d21fa7b62f9140f17915077127dd70f8bd7a2a0997a35 url: "https://pub.dev" source: hosted - version: "2.0.5" + version: "2.5.0" csslib: dependency: transitive description: From ad06c2e9fe5eb3f2feecad5aafda2d10d4f850e9 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Wed, 15 Mar 2023 16:53:26 +0530 Subject: [PATCH 167/190] add comman in app_en.arb --- lib/l10n/arb/app_en.arb | 2 +- pubspec.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 136abe0bd..259ffff9c 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -813,6 +813,6 @@ "mnemonicsVerifiedMessage": "Your revovery phrase is saved correctly.", "letsGo": "Let's Go", "companySupport": "Company support", - "sendAnEmail": "Send an email" + "sendAnEmail": "Send an email", "service_not_registered_message": "This is services is not registered." } \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 92e883a23..8f837da20 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2423,5 +2423,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.19.0 <3.7.0" + dart: ">=2.19.0 <3.0.0" flutter: ">=3.7.0-0" From 10c89ccbe77a04975718ccde7483d63f1818af74 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Wed, 15 Mar 2023 15:11:11 +0330 Subject: [PATCH 168/190] add bloometa card in discover and update description #1461 --- lib/app/shared/constants/constant_list.dart | 2 +- .../response_string/response_string.dart | 3 +++ .../response_string_extension.dart | 10 ++++++++++ .../message_handler/global_message.dart | 9 +++++++++ .../message_handler/response_message.dart | 12 +++++++++++ .../home_credential/home_credential.dart | 5 +++++ lib/l10n/arb/app_en.arb | 5 ++++- lib/l10n/untranslated.json | 20 +++++++++++++++---- 8 files changed, 60 insertions(+), 6 deletions(-) diff --git a/lib/app/shared/constants/constant_list.dart b/lib/app/shared/constants/constant_list.dart index e11d0fd01..81fb78c07 100644 --- a/lib/app/shared/constants/constant_list.dart +++ b/lib/app/shared/constants/constant_list.dart @@ -4,7 +4,7 @@ class DiscoverList { static final List gamingCategories = [ CredentialSubjectType.tezotopiaMembership, CredentialSubjectType.chainbornMembership, - // CredentialSubjectType.bloometaPass, + CredentialSubjectType.bloometaPass, CredentialSubjectType.troopezPass, CredentialSubjectType.pigsPass, CredentialSubjectType.matterlightPass, diff --git a/lib/app/shared/enum/message/response_string/response_string.dart b/lib/app/shared/enum/message/response_string/response_string.dart index cc1458efa..591fca295 100644 --- a/lib/app/shared/enum/message/response_string/response_string.dart +++ b/lib/app/shared/enum/message/response_string/response_string.dart @@ -135,4 +135,7 @@ enum ResponseString { RESPONSE_STRING_transactionIsLikelyToFail, RESPONSE_STRING_linkedInBannerSuccessfullyExported, RESPONSE_STRING_credentialSuccessfullyExported, + RESPONSE_STRING_bloometaPassExpirationDate, + RESPONSE_STRING_bloometaPassWhyGetThisCard, + RESPONSE_STRING_bloometaPassHowToGetIt, } diff --git a/lib/app/shared/enum/message/response_string/response_string_extension.dart b/lib/app/shared/enum/message/response_string/response_string_extension.dart index 78199f64d..3e7029399 100644 --- a/lib/app/shared/enum/message/response_string/response_string_extension.dart +++ b/lib/app/shared/enum/message/response_string/response_string_extension.dart @@ -4,6 +4,16 @@ extension ResponseStringX on ResponseString { String localise(BuildContext context) { final GlobalMessage globalMessage = GlobalMessage(context.l10n); switch (this) { + + case ResponseString.RESPONSE_STRING_bloometaPassHowToGetIt: + return globalMessage.RESPONSE_STRING_bloometaPassHowToGetIt; + + case ResponseString.RESPONSE_STRING_bloometaPassExpirationDate: + return globalMessage.RESPONSE_STRING_bloometaPassExpirationDate; + + case ResponseString.RESPONSE_STRING_bloometaPassWhyGetThisCard: + return globalMessage.RESPONSE_STRING_bloometaPassWhyGetThisCard; + case ResponseString.RESPONSE_STRING_identityProofDummyDescription: return globalMessage.RESPONSE_STRING_identityProofDummyDescription; diff --git a/lib/app/shared/message_handler/global_message.dart b/lib/app/shared/message_handler/global_message.dart index 55493811a..90fc3ba13 100644 --- a/lib/app/shared/message_handler/global_message.dart +++ b/lib/app/shared/message_handler/global_message.dart @@ -8,6 +8,15 @@ class GlobalMessage { String get RESPONSE_STRING_identityProofDummyDescription => l10n.identityProofDummyDescription; + String get RESPONSE_STRING_bloometaPassHowToGetIt => + l10n.bloometaPassHowToGetIt; + + String get RESPONSE_STRING_bloometaPassExpirationDate => + l10n.bloometaPassExpirationDate; + + String get RESPONSE_STRING_bloometaPassWhyGetThisCard => + l10n.bloometaPassWhyGetThisCard; + String get RESPONSE_STRING_over18DummyDescription => l10n.over18ProofDummyDescription; diff --git a/lib/app/shared/message_handler/response_message.dart b/lib/app/shared/message_handler/response_message.dart index 36cacc812..d64b0d44c 100644 --- a/lib/app/shared/message_handler/response_message.dart +++ b/lib/app/shared/message_handler/response_message.dart @@ -10,6 +10,18 @@ class ResponseMessage with MessageHandler { String getMessage(BuildContext context, MessageHandler messageHandler) { if (messageHandler is ResponseMessage) { switch (messageHandler.message) { + case ResponseString.RESPONSE_STRING_bloometaPassWhyGetThisCard: + return ResponseString.RESPONSE_STRING_bloometaPassWhyGetThisCard + .localise(context); + + case ResponseString.RESPONSE_STRING_bloometaPassExpirationDate: + return ResponseString.RESPONSE_STRING_bloometaPassExpirationDate + .localise(context); + + case ResponseString.RESPONSE_STRING_bloometaPassHowToGetIt: + return ResponseString.RESPONSE_STRING_bloometaPassHowToGetIt + .localise(context); + case ResponseString.RESPONSE_STRING_identityProofDummyDescription: return ResponseString.RESPONSE_STRING_identityProofDummyDescription .localise(context); diff --git a/lib/dashboard/home/tab_bar/credentials/models/home_credential/home_credential.dart b/lib/dashboard/home/tab_bar/credentials/models/home_credential/home_credential.dart index 049877bec..f2685cc83 100644 --- a/lib/dashboard/home/tab_bar/credentials/models/home_credential/home_credential.dart +++ b/lib/dashboard/home/tab_bar/credentials/models/home_credential/home_credential.dart @@ -249,6 +249,11 @@ class HomeCredential extends Equatable { case CredentialSubjectType.bloometaPass: image = ImageStrings.bloometaDummy; link = Urls.bloometaCardUrl; + whyGetThisCard = + ResponseString.RESPONSE_STRING_bloometaPassWhyGetThisCard; + expirationDateDetails = + ResponseString.RESPONSE_STRING_bloometaPassExpirationDate; + howToGetIt = ResponseString.RESPONSE_STRING_bloometaPassHowToGetIt; break; case CredentialSubjectType.ethereumAssociatedWallet: diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 259ffff9c..5d634c6c1 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -814,5 +814,8 @@ "letsGo": "Let's Go", "companySupport": "Company support", "sendAnEmail": "Send an email", - "service_not_registered_message": "This is services is not registered." + "service_not_registered_message": "This is services is not registered.", + "bloometaPassHowToGetIt": "You need to present an Email proof and an Over 18 proof. If you don’t have those credentials already, claim them directly in Altme “Discover” section.", + "bloometaPassExpirationDate": "This card will remain active and reusable for 1 YEAR.", + "bloometaPassWhyGetThisCard": "Be among the few to get access to limited edition mints, gaming highlights and future airdrops. Claim your card today and enter the Bloometaverse !" } \ No newline at end of file diff --git a/lib/l10n/untranslated.json b/lib/l10n/untranslated.json index 4327bd6bd..2ff2b5321 100644 --- a/lib/l10n/untranslated.json +++ b/lib/l10n/untranslated.json @@ -749,7 +749,10 @@ "mnemonicsVerifiedMessage", "companySupport", "sendAnEmail", - "service_not_registered_message" + "service_not_registered_message", + "bloometaPassHowToGetIt", + "bloometaPassExpirationDate", + "bloometaPassWhyGetThisCard" ], "es": [ @@ -1502,7 +1505,10 @@ "mnemonicsVerifiedMessage", "companySupport", "sendAnEmail", - "service_not_registered_message" + "service_not_registered_message", + "bloometaPassHowToGetIt", + "bloometaPassExpirationDate", + "bloometaPassWhyGetThisCard" ], "fr": [ @@ -1539,7 +1545,10 @@ "mnemonicsVerifiedMessage", "companySupport", "sendAnEmail", - "service_not_registered_message" + "service_not_registered_message", + "bloometaPassHowToGetIt", + "bloometaPassExpirationDate", + "bloometaPassWhyGetThisCard" ], "it": [ @@ -2292,6 +2301,9 @@ "mnemonicsVerifiedMessage", "companySupport", "sendAnEmail", - "service_not_registered_message" + "service_not_registered_message", + "bloometaPassHowToGetIt", + "bloometaPassExpirationDate", + "bloometaPassWhyGetThisCard" ] } From 581b18d4253ea7aacfff4ea1e3f5b8308a47ea30 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Wed, 15 Mar 2023 17:25:52 +0530 Subject: [PATCH 169/190] add ebsi credential using p256 key --- lib/ebsi/initiate_ebsi_credential_issuance.dart | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/lib/ebsi/initiate_ebsi_credential_issuance.dart b/lib/ebsi/initiate_ebsi_credential_issuance.dart index b83fd899a..903e0f7bd 100644 --- a/lib/ebsi/initiate_ebsi_credential_issuance.dart +++ b/lib/ebsi/initiate_ebsi_credential_issuance.dart @@ -1,7 +1,4 @@ -import 'package:altme/app/shared/constants/parameters.dart'; -import 'package:altme/app/shared/constants/secure_storage_keys.dart'; -import 'package:altme/app/shared/dio_client/dio_client.dart'; -import 'package:altme/app/shared/launch_url/launch_url.dart'; +import 'package:altme/app/app.dart'; import 'package:altme/ebsi/add_ebsi_credential.dart'; import 'package:altme/wallet/wallet.dart'; import 'package:dio/dio.dart'; @@ -17,17 +14,12 @@ Future initiateEbsiCredentialIssuance( final Ebsi ebsi = Ebsi(Dio()); final Uri uriFromScannedResponse = Uri.parse(scannedResponse); if (uriFromScannedResponse.queryParameters['pre-authorized_code'] != null) { - final mnemonic = await getSecureStorage.get(SecureStorageKeys.ssiMnemonic); - final privateKey = await ebsi.privateKeyFromMnemonic(mnemonic: mnemonic!); - - // final mnemonic = await secureStorage.get( - // SecureStorageKeys.ssiMnemonic, - // ); + final String p256PrivateKey = await getRandomP256PrivateKey(secureStorage); final dynamic encodedCredentialFromEbsi = await ebsi.getCredential( uriFromScannedResponse, null, - privateKey, + p256PrivateKey, ); await addEbsiCredential( From 31baed2608aff98570cab2280d27a5853b4be70c Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Wed, 15 Mar 2023 16:12:18 +0330 Subject: [PATCH 170/190] change the chat screen title for loyalty cards #1463 --- .../drawer/chat_room/view/chat_room_view.dart | 5 ++++- .../view/loyalty_card_support_chat_page.dart | 5 +++++ .../detail/view/credentials_details_page.dart | 6 +++++- lib/l10n/arb/app_en.arb | 3 ++- lib/l10n/untranslated.json | 12 ++++++++---- pubspec.lock | 2 +- 6 files changed, 25 insertions(+), 8 deletions(-) diff --git a/lib/dashboard/drawer/chat_room/view/chat_room_view.dart b/lib/dashboard/drawer/chat_room/view/chat_room_view.dart index f5b918ab0..b7d3cbd33 100644 --- a/lib/dashboard/drawer/chat_room/view/chat_room_view.dart +++ b/lib/dashboard/drawer/chat_room/view/chat_room_view.dart @@ -12,9 +12,11 @@ class ChatRoomView extends StatefulWidget { const ChatRoomView({ super.key, this.appBarTitle, + this.chatWelcomeMessage, }); final String? appBarTitle; + final String? chatWelcomeMessage; @override _ChatRoomViewState createState() => _ChatRoomViewState(); @@ -104,7 +106,8 @@ class _ChatRoomViewState extends State { mainAxisSize: MainAxisSize.min, children: [ Text( - l10n.supportChatWelcomeMessage, + widget.chatWelcomeMessage ?? + l10n.supportChatWelcomeMessage, textAlign: TextAlign.center, style: Theme.of(context).textTheme.bodyMedium, ), diff --git a/lib/dashboard/drawer/loyalty_card_support_chat/view/loyalty_card_support_chat_page.dart b/lib/dashboard/drawer/loyalty_card_support_chat/view/loyalty_card_support_chat_page.dart index fae840c0a..958806acb 100644 --- a/lib/dashboard/drawer/loyalty_card_support_chat/view/loyalty_card_support_chat_page.dart +++ b/lib/dashboard/drawer/loyalty_card_support_chat/view/loyalty_card_support_chat_page.dart @@ -8,16 +8,19 @@ class LoyaltyCardSupportChatPage extends StatelessWidget { const LoyaltyCardSupportChatPage({ super.key, this.appBarTitle, + this.chatWelcomeMessage, required this.companySupportId, required this.loyaltyCardType, }); final String? appBarTitle; + final String? chatWelcomeMessage; final String companySupportId; final String loyaltyCardType; static Route route({ String? appBarTitle, + String? chatWelcomeMessage, required String companySupportId, required String loyaltyCardType, }) { @@ -26,6 +29,7 @@ class LoyaltyCardSupportChatPage extends StatelessWidget { appBarTitle: appBarTitle, companySupportId: companySupportId, loyaltyCardType: loyaltyCardType, + chatWelcomeMessage: chatWelcomeMessage, ), settings: const RouteSettings(name: '/loyaltyCardSupportChatPage'), ); @@ -44,6 +48,7 @@ class LoyaltyCardSupportChatPage extends StatelessWidget { ), child: ChatRoomView( appBarTitle: appBarTitle, + chatWelcomeMessage: chatWelcomeMessage, ), ); } 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 b192b63b9..a21afb6a1 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 @@ -1,3 +1,5 @@ +// ignore_for_file: lines_longer_than_80_chars + import 'dart:convert'; import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; @@ -158,7 +160,9 @@ class _CredentialsDetailsViewState extends State { /// TODO(Taleb): read the ChatSupport property from credential /// json and replace by this '@bloometa:matrix.talao.co' companySupportId: '@bloometa:matrix.talao.co', - appBarTitle: l10n.companySupport, + chatWelcomeMessage: l10n.cardChatWelcomeMessage, + appBarTitle: + '${l10n.chatWith} ${widget.credentialModel.credentialPreview.credentialSubjectModel.issuedBy?.name}', loyaltyCardType: widget.credentialModel.credentialPreview .credentialSubjectModel.credentialSubjectType.name, ), diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 5d634c6c1..5b6510689 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -802,6 +802,7 @@ "help": "Help", "searchCredentials": "Search credentials", "supportChatWelcomeMessage": "Welcome to our chat support! We're here to assist you with any questions or concerns you may have about Altme.", + "cardChatWelcomeMessage": "Welcome to our chat support! We're here to assist you with any questions or concerns.", "creator": "Creator", "contractAddress": "Contract address", "lastMetadataSync": "Last metadata sync", @@ -812,7 +813,7 @@ "welDone": "Well done!", "mnemonicsVerifiedMessage": "Your revovery phrase is saved correctly.", "letsGo": "Let's Go", - "companySupport": "Company support", + "chatWith": "Chat with", "sendAnEmail": "Send an email", "service_not_registered_message": "This is services is not registered.", "bloometaPassHowToGetIt": "You need to present an Email proof and an Over 18 proof. If you don’t have those credentials already, claim them directly in Altme “Discover” section.", diff --git a/lib/l10n/untranslated.json b/lib/l10n/untranslated.json index 2ff2b5321..05562f02f 100644 --- a/lib/l10n/untranslated.json +++ b/lib/l10n/untranslated.json @@ -738,6 +738,7 @@ "help", "searchCredentials", "supportChatWelcomeMessage", + "cardChatWelcomeMessage", "creator", "contractAddress", "lastMetadataSync", @@ -747,7 +748,7 @@ "verifyLater", "welDone", "mnemonicsVerifiedMessage", - "companySupport", + "chatWith", "sendAnEmail", "service_not_registered_message", "bloometaPassHowToGetIt", @@ -1494,6 +1495,7 @@ "help", "searchCredentials", "supportChatWelcomeMessage", + "cardChatWelcomeMessage", "creator", "contractAddress", "lastMetadataSync", @@ -1503,7 +1505,7 @@ "verifyLater", "welDone", "mnemonicsVerifiedMessage", - "companySupport", + "chatWith", "sendAnEmail", "service_not_registered_message", "bloometaPassHowToGetIt", @@ -1534,6 +1536,7 @@ "help", "searchCredentials", "supportChatWelcomeMessage", + "cardChatWelcomeMessage", "creator", "contractAddress", "lastMetadataSync", @@ -1543,7 +1546,7 @@ "verifyLater", "welDone", "mnemonicsVerifiedMessage", - "companySupport", + "chatWith", "sendAnEmail", "service_not_registered_message", "bloometaPassHowToGetIt", @@ -2290,6 +2293,7 @@ "help", "searchCredentials", "supportChatWelcomeMessage", + "cardChatWelcomeMessage", "creator", "contractAddress", "lastMetadataSync", @@ -2299,7 +2303,7 @@ "verifyLater", "welDone", "mnemonicsVerifiedMessage", - "companySupport", + "chatWith", "sendAnEmail", "service_not_registered_message", "bloometaPassHowToGetIt", diff --git a/pubspec.lock b/pubspec.lock index 8f837da20..92e883a23 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2423,5 +2423,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.19.0 <3.0.0" + dart: ">=2.19.0 <3.7.0" flutter: ">=3.7.0-0" From c5c119ffa6870ccdf5a64f8d003ed11f844b1340 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Wed, 15 Mar 2023 16:14:31 +0330 Subject: [PATCH 171/190] remove duplicate key in app_en arb --- lib/l10n/arb/app_en.arb | 1 - pubspec.lock | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 5b6510689..8d9aee552 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -812,7 +812,6 @@ "verifyLater": "Verify Later", "welDone": "Well done!", "mnemonicsVerifiedMessage": "Your revovery phrase is saved correctly.", - "letsGo": "Let's Go", "chatWith": "Chat with", "sendAnEmail": "Send an email", "service_not_registered_message": "This is services is not registered.", diff --git a/pubspec.lock b/pubspec.lock index 92e883a23..8f837da20 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2423,5 +2423,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.19.0 <3.7.0" + dart: ">=2.19.0 <3.0.0" flutter: ">=3.7.0-0" From 45e39326216896dd275d985a2d229ec5d60c6172 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Thu, 16 Mar 2023 10:44:54 +0530 Subject: [PATCH 172/190] bug fix verifiable id #1419 --- .../models/eu_verifiable_id/eu_verifiable_id_model.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dashboard/home/tab_bar/credentials/models/eu_verifiable_id/eu_verifiable_id_model.dart b/lib/dashboard/home/tab_bar/credentials/models/eu_verifiable_id/eu_verifiable_id_model.dart index c844382ab..610e17faf 100644 --- a/lib/dashboard/home/tab_bar/credentials/models/eu_verifiable_id/eu_verifiable_id_model.dart +++ b/lib/dashboard/home/tab_bar/credentials/models/eu_verifiable_id/eu_verifiable_id_model.dart @@ -20,7 +20,7 @@ class EUVerifiableIdModel extends CredentialSubjectModel { super.type, super.issuedBy, }) : super( - credentialSubjectType: CredentialSubjectType.euDiplomaCard, + credentialSubjectType: CredentialSubjectType.euVerifiableId, credentialCategory: CredentialCategory.educationCards, ); From 8ed41c99a1ab10a296dcd55acaacac4b8d7114db Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Thu, 16 Mar 2023 12:28:19 +0330 Subject: [PATCH 173/190] update the image_picker plugin --- pubspec.lock | 6 +++--- pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 8f837da20..909e00504 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1070,10 +1070,10 @@ packages: dependency: "direct main" description: name: image_picker - sha256: f98d76672d309c8b7030c323b3394669e122d52b307d2bbd8d06bd70f5b2aabe + sha256: "64b21d9f0e065f9ab0e4dde458076226c97382cc0c6949144cb874c62bf8e9f8" url: "https://pub.dev" source: hosted - version: "0.8.6+1" + version: "0.8.7" image_picker_android: dependency: transitive description: @@ -2423,5 +2423,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.19.0 <3.0.0" + dart: ">=2.19.0 <3.7.0" flutter: ">=3.7.0-0" diff --git a/pubspec.yaml b/pubspec.yaml index 8c4aa493b..86ab23646 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -62,7 +62,7 @@ dependencies: #google_mlkit_face_detection: ^0.5.0 http: ^0.13.5 image: ^3.0.2 - image_picker: ^0.8.6+1 + image_picker: ^0.8.7 intl: ^0.17.0 #flutter_localizations from sdk which depends on intl 0.17.0 jose: ^0.3.3 json_annotation: ^4.8.0 From 3575cad76eae58f8da0de4231f28b12181b76d02 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Thu, 16 Mar 2023 12:55:16 +0330 Subject: [PATCH 174/190] fix null exceptions on Matrix chat --- .../chat_room/matrix_chat/matrix_chat_impl.dart | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/dashboard/drawer/chat_room/matrix_chat/matrix_chat_impl.dart b/lib/dashboard/drawer/chat_room/matrix_chat/matrix_chat_impl.dart index 665b09359..a4e1bff89 100644 --- a/lib/dashboard/drawer/chat_room/matrix_chat/matrix_chat_impl.dart +++ b/lib/dashboard/drawer/chat_room/matrix_chat/matrix_chat_impl.dart @@ -175,8 +175,8 @@ class MatrixChatImpl extends MatrixChatInterface { id: const Uuid().v4(), remoteId: event.eventId, name: event.body, - size: event.content['info']['size'] as num, - uri: getUrlFromUri(uri: event.content['url'] as String), + size: event.content['info']['size'] as num? ?? 0, + uri: getUrlFromUri(uri: event.content['url'] as String? ?? ''), status: mapEventStatusToMessageStatus(event.status), createdAt: event.originServerTs.millisecondsSinceEpoch, author: User( @@ -188,8 +188,8 @@ class MatrixChatImpl extends MatrixChatInterface { id: const Uuid().v4(), remoteId: event.eventId, name: event.body, - size: event.content['info']['size'] as num, - uri: getUrlFromUri(uri: event.content['url'] as String), + size: event.content['info']['size'] as num? ?? 0, + uri: getUrlFromUri(uri: event.content['url'] as String? ?? ''), status: mapEventStatusToMessageStatus(event.status), createdAt: event.originServerTs.millisecondsSinceEpoch, author: User( @@ -201,11 +201,11 @@ class MatrixChatImpl extends MatrixChatInterface { id: const Uuid().v4(), remoteId: event.eventId, duration: Duration( - milliseconds: event.content['info']['duration'] as int, + milliseconds: event.content['info']['duration'] as int? ?? 0, ), name: event.body, - size: event.content['info']['size'] as num, - uri: getUrlFromUri(uri: event.content['url'] as String), + size: event.content['info']['size'] as num? ?? 0, + uri: getUrlFromUri(uri: event.content['url'] as String? ?? ''), status: mapEventStatusToMessageStatus(event.status), createdAt: event.originServerTs.millisecondsSinceEpoch, author: User( @@ -403,6 +403,7 @@ class MatrixChatImpl extends MatrixChatInterface { int width = 500, int height = 500, }) { + if (uri.trim().isEmpty) return ''; return '${Urls.matrixHomeServer}/_matrix/media/v3/thumbnail/${Urls.matrixHomeServer.replaceAll('https://', '')}/${uri.split('/').last}?width=$width&height=$height'; } From e057a8c28b4378e643cd294cc173b0ddd1adb981 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Thu, 16 Mar 2023 13:09:15 +0330 Subject: [PATCH 175/190] update the matrix plugin --- pubspec.lock | 4 ++-- pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 909e00504..d0eea0657 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1276,10 +1276,10 @@ packages: dependency: "direct main" description: name: matrix - sha256: "4c69e3e6fb75703b8dec9cde5196bfc46c8f54ea7301681624fdb590b34852b8" + sha256: "8c07fa7b558d28891ea2609542295e4bb3e8360545540b434abaf055dde3a59c" url: "https://pub.dev" source: hosted - version: "0.15.13" + version: "0.18.0" matrix_api_lite: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 86ab23646..9f264d719 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -73,7 +73,7 @@ dependencies: path: packages/key_generator local_auth: ^2.1.3 logger: ^1.1.0 - matrix: ^0.15.13 # new version available + matrix: ^0.18.0 mime: ^1.0.4 mobile_scanner: ^3.0.0 network_image_mock: ^2.1.1 From a10a4b8b978bdfa3c238c17186aee6161c4f3351 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Thu, 16 Mar 2023 14:33:22 +0330 Subject: [PATCH 176/190] add chat button in Tabbar for cards --- .../detail/view/credentials_details_page.dart | 54 +++++++++++-------- lib/l10n/arb/app_en.arb | 3 +- lib/l10n/untranslated.json | 12 +++-- pubspec.yaml | 1 + 4 files changed, 42 insertions(+), 28 deletions(-) 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 a21afb6a1..77ce81f53 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 @@ -150,29 +150,6 @@ class _CredentialsDetailsViewState extends State { title: widget.readOnly ? l10n.linkedInProfile : l10n.cardDetails, titleAlignment: Alignment.topCenter, titleLeading: const BackLeadingButton(), - - /// TODO(Taleb): check if json contains the ChatSupport then enable chat button - titleTrailing: IconButton( - onPressed: () { - Navigator.push( - context, - LoyaltyCardSupportChatPage.route( - /// TODO(Taleb): read the ChatSupport property from credential - /// json and replace by this '@bloometa:matrix.talao.co' - companySupportId: '@bloometa:matrix.talao.co', - chatWelcomeMessage: l10n.cardChatWelcomeMessage, - appBarTitle: - '${l10n.chatWith} ${widget.credentialModel.credentialPreview.credentialSubjectModel.issuedBy?.name}', - loyaltyCardType: widget.credentialModel.credentialPreview - .credentialSubjectModel.credentialSubjectType.name, - ), - ); - }, - icon: Icon( - Icons.support_agent_rounded, - color: Theme.of(context).colorScheme.onBackground, - ), - ), padding: const EdgeInsets.symmetric(horizontal: 10), scrollView: false, body: Column( @@ -221,6 +198,37 @@ class _CredentialsDetailsViewState extends State { ), ), ), + + /// TODO(Taleb): check if json contains the + /// ChatSupport then enable chat button + Expanded( + child: CredentialDetailTabbar( + isSelected: false, + title: l10n.chat, + onTap: () { + Navigator.push( + context, + LoyaltyCardSupportChatPage.route( + /// TODO(Taleb): read the ChatSupport + /// property from credential + /// json and replace by this '@bloometa:matrix.talao.co' + companySupportId: + '@bloometa:matrix.talao.co', + chatWelcomeMessage: + l10n.cardChatWelcomeMessage, + appBarTitle: + '${l10n.chatWith} ${widget.credentialModel.credentialPreview.credentialSubjectModel.issuedBy?.name}', + loyaltyCardType: widget + .credentialModel + .credentialPreview + .credentialSubjectModel + .credentialSubjectType + .name, + ), + ); + }, + ), + ), ], ), Divider( diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 8d9aee552..609479352 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -817,5 +817,6 @@ "service_not_registered_message": "This is services is not registered.", "bloometaPassHowToGetIt": "You need to present an Email proof and an Over 18 proof. If you don’t have those credentials already, claim them directly in Altme “Discover” section.", "bloometaPassExpirationDate": "This card will remain active and reusable for 1 YEAR.", - "bloometaPassWhyGetThisCard": "Be among the few to get access to limited edition mints, gaming highlights and future airdrops. Claim your card today and enter the Bloometaverse !" + "bloometaPassWhyGetThisCard": "Be among the few to get access to limited edition mints, gaming highlights and future airdrops. Claim your card today and enter the Bloometaverse !", + "chat": "Chat" } \ No newline at end of file diff --git a/lib/l10n/untranslated.json b/lib/l10n/untranslated.json index 05562f02f..a53e1a637 100644 --- a/lib/l10n/untranslated.json +++ b/lib/l10n/untranslated.json @@ -753,7 +753,8 @@ "service_not_registered_message", "bloometaPassHowToGetIt", "bloometaPassExpirationDate", - "bloometaPassWhyGetThisCard" + "bloometaPassWhyGetThisCard", + "chat" ], "es": [ @@ -1510,7 +1511,8 @@ "service_not_registered_message", "bloometaPassHowToGetIt", "bloometaPassExpirationDate", - "bloometaPassWhyGetThisCard" + "bloometaPassWhyGetThisCard", + "chat" ], "fr": [ @@ -1551,7 +1553,8 @@ "service_not_registered_message", "bloometaPassHowToGetIt", "bloometaPassExpirationDate", - "bloometaPassWhyGetThisCard" + "bloometaPassWhyGetThisCard", + "chat" ], "it": [ @@ -2308,6 +2311,7 @@ "service_not_registered_message", "bloometaPassHowToGetIt", "bloometaPassExpirationDate", - "bloometaPassWhyGetThisCard" + "bloometaPassWhyGetThisCard", + "chat" ] } diff --git a/pubspec.yaml b/pubspec.yaml index 9f264d719..b1ac15ad6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -57,6 +57,7 @@ dependencies: flutter_markdown: ^0.6.14 flutter_native_timezone: ^2.0.0 flutter_olm: ^1.2.0 + # flutter_openssl_crypto: ^0.1.0 flutter_svg: ^2.0.0+1 google_fonts: ^4.0.3 #google_mlkit_face_detection: ^0.5.0 From e38ee47a33342c828f24ce27037b3328b7d0f302 Mon Sep 17 00:00:00 2001 From: hawkbee1 Date: Thu, 16 Mar 2023 15:28:29 +0100 Subject: [PATCH 177/190] version 1.11.4+163 --- .fvm/fvm_config.json | 2 +- lib/app/shared/constants/constant_list.dart | 8 ++++---- .../qr_code/qr_code_scan/view/qr_code_scan_page.dart | 8 ++------ .../qr_code/qr_code_scan/view/qr_scanner_page.dart | 5 ++--- pubspec.lock | 8 ++++---- pubspec.yaml | 6 +++--- 6 files changed, 16 insertions(+), 21 deletions(-) diff --git a/.fvm/fvm_config.json b/.fvm/fvm_config.json index 6e3d57e67..69fe1472f 100644 --- a/.fvm/fvm_config.json +++ b/.fvm/fvm_config.json @@ -1,4 +1,4 @@ { - "flutterSdkVersion": "3.7.6", + "flutterSdkVersion": "3.7.7", "flavors": {} } \ No newline at end of file diff --git a/lib/app/shared/constants/constant_list.dart b/lib/app/shared/constants/constant_list.dart index e11d0fd01..d838e8ba2 100644 --- a/lib/app/shared/constants/constant_list.dart +++ b/lib/app/shared/constants/constant_list.dart @@ -5,11 +5,11 @@ class DiscoverList { CredentialSubjectType.tezotopiaMembership, CredentialSubjectType.chainbornMembership, // CredentialSubjectType.bloometaPass, - CredentialSubjectType.troopezPass, - CredentialSubjectType.pigsPass, - CredentialSubjectType.matterlightPass, + // CredentialSubjectType.troopezPass, + // CredentialSubjectType.pigsPass, + // CredentialSubjectType.matterlightPass, // CredentialSubjectType.dogamiPass, - CredentialSubjectType.bunnyPass, + // CredentialSubjectType.bunnyPass, ]; static final List communityCategories = [ diff --git a/lib/dashboard/qr_code/qr_code_scan/view/qr_code_scan_page.dart b/lib/dashboard/qr_code/qr_code_scan/view/qr_code_scan_page.dart index fab89c213..5be6463a0 100644 --- a/lib/dashboard/qr_code/qr_code_scan/view/qr_code_scan_page.dart +++ b/lib/dashboard/qr_code/qr_code_scan/view/qr_code_scan_page.dart @@ -94,12 +94,8 @@ class _QrCodeScanPageState extends State { child: MobileScanner( key: qrKey, controller: scannerController, - onDetect: (capture) { - final List qrcodes = capture.barcodes; - final Barcode qrcode = qrcodes[0]; - for (final barcode in qrcodes) { - debugPrint('Barcode found! ${barcode.rawValue}'); - } + allowDuplicates: false, + onDetect: (qrcode, args) { if (qrcode.rawValue == null) { context.read().emitError( ResponseMessage( diff --git a/lib/dashboard/qr_code/qr_code_scan/view/qr_scanner_page.dart b/lib/dashboard/qr_code/qr_code_scan/view/qr_scanner_page.dart index e301b4602..be6ce47d4 100644 --- a/lib/dashboard/qr_code/qr_code_scan/view/qr_scanner_page.dart +++ b/lib/dashboard/qr_code/qr_code_scan/view/qr_scanner_page.dart @@ -65,9 +65,8 @@ class _QrScannerPageState extends State { key: qrKey, fit: BoxFit.cover, controller: scannerController, - onDetect: (capture) { - final List qrcodes = capture.barcodes; - final Barcode qrcode = qrcodes[0]; + allowDuplicates: false, + onDetect: (qrcode, args) { if (qrcode.rawValue == null) { Navigator.of(context).pop(); } else { diff --git a/pubspec.lock b/pubspec.lock index 45d70aef8..fbfc49267 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1316,10 +1316,10 @@ packages: dependency: "direct main" description: name: mobile_scanner - sha256: "4045e8441e21f1fb8998a76fbffd054510dd3a3b1dee55c7c9a2083eee687345" + sha256: "531725451c7506f4c57b4720da1fe33a1394cdf9d4a5075a3a2dd51e21113928" url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "2.1.0" mockingjay: dependency: "direct dev" description: @@ -1444,10 +1444,10 @@ packages: dependency: "direct main" description: name: passbase_flutter - sha256: "200d5ed74aac08ae420f5f4fe962e62c4a508af66d7aceb873fc29d7f08ad73e" + sha256: bb8b350718fed101359753bbe254165b40cb51d5802d8bd1f418e1384b695f64 url: "https://pub.dev" source: hosted - version: "2.13.7" + version: "2.14.0" path: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 264000e24..f0a3de86e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: altme description: AltMe Flutter App -version: 1.11.3+161 +version: 1.11.4+163 publish_to: none environment: @@ -75,12 +75,12 @@ dependencies: logger: ^1.1.0 matrix: ^0.15.13 # new version available mime: ^1.0.4 - mobile_scanner: ^3.0.0 + mobile_scanner: ^2.1.0 network_image_mock: ^2.1.1 no_screenshot: ^0.0.1+6 open_filex: ^4.3.2 package_info_plus: ^3.0.3 - passbase_flutter: ^2.13.3 # new version available + passbase_flutter: ^2.14.0 path: ^1.8.2 # flutter_test from sdk depends on path 1.8.2 permission_handler: ^10.2.0 platform_device_id: ^1.0.1 From 954288ee6ce55ef919f2f8d8816b188a9665395c Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Fri, 17 Mar 2023 11:37:59 +0530 Subject: [PATCH 178/190] move blockchain accounts to the bottom #1448 --- .../list/widgets/credential_list_data.dart | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/dashboard/home/tab_bar/credentials/list/widgets/credential_list_data.dart b/lib/dashboard/home/tab_bar/credentials/list/widgets/credential_list_data.dart index 40860d5ba..c63520281 100644 --- a/lib/dashboard/home/tab_bar/credentials/list/widgets/credential_list_data.dart +++ b/lib/dashboard/home/tab_bar/credentials/list/widgets/credential_list_data.dart @@ -57,17 +57,6 @@ class CredentialListData extends StatelessWidget { ), const SizedBox(height: Sizes.spaceNormal), ], - if (advanceSettingsState.isBlockchainAccountsEnabled && - state.blockchainAccountsCredentials.isNotEmpty) ...[ - /// BlockchainAccounts Credentials - HomeCredentialWidget( - title: l10n.blockchainAccounts, - credentials: state.blockchainAccountsCredentials, - categorySubtitle: - l10n.blockchainAccountsCredentialHomeSubtitle, - ), - const SizedBox(height: Sizes.spaceNormal), - ], if (advanceSettingsState.isEducationEnabled && state.educationCredentials.isNotEmpty) ...[ /// Education Credentials @@ -110,6 +99,17 @@ class CredentialListData extends StatelessWidget { ), const SizedBox(height: Sizes.spaceNormal), ], + if (advanceSettingsState.isBlockchainAccountsEnabled && + state.blockchainAccountsCredentials.isNotEmpty) ...[ + /// BlockchainAccounts Credentials + HomeCredentialWidget( + title: l10n.blockchainAccounts, + credentials: state.blockchainAccountsCredentials, + categorySubtitle: + l10n.blockchainAccountsCredentialHomeSubtitle, + ), + const SizedBox(height: Sizes.spaceNormal), + ], ], ), ), From c6f3c2a81e0606ea66cdc7aa93d4bf4d10fa8dd0 Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Fri, 17 Mar 2023 11:42:12 +0530 Subject: [PATCH 179/190] handle secure storage delete by deleteAll() --- lib/wallet/cubit/wallet_cubit.dart | 60 ++---------------------------- 1 file changed, 4 insertions(+), 56 deletions(-) diff --git a/lib/wallet/cubit/wallet_cubit.dart b/lib/wallet/cubit/wallet_cubit.dart index ace8f448b..76f7a48e7 100644 --- a/lib/wallet/cubit/wallet_cubit.dart +++ b/lib/wallet/cubit/wallet_cubit.dart @@ -655,63 +655,11 @@ class WalletCubit extends Cubit { } Future resetWallet() async { - /// reward operations id in all accounts - for (final cryptoAccountData in state.cryptoAccount.data) { - await secureStorageProvider.delete( - SecureStorageKeys.lastNotifiedXTZRewardId + - cryptoAccountData.walletAddress, - ); - await secureStorageProvider.delete( - SecureStorageKeys.lastNotifiedUNORewardId + - cryptoAccountData.walletAddress, - ); - } - - ///Matrix - await secureStorageProvider - .delete(SecureStorageKeys.isUserRegisteredMatrix); - await secureStorageProvider.delete(SecureStorageKeys.supportRoomId); - - /// ssi - await secureStorageProvider.delete(SecureStorageKeys.ssiMnemonic); - await secureStorageProvider.delete(SecureStorageKeys.ssiKey); - - /// did - await secureStorageProvider.delete(SecureStorageKeys.did); - await secureStorageProvider.delete(SecureStorageKeys.didMethod); - await secureStorageProvider.delete(SecureStorageKeys.didMethodName); - await secureStorageProvider.delete(SecureStorageKeys.verificationMethod); - - /// crypto - await secureStorageProvider.delete(SecureStorageKeys.cryptoAccount); - await secureStorageProvider - .delete(SecureStorageKeys.cryptoAccounTrackingIndex); - await secureStorageProvider.delete(SecureStorageKeys.tezosDerivePathIndex); - await secureStorageProvider - .delete(SecureStorageKeys.ethereumDerivePathIndex); - await secureStorageProvider.delete(SecureStorageKeys.fantomDerivePathIndex); - await secureStorageProvider - .delete(SecureStorageKeys.polygonDerivePathIndex); - await secureStorageProvider - .delete(SecureStorageKeys.binanceDerivePathIndex); - await secureStorageProvider.delete(SecureStorageKeys.currentCryptoIndex); - await secureStorageProvider.delete(SecureStorageKeys.data); - - /// credentials - await credentialsRepository.deleteAll(); - - /// passBase - await secureStorageProvider.delete(SecureStorageKeys.passBaseStatus); - await secureStorageProvider - .delete(SecureStorageKeys.passBaseVerificationDate); - await secureStorageProvider.delete(SecureStorageKeys.preAuthorizedCode); - - /// user data - await profileCubit.resetProfile(); - await secureStorageProvider.delete(SecureStorageKeys.pinCode); + await secureStorageProvider.deleteAll(); - //save dapps - await connectedDappRepository.deleteAll(); + // await credentialsRepository.deleteAll(); + // await profileCubit.resetProfile(); + // await connectedDappRepository.deleteAll(); /// clear app states homeCubit.emitHasNoWallet(); From 79cb4c842c5da9c06443ef06932ec51a6dfb13f9 Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Fri, 17 Mar 2023 11:38:31 +0330 Subject: [PATCH 180/190] get chatSupport value from json --- .../detail/view/credentials_details_page.dart | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) 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 77ce81f53..1297798af 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 @@ -198,37 +198,37 @@ class _CredentialsDetailsViewState extends State { ), ), ), - - /// TODO(Taleb): check if json contains the - /// ChatSupport then enable chat button - Expanded( - child: CredentialDetailTabbar( - isSelected: false, - title: l10n.chat, - onTap: () { - Navigator.push( - context, - LoyaltyCardSupportChatPage.route( - /// TODO(Taleb): read the ChatSupport - /// property from credential - /// json and replace by this '@bloometa:matrix.talao.co' - companySupportId: - '@bloometa:matrix.talao.co', - chatWelcomeMessage: - l10n.cardChatWelcomeMessage, - appBarTitle: - '${l10n.chatWith} ${widget.credentialModel.credentialPreview.credentialSubjectModel.issuedBy?.name}', - loyaltyCardType: widget - .credentialModel - .credentialPreview - .credentialSubjectModel - .credentialSubjectType - .name, - ), - ); - }, + if (widget.credentialModel + .data['credentialSubject'] + ?['chatSupport'] != + null) + Expanded( + child: CredentialDetailTabbar( + isSelected: false, + title: l10n.chat, + onTap: () { + Navigator.push( + context, + LoyaltyCardSupportChatPage.route( + companySupportId: widget + .credentialModel + .data['credentialSubject'] + ?['chatSupport'] as String, + chatWelcomeMessage: + l10n.cardChatWelcomeMessage, + appBarTitle: + '${l10n.chatWith} ${widget.credentialModel.credentialPreview.credentialSubjectModel.issuedBy?.name}', + loyaltyCardType: widget + .credentialModel + .credentialPreview + .credentialSubjectModel + .credentialSubjectType + .name, + ), + ); + }, + ), ), - ), ], ), Divider( From 733cd20a374e5e576292ede4d761709e699656af Mon Sep 17 00:00:00 2001 From: hawkbee1 Date: Fri, 17 Mar 2023 09:59:58 +0100 Subject: [PATCH 181/190] version: 1.11.4+152 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index b63684390..2bf956e5c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: altme description: AltMe Flutter App -version: 1.11.4+151 +version: 1.11.4+152 publish_to: none environment: From 40f12924d8a8e904b2010bcd50049a079382ddda Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Fri, 17 Mar 2023 14:32:30 +0530 Subject: [PATCH 182/190] update discover details data #1466 --- .../response_string/response_string.dart | 3 + .../response_string_extension.dart | 10 +- .../credential_subject_type_extension.dart | 113 ++++++++++++++++++ .../message_handler/global_message.dart | 6 + .../message_handler/response_message.dart | 26 +++- .../discover/view/discover_details_page.dart | 15 ++- .../widget/discover_dynamic_detail.dart | 5 +- .../home_credential/home_credential.dart | 27 ++++- lib/l10n/arb/app_en.arb | 5 +- lib/l10n/arb/app_fr.arb | 1 - lib/l10n/untranslated.json | 19 ++- pubspec.lock | 2 +- 12 files changed, 213 insertions(+), 19 deletions(-) diff --git a/lib/app/shared/enum/message/response_string/response_string.dart b/lib/app/shared/enum/message/response_string/response_string.dart index 591fca295..bf9dacaa0 100644 --- a/lib/app/shared/enum/message/response_string/response_string.dart +++ b/lib/app/shared/enum/message/response_string/response_string.dart @@ -138,4 +138,7 @@ enum ResponseString { RESPONSE_STRING_bloometaPassExpirationDate, RESPONSE_STRING_bloometaPassWhyGetThisCard, RESPONSE_STRING_bloometaPassHowToGetIt, + RESPONSE_STRING_tezotopiaMembershipLongDescription, + RESPONSE_STRING_chainbornMembershipLongDescription, + RESPONSE_STRING_bloometaPassLongDescription, } diff --git a/lib/app/shared/enum/message/response_string/response_string_extension.dart b/lib/app/shared/enum/message/response_string/response_string_extension.dart index 3e7029399..ce09e0895 100644 --- a/lib/app/shared/enum/message/response_string/response_string_extension.dart +++ b/lib/app/shared/enum/message/response_string/response_string_extension.dart @@ -4,7 +4,6 @@ extension ResponseStringX on ResponseString { String localise(BuildContext context) { final GlobalMessage globalMessage = GlobalMessage(context.l10n); switch (this) { - case ResponseString.RESPONSE_STRING_bloometaPassHowToGetIt: return globalMessage.RESPONSE_STRING_bloometaPassHowToGetIt; @@ -431,6 +430,15 @@ extension ResponseStringX on ResponseString { case ResponseString.RESPONSE_STRING_verifiableIdCardDummyDesc: return globalMessage.RESPONSE_STRING_verifiableIdCardDummyDesc; + + case ResponseString.RESPONSE_STRING_tezotopiaMembershipLongDescription: + return globalMessage.RESPONSE_STRING_tezotopiaMembershipLongDescription; + + case ResponseString.RESPONSE_STRING_chainbornMembershipLongDescription: + return globalMessage.RESPONSE_STRING_chainbornMembershipLongDescription; + + case ResponseString.RESPONSE_STRING_bloometaPassLongDescription: + return globalMessage.RESPONSE_STRING_bloometaPassLongDescription; } } } 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 235823689..4ccba3136 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 @@ -449,4 +449,117 @@ extension CredentialSubjectTypeExtension on CredentialSubjectType { } return null; } + + String get title { + switch (this) { + case CredentialSubjectType.bloometaPass: + return 'Bloometa'; + case CredentialSubjectType.troopezPass: + return 'Troopez Pass'; + case CredentialSubjectType.pigsPass: + return 'Pigs Pass'; + case CredentialSubjectType.matterlightPass: + return 'Matterlight Pass'; + case CredentialSubjectType.dogamiPass: + return 'Dogami Pass'; + case CredentialSubjectType.bunnyPass: + return 'Bunny Pass'; + case CredentialSubjectType.tezotopiaMembership: + return 'Membership Card'; + case CredentialSubjectType.chainbornMembership: + return 'Chainborn'; + case CredentialSubjectType.twitterCard: + return 'Twitter Account Proof'; + case CredentialSubjectType.ageRange: + return 'Age Range'; + case CredentialSubjectType.nationality: + return 'Nationality'; + case CredentialSubjectType.gender: + return 'Gender'; + case CredentialSubjectType.walletCredential: + return 'Wallet Credential'; + case CredentialSubjectType.tezosAssociatedWallet: + return 'Tezos Associated Address'; + case CredentialSubjectType.ethereumAssociatedWallet: + return 'Ethereum Associated Address'; + case CredentialSubjectType.fantomAssociatedWallet: + return 'Fantom Associated Address'; + case CredentialSubjectType.polygonAssociatedWallet: + return 'Polygon Associated Address'; + case CredentialSubjectType.binanceAssociatedWallet: + return 'Binance Associated Address'; + case CredentialSubjectType.ethereumPooAddress: + return 'Ethereum Poo Address'; + case CredentialSubjectType.fantomPooAddress: + return 'Fantom Poo Address'; + case CredentialSubjectType.polygonPooAddress: + return 'Polygon Poo Address'; + case CredentialSubjectType.binancePooAddress: + return 'Binance Poo Address'; + case CredentialSubjectType.tezosPooAddress: + return 'Tezos Poo Address'; + case CredentialSubjectType.certificateOfEmployment: + return 'Certificate of Employment'; + case CredentialSubjectType.ecole42LearningAchievement: + return 'Ecole42 Learning Achievement'; + case CredentialSubjectType.emailPass: + return 'Email Pass'; + case CredentialSubjectType.identityPass: + return 'Identity Pass'; + case CredentialSubjectType.verifiableIdCard: + return 'VerifiableId'; + case CredentialSubjectType.linkedInCard: + return 'Linkedin Card'; + case CredentialSubjectType.learningAchievement: + return 'Learning Achievement'; + case CredentialSubjectType.loyaltyCard: + return 'Loyalty Card'; + case CredentialSubjectType.over18: + return 'Over18'; + case CredentialSubjectType.over13: + return 'Over13'; + case CredentialSubjectType.passportFootprint: + return 'Passport Number'; + case CredentialSubjectType.phonePass: + return 'Phone Proof'; + case CredentialSubjectType.professionalExperienceAssessment: + return 'Professional Experience Assessment'; + case CredentialSubjectType.professionalSkillAssessment: + return 'Professional Skill Assessment'; + case CredentialSubjectType.professionalStudentCard: + return 'Professional Student Card'; + case CredentialSubjectType.residentCard: + return 'Resident Card'; + case CredentialSubjectType.selfIssued: + return 'Self Issued'; + case CredentialSubjectType.studentCard: + return 'Student Card'; + case CredentialSubjectType.voucher: + return 'Voucher'; + case CredentialSubjectType.tezVoucher: + return 'TezVoucher'; + case CredentialSubjectType.talaoCommunityCard: + return 'Talao Community'; + case CredentialSubjectType.diplomaCard: + return 'Verifiable Diploma'; + case CredentialSubjectType.aragoPass: + return 'Arago Pass'; + case CredentialSubjectType.aragoEmailPass: + return 'Arago Email Pass'; + case CredentialSubjectType.aragoIdentityCard: + return 'Arago Id Card'; + case CredentialSubjectType.aragoLearningAchievement: + return 'Arago Learning Achievement'; + case CredentialSubjectType.aragoOver18: + return 'Arago Over18'; + case CredentialSubjectType.pcdsAgentCertificate: + return 'PCDS Agent Certificate'; + case CredentialSubjectType.euDiplomaCard: + return 'EU Diploma'; + case CredentialSubjectType.euVerifiableId: + return 'EU VerifiableID'; + case CredentialSubjectType.defaultCredential: + return ''; + } + } } diff --git a/lib/app/shared/message_handler/global_message.dart b/lib/app/shared/message_handler/global_message.dart index 90fc3ba13..ea42bc12f 100644 --- a/lib/app/shared/message_handler/global_message.dart +++ b/lib/app/shared/message_handler/global_message.dart @@ -364,4 +364,10 @@ class GlobalMessage { l10n.transactionIsLikelyToFail; String get RESPONSE_STRING_verifiableIdCardDummyDesc => l10n.verifiableIdCardDummyDesc; + String get RESPONSE_STRING_tezotopiaMembershipLongDescription => + l10n.tezotopiaMembershipLongDescription; + String get RESPONSE_STRING_chainbornMembershipLongDescription => + l10n.chainbornMembershipLongDescription; + String get RESPONSE_STRING_bloometaPassLongDescription => + l10n.bloometaPassLongDescription; } diff --git a/lib/app/shared/message_handler/response_message.dart b/lib/app/shared/message_handler/response_message.dart index d64b0d44c..043ba396e 100644 --- a/lib/app/shared/message_handler/response_message.dart +++ b/lib/app/shared/message_handler/response_message.dart @@ -19,8 +19,8 @@ class ResponseMessage with MessageHandler { .localise(context); case ResponseString.RESPONSE_STRING_bloometaPassHowToGetIt: - return ResponseString.RESPONSE_STRING_bloometaPassHowToGetIt - .localise(context); + return ResponseString.RESPONSE_STRING_bloometaPassHowToGetIt.localise( + context); case ResponseString.RESPONSE_STRING_identityProofDummyDescription: return ResponseString.RESPONSE_STRING_identityProofDummyDescription @@ -605,25 +605,47 @@ class ResponseMessage with MessageHandler { .localise( context, ); + case ResponseString.RESPONSE_STRING_linkedinCardWhyGetThisCard: return ResponseString.RESPONSE_STRING_linkedinCardWhyGetThisCard .localise( context, ); + case ResponseString.RESPONSE_STRING_linkedinCardExpirationDate: return ResponseString.RESPONSE_STRING_linkedinCardExpirationDate .localise( context, ); + case ResponseString.RESPONSE_STRING_linkedinCardHowToGetIt: return ResponseString.RESPONSE_STRING_linkedinCardHowToGetIt.localise( context, ); + case ResponseString.RESPONSE_STRING_verifiableIdCardDummyDesc: return ResponseString.RESPONSE_STRING_verifiableIdCardDummyDesc .localise( context, ); + + case ResponseString.RESPONSE_STRING_tezotopiaMembershipLongDescription: + return ResponseString + .RESPONSE_STRING_tezotopiaMembershipLongDescription.localise( + context, + ); + + case ResponseString.RESPONSE_STRING_chainbornMembershipLongDescription: + return ResponseString + .RESPONSE_STRING_chainbornMembershipLongDescription.localise( + context, + ); + + case ResponseString.RESPONSE_STRING_bloometaPassLongDescription: + return ResponseString.RESPONSE_STRING_bloometaPassLongDescription + .localise( + context, + ); } } return ''; diff --git a/lib/dashboard/discover/view/discover_details_page.dart b/lib/dashboard/discover/view/discover_details_page.dart index 4d84ac04a..bad2aafbd 100644 --- a/lib/dashboard/discover/view/discover_details_page.dart +++ b/lib/dashboard/discover/view/discover_details_page.dart @@ -146,13 +146,22 @@ class DetailFields extends StatelessWidget { ); } return Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (homeCredential.websiteGameLink != null) + if (homeCredential.websiteLink != null) DiscoverDynamicDetial( - title: l10n.websiteGame, - value: homeCredential.websiteGameLink!, + title: l10n.website, + value: homeCredential.websiteLink!, format: AltMeStrings.uri, ), + if (homeCredential.longDescription != null) + DiscoverDynamicDetial( + title: homeCredential.credentialSubjectType.title, + value: homeCredential.whyGetThisCard!.getMessage( + context, + homeCredential.longDescription!, + ), + ), if (homeCredential.whyGetThisCard != null) DiscoverDynamicDetial( title: l10n.whyGetThisCard, diff --git a/lib/dashboard/discover/widget/discover_dynamic_detail.dart b/lib/dashboard/discover/widget/discover_dynamic_detail.dart index 34e27b4bb..b8fff09a5 100644 --- a/lib/dashboard/discover/widget/discover_dynamic_detail.dart +++ b/lib/dashboard/discover/widget/discover_dynamic_detail.dart @@ -1,4 +1,5 @@ import 'package:altme/app/app.dart'; +import 'package:altme/l10n/l10n.dart'; import 'package:altme/theme/theme.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; @@ -32,7 +33,9 @@ class DiscoverDynamicDetial extends StatelessWidget { children: [ TextSpan(text: '$title: ', style: titleTheme), TextSpan( - text: value, + text: (format != null && format == AltMeStrings.uri) + ? context.l10n.link + : value, style: valueTheme, recognizer: TapGestureRecognizer() ..onTap = () async { diff --git a/lib/dashboard/home/tab_bar/credentials/models/home_credential/home_credential.dart b/lib/dashboard/home/tab_bar/credentials/models/home_credential/home_credential.dart index f2685cc83..5db7da865 100644 --- a/lib/dashboard/home/tab_bar/credentials/models/home_credential/home_credential.dart +++ b/lib/dashboard/home/tab_bar/credentials/models/home_credential/home_credential.dart @@ -14,10 +14,11 @@ class HomeCredential extends Equatable { required this.isDummy, this.dummyDescription, required this.credentialSubjectType, - this.websiteGameLink, + this.websiteLink, this.whyGetThisCard, this.expirationDateDetails, this.howToGetIt, + this.longDescription, }); factory HomeCredential.fromJson(Map json) => @@ -35,11 +36,12 @@ class HomeCredential extends Equatable { factory HomeCredential.isDummy(CredentialSubjectType credentialSubjectType) { String? image; String? link; - String? websiteGameLink; + String? websiteLink; ResponseString? whyGetThisCard; ResponseString? expirationDateDetails; ResponseString? howToGetIt; ResponseString? dummyDesc; + ResponseString? longDescription; switch (credentialSubjectType) { case CredentialSubjectType.walletCredential: @@ -121,7 +123,7 @@ class HomeCredential extends Equatable { case CredentialSubjectType.tezVoucher: image = ImageStrings.dummyTezotopiaVoucherCard; link = Urls.tezotopiaVoucherUrl; - websiteGameLink = 'https://tezotopia.com'; + websiteLink = 'https://tezotopia.com'; whyGetThisCard = ResponseString.RESPONSE_STRING_tezVoucherWhyGetThisCard; expirationDateDetails = @@ -164,17 +166,22 @@ class HomeCredential extends Equatable { ResponseString.RESPONSE_STRING_tezotopiaMembershipExpirationDate; howToGetIt = ResponseString.RESPONSE_STRING_tezotopiaMembershipHowToGetIt; + longDescription = + ResponseString.RESPONSE_STRING_tezotopiaMembershipLongDescription; break; case CredentialSubjectType.chainbornMembership: image = ImageStrings.chainbornMemberShipDummy; link = Urls.chainbornMembershipCardUrl; + websiteLink = 'https://chainborn.xyz/'; whyGetThisCard = ResponseString.RESPONSE_STRING_chainbornMembershipWhyGetThisCard; expirationDateDetails = ResponseString.RESPONSE_STRING_chainbornMembershipExpirationDate; howToGetIt = ResponseString.RESPONSE_STRING_chainbornMembershipHowToGetIt; + longDescription = + ResponseString.RESPONSE_STRING_chainbornMembershipLongDescription; break; case CredentialSubjectType.twitterCard: @@ -248,12 +255,15 @@ class HomeCredential extends Equatable { case CredentialSubjectType.bloometaPass: image = ImageStrings.bloometaDummy; + websiteLink = 'https://bloometa.com'; link = Urls.bloometaCardUrl; whyGetThisCard = ResponseString.RESPONSE_STRING_bloometaPassWhyGetThisCard; expirationDateDetails = ResponseString.RESPONSE_STRING_bloometaPassExpirationDate; howToGetIt = ResponseString.RESPONSE_STRING_bloometaPassHowToGetIt; + longDescription = + ResponseString.RESPONSE_STRING_bloometaPassLongDescription; break; case CredentialSubjectType.ethereumAssociatedWallet: @@ -305,8 +315,10 @@ class HomeCredential extends Equatable { ? null : ResponseMessage(expirationDateDetails), howToGetIt: howToGetIt == null ? null : ResponseMessage(howToGetIt), - websiteGameLink: websiteGameLink, + websiteLink: websiteLink, dummyDescription: dummyDesc == null ? null : ResponseMessage(dummyDesc), + longDescription: + longDescription == null ? null : ResponseMessage(longDescription), ); } @@ -317,13 +329,15 @@ class HomeCredential extends Equatable { final String? image; final bool isDummy; final CredentialSubjectType credentialSubjectType; - final String? websiteGameLink; + final String? websiteLink; @JsonKey(includeFromJson: false, includeToJson: false) final MessageHandler? whyGetThisCard; @JsonKey(includeFromJson: false, includeToJson: false) final MessageHandler? expirationDateDetails; @JsonKey(includeFromJson: false, includeToJson: false) final MessageHandler? howToGetIt; + @JsonKey(includeFromJson: false, includeToJson: false) + final MessageHandler? longDescription; Map toJson() => _$HomeCredentialToJson(this); @@ -334,10 +348,11 @@ class HomeCredential extends Equatable { image, isDummy, credentialSubjectType, - websiteGameLink, + websiteLink, whyGetThisCard, expirationDateDetails, howToGetIt, dummyDescription, + longDescription, ]; } diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 609479352..d11566c79 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -575,7 +575,7 @@ "transactionErrorTxRollupInvalidZeroTransfer": "A transfer's amount must be greater than zero.", "transactionErrorTxRollupUnknownAddress": "The address must exist in the context when signing a transfer with it.", "transactionErrorInactiveChain": "Attempted validation of a block from an inactive chain.", - "websiteGame": "Website game", + "website": "Website", "whyGetThisCard": "Why get this card", "howToGetIt": "How to get it", "emailPassWhyGetThisCard": "This proof may be required by some Web 3 Apps / Websites to access their service or claim benefits : Membership card, Loyalty card, Rewards, etc.", @@ -583,9 +583,11 @@ "emailPassHowToGetIt": "It’s super easy. Altme will verify your email ownership by sending a code by email.", "tezotopiaMembershipWhyGetThisCard": "This Membership card will give you 25% cash backs on ALL Tezotopia Game transactions when you buy a Drops on the marketplace or mint an NFT on starbase.", "tezotopiaMembershipExpirationDate": "This card will remain active and reusable for 1 YEAR.", + "tezotopiaMembershipLongDescription": "Tezotopia is a real-time metaverse NFT game on Tezos, where players yield farm with Tezotops, engage in battles for rewards, and claim land in an immersive blockchain space adventure. Explore the metaverse, collect NFTs and conquer Tezotopia.", "chainbornMembershipHowToGetIt": "To get this card, you need to summon a “Hero” in Chainborn game and an Email Proof. You can find the “Email proof” card in Altme “Discover” section.", "chainbornMembershipWhyGetThisCard": "Be among the few that have access to exclusive Chainborn store content, airdrops and other member-only benefits !", "chainbornMembershipExpirationDate": "This card will remain active and reusable for 1 YEAR.", + "chainbornMembershipLongDescription": "Chainborn is an exciting NFT battle game where players use their own NFTs as heroes, competing for loot and glory. Engage in thrilling fights, gain experience points to boost your hero's strength and health, and enhance the value of your NFTs in this captivating Tezos-based adventure.", "twitterHowToGetIt": "Follow the steps on TezosProfiles https://tzprofiles.com/connect. Then, claim the “Twitter account” card in Altme. Make sure to sign the transaction on TZPROFILES with the same account you are using in Altme.", "twitterWhyGetThisCard": "This card is a proof that you own your twitter account. Use it to prove your twitter account ownership whenever you need.", "twitterExpirationDate": "This card will be active for 1 year.", @@ -818,5 +820,6 @@ "bloometaPassHowToGetIt": "You need to present an Email proof and an Over 18 proof. If you don’t have those credentials already, claim them directly in Altme “Discover” section.", "bloometaPassExpirationDate": "This card will remain active and reusable for 1 YEAR.", "bloometaPassWhyGetThisCard": "Be among the few to get access to limited edition mints, gaming highlights and future airdrops. Claim your card today and enter the Bloometaverse !", + "bloometaPassLongDescription": "Bloometa is a user-friendly NFT marketplace that connects in-game assets from different blockchains, making it easy for Metaverse players and GameFi projects to access a curated selection of NFTs. Enjoy multi-chain compatibility and play-to-earn features in a simple, beginner-friendly platform.", "chat": "Chat" } \ No newline at end of file diff --git a/lib/l10n/arb/app_fr.arb b/lib/l10n/arb/app_fr.arb index edec5aaf9..9b99be847 100644 --- a/lib/l10n/arb/app_fr.arb +++ b/lib/l10n/arb/app_fr.arb @@ -569,7 +569,6 @@ "transactionErrorTxRollupInvalidZeroTransfer": "Le montant d'un virement doit être supérieur à zéro.", "transactionErrorTxRollupUnknownAddress": "L'adresse doit exister dans le contexte lors de la signature d'un transfert avec elle.", "transactionErrorInactiveChain": "Tentative de validation d'un bloc d'une chaîne inactive.", - "websiteGame": "Jeu de site Web", "whyGetThisCard": "Pourquoi obtenir cette carte", "howToGetIt": "Comment l'obtenir", "emailPassWhyGetThisCard": "Ce justificatif peut être exigé par certaines Apps / Sites Web du Web 3 pour accéder à leur service ou réclamer des avantages : Carte de membre, Carte de fidélité, Rewards, etc.", diff --git a/lib/l10n/untranslated.json b/lib/l10n/untranslated.json index a53e1a637..72973c1ab 100644 --- a/lib/l10n/untranslated.json +++ b/lib/l10n/untranslated.json @@ -511,7 +511,7 @@ "transactionErrorTxRollupInvalidZeroTransfer", "transactionErrorTxRollupUnknownAddress", "transactionErrorInactiveChain", - "websiteGame", + "website", "whyGetThisCard", "howToGetIt", "emailPassWhyGetThisCard", @@ -519,9 +519,11 @@ "emailPassHowToGetIt", "tezotopiaMembershipWhyGetThisCard", "tezotopiaMembershipExpirationDate", + "tezotopiaMembershipLongDescription", "chainbornMembershipHowToGetIt", "chainbornMembershipWhyGetThisCard", "chainbornMembershipExpirationDate", + "chainbornMembershipLongDescription", "twitterHowToGetIt", "twitterWhyGetThisCard", "twitterExpirationDate", @@ -754,6 +756,7 @@ "bloometaPassHowToGetIt", "bloometaPassExpirationDate", "bloometaPassWhyGetThisCard", + "bloometaPassLongDescription", "chat" ], @@ -1269,7 +1272,7 @@ "transactionErrorTxRollupInvalidZeroTransfer", "transactionErrorTxRollupUnknownAddress", "transactionErrorInactiveChain", - "websiteGame", + "website", "whyGetThisCard", "howToGetIt", "emailPassWhyGetThisCard", @@ -1277,9 +1280,11 @@ "emailPassHowToGetIt", "tezotopiaMembershipWhyGetThisCard", "tezotopiaMembershipExpirationDate", + "tezotopiaMembershipLongDescription", "chainbornMembershipHowToGetIt", "chainbornMembershipWhyGetThisCard", "chainbornMembershipExpirationDate", + "chainbornMembershipLongDescription", "twitterHowToGetIt", "twitterWhyGetThisCard", "twitterExpirationDate", @@ -1512,6 +1517,7 @@ "bloometaPassHowToGetIt", "bloometaPassExpirationDate", "bloometaPassWhyGetThisCard", + "bloometaPassLongDescription", "chat" ], @@ -1523,6 +1529,9 @@ "importWalletTextRecoveryPhraseOnly", "importWalletHintTextRecoveryPhraseOnly", "educationCredentialHomeSubtitle", + "website", + "tezotopiaMembershipLongDescription", + "chainbornMembershipLongDescription", "chooseMethodPageAgeRangeTitle", "kycTitle", "kycSubtitle", @@ -1554,6 +1563,7 @@ "bloometaPassHowToGetIt", "bloometaPassExpirationDate", "bloometaPassWhyGetThisCard", + "bloometaPassLongDescription", "chat" ], @@ -2069,7 +2079,7 @@ "transactionErrorTxRollupInvalidZeroTransfer", "transactionErrorTxRollupUnknownAddress", "transactionErrorInactiveChain", - "websiteGame", + "website", "whyGetThisCard", "howToGetIt", "emailPassWhyGetThisCard", @@ -2077,9 +2087,11 @@ "emailPassHowToGetIt", "tezotopiaMembershipWhyGetThisCard", "tezotopiaMembershipExpirationDate", + "tezotopiaMembershipLongDescription", "chainbornMembershipHowToGetIt", "chainbornMembershipWhyGetThisCard", "chainbornMembershipExpirationDate", + "chainbornMembershipLongDescription", "twitterHowToGetIt", "twitterWhyGetThisCard", "twitterExpirationDate", @@ -2312,6 +2324,7 @@ "bloometaPassHowToGetIt", "bloometaPassExpirationDate", "bloometaPassWhyGetThisCard", + "bloometaPassLongDescription", "chat" ] } diff --git a/pubspec.lock b/pubspec.lock index d0eea0657..29fc43cb8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2423,5 +2423,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.19.0 <3.7.0" + dart: ">=2.19.0 <3.0.0" flutter: ">=3.7.0-0" From 3e5b08051afb337b91d747b17f5fbd6de9de2ffc Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Fri, 17 Mar 2023 15:09:43 +0330 Subject: [PATCH 183/190] add except keys when reset wallet --- lib/wallet/cubit/wallet_cubit.dart | 16 ++++++++++++++-- pubspec.lock | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/lib/wallet/cubit/wallet_cubit.dart b/lib/wallet/cubit/wallet_cubit.dart index 76f7a48e7..e61aa35a3 100644 --- a/lib/wallet/cubit/wallet_cubit.dart +++ b/lib/wallet/cubit/wallet_cubit.dart @@ -654,8 +654,20 @@ class WalletCubit extends Cubit { } } - Future resetWallet() async { - await secureStorageProvider.deleteAll(); + Future resetWallet({ + List? exceptKeys = const [SecureStorageKeys.version], + }) async { + if (exceptKeys == null || exceptKeys.isEmpty) { + await secureStorageProvider.deleteAll(); + } else { + final keyValues = await secureStorageProvider.getAllValues(); + final keys = keyValues.keys.toList(); + for (final key in keys) { + if (!exceptKeys.contains(key)) { + await secureStorageProvider.delete(key); + } + } + } // await credentialsRepository.deleteAll(); // await profileCubit.resetProfile(); diff --git a/pubspec.lock b/pubspec.lock index 29fc43cb8..d0eea0657 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2423,5 +2423,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.19.0 <3.0.0" + dart: ">=2.19.0 <3.7.0" flutter: ">=3.7.0-0" From c3727464f2263e96106f5812a228693a24706eda Mon Sep 17 00:00:00 2001 From: TalebRafiepour Date: Fri, 17 Mar 2023 15:18:55 +0330 Subject: [PATCH 184/190] prevent chat initialzing when wallet not created yet --- lib/dashboard/drawer/chat_room/cubit/chat_room_cubit.dart | 6 +++++- .../drawer/chat_room/matrix_chat/matrix_chat_impl.dart | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/dashboard/drawer/chat_room/cubit/chat_room_cubit.dart b/lib/dashboard/drawer/chat_room/cubit/chat_room_cubit.dart index bf8d2ed97..ec922605c 100644 --- a/lib/dashboard/drawer/chat_room/cubit/chat_room_cubit.dart +++ b/lib/dashboard/drawer/chat_room/cubit/chat_room_cubit.dart @@ -26,7 +26,11 @@ abstract class ChatRoomCubit extends Cubit { }) : super( const ChatRoomState(), ) { - init(); + secureStorageProvider.get(SecureStorageKeys.did).then((did) { + if (did != null && did.isNotEmpty) { + init(); + } + }); } final SecureStorageProvider secureStorageProvider; diff --git a/lib/dashboard/drawer/chat_room/matrix_chat/matrix_chat_impl.dart b/lib/dashboard/drawer/chat_room/matrix_chat/matrix_chat_impl.dart index a4e1bff89..7407c1fa2 100644 --- a/lib/dashboard/drawer/chat_room/matrix_chat/matrix_chat_impl.dart +++ b/lib/dashboard/drawer/chat_room/matrix_chat/matrix_chat_impl.dart @@ -36,7 +36,11 @@ class MatrixChatImpl extends MatrixChatInterface { required this.dioClient, required this.secureStorageProvider, }) { - init(); + secureStorageProvider.get(SecureStorageKeys.did).then((did) { + if (did != null && did.isNotEmpty) { + init(); + } + }); } static MatrixChatImpl? _instance; From 071b362f6d3ebbd7bed8ae8d909d2e8c2da9d441 Mon Sep 17 00:00:00 2001 From: hawkbee1 Date: Fri, 17 Mar 2023 18:00:37 +0100 Subject: [PATCH 185/190] backup does not work on android #1452 --- lib/app/shared/helper_functions/helper_functions.dart | 3 +++ pubspec.lock | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/app/shared/helper_functions/helper_functions.dart b/lib/app/shared/helper_functions/helper_functions.dart index cbf536690..b2cb27fbf 100644 --- a/lib/app/shared/helper_functions/helper_functions.dart +++ b/lib/app/shared/helper_functions/helper_functions.dart @@ -272,6 +272,9 @@ Future> getssiMnemonicsInList( } Future getStoragePermission() async { + if (isAndroid()) { + return true; + } if (await Permission.storage.request().isGranted) { return true; } else if (await Permission.storage.request().isPermanentlyDenied) { diff --git a/pubspec.lock b/pubspec.lock index d0eea0657..29fc43cb8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2423,5 +2423,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.19.0 <3.7.0" + dart: ">=2.19.0 <3.0.0" flutter: ">=3.7.0-0" From 3677fe101bc723950ccf2053675cc8fd6a38a71d Mon Sep 17 00:00:00 2001 From: hawkbee <49282360+hawkbee1@users.noreply.github.com> Date: Sat, 18 Mar 2023 12:21:38 +0100 Subject: [PATCH 186/190] Merge main in 1.11.4 (#1476) * realease candidate 1.11.2+160 whith whatsnew, ebsi key from mnemonic and bloometa hidden * ensure same functionalities for EBSI from QRCode or Deeplink * EBSI: secp256k1 key when using mnemonic * version: 1.11.3+161 * version 1.11.4+163 * update FAQ + No BOLD in the font for FAQ questions => Normal font will be easier to read #1444 * show bloometa card --- .fvm/fvm_config.json | 2 +- assets/faq.json | 114 ++++++------------ lib/app/shared/constants/constant_list.dart | 8 +- .../qr_code_scan/view/qr_code_scan_page.dart | 8 +- .../qr_code_scan/view/qr_scanner_page.dart | 5 +- .../initiate_ebsi_credential_issuance.dart | 9 +- lib/scan/cubit/scan_cubit.dart | 9 +- lib/theme/app_theme/app_theme.dart | 2 +- packages/ebsi/lib/src/ebsi.dart | 2 - pubspec.lock | 8 +- pubspec.yaml | 6 +- 11 files changed, 66 insertions(+), 107 deletions(-) diff --git a/.fvm/fvm_config.json b/.fvm/fvm_config.json index 6e3d57e67..69fe1472f 100644 --- a/.fvm/fvm_config.json +++ b/.fvm/fvm_config.json @@ -1,4 +1,4 @@ { - "flutterSdkVersion": "3.7.6", + "flutterSdkVersion": "3.7.7", "flavors": {} } \ No newline at end of file diff --git a/assets/faq.json b/assets/faq.json index 37ece00d0..d2c9d0ddf 100644 --- a/assets/faq.json +++ b/assets/faq.json @@ -1,117 +1,77 @@ { "faq": [ { - "que": "What can I do with Altme ?", - "ans": "Altme give users the ability to take back control of their personal information and share these with Web 3 applications when its required. For example, with Altme users can present digital credentials (Over 18 proof, KYC...) in order to access certain services or take advantage of certain offers (membership card, loyalty card, Exclusive pass...)." + "que": "1. What is Altme?", + "ans": "Altme is a crypto wallet that fully supports digital assets and digital identity, enabling users to securely and privately store their digital assets (NFTs, collectibles, coins) and personal information (ID card, proof of age…) in the form of Verifiable Credentials." }, { - "que": "Why is Altme called a Universal wallet and why is it different from other digital wallets ?", - "ans": "Altme is called a 'Universal wallet' because just like a physical wallet in which you can carry your ID cards and funds, you can use the Altme wallet to gather digital credentials (Identity card, Age range proof, diploma, etc.) and digital assets (Cryptos, NFTs, tokens...). The future of digital identity is being build on Self-Sovereign Identity (SSI standard), which is the technology that powers Altme wallet and give its users the ability to manage digital credentials in a privacy-preserving and decentralized manner." + "que": "2. How is Altme different from other wallets?", + "ans": "Altme wallet is built on Self-Sovereign Identity (SSI), a new digital identity standard that enables the management of digital credentials in a decentralized and privacy-preserving manner." }, { - "que": "What is Self-Sovereign Identity (SSI) ?", - "ans": "Self-Sovereign Identity, or SSI, is a new technology compatible with blockchain that gives users and organizations the ability to control their own digital credentials and identity data, rather than relying on a central authority or central database to manage their personal information. With SSI, users are able to store, manage and share their personal data in a decentralize manner, enabling greater privacy and security. SSI also uses Zero-Knowledge Proof and 'Selective Disclosure' to protect users privacy." + "que": "3. What is Self-Sovereign Identity?", + "ans": "Self-Sovereign Identity (SSI) is a new technology compatible with blockchain that gives users the ability to control their own identity data, without relying on a central authority or database to manage their personal information." }, { - "que": "How can I use my Altme SSI wallet to manage and share my verifiable credentials?", - "ans": "When you receive a verifiable credential in the Altme wallet, it is added to your digital identity. You can then use this credential to prove your identity or access certain services. For example, you might use a membership credential to access a members-only area of a website, or an 'Over 18' proof to access a DeFi application. You can use the “scan” button in Altme to share credentials when it is requested. It’s super easy. Just scan the QR code, and accept the request to share your digital credentials." + "que": "4. What are 'Verifiable Credentials'?", + "ans": "Verifiable Credentials (VCs) are tamper-proof and extremely secure digital documents (Proof of Age, diploma, ID Card, membership card…) that can be instantly shared and verified by a third-party." }, { - "que": "What is the level of security of Altme wallet ?", - "ans": "Altme wallet is self-custodial and protected by military grade encryption and biometrics authentication. Your data are encrypted and stored on your device, enabling better privacy and security." + "que": "5. How can I use/share my Verifiable Credentials?", + "ans": "When you obtain a verifiable credential in Altme, you can use it to prove things about yourself by scanning a simple QR code. For example, you can use your 'Over 18' proof to prove that you are over 18 years old to certain Web 3 applications." }, { - "que": "What does it mean that Altme is “self-custodial” ?", - "ans": "A self-custodial wallet (= non-custodial) means that your data are encrypted and stored on your device. Only you can access your keys, data and assets. Not us. Not anyone else." + "que": "6. What is the level of security of Altme wallet?", + "ans": "Altme wallet is self-custodial and protected by military-grade encryption and biometric authentication. Your data is encrypted and stored on your device, enabling unbeatable privacy and security." }, { - "que": "Are there any risks related to verifying my address to get a 'proof the ownership' in Altme ?", - "ans": "No there is zero risk. You don’t need to share or disclose any passphrase or private keys to get a proof of ownership. All you have to do is scan a QR code with your favorite crypto wallet (address) in order to get the credential that proves you hold the private key of this address. Altme wallet is never connected your crypto wallet." + "que": "7. What does it mean that Altme is 'self-custodial'?", + "ans": "A self-custodial (= non custodial) wallet means that your digital assets and data are encrypted and stored on your device. Only you can access your keys, data, and assets. Not us. Not anyone else." }, { - "que": "How does Altme wallet protect my personal information?", - "ans": "Altme is built on Self-Sovereign Identity (SSI) technology which is a new digital identity standard that enables the storage and management of personal information in a decentralized, secure and private way. All your personal information are encrypted and stored in your phone. Only you have the power to share credentials information that are encrypted in your wallet." + "que": "8. Where are my personal data stored?", + "ans": "Your identity and personal data are encrypted and stored off-chain, on your phone." }, { - "que": "Where are my digital credentials stored in Altme ?", - "ans": "Your digital credentials are encrypted and stored on your phone. There is no central database or cloud that stores your data. This is why it is called “self-sovereign identity” (SSI). Only you should own and access your data." + "que": "9. How do I backup my keys and verifiable credentials?", + "ans": "To backup your Altme wallet, write down your passphrase (12 words) and download your digital credential files, then store them in a secure location." }, { - "que": "How do I backup my keys and credentials in Altme ?", - "ans": "To backup your Altme wallet, you need to write down your passphrase and download your digital credentials. It's important to store these in a secure location, such as a password-protected file or a physical safe. If you lose access to your Altme wallet, there is nothing we can do to help you. Altme is self-custodial which means only you can control your keys and your credentials." + "que": "10. What cryptocurrencies and NFTs are supported by Altme?", + "ans": "Altme supports tokens from several blockchains, including Ethereum, Polygon, Tezos, Fantom, Binance Chain…" }, { - "que": "What are Verifiable Credentials and why is it useful ?", - "ans": "Verifiable Credentials (VCs) are an open standard for digital claims. They can represent information such as identity card, an Over 18 Proof or a proof of ownership of a crypto address. They have many advantages, including the fact that they are digitally signed, making them tamper-proof, extremely secure and instantly verifiable by a third-party." + "que": "11. How can I deposit or withdraw funds from my Altme wallet?", + "ans": "You can purchase native coins directly in Altme and send them to your desired blockchain addresses." }, { - "que": "What is 'elective disclosure' ?", - "ans": "Selective disclosure is a way for SSI users to reveal certain pieces of information about their identity, without disclosing sensitive information. It helps increase the security of online interactions. For exemple, reveal that you are 'Over 18' without revealing your name or address." + "que": "12. Can I trade cryptocurrencies using Altme?", + "ans": "While you cannot directly trade cryptocurrencies in Altme, you can easily connect your wallet with decentralized exchanges (DEX) to trade any coins." }, { - "que": "What should I do if I find a bug?", - "ans": "You can contact us at our support email : support@altme.io" + "que": "13. Is Altme available in languages other than English?", + "ans": "At this time, Altme is only available in English." }, { - "que": "How can I contact Altme if I need supports ?", - "ans": "You can contact us by using our support email : support@altme.io. Currently, the Altme SSI wallet is only available in English. However, we are working on expanding the availability of the wallet to other languages in the future." + "que": "14. How can I restore my wallet?", + "ans": "You can backup your Altme wallet by using your recovery phrase. Find your recovery phrase by going to “Parameters”, then “Wallet Security”, and finally “Show Wallet Recovery Phrase”." }, { - "que": "What are Altme's Terms of Service?", - "ans": "Redirect to CGU.", - "href": "https://altme.io/cgu" - }, - { - "que": "Can I restore my wallet?", - "ans": "You can recover your wallet thanks to its recovery phrase, you can find it in parameters > blockchain settings > show wallet recovery phrase." - }, - { - "que": "How can I secure my wallet?", - "ans": "You can secure your wallet with biometric recognition and a 4-digit code." - }, - { - "que": "Where is my public address?", - "ans": "To find your public address you have to go in parameters > blockchain settings > manage blockchain account for see each address by account." - }, - { - "que": "How can I use my existing crypto wallet with Altme ?", - "ans": "It's extremely simple ! All you have to do is to verify your favorite wallet address and get a 'Proof of ownership' credential in Altme. You can follow the process above to start." - }, - { - "que": "Which crypto wallets are compatible with Altme ?", - "ans": "Right now, you can easily prove ownership of any Tezos wallets like Temple, Kukai, Naan, Autonomy, Ait Gap, Umami...Soon, you will be able to do the same with Ethereum and EVM compatible wallets like Metamask, Trust wallet, etc." + "que": "15. What if I lose my recovery phrase?", + "ans": "If you lose your recovery phrase, it is not possible for us to help you retrieve it. It is crucial to keep it in a safe and secure location." }, { - "que": "Do I need cryptocurrency to use Altme?", - "ans": "There is no need for cryptocurrency to use Altme. You can have an account only to confirm your information without having cryptocurrency." + "que": "16. Why my redirect does not go on the App? IOS/ANDROID", + "ans": "If the redirection is not done automatically you must go to your phone's settings > Settings > Apps > Altme/Talao > Set as default > Supported web addresses > app.altme.io -> On" }, { - "que": "Does Altme support NFT?", - "ans": "Altme support NFT in the wallet part." + "que": "17. How can I contact Altme if I need support?", + "ans": "If you find a bug, please contact our support team using the in-app chat or email support@altme.io." }, { - "que": "How do I deposit/receive/send money?", - "ans": "You can deposit money using your credit card or Apple Pay. You can receive money in your crypto-wallet for each incoming transaction with your iD address for each cryptocurrency. You can send money to the crypto-wallet you want thanks to its ID address of the desired crypto-currency." - }, - { - "que": "How do I send my ETH and tokens?", - "ans": "To send tokens you have to go to CARDS then TOKENS, select your token and define how many and to whom and send (find your token ID on the reveice part." - }, - { - "que": "Why is my balance not updating?", - "ans": "You have to wait or refresh the tokens page." - }, - { - "que": "Why is my NFT not showing up?", - "ans": "You have to wait or refresh the NFT part." - }, - { - "que": "Why do I need to approve tokens before trading?", - "ans": "You must approve the tokens before trading to verify and ensure the transaction." - }, - { - "que": "Will Altme charge for exchanges?", - "ans": "Currently there is a fixed rate on sending tokens from the TEZOS blockchain at 0.0219XTZ (about 0.017€) and rates calculated on the ethereum blockchains." + "que": "18. What are Altme's Terms of Service?", + "ans": "You can find Altme's Terms of Service by visiting : ", + "href": "https://altme.io/cgu" } ] } \ No newline at end of file diff --git a/lib/app/shared/constants/constant_list.dart b/lib/app/shared/constants/constant_list.dart index 81fb78c07..b509531f4 100644 --- a/lib/app/shared/constants/constant_list.dart +++ b/lib/app/shared/constants/constant_list.dart @@ -5,11 +5,11 @@ class DiscoverList { CredentialSubjectType.tezotopiaMembership, CredentialSubjectType.chainbornMembership, CredentialSubjectType.bloometaPass, - CredentialSubjectType.troopezPass, - CredentialSubjectType.pigsPass, - CredentialSubjectType.matterlightPass, + // CredentialSubjectType.troopezPass, + // CredentialSubjectType.pigsPass, + // CredentialSubjectType.matterlightPass, // CredentialSubjectType.dogamiPass, - CredentialSubjectType.bunnyPass, + // CredentialSubjectType.bunnyPass, ]; static final List communityCategories = [ diff --git a/lib/dashboard/qr_code/qr_code_scan/view/qr_code_scan_page.dart b/lib/dashboard/qr_code/qr_code_scan/view/qr_code_scan_page.dart index fab89c213..5be6463a0 100644 --- a/lib/dashboard/qr_code/qr_code_scan/view/qr_code_scan_page.dart +++ b/lib/dashboard/qr_code/qr_code_scan/view/qr_code_scan_page.dart @@ -94,12 +94,8 @@ class _QrCodeScanPageState extends State { child: MobileScanner( key: qrKey, controller: scannerController, - onDetect: (capture) { - final List qrcodes = capture.barcodes; - final Barcode qrcode = qrcodes[0]; - for (final barcode in qrcodes) { - debugPrint('Barcode found! ${barcode.rawValue}'); - } + allowDuplicates: false, + onDetect: (qrcode, args) { if (qrcode.rawValue == null) { context.read().emitError( ResponseMessage( diff --git a/lib/dashboard/qr_code/qr_code_scan/view/qr_scanner_page.dart b/lib/dashboard/qr_code/qr_code_scan/view/qr_scanner_page.dart index e301b4602..be6ce47d4 100644 --- a/lib/dashboard/qr_code/qr_code_scan/view/qr_scanner_page.dart +++ b/lib/dashboard/qr_code/qr_code_scan/view/qr_scanner_page.dart @@ -65,9 +65,8 @@ class _QrScannerPageState extends State { key: qrKey, fit: BoxFit.cover, controller: scannerController, - onDetect: (capture) { - final List qrcodes = capture.barcodes; - final Barcode qrcode = qrcodes[0]; + allowDuplicates: false, + onDetect: (qrcode, args) { if (qrcode.rawValue == null) { Navigator.of(context).pop(); } else { diff --git a/lib/ebsi/initiate_ebsi_credential_issuance.dart b/lib/ebsi/initiate_ebsi_credential_issuance.dart index 903e0f7bd..4245ffe83 100644 --- a/lib/ebsi/initiate_ebsi_credential_issuance.dart +++ b/lib/ebsi/initiate_ebsi_credential_issuance.dart @@ -14,12 +14,17 @@ Future initiateEbsiCredentialIssuance( final Ebsi ebsi = Ebsi(Dio()); final Uri uriFromScannedResponse = Uri.parse(scannedResponse); if (uriFromScannedResponse.queryParameters['pre-authorized_code'] != null) { - final String p256PrivateKey = await getRandomP256PrivateKey(secureStorage); + final mnemonic = await getSecureStorage.get(SecureStorageKeys.ssiMnemonic); + final privateKey = await ebsi.privateKeyFromMnemonic(mnemonic: mnemonic!); + + // final mnemonic = await secureStorage.get( + // SecureStorageKeys.ssiMnemonic, + // ); final dynamic encodedCredentialFromEbsi = await ebsi.getCredential( uriFromScannedResponse, null, - p256PrivateKey, + privateKey, ); await addEbsiCredential( diff --git a/lib/scan/cubit/scan_cubit.dart b/lib/scan/cubit/scan_cubit.dart index 5b9464c0f..60bc5d00c 100644 --- a/lib/scan/cubit/scan_cubit.dart +++ b/lib/scan/cubit/scan_cubit.dart @@ -59,15 +59,16 @@ class ScanCubit extends Cubit { if (uri.queryParameters['scope'] == 'openid' || uri.toString().startsWith('openid://?client_id')) { final ebsi = Ebsi(Dio()); - - final String p256PrivateKey = - await getRandomP256PrivateKey(secureStorageProvider); + final mnemonic = + await getSecureStorage.get(SecureStorageKeys.ssiMnemonic); + final privateKey = + await ebsi.privateKeyFromMnemonic(mnemonic: mnemonic!); final credentialList = credentialsToBePresented! .map((e) => jsonEncode(e.toJson())) .toList(); - await ebsi.sendPresentation(uri, credentialList, null, p256PrivateKey); + await ebsi.sendPresentation(uri, credentialList, null, privateKey); await presentationActivity( credentialModels: credentialsToBePresented, diff --git a/lib/theme/app_theme/app_theme.dart b/lib/theme/app_theme/app_theme.dart index facdad540..0e76e46e8 100644 --- a/lib/theme/app_theme/app_theme.dart +++ b/lib/theme/app_theme/app_theme.dart @@ -941,7 +941,7 @@ extension CustomTextTheme on TextTheme { TextStyle get faqQue => GoogleFonts.roboto( color: const Color(0xffFFFFFF), fontSize: 16, - fontWeight: FontWeight.w700, + fontWeight: FontWeight.normal, ); TextStyle get faqAns => GoogleFonts.roboto( diff --git a/packages/ebsi/lib/src/ebsi.dart b/packages/ebsi/lib/src/ebsi.dart index 2af6efd9f..2472847df 100644 --- a/packages/ebsi/lib/src/ebsi.dart +++ b/packages/ebsi/lib/src/ebsi.dart @@ -583,8 +583,6 @@ class Ebsi { privateKey['crv'] = 'P-256K'; } - print(tokenParameters.publicJWK); - final key = JsonWebKey.fromJson(tokenParameters.privateKey); final vpBuilder = JsonWebSignatureBuilder() diff --git a/pubspec.lock b/pubspec.lock index 29fc43cb8..738a2f034 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1316,10 +1316,10 @@ packages: dependency: "direct main" description: name: mobile_scanner - sha256: "4045e8441e21f1fb8998a76fbffd054510dd3a3b1dee55c7c9a2083eee687345" + sha256: "531725451c7506f4c57b4720da1fe33a1394cdf9d4a5075a3a2dd51e21113928" url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "2.1.0" mockingjay: dependency: "direct dev" description: @@ -1444,10 +1444,10 @@ packages: dependency: "direct main" description: name: passbase_flutter - sha256: "200d5ed74aac08ae420f5f4fe962e62c4a508af66d7aceb873fc29d7f08ad73e" + sha256: bb8b350718fed101359753bbe254165b40cb51d5802d8bd1f418e1384b695f64 url: "https://pub.dev" source: hosted - version: "2.13.7" + version: "2.14.0" path: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index b1ac15ad6..baf969d78 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: altme description: AltMe Flutter App -version: 1.12.0+162 +version: 1.12.1+175 publish_to: none environment: @@ -76,12 +76,12 @@ dependencies: logger: ^1.1.0 matrix: ^0.18.0 mime: ^1.0.4 - mobile_scanner: ^3.0.0 + mobile_scanner: ^2.1.0 network_image_mock: ^2.1.1 no_screenshot: ^0.0.1+6 open_filex: ^4.3.2 package_info_plus: ^3.0.3 - passbase_flutter: ^2.13.3 # new version available + passbase_flutter: ^2.14.0 path: ^1.8.2 # flutter_test from sdk depends on path 1.8.2 path_provider: ^2.0.13 permission_handler: ^10.2.0 From cd3619819162dd64b156d6bf8a96ede2ced9b1d0 Mon Sep 17 00:00:00 2001 From: hawkbee1 Date: Sat, 18 Mar 2023 12:49:27 +0100 Subject: [PATCH 187/190] secp256k1 --- lib/ebsi/verify_encoded_data.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/ebsi/verify_encoded_data.dart b/lib/ebsi/verify_encoded_data.dart index 2af69541e..932037083 100644 --- a/lib/ebsi/verify_encoded_data.dart +++ b/lib/ebsi/verify_encoded_data.dart @@ -10,11 +10,13 @@ Future verifyEncodedData( String jwt, ) async { final Ebsi ebsi = Ebsi(Dio()); + final mnemonic = await getSecureStorage.get(SecureStorageKeys.ssiMnemonic); + final privateKey = await ebsi.privateKeyFromMnemonic(mnemonic: mnemonic!); - final String p256PrivateKey = - await getRandomP256PrivateKey(secureStorageProvider); + // final String p256PrivateKey = + // await getRandomP256PrivateKey(secureStorageProvider); - final holderKid = await ebsi.getKid(null, p256PrivateKey); + final holderKid = await ebsi.getKid(null, privateKey); final VerificationType verificationType = await ebsi.verifyEncodedData( issuerDid: issuerDid, From f915aade3aa213317e69fa8aa04f01a5fb0037fd Mon Sep 17 00:00:00 2001 From: hawkbee1 Date: Sat, 18 Mar 2023 13:16:32 +0100 Subject: [PATCH 188/190] version: 1.12.0+153 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 09cdaf67c..52bcfc799 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: altme description: AltMe Flutter App -version: 1.11.4+152 +version: 1.12.0+153 publish_to: none environment: From f98a4a890e4e32996412c011c7b8091b8901ae61 Mon Sep 17 00:00:00 2001 From: hawkbee1 Date: Sat, 18 Mar 2023 13:20:14 +0100 Subject: [PATCH 189/190] replace Altme by Talao in translation file --- lib/l10n/arb/app_en.arb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index af0fe0073..4cec3515a 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -584,11 +584,11 @@ "tezotopiaMembershipWhyGetThisCard": "This Membership card will give you 25% cash backs on ALL Tezotopia Game transactions when you buy a Drops on the marketplace or mint an NFT on starbase.", "tezotopiaMembershipExpirationDate": "This card will remain active and reusable for 1 YEAR.", "tezotopiaMembershipLongDescription": "Tezotopia is a real-time metaverse NFT game on Tezos, where players yield farm with Tezotops, engage in battles for rewards, and claim land in an immersive blockchain space adventure. Explore the metaverse, collect NFTs and conquer Tezotopia.", - "chainbornMembershipHowToGetIt": "To get this card, you need to summon a “Hero” in Chainborn game and an Email Proof. You can find the “Email proof” card in Altme “Discover” section.", + "chainbornMembershipHowToGetIt": "To get this card, you need to summon a “Hero” in Chainborn game and an Email Proof. You can find the “Email proof” card in Talao “Discover” section.", "chainbornMembershipWhyGetThisCard": "Be among the few that have access to exclusive Chainborn store content, airdrops and other member-only benefits !", "chainbornMembershipExpirationDate": "This card will remain active and reusable for 1 YEAR.", "chainbornMembershipLongDescription": "Chainborn is an exciting NFT battle game where players use their own NFTs as heroes, competing for loot and glory. Engage in thrilling fights, gain experience points to boost your hero's strength and health, and enhance the value of your NFTs in this captivating Tezos-based adventure.", - "twitterHowToGetIt": "Follow the steps on TezosProfiles https://tzprofiles.com/connect. Then, claim the “Twitter account” card in Altme. Make sure to sign the transaction on TZPROFILES with the same account you are using in Altme.", + "twitterHowToGetIt": "Follow the steps on TezosProfiles https://tzprofiles.com/connect. Then, claim the “Twitter account” card in Talao. Make sure to sign the transaction on TZPROFILES with the same account you are using in Talao.", "twitterWhyGetThisCard": "This card is a proof that you own your twitter account. Use it to prove your twitter account ownership whenever you need.", "twitterExpirationDate": "This card will be active for 1 year.", "twitterDummyDesc": "Prove your twitter account ownership", @@ -791,7 +791,7 @@ "scanAndDisplay": "Scan and Display", "whatsNew": "What's new", "okGotIt": "OK, GOT IT!", - "altmeSupport": "Chat with Altme’s support", + "altmeSupport": "Chat with Talao’s support", "transactionDoneDialogDescription": "It can take a few minutes for the transfer to complete", "withdrawalFailedMessage": "The withdrawal from the account was unsuccessful", "credentialRequiredMessage": "You need to get those credentials in your wallet to acquire this card:", @@ -803,7 +803,7 @@ "backToHome": "Back to home", "help": "Help", "searchCredentials": "Search credentials", - "supportChatWelcomeMessage": "Welcome to our chat support! We're here to assist you with any questions or concerns you may have about Altme.", + "supportChatWelcomeMessage": "Welcome to our chat support! We're here to assist you with any questions or concerns you may have about Talao.", "cardChatWelcomeMessage": "Welcome to our chat support! We're here to assist you with any questions or concerns.", "creator": "Creator", "contractAddress": "Contract address", @@ -817,7 +817,7 @@ "chatWith": "Chat with", "sendAnEmail": "Send an email", "service_not_registered_message": "This is services is not registered.", - "bloometaPassHowToGetIt": "You need to present an Email proof and an Over 18 proof. If you don’t have those credentials already, claim them directly in Altme “Discover” section.", + "bloometaPassHowToGetIt": "You need to present an Email proof and an Over 18 proof. If you don’t have those credentials already, claim them directly in Talao “Discover” section.", "bloometaPassExpirationDate": "This card will remain active and reusable for 1 YEAR.", "bloometaPassWhyGetThisCard": "Be among the few to get access to limited edition mints, gaming highlights and future airdrops. Claim your card today and enter the Bloometaverse !", "bloometaPassLongDescription": "Bloometa is a user-friendly NFT marketplace that connects in-game assets from different blockchains, making it easy for Metaverse players and GameFi projects to access a curated selection of NFTs. Enjoy multi-chain compatibility and play-to-earn features in a simple, beginner-friendly platform.", From abbf03567261272445448c57567ee3490e95fd15 Mon Sep 17 00:00:00 2001 From: hawkbee1 Date: Sat, 18 Mar 2023 13:21:51 +0100 Subject: [PATCH 190/190] update FAQ TALAO --- assets/faq.json | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/assets/faq.json b/assets/faq.json index 74e4896f7..f6758e20b 100644 --- a/assets/faq.json +++ b/assets/faq.json @@ -9,8 +9,8 @@ "ans": "Talao is called a 'Universal wallet' because just like a physical wallet in which you can carry your ID cards and funds, you can use the Talao wallet to gather digital credentials (Identity card, Age range proof, diploma, etc.) and digital assets (Cryptos, NFTs, tokens...). The future of digital identity is being build on Self-Sovereign Identity (SSI standard), which is the technology that powers Talao wallet and give its users the ability to manage digital credentials in a privacy-preserving and decentralized manner." }, { - "que": "3. What is Self-Sovereign Identity?", - "ans": "Self-Sovereign Identity (SSI) is a new technology compatible with blockchain that gives users the ability to control their own identity data, without relying on a central authority or database to manage their personal information." + "que": "What is Self-Sovereign Identity (SSI) ?", + "ans": "Self-Sovereign Identity, or SSI, is a new technology compatible with blockchain that gives users and organizations the ability to control their own digital credentials and identity data, rather than relying on a central authority or central database to manage their personal information. With SSI, users are able to store, manage and share their personal data in a decentralize manner, enabling greater privacy and security. SSI also uses Zero-Knowledge Proof and 'Selective Disclosure' to protect users privacy." }, { "que": "How can I use my Talao SSI wallet to manage and share my verifiable credentials?", @@ -41,12 +41,12 @@ "ans": "To backup your Talao wallet, you need to write down your passphrase and download your digital credentials. It's important to store these in a secure location, such as a password-protected file or a physical safe. If you lose access to your Talao wallet, there is nothing we can do to help you. Talao is self-custodial which means only you can control your keys and your credentials." }, { - "que": "11. How can I deposit or withdraw funds from my Altme wallet?", - "ans": "You can purchase native coins directly in Altme and send them to your desired blockchain addresses." + "que": "What are Verifiable Credentials and why is it useful ?", + "ans": "Verifiable Credentials (VCs) are an open standard for digital claims. They can represent information such as identity card, an Over 18 Proof or a proof of ownership of a crypto address. They have many advantages, including the fact that they are digitally signed, making them tamper-proof, extremely secure and instantly verifiable by a third-party." }, { - "que": "12. Can I trade cryptocurrencies using Altme?", - "ans": "While you cannot directly trade cryptocurrencies in Altme, you can easily connect your wallet with decentralized exchanges (DEX) to trade any coins." + "que": "What is 'elective disclosure' ?", + "ans": "Selective disclosure is a way for SSI users to reveal certain pieces of information about their identity, without disclosing sensitive information. It helps increase the security of online interactions. For exemple, reveal that you are 'Over 18' without revealing your name or address." }, { "que": "What should I do if I find a bug?", @@ -56,6 +56,10 @@ "que": "How can I contact Talao if I need supports ?", "ans": "You can contact us by using our support email : support@Talao.io. Currently, the Talao SSI wallet is only available in English. However, we are working on expanding the availability of the wallet to other languages in the future." }, + { + "que": "Why my redirect does not go on the App? IOS/ANDROID", + "ans": "If the redirection is not done automatically you must go to your phone's settings > Settings > Apps > Altme/Talao > Set as default > Supported web addresses > app.altme.io -> On" + }, { "que": "What are Talao's Terms of Service?", "ans": "Redirect to CGU.",